1
0
mirror of https://github.com/nicoverbruggen/phpmon.git synced 2025-08-08 04:20:07 +02:00

🚀 Version 2.6

* develop:
  📝 Updated quick troubleshooting
  📝 Updated documentation
  📝 Updated SECURITY
  📝 Updated README
   New build number, updated shortcut keys
  🌐 Add info about `valet install` when running force reload
   Fix my PHP should also restart dnsmasq
   Add menu item to restart dnsmasq
  🔧 Bump build number
  ♻️ Cleanup, updated README (#20)
  ♻️ Refactoring, version bump for M1 support (#20)
   Add support for Homebrew in /opt/homebrew (#20)
  👌 Clean up switchToPhpVersion() and fixMyPhp()
  👌 Avoid using sender.tag
  👌 Improved pipe() method
  🌐 Localized startup environment checks
  🐛 Perform the phpinfo.html on another thread and show busy
  📝 Updated instructions
This commit is contained in:
2021-01-06 17:51:10 +01:00
17 changed files with 347 additions and 150 deletions

View File

@ -25,6 +25,8 @@
C476FF9822B0DD830098105B /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = C476FF9722B0DD830098105B /* Alert.swift */; };
C4811D2422D70A4700B5F6B3 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4811D2322D70A4700B5F6B3 /* App.swift */; };
C4811D2A22D70F9A00B5F6B3 /* MainMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */; };
C486EFFC2586931100A02B2C /* PhpMenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C486EFFB2586931100A02B2C /* PhpMenuItem.swift */; };
C49EAB46259FC305007F6C3B /* Paths.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49EAB45259FC305007F6C3B /* Paths.swift */; };
C4D8016622B1584700C6DA1B /* Startup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D8016522B1584700C6DA1B /* Startup.swift */; };
C4EE188422D3386B00E126E5 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE188322D3386B00E126E5 /* Constants.swift */; };
C4F8C0A422D4F12C002EFE61 /* DateExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F8C0A322D4F12C002EFE61 /* DateExtension.swift */; };
@ -52,6 +54,8 @@
C476FF9722B0DD830098105B /* Alert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Alert.swift; sourceTree = "<group>"; };
C4811D2322D70A4700B5F6B3 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = "<group>"; };
C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainMenu.swift; sourceTree = "<group>"; };
C486EFFB2586931100A02B2C /* PhpMenuItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpMenuItem.swift; sourceTree = "<group>"; };
C49EAB45259FC305007F6C3B /* Paths.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Paths.swift; sourceTree = "<group>"; };
C4D8016522B1584700C6DA1B /* Startup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Startup.swift; sourceTree = "<group>"; };
C4E713562570150F00007428 /* SECURITY.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = SECURITY.md; sourceTree = "<group>"; };
C4E713572570151400007428 /* docs */ = {isa = PBXFileReference; lastKnownFileType = folder; path = docs; sourceTree = "<group>"; };
@ -139,6 +143,7 @@
isa = PBXGroup;
children = (
C47331A1247093B7009A0597 /* StatusMenu.swift */,
C486EFFB2586931100A02B2C /* PhpMenuItem.swift */,
);
path = Menu;
sourceTree = "<group>";
@ -146,6 +151,7 @@
C4811D2622D70CEF00B5F6B3 /* Singletons */ = {
isa = PBXGroup;
children = (
C49EAB45259FC305007F6C3B /* Paths.swift */,
C41C1B4622B009A400E7CF16 /* Shell.swift */,
C42295DC2358D02000E263B2 /* Command.swift */,
C4811D2322D70A4700B5F6B3 /* App.swift */,
@ -268,6 +274,8 @@
C412E5FC25700D5300A1FB67 /* HomebrewPackage.swift in Sources */,
C41C1B3722B0097F00E7CF16 /* AppDelegate.swift in Sources */,
C41C1B4B22B019FF00E7CF16 /* PhpVersion.swift in Sources */,
C486EFFC2586931100A02B2C /* PhpMenuItem.swift in Sources */,
C49EAB46259FC305007F6C3B /* Paths.swift in Sources */,
C476FF9822B0DD830098105B /* Alert.swift in Sources */,
C474B00624C0E98C00066A22 /* LocalNotification.swift in Sources */,
C47331A2247093B7009A0597 /* StatusMenu.swift in Sources */,
@ -415,7 +423,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 26;
CURRENT_PROJECT_VERSION = 31;
DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = phpmon/Info.plist;
@ -423,7 +431,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MARKETING_VERSION = 2.5;
MARKETING_VERSION = 2.6;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -439,7 +447,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 26;
CURRENT_PROJECT_VERSION = 31;
DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = phpmon/Info.plist;
@ -447,7 +455,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MARKETING_VERSION = 2.5;
MARKETING_VERSION = 2.6;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";

View File

@ -4,21 +4,22 @@
PHP Monitor (or phpmon) is a lightweight macOS utility app that runs on your Mac and displays the active PHP version in your status bar.
It also gives you quick access to various useful functionality (like switching PHP versions, restarting services, accessing configuration files, and more).
<img src="./docs/screenshot.png" width="370px" alt="phpmon screenshot (menu bar app)"/>
<img src="./docs/screenshot.png" width="370px" alt="phpmon screenshot"/>
It's also super convenient to switch between different versions of PHP. You'll even get notifications (only if you choose to opt-in, of course)!
For me, it comes in handy when running multiple versions of PHP with Homebrew. If you wish to be able to see at a glance which version is currently linked & active with Laravel Valet, PHP Monitor is your new best friend.
<img src="./docs/notification.png" width="370px" alt="phpmon screenshot (notification)"/>
It's also super convenient to switch between different versions of PHP, or to find your currently active .ini file!
It also gives you quick access to various useful functionality (like accessing configuration files, restarting services, and more).
## 🖥 System requirements
PHP Monitor is a universal application that runs on Apple Silicon **and** Intel-based Macs.
* macOS 10.15 Catalina or higher (works on macOS 11 Big Sur)
* The brew formula `php` has to be installed (which version it is, is detected)
* Laravel Valet 2.x
* Homebrew is installed in `/usr/local/homebrew` or `/opt/homebrew` (the default)
* The brew formula `php` has to be installed (which version is detected)
* Laravel Valet 2.13 or higher
_Please note that future versions of PHP will not work automatically, minor changes are usually required to add support for newer versions of PHP. You may need to update your Valet installation to keep everything working if a major version update of PHP has been released._
@ -71,12 +72,12 @@ This app isn't very complicated after all. In the end, this just (conveniently)
## 🤬 Troubleshooting
**If you are having issues, the first thing you should be doing is installing the latest version of PHP Monitor _and_ Laravel Valet. This can resolve a variety of issues. Don't forget to run `valet install` after upgrading.**
**If you are having issues, the first thing you should be doing is installing the latest version of PHP Monitor _and_ Laravel Valet. This can resolve a variety of issues. To upgrade Valet, run `composer global update`. Don't forget to run `valet install` after upgrading.**
PHP Monitor performs some integrity checks to ensure a good experience when using the app. You'll get a message telling you that PHP Monitor won't work correctly in the following scenarios:
- The PHP binary is not located in `/usr/local/bin/php`
- PHP is missing in `/usr/local/opt`
- The PHP binary is not located in `/usr/local/bin/php` (or `/opt/homebrew/bin/php`)
- PHP is missing in `/usr/local/opt` (or `/opt/homebrew/opt`)
- Laravel Valet is missing in `/usr/local/bin/valet`
- Brew has not been added to sudoers in `/private/etc/sudoers.d/brew`
- Valet has not been added to sudoers in `/private/etc/sudoers.d/valet`
@ -84,9 +85,29 @@ PHP Monitor performs some integrity checks to ensure a good experience when usin
Follow instructions as specified in the alert in order to resolve any issues.
## 📝 Additional information & FAQ
## 🏎 Quick Troubleshooting
Please consult the [additional information][2] file that contains more information. It has answers to additional questions and more information to troubleshoot your problem.
### PHP Monitor tells me `php` is not installed
Try installing again using `brew install php`.
This should resolve the issue! If that does not fix the issue, run `brew link php --force`. (Afterwards, you may need to restart your terminal to make sure the new linked version is detected.)
brew install php
brew link php --force
### Valet sites won't load (502 Bad Gateway)
If you're visiting your `.test` domain, and you're getting a 502 (Bad Gateway) after switching to a different PHP version, you're dealing with a common issue.
This problem is usually resolved by upgrading Valet and running `valet install` again.
composer global update
valet install
## 📝 Additional Info
Please consult the [additional file][2] that contains more information. It may have answers to additional questions and more information to troubleshoot your problem.
## ⭐️ Star me!

View File

@ -1,15 +1,21 @@
# Security Policy
## Supported Versions
## Supported versions
The following versions of PHP Monitor are supported:
Generally speaking, only the latest version of **PHP Monitor** is supported:
| Version | Universal | Supported | Runs on macOS |
| ------- | ------------- | ------------------ | ----- |
| 2.5 | ✅ | ✅ | Catalina (10.15), Big Sur (11.0) |
| 2.4 | ✅ | ❌ | Catalina (10.15), Big Sur (11.0) |
| < 2.4 | | | Catalina (10.15) |
| Version | Apple Silicon | Supported | Supported macOS | Deployment Target |
| ------- | ------------- | ------------------ | ----- | ----- |
| 2.6 | ✅ Universal binary, full support | ✅ | Big Sur (11.0) | macOS 10.14+ |
## Reporting a Vulnerability
The following versions are no longer supported:
Contact Nico Verbruggen at the email address used for the commits in the repository. Please include "PHP Monitor" in the subject.
| Version | Apple Silicon | Supported | Supported macOS | Deployment Target |
| ------- | ------------- | ------------------ | ----- | ----- |
| 2.5 | ✴️ Universal binary<br/>`/usr/local/homebrew` installations only) | ❌ | Big Sur (11.0)<br/>Catalina (10.15) | macOS 10.14+ |
| 2.4 | ✴️ Universal binary<br/>`/usr/local/homebrew` installations only) | ❌ | Big Sur (11.0)<br/>Catalina (10.15) | macOS 10.14+ |
| < 2.4 | (Intel binary<br/>`/usr/local/homebrew` installations only) | ❌ | Catalina (10.15) | macOS 10.14+ |
## Reporting a vulnerability
Contact me (Nico Verbruggen) at the email address used for the commits in the repository. Please include "PHP Monitor" in the subject.

View File

@ -1,22 +1,6 @@
### Q&A
### Quick Setup
#### Q: Does this support Apple Silicon?
Yes. This is a universal app.
#### Q: Is PHP 8.x supported?
Yes.
#### Q: This app is doing network requests? Why?
It's Homebrew. I can't prevent `brew` from doing things via the network when I invoke it.
PHP Monitor itself doesn't do any network requests. Feel free to check the source code or intercept the traffic, if you don't believe me.
#### Q: How can I set this up on a fresh Mac?
If you want to set up your computer for the very first time, here's how I do it:
If you want to set up your computer for the very first time with PHP Monitor, here's how I do it:
Install [Homebrew](https://brew.sh) first.
@ -28,8 +12,14 @@ Install PHP, composer, add to path:
Make sure the following line is not in the comments:
# on an Intel Mac
export PATH=$HOME/bin:/usr/local/bin:$PATH
If you're on an Apple Silicon-based Mac, you'll need to add:
# on an M1 Mac
export PATH=$HOME/bin:/opt/homebrew/bin:$PATH
and add the following to your .zshrc:
export PATH=$HOME/bin:~/.composer/vendor/bin:$PATH
@ -38,7 +28,7 @@ Make sure PHP is linked correctly:
which php
should return: `/usr/local/bin/php`
should return: `/usr/local/bin/php` (or `/opt/homebrew/bin/php`)
composer global require laravel/valet
valet install
@ -49,21 +39,44 @@ This should install `dnsmasq` and set up Valet. Great, almost there!
Finally, run PHP Monitor. Since the app is notarized and signed with a developer ID, it should work.
### FAQ
#### Q: Does this support Apple Silicon?
Yes. This is a universal app.
The following installation paths are supported:
* `/usr/local/homebrew` (default on Intel Macs)
* `/opt/homebrew` (default on Apple Silicon Macs)
#### Q: Is PHP 8.0 supported?
Yes.
#### Q: This app is doing network requests? Why?
It's Homebrew. I can't prevent `brew` from doing things via the network when I invoke it.
PHP Monitor itself doesn't do any network requests. Feel free to check the source code or intercept the traffic, if you don't believe me.
#### Q: I want PHP Monitor to start up when I boot my Mac!
You can do this by dragging *PHP Monitor.app* into the **Login Items** section in **System Preferences > Users & Groups** for your account.
Super convenient!
#### Q: PHP Monitor says that the latest version of PHP is not installed, but it is!
### Q: PHP Monitor says that the latest version of PHP is not installed, but it is!
Try installing again using `brew install php`.
This should resolve the issue.
This should resolve the issue! If that does not fix the issue, run `brew link php --force`. (Afterwards, you may need to restart your terminal to make sure the new linked version is detected.)
#### Q: PHP Monitor says the correct version is loaded, but my Valet sites don't work!
### Q: PHP Monitor says the correct version is loaded, but my Valet sites don't work!
You may need to run `valet install`. (Preferably after updating `valet` by running `composer global update`).
Your sites aren't showing up, or you are seeing a 502? It's a common issue.
You may need to run `valet install`, preferably after updating `valet` by running `composer global update`.
#### Q: PHP Monitor reports another version compared to phpinfo on my local website, what is going on?

View File

@ -7,7 +7,7 @@
5. Notarize and prepare for own distribution
6. After notarization, export .app
7. Create zipped version
8. Calculate SHA256: `openssl dgst -sha256 phpmon-2.x.zip`
9. Upload to GitHub
10. Update Cask
8. Calculate SHA256: `openssl dgst -sha256 phpmon.zip`
9. Upload to GitHub and add to tagged release
10. Update Cask with new version + hash
11. Check new version can be installed via Cask

BIN
docs/notification.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 270 KiB

After

Width:  |  Height:  |  Size: 162 KiB

View File

@ -33,6 +33,12 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
*/
let menu : MainMenu
/**
The paths singleton that determines where Homebrew is installed,
and where to look for binaries.
*/
let paths : Paths
// MARK: - Initializer
/**
@ -42,6 +48,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
self.sharedShell = Shell.user
self.state = App.shared
self.menu = MainMenu.shared
self.paths = Paths.shared
super.init()
}

View File

@ -12,7 +12,7 @@ import AppKit
class Actions {
public static func detectPhpVersions() -> [String] {
let files = Shell.user.pipe("ls /usr/local/opt | grep php@")
let files = Shell.user.pipe("ls \(Paths.optPath()) | grep php@")
var versions = files.components(separatedBy: "\n")
// Remove all empty strings
@ -40,49 +40,50 @@ class Actions {
public static func restartPhpFpm() {
let version = App.shared.currentVersion!.short
if (version == App.shared.brewPhpVersion) {
Shell.user.run("sudo brew services restart php")
Shell.user.run("sudo \(Paths.brew()) services restart php")
} else {
Shell.user.run("sudo brew services restart php@\(version)")
Shell.user.run("sudo \(Paths.brew()) services restart php@\(version)")
}
}
public static func restartNginx()
{
Shell.user.run("sudo brew services restart nginx")
Shell.user.run("sudo \(Paths.brew()) services restart nginx")
}
public static func restartDnsMasq()
{
Shell.user.run("sudo \(Paths.brew()) services restart dnsmasq")
}
/**
Switching to a new PHP version involves:
- unlinking the current version
- stopping the active services
- linking the new desired version
Please note that depending on which version is installed,
the version that is switched to may or may not be identical to `php` (without @version).
*/
public static func switchToPhpVersion(version: String, availableVersions: [String]) {
availableVersions.forEach { (version) in
// Unlink the current version
Shell.user.run("brew unlink php@\(version)")
// Stop the services
if (version == App.shared.brewPhpVersion) {
Shell.user.run("sudo brew services stop php")
} else {
Shell.user.run("sudo brew services stop php@\(version)")
}
}
if (availableVersions.contains(App.shared.brewPhpVersion)) {
// Use the latest version as a default
Shell.user.run("brew link php@\(App.shared.brewPhpVersion) --overwrite --force")
if (version == App.shared.brewPhpVersion) {
// If said version was also requested, all we need to do is start the service
Shell.user.run("sudo brew services start php")
} else {
// Otherwise, link the correct php version + start the correct service
Shell.user.run("brew link php@\(version) --overwrite --force")
Shell.user.run("sudo brew services start php@\(version)")
}
availableVersions.forEach { (available) in
let formula = (available == App.shared.brewPhpVersion) ? "php" : "php@\(available)"
Shell.user.run("\(Paths.brew()) unlink \(formula)")
Shell.user.run("sudo \(Paths.brew()) services stop \(formula)")
}
let formula = (version == App.shared.brewPhpVersion) ? "php" : "php@\(version)"
Shell.user.run("\(Paths.brew()) link \(formula) --overwrite --force")
Shell.user.run("sudo \(Paths.brew()) services start \(formula)")
}
public static func openGenericPhpConfigFolder() {
let files = [NSURL(fileURLWithPath: "/usr/local/etc/php")];
let files = [NSURL(fileURLWithPath: "\(Paths.etcPath())/php")];
NSWorkspace.shared.activateFileViewerSelecting(files as [URL])
}
public static func openPhpConfigFolder(version: String) {
let files = [NSURL(fileURLWithPath: "/usr/local/etc/php/\(version)/php.ini")];
let files = [NSURL(fileURLWithPath: "\(Paths.etcPath())/php/\(version)/php.ini")];
NSWorkspace.shared.activateFileViewerSelecting(files as [URL])
}
@ -93,7 +94,7 @@ class Actions {
public static func didFindXdebug(_ version: String) -> Bool {
let command = """
grep -q 'zend_extension="xdebug.so"' /usr/local/etc/php/\(version)/php.ini; [ $? -eq 0 ] && echo "YES" || echo "NO"
grep -q 'zend_extension="xdebug.so"' \(Paths.etcPath())/php/\(version)/php.ini; [ $? -eq 0 ] && echo "YES" || echo "NO"
"""
let output = Shell.user.pipe(command).trimmingCharacters(in: .whitespacesAndNewlines)
return (output == "YES")
@ -101,7 +102,7 @@ class Actions {
public static func didEnableXdebug(_ version: String) -> Bool {
let command = """
grep -q '; zend_extension="xdebug.so"' /usr/local/etc/php/\(version)/php.ini; [ $? -eq 0 ] && echo "YES" || echo "NO"
grep -q '; zend_extension="xdebug.so"' \(Paths.etcPath())/php/\(version)/php.ini; [ $? -eq 0 ] && echo "YES" || echo "NO"
"""
let output = Shell.user.pipe(command).trimmingCharacters(in: .whitespacesAndNewlines)
return (output == "NO")
@ -110,34 +111,40 @@ class Actions {
public static func toggleXdebug() {
let version = App.shared.currentVersion?.short
var command = """
sed -i '' 's/; zend_extension="xdebug.so"/zend_extension="xdebug.so"/g' /usr/local/etc/php/\(version!)/php.ini
sed -i '' 's/; zend_extension="xdebug.so"/zend_extension="xdebug.so"/g' \(Paths.etcPath())/php/\(version!)/php.ini
"""
if (self.didEnableXdebug(version!)) {
command = """
sed -i '' 's/zend_extension="xdebug.so"/; zend_extension="xdebug.so"/g' /usr/local/etc/php/\(version!)/php.ini
sed -i '' 's/zend_extension="xdebug.so"/; zend_extension="xdebug.so"/g' \(Paths.etcPath())/php/\(version!)/php.ini
"""
}
Shell.user.run(command)
}
// unlink all the crap and link the latest version
// this also restarts all services
/**
Detects all currently available PHP versions, and unlinks each and every one of them.
After this, the brew services are also stopped, the latest PHP version is linked, and php + nginx are restarted.
If this does not solve the issue, the user may need to install additional extensions and/or run `composer global update`.
*/
public static func fixMyPhp() {
Shell.user.run("sudo \(Paths.brew()) services stop dnsmasq")
Shell.user.run("sudo \(Paths.brew()) services start dnsmasq")
let versions = self.detectPhpVersions()
versions.forEach { (version) in
Shell.user.run("brew unlink php@\(version)")
Shell.user.run("\(Paths.brew()) unlink php@\(version)")
if (version == App.shared.brewPhpVersion) {
Shell.user.run("brew services stop php")
Shell.user.run("sudo brew services stop php")
Shell.user.run("\(Paths.brew()) services stop php")
Shell.user.run("sudo \(Paths.brew()) services stop php")
} else {
Shell.user.run("brew services stop php@\(version)")
Shell.user.run("sudo brew services stop php@\(version)")
Shell.user.run("\(Paths.brew()) services stop php@\(version)")
Shell.user.run("sudo \(Paths.brew()) services stop php@\(version)")
}
}
Shell.user.run("brew services stop php")
Shell.user.run("brew services stop nginx")
Shell.user.run("brew link php")
Shell.user.run("sudo brew services restart php")
Shell.user.run("sudo brew services restart nginx")
Shell.user.run("\(Paths.brew()) services stop php")
Shell.user.run("\(Paths.brew()) services stop nginx")
Shell.user.run("\(Paths.brew()) link php")
Shell.user.run("sudo \(Paths.brew()) services restart dnsmasq")
Shell.user.run("sudo \(Paths.brew()) services restart php")
Shell.user.run("sudo \(Paths.brew()) services restart nginx")
}
}

View File

@ -25,49 +25,45 @@ class Startup {
self.failureCallback = failure
self.performEnvironmentCheck(
!Shell.user.pipe("which php").contains("/usr/local/bin/php"),
messageText: "PHP is not correctly installed",
informativeText: "You must install PHP via brew. Try running `which php` in Terminal, it should return `/usr/local/bin/php`. The app will not work correctly until you resolve this issue. (Usually `brew link php` resolves this issue.)",
!Shell.fileExists("\(Paths.binPath())/php"),
messageText: "startup.errors.php_binary.title".localized,
informativeText: "startup.errors.php_binary_desc".localized,
breaking: true
)
self.performEnvironmentCheck(
!Shell.user.pipe("ls /usr/local/opt | grep php").contains("php"),
messageText: "PHP is not correctly installed",
informativeText: "PHP alias was not found in `/usr/local/opt`. The app will not work correctly until you resolve this issue. If you already have the `php` formula installed, you may need to run `brew install php` in order for PHP Monitor to detect this installation.",
!Shell.user.pipe("ls \(Paths.optPath()) | grep php").contains("php"),
messageText: "startup.errors.php_opt.title".localized,
informativeText: "startup.errors.php_opt.desc".localized,
breaking: true
)
self.performEnvironmentCheck(
!Shell.user.pipe("which valet").contains("/usr/local/bin/valet"),
messageText: "Laravel Valet is not correctly installed",
informativeText: "You must install Valet with composer. Try running `which valet` in Terminal, it should return `/usr/local/bin/valet`. The app will not work correctly until you resolve this issue.",
messageText: "startup.errors.valet_executable.title".localized,
informativeText: "startup.errors.valet_executable.desc".localized,
breaking: true
)
self.performEnvironmentCheck(
!Shell.user.pipe("cat /private/etc/sudoers.d/brew").contains("/usr/local/bin/brew"),
messageText: "Brew has not been added to sudoers.d",
informativeText: "You must run `sudo valet trust` to ensure Valet can start and stop services without having to use sudo every time. The app will not work correctly until you resolve this issue.",
!Shell.user.pipe("cat /private/etc/sudoers.d/brew").contains("\(Paths.binPath())/brew"),
messageText: "startup.errors.sudoers_brew.title".localized,
informativeText: "startup.errors.sudoers_brew.desc".localized,
breaking: true
)
self.performEnvironmentCheck(
!Shell.user.pipe("cat /private/etc/sudoers.d/valet").contains("/usr/local/bin/valet"),
messageText: "Valet has not been added to sudoers.d",
informativeText: "You must run `sudo valet trust` to ensure Valet can start and stop services without having to use sudo every time. The app will not work correctly until you resolve this issue.",
messageText: "startup.errors.sudoers_valet.title".localized,
informativeText: "startup.errors.sudoers_valet.desc".localized,
breaking: true
)
let services = Shell.user.pipe("brew services list | grep php")
let services = Shell.user.pipe("\(Paths.brew()) services list | grep php")
self.performEnvironmentCheck(
(services.countInstances(of: "started") > 1),
messageText: "Multiple PHP services are active",
informativeText: "This can cause php-fpm to serve a more recent version of PHP than the one you'd like to see active. Please terminate all extra PHP processes." +
"\n\nThe easiest solution is to choose the option 'Force load latest PHP version' in the menu bar." +
"\n\nAlternatively, you can fix this manually. You can do this by running `brew services list` and running `sudo brew services stop php@7.3` (and use the version that applies)." +
"\n\nPHP Monitor usually handles the starting and stopping of these services, so once the correct version is the only PHP version running you should not have any issues. It is recommended to restart PHP Monitor once you have resolved this issue." +
"\n\nFor more information about this issue, please see the README.md file in the repository on GitHub.",
messageText: "startup.errors.services.title".localized,
informativeText: "startup.errors.services.desc".localized,
breaking: false
)
@ -86,7 +82,7 @@ class Startup {
print("PHP Monitor has determined the application has successfully passed all checks.")
print("Determining which version of PHP is aliased to `php` via Homebrew...")
let brewPhpAlias = Shell.user.pipe("brew info php --json");
let brewPhpAlias = Shell.user.pipe("\(Paths.brew()) info php --json");
App.shared.brewPhpPackage = try! JSONDecoder().decode(
[HomebrewPackage].self,
@ -99,7 +95,7 @@ class Startup {
/**
* Perform an environment check. Will cause the application to terminate, if `breaking` is set to true.
*
* - Parameter condition: Condition to check for
* - Parameter condition: Fail condition to check for; if this returns `true`, the alert will be shown
* - Parameter messageText: Short description of what is wrong
* - Parameter informativeText: Expanded description of the environment check that failed
* - Parameter breaking: If the application should terminate afterwards

View File

@ -19,7 +19,10 @@ class PhpVersion {
var error : Bool = false
init() {
let version = Command.execute(path: "/usr/local/bin/php", arguments: ["-r", "print phpversion();"])
let version = Command.execute(
path: Paths.php(),
arguments: ["-r", "print phpversion();"]
)
if (version == "" || version.contains("Warning")) {
self.short = "💩 BROKEN"

View File

@ -0,0 +1,15 @@
//
// PhpMenuItem.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 13/12/2020.
// Copyright © 2020 Nico Verbruggen. All rights reserved.
//
import Cocoa
class PhpMenuItem: NSMenuItem {
var version: String = ""
}

View File

@ -36,16 +36,22 @@ class StatusMenu : NSMenu {
let version = App.shared.availablePhpVersions[index]
let action = #selector(MainMenu.switchToPhpVersion(sender:))
let brew = (version == App.shared.brewPhpVersion) ? "php" : "php@\(version)"
let menuItem = NSMenuItem(title: "\("mi_php_switch".localized) \(version) (\(brew))", action: (version == App.shared.currentVersion?.short) ? nil : action, keyEquivalent: "\(shortcutKey)")
menuItem.tag = index
let menuItem = PhpMenuItem(title: "\("mi_php_switch".localized) \(version) (\(brew))", action: (version == App.shared.currentVersion?.short) ? nil : action, keyEquivalent: "\(shortcutKey)")
menuItem.version = version
shortcutKey = shortcutKey + 1
self.addItem(menuItem)
}
self.addItem(NSMenuItem.separator())
self.addItem(NSMenuItem(title: "mi_active_services".localized, action: nil, keyEquivalent: ""))
self.addItem(NSMenuItem(title: "mi_restart_php_fpm".localized, action: #selector(MainMenu.restartPhpFpm), keyEquivalent: "f"))
self.addItem(NSMenuItem(title: "mi_restart_dnsmasq".localized, action: #selector(MainMenu.restartDnsMasq), keyEquivalent: "d"))
self.addItem(NSMenuItem(title: "mi_restart_php_fpm".localized, action: #selector(MainMenu.restartPhpFpm), keyEquivalent: "p"))
self.addItem(NSMenuItem(title: "mi_restart_nginx".localized, action: #selector(MainMenu.restartNginx), keyEquivalent: "n"))
self.addItem(NSMenuItem(title: "mi_force_load_latest".localized, action: #selector(MainMenu.forceRestartLatestPhp), keyEquivalent: ""))
self.addItem(NSMenuItem(title: "mi_restart_all_services".localized, action: #selector(MainMenu.restartAllServices), keyEquivalent: "s"))
self.addItem(NSMenuItem.separator())
self.addItem(NSMenuItem(title: "mi_diagnostics".localized, action: nil, keyEquivalent: ""))
self.addItem(NSMenuItem(title: "mi_force_load_latest".localized, action: #selector(MainMenu.forceRestartLatestPhp), keyEquivalent: "f"))
}
if (App.shared.busy) {
self.addItem(NSMenuItem(title: "mi_busy".localized, action: nil, keyEquivalent: ""))

View File

@ -17,9 +17,12 @@
"mi_php_broken_3" = "You could also try switching to another version.";
"mi_php_broken_4" = "Running `brew reinstall php` (or for the equivalent version) might help.";
"mi_diagnostics" = "Diagnostics";
"mi_active_services" = "Active Services";
"mi_restart_php_fpm" = "Restart php-fpm service";
"mi_restart_nginx" = "Restart nginx service";
"mi_restart_php_fpm" = "Restart service: php";
"mi_restart_nginx" = "Restart service: nginx";
"mi_restart_dnsmasq" = "Restart service: dnsmasq";
"mi_restart_all_services" = "Restart all services";
"mi_force_load_latest" = "Force load latest PHP version";
"mi_configuration" = "Configuration";
@ -47,10 +50,36 @@
// Force Reload Done
"alert.force_reload_done.title" = "PHP has been force reloaded";
"alert.force_reload_done.info" = "All appropriate services have been restarted, and the latest version of PHP is now active. You can now try switching to another version of PHP.";
"alert.force_reload_done.info" = "All appropriate services have been restarted, and the latest version of PHP is now active. You can now try switching to another version of PHP. If visiting sites still does not work, you may try running `valet install` again, this can fix a 502 issue (Bad Gateway).";
// PHP Monitor Cannot Start
"alert.cannot_start.title" = "PHP Monitor cannot start";
"alert.cannot_start.info" = "The issue you were just notified about is keeping PHP Monitor from functioning correctly. Please fix the issue and restart PHP Monitor. After clicking on OK, PHP Monitor will close.\n\nIf you have fixed the issue (or don't remember what the exact issue is) you can click on Retry, which will have PHP Monitor retry the startup checks.";
"alert.cannot_start.close" = "Close";
"alert.cannot_start.retry" = "Retry";
// STARTUP
/// 1. PHP binary not found
"startup.errors.php_binary.title" = "PHP is not correctly installed";
"startup.errors.php_binary_desc" = "You must install PHP via brew. Try running `which php` in Terminal, it should return `/usr/local/bin/php` (or `/opt/homebrew/bin/php`). The app will not work correctly until you resolve this issue. (Usually `brew link php` resolves this issue.)";
/// 2. PHP not found in /usr/local/opt or /opt/homebrew/opt
"startup.errors.php_opt.title" = "PHP is not correctly installed";
"startup.errors.php_opt.desc" = "PHP alias was not found in `/usr/local/opt` (or `/opt/homebrew/opt`). The app will not work correctly until you resolve this issue. If you already have the `php` formula installed, you may need to run `brew install php` in order for PHP Monitor to detect this installation.";
/// 3. Valet not installed
"startup.errors.valet_executable.title" = "Laravel Valet is not correctly installed";
"startup.errors.valet_executable.desc" = "You must install Valet with composer. Try running `which valet` in Terminal, it should return `/usr/local/bin/valet`. The app will not work correctly until you resolve this issue.";
/// 4. Brew & sudoers
"startup.errors.sudoers_brew.title" = "Brew has not been added to sudoers.d";
"startup.errors.sudoers_brew.desc" = "You must run `sudo valet trust` to ensure Valet can start and stop services without having to use sudo every time. The app will not work correctly until you resolve this issue.";
/// 5. Valet & sudoers
"startup.errors.sudoers_valet.title" = "Valet has not been added to sudoers.d";
"startup.errors.sudoers_valet.desc" = "You must run `sudo valet trust` to ensure Valet can start and stop services without having to use sudo every time. The app will not work correctly until you resolve this issue.";
/// 6. Multiple services active
"startup.errors.services.title" = "Multiple PHP services are active";
"startup.errors.services.desc" = "This can cause php-fpm to serve a more recent version of PHP than the one you'd like to see active. Please terminate all extra PHP processes.\n\nThe easiest solution is to choose the option 'Force load latest PHP version' in the menu bar.\n\nAlternatively, you can fix this manually. You can do this by running `brew services list` and running `sudo brew services stop php@7.3` (and use the version that applies).\n\nPHP Monitor usually handles the starting and stopping of these services, so once the correct version is the only PHP version running you should not have any issues. It is recommended to restart PHP Monitor once you have resolved this issue.\n\nFor more information about this issue, please see the README.md file in the repository on GitHub.";

View File

@ -186,12 +186,26 @@ class MainMenu: NSObject, NSWindowDelegate {
})
}
@objc public func restartAllServices() {
self.waitAndExecute({
Actions.restartDnsMasq()
Actions.restartPhpFpm()
Actions.restartNginx()
})
}
@objc public func restartNginx() {
self.waitAndExecute({
Actions.restartNginx()
})
}
@objc public func restartDnsMasq() {
self.waitAndExecute({
Actions.restartDnsMasq()
})
}
@objc public func toggleXdebug() {
self.waitAndExecute({
Actions.toggleXdebug()
@ -199,9 +213,12 @@ class MainMenu: NSObject, NSWindowDelegate {
}
@objc public func openPhpInfo() {
self.waitAndExecute({
try! "<?php phpinfo();".write(toFile: "/tmp/phpmon_phpinfo.php", atomically: true, encoding: .utf8)
Shell.user.run("/usr/local/bin/php-cgi -q /tmp/phpmon_phpinfo.php > /tmp/phpmon_phpinfo.html")
Shell.user.run("\(Paths.binPath())/php-cgi -q /tmp/phpmon_phpinfo.php > /tmp/phpmon_phpinfo.html")
}, {
NSWorkspace.shared.open(URL(string: "file:///private/tmp/phpmon_phpinfo.html")!)
})
}
@objc public func forceRestartLatestPhp() {
@ -234,11 +251,9 @@ class MainMenu: NSObject, NSWindowDelegate {
Actions.openValetConfigFolder()
}
@objc public func switchToPhpVersion(sender: AnyObject) {
@objc public func switchToPhpVersion(sender: PhpMenuItem) {
print("Switching to: PHP \(sender.version)")
self.setBusyImage()
// TODO: A wise man once said: using tags is not good. Fix this.
let index = sender.tag!
let version = App.shared.availablePhpVersions[index]
App.shared.busy = true
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
// Update the PHP version in the status bar
@ -247,7 +262,7 @@ class MainMenu: NSObject, NSWindowDelegate {
self.update()
// Switch the PHP version
Actions.switchToPhpVersion(
version: version,
version: sender.version,
availableVersions: App.shared.availablePhpVersions
)
// Mark as no longer busy
@ -258,8 +273,8 @@ class MainMenu: NSObject, NSWindowDelegate {
self.update()
// Send a notification that the switch has been completed
LocalNotification.send(
title: String(format: "notification.version_changed_title".localized, version),
subtitle: String(format: "notification.version_changed_desc".localized, version)
title: String(format: "notification.version_changed_title".localized, sender.version),
subtitle: String(format: "notification.version_changed_desc".localized, sender.version)
)
}
}

View File

@ -0,0 +1,66 @@
//
// Paths.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 01/01/2021.
// Copyright © 2021 Nico Verbruggen. All rights reserved.
//
import Foundation
enum HomebrewDir: String {
case opt = "/opt/homebrew"
case usr = "/usr/local"
}
class Paths {
static let shared = Paths()
var baseDir : HomebrewDir
init() {
let optBrewFound = Shell.fileExists("\(HomebrewDir.opt.rawValue)/bin/brew")
let usrBrewFound = Shell.fileExists("\(HomebrewDir.usr.rawValue)/bin/brew")
if (optBrewFound) {
// This is usually the case with Homebrew installed on Apple Silicon
self.baseDir = .opt
} else if (usrBrewFound) {
// This is usually the case with Homebrew installed on Intel (or Rosetta 2)
self.baseDir = .usr
} else {
// Falling back to default "legacy" Homebrew location (for Intel)
print("Seems like we couldn't determine the Homebrew directory.")
print("This usually means we're in trouble... (no Homebrew?)")
self.baseDir = .usr
}
print("Homebrew directory: \(self.baseDir)")
}
// - MARK: Binaries
public static func brew() -> String {
return "\(self.binPath())/brew"
}
public static func php() -> String {
return "\(self.binPath())/php"
}
// - MARK: Paths
public static func binPath() -> String {
return "\(self.shared.baseDir.rawValue)/bin"
}
public static func optPath() -> String {
return "\(self.shared.baseDir.rawValue)/opt"
}
public static func etcPath() -> String {
return "\(self.shared.baseDir.rawValue)/etc"
}
}

View File

@ -34,20 +34,25 @@ class Shell {
*/
public func pipe(_ command: String, shell: String = "/bin/sh") -> String {
let task = Process()
let pipe = Pipe()
task.launchPath = shell
task.arguments = ["--login", "-c", command]
let pipe = Pipe()
task.standardOutput = pipe
task.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
return String(
data: pipe.fileHandleForReading.readDataToEndOfFile(),
encoding: .utf8
)!
}
let output: String = NSString(
data: data,
encoding: String.Encoding.utf8.rawValue
)! as String
return output
/**
Checks if a file exists at the provided path.
*/
public static func fileExists(_ path: String) -> Bool {
return Shell.user.pipe(
"if [ -f \(path) ]; then echo \"PHP_Y_FE\"; fi"
).contains("PHP_Y_FE")
}
}