For the last few months, I’ve been busy adding various new features and improvements to PHP Monitor, and there’s a monthly release schedule associated with the app again.
As we’re getting closer to the end of November, it’s that time of the year that it’s necessary to be excited about the upcoming release of PHP, and that usually means I have to push out the obligatory build of PHP Monitor with “support for PHP 8.5”.
I have a little bit more time to work on PHP Monitor now, as I’m currently also in this interesting phase where I’m working part-time again (this time for my own company) while simultaneously wanting to also do some projects for myself.
In an ideal world, I could make maintaining PHP Monitor and bringing improvements to the app my job, but sadly that isn’t possible right now. Despite that, I did make a few decisions in regard to the app.
The goal has always been to keep the app open source, despite a few recommendations a couple of years ago to turn PHP Monitor into a paid product.
Every month, I want to be able to keep improving the app, modernizing the code (migrating away from older frameworks and approaches) and I suspect that sometimes this will result in updates without major additions.
Let’s go over what’s been happening for the last few months.
PHP Monitor 25.06: Visual Tweaks
You can read more about what was introduced in June’s release by reading my last blog post.
This is the first release that adopted the new versioning system. PHP Monitor 7.2 was the last release prior to the new versioning system, and it only received two minor version updates since 7.0 due to burnout and my health issues I was dealing with at the time.
In this release, various icons were added to the menu items to bring the app in line with the upcoming version of macOS, Tahoe. A new icon was also added.
This ended up being a mostly “visual tweak” update, but it was a welcome one.
PHP Monitor 25.07: Prep Release
July’s release came with a fix for valet secure for domains with the same path. Also, the app now displays which version of Laravel Valet is running in the menu.
Internal logging was improved, and a couple of longstanding bugs were fixed. It’s good to get some of this stuff sorted, but for most users this ends up being a minor update, and preparation for the next (bigger) updates.
PHP Monitor 25.08: Speed Boost & Fixes
August’s release was more significant: it introduced various speed improvements for Homebrew operations, automatic fixes for recommendations, and a new feature that lets you upgrade Laravel Valet via the GUI.
First off, installing PHP versions and extensions via Homebrew 6.4 or newer now uses the HOMEBREW_DOWNLOAD_CONCURRENCY=auto environment variable, which should significantly speed up installations and upgrades.
This concurrency setting will be enabled by default for brew in the future, but you can benefit from it today with the flag I set manually.
Also new: if there are any updates available to Laravel Valet, you will now be able to see that in the menu bar. Super convenient!
Various improvements to PHP Doctor with automatic fixes are also nice. You can now click a single “Fix Automatically” button and fix a bunch of problems. Here’s what can be fixed with a click:
- Any missing taps related to PHP and/or PHP extensions can be installed automatically. These are important if the PHP Version Manager and/or PHP Extension Manager are to work correctly.
- In case Xdebug is missing
xdebug.mode, you can have that fixed automatically. This will ensure the menu item to switch modes will be displayed in PHP Monitor’s menu. - PHP Monitor helpers not included in PATH? Have it added to your
.zshrcautomatically. This only works if your chosen shell is/bin/zsh(the default). This will let you use the. pm84type helpers.
This release also introduced preliminary macOS Tahoe support.
PHP Monitor 25.09: Improved Update Checking
In September’s update, I finally implemented a more robust “background update check” feature, which now does a few new things. First off, the app now checks for updates by contacting the PHP Monitor API.
The app will broadcast the following required information:
- What version of macOS is currently being used. This is relevant because I want to prevent clients running on versions of macOS that won’t be supported for future releases to not get a notice about updates.
- What version and build of PHP Monitor is currently being used. This one seems rather obvious, but without knowing what version is currently being used, the app can’t determine if a newer version is available. This bit of info is also relevant to point #1 above.
What information is logged?
For analytics purposes, this information is logged to the database. I also hash and salt the IP address of the requester, which can be used to prevent bad actors from doing too many requests to my server, while not impacting the privacy of users, who remain anonymous.
Despite this careful approach, if you do not want the app to send any information from your system, you can always disable the automatic update check.
This information sent with the update check currently gives me insights on:
- What version(s) of PHP Monitor are in use. Since older versions don’t use this endpoint, this only applies to more recent versions of the app, of course.
- What version of macOS is most popular. This also tells me what percentage of people are still on an older version of macOS.
- How many daily users the app has. As more people upgrade to version 25.09, the daily check-in feature of the background update check will be more robust and tell me how many concurrent daily users there are, on average.
In my analytics dashboard, I can then see that information. For example, let’s take a look at the current distribution of macOS users, which is relevant information when considering if I should drop support for an older version:
{
"update_check_ins": {
"lifetime_requests": xxxxxxx,
"daily_unique_users": {
"2025-09-30": xxxxx,
"2025-10-01": xxxxx,
"2025-10-02": xxxxx,
...
},
"macos_distribution": {
"26.x": {
"name": "macOS Tahoe",
"unique_users": xxxxx,
"percentage": "49.4%"
},
"15.x": {
"name": "macOS Sequoia",
"unique_users": xxxxx,
"percentage": "43.9%"
},
"14.x": {
"name": "macOS Sonoma",
"unique_users": xxxxx,
"percentage": "3.4%"
},
"13.x": {
"name": "macOS Ventura",
"unique_users": xxxxx,
"percentage": "3.2%"
}
}
}
}
Since I’m sending information about which version of macOS is running, I have another nice benefit. For future releases of PHP Monitor, I can turn off update notifications for specific versions of macOS, so that you don’t get notified about updates that are incompatible with your version of macOS.
This release also introduced proper support for macOS Tahoe. I’m not sure I love those rounded corners, but it is what it is.
Under consideration: additional opt-in analytics
I plan on also adding additional anonymous analytics to the app. I will also (obviously) provide a dedicated opt-out mechanism for this, because that seems sensible given the app will keep track of what features are being used the most.
To be clear, this will only be “feature use counts”, the most simple kind of analytics which won’t be associated with individual users.
So, for example, if you switch the PHP version, an event will be logged. After a while (every 15 minutes or so), the batched events are planned to be sent to the server with a simple JSON request, like this:
{
"events": {
"switch_global_php": 3,
"apply_isolation": 1,
"open_domains_window": 2,
"open_php_version_manager": 1,
}
}
Notice how no personal information is included: no information about what sites you are running, what PHP versions, simply how many times a particular event occurred.
This feature is currently planned for next year.
PHP Monitor 25.10: Big One!
October’s update is coming in a few days late, but I’ve got good news. This is a rather significant release! There’s also a good reason why I was able to dedicate a significant portion of my time to the app this month.
Last month, I also received a significant donation (a grant of over 1200 dollars!) to help me further build PHP Monitor. A very generous individual provided this sum.
I’m incredibly grateful for this, since it helps me cover costs for newer hardware, server costs, etc. A very nice motivational message was also included, which really made my day.
I want to take this opportunity to thank everyone who has supported me financially over the years! If you’d like similarly keep me working on PHP Monitor and keep development going, you can donate here.
Renewing expired certificates
As a user of PHP Monitor, this is the change you are most likely to notice with 25.10, and it’s a nice quality of life improvement.
If you’ve been using Laravel Valet for longer than a year on a given machine, you may have bumped into this particular issue: SSL certificates tend to expire. Yeah.
Previous versions of PHP Monitor simply checked for the existence of these certificates, but thankfully this is now different.
Both PHP Doctor and the Domains window will alert you to this issue. If you’ve identified this issue via PHP Doctor, pressing “Fix Automatically” will open the Domains window for you, and present an alert similar to this:
It used to be that you needed to unsecure and re-secure the domain to fix this. If you set up your machine and many domains… this would be a time-consuming task, especially because there’s always a few seconds of waiting between securing/unsecuring the domain.
Now, this process has been automated. Because Valet restarts nginx each time, this process will take a while if you have many domains, but I did not want to implement my own certificate renewal process. At least you don’t need to actually do it yourself anymore! :)
Architectural changes
Because this release is a little interesting from a technical POV, I’m going to get a little technical. For various systems in the app, static singletons were being used throughout the app for the last few years, because that was sort of the way to go, back in the day.
Some key abstractions also used this singleton pattern. One such abstraction was the FileSystem abstraction, which looked like this for previous version of the app:
var FileSystem: FileSystemProtocol {
return ActiveFileSystem.shared
}
class ActiveFileSystem {
static var shared: FileSystemProtocol = RealFileSystem()
public static func useTestable(_ files: [String: FakeFile]) {
Self.shared = TestableFileSystem(files: files)
}
public static func useSystem() {
Self.shared = RealFileSystem()
}
}
In theory (and practice), it was a really solid solution: a shared instance could be configured as either a TestableFileSystem or a RealFileSystem, which would make it easy to test various configurations (real or fake) for various tests: unit tests, feature tests and UI tests.
As long as those tests would be executed serially… which is no longer the case with the introduction of Swift Testing. (And I did just migrate all of my tests to Swift Testing!)
Let’s cover why this was causing issues, in particular with concurrent testing. Here’s what a test struct with Swift Testing used to look like:
init() throws {
ActiveFileSystem.useTestable([
"/home/user/bin/foo": .fake(.binary),
"/home/user/docs": .fake(.symlink, "/home/user/documents"),
"/home/user/documents/script.sh": .fake(.text, "echo 'cool';"),
"/home/user/documents/nice.txt": .fake(.text, "69"),
"/home/user/documents/filters/filter1.txt": .fake(.text, "F1"),
"/home/user/documents/filters/filter2.txt": .fake(.text, "F2")
])
}
@Test func intermediate_directories_are_automatically_created() {
#expect(FileSystem.directoryExists("/"))
#expect(FileSystem.directoryExists("/home"))
#expect(FileSystem.directoryExists("/home/user"))
#expect(FileSystem.directoryExists("/home/user/documents"))
#expect(FileSystem.directoryExists("/home/user/bin"))
}
// and more tests which run at the same time
When you ran multiple tests that would mutate the fake (or real) ActiveFileSystem.shared instance from different places (i.e. adding new directories, deleting files), everything would simply break.
To prevent this issue from occurring, you would need a dedicated FileSystem instance per test method (or file, depending on whether those individual tests need to run in parallel, too).
This also means that you’d need to be able to provide dedicated instances of various abstractions when running unit tests.
Thankfully, there is a relatively simple solution for this: dependency injection. Now, a Container instance is injected where required, and this gives access to the various abstractions I provide in it.
class Container {
var shell: ShellProtocol!
var filesystem: FileSystemProtocol!
var command: CommandProtocol!
// and more
}
Here’s what a test currently looks like, with the new Container-based approach. Because a dedicated container is now created for each test, there are no more issues when multiple tests run concurrently:
var container: Container
init() throws {
container = Container.fake(files: [
"/home/user/bin/foo": .fake(.binary),
"/home/user/docs": .fake(.symlink, "/home/user/documents"),
"/home/user/documents/script.sh": .fake(.text, "echo 'cool';"),
"/home/user/documents/nice.txt": .fake(.text, "69"),
"/home/user/documents/filters/filter1.txt": .fake(.text, "F1"),
"/home/user/documents/filters/filter2.txt": .fake(.text, "F2")
])
}
@Test func intermediate_directories_are_automatically_created() {
#expect(container.filesystem.directoryExists("/"))
#expect(container.filesystem.directoryExists("/home"))
#expect(container.filesystem.directoryExists("/home/user"))
#expect(container.filesystem.directoryExists("/home/user/documents"))
#expect(container.filesystem.directoryExists("/home/user/bin"))
}
// and more tests which run at the same time
There is still work to be done to get rid of all static singletons this way, but the most important ones preventing concurrent unit tests from working have all been addressed.
Automatic crash reporting
For the last past years, I’ve included instructions in the README file for folks to send me their crash logs when the app obviously crashes. Very kind developers have done so, and I’ve been able to fix a variety of crashes over the years.
I eventually included further verbose logging to the app which can be enabled by following steps I outline in the FAQ, and this helps further identify issues, as long as people make the effort to report them.
There’s also some obscure crashes, crashes you might not even notice. At some point you may notice that PHP Monitor has vanished from the menu bar, you start it back up, and all seems to work fine. Maybe it didn’t start up properly?
Maybe PHP Monitor stopped working at some point? Who knows. In certain cases, this may have been a subtle concurrency issue that took down the app, and left behind a crash file, but you may not have done anything in particular that might have caused the issue.
If this happens, and it has happened to me, you don’t actually care to investigate. Now, since I don’t have automatic crash reporting via Firebase or something like that, I was not getting those crash reports at all.
This changes with this month’s release, as I’ve now included PLCrashReporter, which keeps track of whether the app crashed, and then sends the text report to my own server if you choose to share the crash report… no third-party APIs required!
Note that the unsymbolicated crash reports are sent without PII:
Incident Identifier: 7200729B-2730-44C7-AD3E-156CA76FE721
Hardware Model: Mac16,11
Process: PHP Monitor EAP [11594]
Path: /Applications/PHP Monitor EAP.app/Contents/MacOS/PHP Monitor EAP
Identifier: com.nicoverbruggen.phpmon.eap
Version: 25.10 (1681)
Code Type: ARM-64
Parent Process: launchd [1]
Date/Time: 2025-11-07 12:11:57 +0000
OS Version: Mac OS X 26.1 (25B78)
Report Version: 104
Exception Type: SIGTRAP
Exception Codes: TRAP_BRKPT at 0x1a50a2554
Crashed Thread: 5
Thread 5 Crashed:
0 libswiftCore.dylib 0x00000001a50a2554 0x1a4f6b000 + 1275220
1 libswiftCore.dylib 0x00000001a5110404 0x1a4f6b000 + 1725444
2 libswift_Concurrency.dylib 0x000000028c1c4994 0x28c161000 + 407956
3 libswift_Concurrency.dylib 0x000000028c1c5d88 0x28c161000 + 413064
4 libdispatch.dylib 0x0000000191b7efc8 0x191b6b000 + 81864
5 libdispatch.dylib 0x0000000191b7f784 0x191b6b000 + 83844
6 libsystem_pthread.dylib 0x0000000191d25e10 0x191d23000 + 11792
7 libsystem_pthread.dylib 0x0000000191d24b9c 0x191d23000 + 7068
If you’ve got experience with these crash reports, you can see that there’s no stack trace here. I have access to the DSYM files which will allow me to symbolicate these crashes and find out what’s causing them.
That is, if it’s no Swift Concurrency crashes… those tend to be a bit cryptic sometimes. With the help of Claude Code I like to think I’ve been able to address a couple of those crashes, by the way.
But if anything still goes wrong, I now have access to more useful crash reports that are much easier to send my way now. In future versions of PHP Monitor, I’m considering including a custom field where people can enter their email address so I can get in touch if the issue is problematic.
Coming in 25.11: Support for PHP 8.5
As is the case each year, I also release an update when the latest version of PHP becomes stable sometime in November.
This time, PHP 8.5 is releasing later this month, and it’s looking to be very interesting. I always try to be up-to-date as quickly as possible, so it’s important for my own sites and projects that I get PHP Monitor ready for this new version of PHP, too.
PHP Monitor 25.11 will likely be out in a week or two, which is a few days after PHP 8.5 releases officially. As part of this update, daily builds of PHP 8.6 will also become available for installation, as is always the case.
Depending on how much time I have for other features and improvements, I intend on doing some further polishing of the app as part of v25.11, but it’s going to depend on a couple of external factors, too.
This was not generated: Unless explicitly specified, the posts on my blog are my own creative work. 100% written (typos included) and edited by me; not generated using e.g. large language models.

Comments
Nothing yet. Say the first thing.
Sign in to join the conversation.