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

Compare commits

...

19 Commits
v1.7 ... v2.1

Author SHA1 Message Date
9fd8e7042a 🚀 Version 2.1
Merge branch 'develop'

* develop:
  📝 Add installation instructions
  📝 Added docs for release procedure
  📝 Updated README
  🔧 Hardened runtime (required for notarization)
  🍱 Updated icon for macOS 11 style, updated alert
2020-07-11 10:41:11 +02:00
cb62a20f2a 📝 Add installation instructions 2020-07-11 10:40:02 +02:00
bb1742a390 📝 Added docs for release procedure 2020-07-11 10:05:47 +02:00
6db43beddf 📝 Updated README 2020-07-11 01:07:51 +02:00
7e2c7cdd59 🔧 Hardened runtime (required for notarization) 2020-07-11 01:00:31 +02:00
e76dd0daeb 🍱 Updated icon for macOS 11 style, updated alert 2020-07-11 00:49:32 +02:00
0c33e8f8cc 🎉 Version 2.0 2020-05-17 11:36:31 +02:00
a6a196518a 🔀 Merge branch 'develop'
* develop:
  📝 Changes to the README, bump version number
  ♻️ Menu refactor
  ♻️ Use Localizable.strings
  ♻️ Change default shell for commands, cleanup files
2020-05-17 00:22:40 +02:00
a287ebf6e4 📝 Changes to the README, bump version number 2020-05-17 00:17:45 +02:00
2200b395f1 Merge branch 'cleanup' into develop
* cleanup:
  ♻️ Menu refactor
  ♻️ Use Localizable.strings
  ♻️ Change default shell for commands, cleanup files
2020-05-16 23:56:33 +02:00
bc8a572072 ♻️ Menu refactor 2020-05-16 23:55:04 +02:00
1c455ea05a ♻️ Use Localizable.strings 2020-05-16 23:32:34 +02:00
f2d01748be ♻️ Change default shell for commands, cleanup files
The shell that is now invoked is /bin/sh as opposed to /bin/bash, which
has proven to be faster in various cases.
2020-05-16 23:06:52 +02:00
d3e59e560f New beta for version 1.9 2020-05-13 17:34:51 +02:00
55e849c21b Various fixes and improvements
- Prevent crashes with incorrectly loading modules
- Prevent crashes when dyld libraries are missing
- Indicate when the PHP installation is broken
- New warning added at boot (multiple services)
- Added "Force load latest PHP version" option

It is known that PHP 5.6, 7.0 and 7.1 are causing issues with the
newer versions of certain libraries ("dyld library" warning).

It is recommended to only use PHP 7.2, PHP 7.3 and PHP 7.4 for a minimal
amount of issues. Otherwise, there are certain fixes that are possible
but they are not supported via PHP Monitor since they require manual
formula changes.

For more information, see:
https://github.com/eXolnet/homebrew-deprecated/issues/23#issuecomment-619976586
2020-05-13 17:26:00 +02:00
50f6afb3c5 🔀 Merge branch 'develop'
* develop:
   Add option to restart nginx, open valet config
   Allow restarting of PHP-FPM
  📝 Update copyright message
2020-02-20 17:24:48 +01:00
67e80aac8d Add option to restart nginx, open valet config 2020-02-20 17:24:21 +01:00
e683b6bc9a Allow restarting of PHP-FPM 2020-02-20 16:12:38 +01:00
e854ebe114 📝 Update copyright message 2020-02-20 11:58:16 +01:00
29 changed files with 523 additions and 178 deletions

View File

@ -15,6 +15,9 @@
C41C1B4B22B019FF00E7CF16 /* PhpVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4A22B019FF00E7CF16 /* PhpVersion.swift */; };
C41C1B4D22B0215A00E7CF16 /* Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4C22B0215A00E7CF16 /* Actions.swift */; };
C42295DD2358D02000E263B2 /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42295DC2358D02000E263B2 /* Command.swift */; };
C46FA23F246C358E00944F05 /* StringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46FA23E246C358E00944F05 /* StringExtension.swift */; };
C473319F2470923A009A0597 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C473319E2470923A009A0597 /* Localizable.strings */; };
C47331A2247093B7009A0597 /* StatusMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47331A1247093B7009A0597 /* StatusMenu.swift */; };
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 */; };
@ -35,6 +38,9 @@
C41C1B4A22B019FF00E7CF16 /* PhpVersion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpVersion.swift; sourceTree = "<group>"; };
C41C1B4C22B0215A00E7CF16 /* Actions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Actions.swift; sourceTree = "<group>"; };
C42295DC2358D02000E263B2 /* Command.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Command.swift; sourceTree = "<group>"; };
C46FA23E246C358E00944F05 /* StringExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringExtension.swift; sourceTree = "<group>"; };
C473319E2470923A009A0597 /* Localizable.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = Localizable.strings; sourceTree = "<group>"; };
C47331A1247093B7009A0597 /* StatusMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusMenu.swift; sourceTree = "<group>"; };
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>"; };
@ -84,6 +90,7 @@
C41C1B3F22B0098000E7CF16 /* Info.plist */,
C41C1B4022B0098000E7CF16 /* phpmon.entitlements */,
C41C1B3A22B0098000E7CF16 /* Assets.xcassets */,
C473319E2470923A009A0597 /* Localizable.strings */,
);
path = phpmon;
sourceTree = "<group>";
@ -91,6 +98,7 @@
C41E181722CB61EB0072CF09 /* Classes */ = {
isa = PBXGroup;
children = (
C47331A0247093AC009A0597 /* Menu */,
C4811D2722D70D8E00B5F6B3 /* Commands */,
C4811D2822D70D9C00B5F6B3 /* Helpers */,
);
@ -105,13 +113,21 @@
path = "View Controllers";
sourceTree = "<group>";
};
C47331A0247093AC009A0597 /* Menu */ = {
isa = PBXGroup;
children = (
C47331A1247093B7009A0597 /* StatusMenu.swift */,
);
path = Menu;
sourceTree = "<group>";
};
C4811D2622D70CEF00B5F6B3 /* Singletons */ = {
isa = PBXGroup;
children = (
C41C1B4622B009A400E7CF16 /* Shell.swift */,
C42295DC2358D02000E263B2 /* Command.swift */,
C4811D2322D70A4700B5F6B3 /* App.swift */,
C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */,
C42295DC2358D02000E263B2 /* Command.swift */,
);
path = Singletons;
sourceTree = "<group>";
@ -139,6 +155,7 @@
isa = PBXGroup;
children = (
C4F8C0A322D4F12C002EFE61 /* DateExtension.swift */,
C46FA23E246C358E00944F05 /* StringExtension.swift */,
);
path = Extensions;
sourceTree = "<group>";
@ -203,6 +220,7 @@
files = (
C41C1B3B22B0098000E7CF16 /* Assets.xcassets in Resources */,
C41C1B3E22B0098000E7CF16 /* Main.storyboard in Resources */,
C473319F2470923A009A0597 /* Localizable.strings in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -224,6 +242,8 @@
C41C1B3722B0097F00E7CF16 /* AppDelegate.swift in Sources */,
C41C1B4B22B019FF00E7CF16 /* PhpVersion.swift in Sources */,
C476FF9822B0DD830098105B /* Alert.swift in Sources */,
C47331A2247093B7009A0597 /* StatusMenu.swift in Sources */,
C46FA23F246C358E00944F05 /* StringExtension.swift in Sources */,
C4EE188422D3386B00E126E5 /* Constants.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -362,19 +382,21 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = phpmon/phpmon.entitlements;
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 17;
CURRENT_PROJECT_VERSION = 23;
DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = phpmon/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MARKETING_VERSION = 1.7;
MARKETING_VERSION = 2.1;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
};
name = Debug;
@ -384,19 +406,21 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = phpmon/phpmon.entitlements;
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 17;
CURRENT_PROJECT_VERSION = 23;
DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = phpmon/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MARKETING_VERSION = 1.7;
MARKETING_VERSION = 2.1;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
};
name = Release;

View File

@ -1,38 +1,43 @@
# PHP Monitor
PHP Monitor (or phpmon) is a macOS utility that runs on your Mac and displays the active PHP version in your status bar.
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="278px" alt="phpmon screenshot"/>
For me, it comes in handy when running multiple versions of PHP with Homebrew and you wish to be able to see at a glance which version is currently linked & active with Laravel Valet, and switch between versions.
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.
## System requirements
It's also super convenient to and switch between versions.
**Minimal system requirements**
* macOS 10.14 or higher
* PHP 7.4 installed via Homebrew
* Laravel Valet 2.3 or higher installed
**Recommended system**
## 🖥 System requirements
* macOS 10.15 Catalina
* PHP 7.4 installed with Homebrew 2.2
- other versions of PHP are optional
- includes support for PHP 5.6 and PHP 7.0 [as well](https://github.com/eXolnet/homebrew-deprecated)
* Laravel Valet 2.5.x installed
* PHP 7.4 installed with Homebrew 2.x
* Laravel Valet 2.x
## Why I built this
_Please note that future versions of PHP will not work automatically, minor changes are required to add support for newer versions of PHP._
## 🚀 How to install
You can install via Homebrew, or may download the latest [release](https://github.com/nicoverbruggen/phpmon/releases).
To install via Homebrew, run:
brew tap nicoverbruggen/homebrew-cask
brew cask install phpmon
## 👨‍💻 Why I built this
I wanted to be able to see at a glance which version of PHP was linked, and handle dealing with Laravel Valet in a simple app without having to deal with the terminal every time.
Initially, I had an Alfred workflow for this. But this does the job as well, while also showing me at all times which version of PHP is linked (which is the main benefit over e.g. an Alfred workflow).
## How it works
## 🚜 How it works
### Version detection
This utility runs `php -r 'print phpversion();'` in the background periodically (every 60 seconds) and extracts the version number.
This utility runs `php -r 'print phpversion()'` in the background periodically (every 60 seconds).
### Switching PHP versions
@ -48,8 +53,9 @@ The utility runs the following commands:
- Unlink all detected PHP versions
- Switch to PHP 7.4 (this is done in order to ensure that Valet works, even when attempting to use PHP 5.6)
- Tell Valet to switch to a specific PHP version
- Stop all php-fpm service instances
- Link the desired version of PHP
- Start the correct php-fpm service for the desired PHP version
### Want to know more?
@ -57,9 +63,9 @@ If you want to know more about how this works, I recommend you check out the sou
This app isn't very complicated after all. In the end, this just (conveniently) executes some shell commands.
## Troubleshooting
## 🤬 Troubleshooting
### Reasons for alerts at startup
**If you are having issues, the first thing you should be doing is installing the latest version of PHP Monitor. This can resolve a variety of issues.**
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:
@ -68,15 +74,14 @@ PHP Monitor performs some integrity checks to ensure a good experience when usin
- 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`
- Multiple PHP services are active (see more info below)
Follow instructions as specified in the alert in order to resolve any issues.
### Still seeing another PHP version (from before switching versions)?
## 📝 Additional information
If you're still seeing an old version of PHP in your scripts — e.g. when running `phpinfo()` — I recommend you shut down the PHP service by running:
Please consult the [additional information](docs/ADDITIONAL.md) file that contains more information.
sudo brew services stop php
## ⭐️ Is this helpful?
Please note that PHP Monitor will not be able to stop this service (it doesn't run as an administrator), so you'll need to handle this yourself.
You should only have to do this **once**, and then PHP Monitor should work as usual.
If this software has been useful to you, star the repository so I know that the software is being used. I did not include any tracking or analytics software, so if you encounter issues, let me know via an issue.

Binary file not shown.

99
docs/ADDITIONAL.md Normal file
View File

@ -0,0 +1,99 @@
### Q&A
#### Q: This app is doing network requests?
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:
Install [Homebrew](https://brew.sh) first.
Install PHP, composer, add to path:
brew install php
brew install composer
nano .zshrc
Make sure the following line is not in the comments:
export PATH=$HOME/bin:/usr/local/bin:$PATH
and add the following to your .zshrc:
export PATH=$HOME/bin:~/.composer/vendor/bin:$PATH
Make sure PHP is linked correctly:
which php
should return: `/usr/local/bin/php`
composer global require laravel/valet
valet install
This should install `dnsmasq` and set up Valet. Great, almost there!
valet trust
Finally, run PHP Monitor. Since the app is notarized and signed with a developer ID, it should work.
#### 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!
Try installing again using `brew install php@7.4`.
This should resolve the issue.
#### Q: PHP Monitor reports another version compared to phpinfo on my local website, what is going on?
_Beginning with version 2.0 you'll get alerts about this at startup._
If you're still seeing another version of PHP in your scripts running on your local webserver (nginx) — e.g. when running `phpinfo()` — I recommend you shut down all PHP services that are currently active. You can find out what services are active by running:
sudo brew services list | grep php
This will present to you a list of services, like so (depending on the installed versions of PHP):
```
php started root /Library/LaunchDaemons/homebrew.mxcl.php.plist
php@5.6 stopped
php@7.0 stopped
php@7.1 stopped
php@7.2 stopped
php@7.3 stopped
```
You'll want to make sure that **only one service is running** and that it is running **as `root`**. You can terminate a service by running:
sudo brew services stop {service_name}
So in order to disable PHP 7.3, you'd need to run:
sudo brew services stop php@7.3
If you notice that PHP FPM is running as your own user account, you can turn off the service by running:
brew services stop php@7.3
The easiest way to make sure that PHP Monitor works again is to run the following commands:
sudo brew services stop php
sudo brew services stop php@7.3
sudo brew services stop php@7.2
sudo brew services stop php@7.1
sudo brew services stop php@7.0
sudo brew services stop php@5.6
sudo brew services stop nginx
Then, in PHP Monitor, select "Restart php-fpm service", which should start the service.
Alternatively, you can run `sudo brew services start php@7.4` where `7.4` is your preferred version of PHP (for the latest version of PHP, you may omit `@7.4` like in the example above).

13
docs/RELEASE.md Normal file
View File

@ -0,0 +1,13 @@
# Release Procedure
1. Merge into `master`
2. Create tag
3. Add changes to changelog
4. Archive
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
11. Check new version can be installed via Cask

Binary file not shown.

Before

Width:  |  Height:  |  Size: 314 KiB

After

Width:  |  Height:  |  Size: 615 KiB

View File

@ -1,68 +1,68 @@
{
"images" : [
{
"size" : "16x16",
"idiom" : "mac",
"filename" : "icon_16x16.png",
"scale" : "1x"
"idiom" : "mac",
"scale" : "1x",
"size" : "16x16"
},
{
"size" : "16x16",
"idiom" : "mac",
"filename" : "icon_16x16@2x.png",
"scale" : "2x"
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "icon_32x32.png",
"scale" : "1x"
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "icon_32x32@2x.png",
"scale" : "2x"
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "icon_128x128.png",
"scale" : "1x"
"idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "icon_128x128@2x.png",
"scale" : "2x"
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "icon_256x256.png",
"scale" : "1x"
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "icon_256x256@2x.png",
"scale" : "2x"
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "icon_512x512.png",
"scale" : "1x"
"idiom" : "mac",
"scale" : "1x",
"size" : "512x512"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "icon_512x512@2x.png",
"scale" : "2x"
"idiom" : "mac",
"scale" : "2x",
"size" : "512x512"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 647 B

After

Width:  |  Height:  |  Size: 714 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 422 KiB

After

Width:  |  Height:  |  Size: 315 KiB

View File

@ -26,6 +26,20 @@ class Actions {
return versionsOnly
}
public static func restartPhpFpm() {
let version = App.shared.currentVersion!.short
if (version == Constants.LatestPhpVersion) {
Shell.user.run("sudo brew services restart php")
} else {
Shell.user.run("sudo brew services restart php@\(version)")
}
}
public static func restartNginx()
{
Shell.user.run("sudo brew services restart nginx")
}
public static func switchToPhpVersion(version: String, availableVersions: [String]) {
availableVersions.forEach { (version) in
// Unlink the current version
@ -51,12 +65,22 @@ class Actions {
}
}
public static func openGenericPhpConfigFolder() {
let files = [NSURL(fileURLWithPath: "/usr/local/etc/php")];
NSWorkspace.shared.activateFileViewerSelecting(files as [URL])
}
public static func openPhpConfigFolder(version: String) {
let files = [NSURL(fileURLWithPath: "/usr/local/etc/php/\(version)/php.ini")];
NSWorkspace.shared.activateFileViewerSelecting(files as [URL])
}
public static func XdebugFound(_ version: String) -> Bool {
public static func openValetConfigFolder() {
let files = [NSURL(fileURLWithPath: NSString(string: "~/.config/valet").expandingTildeInPath)];
NSWorkspace.shared.activateFileViewerSelecting(files as [URL])
}
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"
"""
@ -64,7 +88,7 @@ class Actions {
return (output == "YES")
}
public static func XdebugEnabled(_ version: String) -> Bool {
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"
"""
@ -77,11 +101,32 @@ class Actions {
var command = """
sed -i '' 's/; zend_extension="xdebug.so"/zend_extension="xdebug.so"/g' /usr/local/etc/php/\(version!)/php.ini
"""
if (self.XdebugEnabled(version!)) {
if (self.didEnableXdebug(version!)) {
command = """
sed -i '' 's/zend_extension="xdebug.so"/; zend_extension="xdebug.so"/g' /usr/local/etc/php/\(version!)/php.ini
"""
}
Shell.user.run(command)
}
// unlink all the crap and link the latest version
// this also restarts all services
public static func fixMyPhp() {
let versions = self.detectPhpVersions()
versions.forEach { (version) in
Shell.user.run("brew unlink php@\(version)")
if (version == Constants.LatestPhpVersion) {
Shell.user.run("brew services stop php")
Shell.user.run("sudo 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("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")
}
}

View File

@ -15,19 +15,19 @@ class Startup {
self.presentAlertOnMainThreadIf(
!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."
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.)"
)
self.presentAlertOnMainThreadIf(
!Shell.user.pipe("ls /usr/local/opt | grep php@7.4").contains("php@7.4"),
messageText: "PHP 7.4 is not correctly installed",
informativeText: "PHP 7.4 alias was not found in `/usr/local/opt`. The app will not work correctly until you resolve this issue."
informativeText: "PHP 7.4 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@7.4` in order for PHP Monitor to detect this installation."
)
self.presentAlertOnMainThreadIf(
!Shell.user.pipe("which valet").contains("/usr/local/bin/valet"),
messageText: "Laravel Valet is not correctly installed",
informativeText: "You must install Valet via brew. Try running `which valet` in Terminal, it should return `/usr/local/bin/valet`. The app will not work correctly until you resolve this issue."
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."
)
self.presentAlertOnMainThreadIf(
@ -41,6 +41,17 @@ class Startup {
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."
)
let services = Shell.user.pipe("brew services list | grep php")
self.presentAlertOnMainThreadIf(
(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."
)
}
private static func presentAlertOnMainThreadIf(

View File

@ -16,9 +16,18 @@ class PhpVersion {
var xdebugFound: Bool = false
var xdebugEnabled : Bool = false
var error : Bool = false
init() {
let version = Command.execute(path: "/usr/local/bin/php", arguments: ["-r", "print phpversion();"])
if (version == "" || version.contains("Warning")) {
self.short = "💩 BROKEN"
self.long = "";
self.error = true
return;
}
// That's the long version
self.long = version
@ -29,9 +38,11 @@ class PhpVersion {
self.short = segments[0...1].joined(separator: ".")
// Load xdebug support
self.xdebugFound = Actions.XdebugFound(self.short)
self.xdebugFound = Actions.didFindXdebug(self.short)
if (self.xdebugFound) {
self.xdebugEnabled = Actions.XdebugEnabled(self.short)
}
self.xdebugEnabled = Actions.didEnableXdebug(self.short)
}
self.error = false;
}
}

View File

@ -0,0 +1,88 @@
//
// MainMenuBuilder.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 16/05/2020.
// Copyright © 2020 Nico Verbruggen. All rights reserved.
//
import Cocoa
class StatusMenu : NSMenu {
public func addPhpVersionMenuItems()
{
var string = "We are not sure what version of PHP you are running."
if (App.shared.currentVersion != nil) {
if (!App.shared.currentVersion!.error) {
string = "You are running PHP \(App.shared.currentVersion!.long)"
self.addItem(NSMenuItem(title: string, action: nil, keyEquivalent: ""))
} else {
// in case of an error show the error message
self.addItem(NSMenuItem(title: "Oof! It appears your PHP installation is broken...", action: nil, keyEquivalent: ""))
self.addItem(NSMenuItem(title: "Try running `php -v` in your terminal.", action: nil, keyEquivalent: ""))
self.addItem(NSMenuItem(title: "You could also try switching to another version.", action: nil, keyEquivalent: ""))
self.addItem(NSMenuItem(title: "Running `brew reinstall php` (or for the equivalent version) might help.", action: nil, keyEquivalent: ""))
}
}
}
public func addPhpActionMenuItems()
{
if (App.shared.availablePhpVersions.count > 0 && !App.shared.busy) {
var shortcutKey = 1
for index in (0..<App.shared.availablePhpVersions.count).reversed() {
let version = App.shared.availablePhpVersions[index]
let action = #selector(MainMenu.switchToPhpVersion(sender:))
let menuItem = NSMenuItem(title: "Switch to PHP \(version)", action: (version == App.shared.currentVersion?.short) ? nil : action, keyEquivalent: "\(shortcutKey)")
menuItem.tag = index
shortcutKey = shortcutKey + 1
self.addItem(menuItem)
}
self.addItem(NSMenuItem.separator())
self.addItem(NSMenuItem(title: "Active Services", action: nil, keyEquivalent: ""))
self.addItem(NSMenuItem(title: "Restart php-fpm service", action: #selector(MainMenu.restartPhpFpm), keyEquivalent: "f"))
self.addItem(NSMenuItem(title: "Restart nginx service", action: #selector(MainMenu.restartNginx), keyEquivalent: "n"))
self.addItem(NSMenuItem(title: "Force load latest PHP version", action: #selector(MainMenu.forceRestartLatestPhp), keyEquivalent: ""))
}
if (App.shared.busy) {
self.addItem(NSMenuItem(title: "PHP Monitor is busy...", action: nil, keyEquivalent: ""))
}
}
public func addPhpConfigurationMenuItems()
{
if (App.shared.currentVersion != nil) {
self.addItem(NSMenuItem(title: "Configuration", action: nil, keyEquivalent: ""))
self.addItem(NSMenuItem(title: "Valet configuration (.config/valet)", action: #selector(MainMenu.openValetConfigFolder), keyEquivalent: "v"))
self.addItem(NSMenuItem(title: "PHP configuration file (php.ini)", action: #selector(MainMenu.openActiveConfigFolder), keyEquivalent: "c"))
self.addItem(NSMenuItem.separator())
self.addItem(NSMenuItem(title: "Enabled Extensions", action: nil, keyEquivalent: ""))
self.addXdebugMenuItem()
}
}
private func addXdebugMenuItem()
{
let xdebugFound = App.shared.currentVersion!.xdebugFound
if (xdebugFound) {
let xdebugOn = App.shared.currentVersion!.xdebugEnabled
let xdebugToggleMenuItem = NSMenuItem(
title: "Xdebug",
action: #selector(MainMenu.toggleXdebug), keyEquivalent: "x"
)
if (xdebugOn) {
xdebugToggleMenuItem.state = .on
}
self.addItem(xdebugToggleMenuItem)
} else {
let disabledItem = NSMenuItem(
title: "xdebug.so missing",
action: nil, keyEquivalent: "x"
)
disabledItem.isEnabled = false
self.addItem(disabledItem)
}
}
}

View File

@ -10,8 +10,7 @@ import Cocoa
extension Date
{
func toString() -> String
{
func toString() -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
return dateFormatter.string(from: self)

View File

@ -0,0 +1,32 @@
//
// StringExtension.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 13/05/2020.
// Copyright © 2020 Nico Verbruggen. All rights reserved.
//
import Foundation
extension String {
var localized: String {
return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: "")
}
func countInstances(of stringToFind: String) -> Int {
if (stringToFind.isEmpty) {
return 0
}
var count = 0
var searchRange: Range<String.Index>?
while let foundRange = range(of: stringToFind, options: [], range: searchRange) {
count += 1
searchRange = Range(uncheckedBounds: (lower: foundRange.upperBound, upper: endIndex))
}
return count
}
}

View File

@ -27,7 +27,7 @@
<key>LSUIElement</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2019 Nico Verbruggen. All rights reserved.</string>
<string>Copyright © 2020 Nico Verbruggen. All rights reserved.</string>
<key>NSMainStoryboardFile</key>
<string>Main</string>
<key>NSPrincipalClass</key>

View File

@ -0,0 +1,17 @@
/*
Strings.strings
PHP Monitor
Created by Nico Verbruggen on 16/05/2020.
Copyright © 2020 Nico Verbruggen. All rights reserved.
*/
// ALERTS
// Force Reload Started
"alert.force_reload.title" = "PHP Monitor will force reload the latest version of PHP";
"alert.force_reload.info" = "This can take a while. You'll get another alert when the force reload has completed.";
// 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.";

View File

@ -10,6 +10,7 @@ import Cocoa
class Command {
/// Immediately executes a command.
public static func execute(path: String, arguments: [String]) -> String {
let task = Process()
task.launchPath = path
@ -24,27 +25,4 @@ class Command {
return output;
}
public static func experiment() {
/*
print("Running '/usr/local/bin/php -v' directly...")
print("========================================")
var start = DispatchTime.now()
print(Command.execute(path: "/usr/local/bin/php", arguments: ["-v"]))
var end = DispatchTime.now()
var nanoTime = end.uptimeNanoseconds - start.uptimeNanoseconds
var timeInterval = Double(nanoTime) / 1_000_000_000
print("Time to run command directly: \(timeInterval) seconds")
print("")
print("Running 'bash -> php -v'...")
print("========================================")
start = DispatchTime.now()
print(Shell.user.pipe("php -v"))
end = DispatchTime.now()
nanoTime = end.uptimeNanoseconds - start.uptimeNanoseconds
timeInterval = Double(nanoTime) / 1_000_000_000
print("Time to run command via bash: \(timeInterval) seconds")
*/
}
}

View File

@ -39,57 +39,31 @@ class MainMenu: NSObject, NSWindowDelegate {
public func update() {
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
let menu = NSMenu()
var string = "We are not sure what version of PHP you are running."
if (App.shared.currentVersion != nil) {
string = "You are running PHP \(App.shared.currentVersion!.long)"
}
menu.addItem(NSMenuItem(title: string, action: nil, keyEquivalent: ""))
// Create a new menu
let menu = StatusMenu()
// Add the PHP versions (or error messages)
menu.addPhpVersionMenuItems()
menu.addItem(NSMenuItem.separator())
if (App.shared.availablePhpVersions.count > 0 && !App.shared.busy) {
var shortcutKey = 1
for index in (0..<App.shared.availablePhpVersions.count).reversed() {
let version = App.shared.availablePhpVersions[index]
let action = #selector(self.switchToPhpVersion(sender:))
let menuItem = NSMenuItem(title: "Switch to PHP \(version)", action: (version == App.shared.currentVersion?.short) ? nil : action, keyEquivalent: "\(shortcutKey)")
menuItem.tag = index
shortcutKey = shortcutKey + 1
menu.addItem(menuItem)
}
// Add the possible actions
menu.addPhpActionMenuItems()
menu.addItem(NSMenuItem.separator())
}
if (App.shared.busy) {
menu.addItem(NSMenuItem(title: "Switching PHP versions...", action: nil, keyEquivalent: ""))
menu.addItem(NSMenuItem.separator())
}
if (App.shared.currentVersion != nil) {
menu.addItem(NSMenuItem(title: "PHP configuration file (php.ini)", action: #selector(self.openActiveConfigFolder), keyEquivalent: "c"))
let xdebugFound = App.shared.currentVersion!.xdebugFound
if (xdebugFound) {
let xdebugOn = App.shared.currentVersion!.xdebugEnabled
let xdebugToggleMenuItem = NSMenuItem(
title: "Xdebug",
action: #selector(self.toggleXdebug), keyEquivalent: "x"
)
if (xdebugOn) {
xdebugToggleMenuItem.state = .on
}
menu.addItem(xdebugToggleMenuItem)
} else {
let disabledItem = NSMenuItem(
title: "xdebug.so missing",
action: nil, keyEquivalent: "x"
)
disabledItem.isEnabled = false
menu.addItem(disabledItem)
}
}
// Add information about services & actions
menu.addPhpConfigurationMenuItems()
menu.addItem(NSMenuItem.separator())
// Add about & quit menu items
menu.addItem(NSMenuItem(title: "About PHP Monitor", action: #selector(self.openAbout), keyEquivalent: ""))
menu.addItem(NSMenuItem(title: "Quit PHP Monitor", action: #selector(self.terminateApp), keyEquivalent: "q"))
// Make sure every item can be interacted with
menu.items.forEach({ (item) in
item.target = self
})
menu.addItem(NSMenuItem(title: "Quit PHP Monitor", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q"))
// Update the menu item on the main thread
DispatchQueue.main.async {
self.statusItem.menu = menu
}
@ -97,11 +71,7 @@ class MainMenu: NSObject, NSWindowDelegate {
}
func setStatusBarImage(version: String) {
self.setStatusBar(
image: MenuBarImageGenerator.textToImage(
text: version
)
)
self.setStatusBar(image: MenuBarImageGenerator.textToImage(text: version))
}
func setStatusBar(image: NSImage) {
@ -111,7 +81,25 @@ class MainMenu: NSObject, NSWindowDelegate {
}
}
// MARK: - Callable via Obj-C (#selector)
// MARK: - Nicer callbacks
private func waitAndExecute(_ execute: @escaping () -> Void, _ completion: @escaping () -> Void = {})
{
App.shared.busy = true
self.setBusyImage()
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
self.update()
execute()
App.shared.busy = false
DispatchQueue.main.async {
self.updatePhpVersionInStatusBar()
self.update()
completion()
}
}
}
// MARK: - Actions
@objc func updatePhpVersionInStatusBar() {
App.shared.currentVersion = PhpVersion()
@ -127,17 +115,60 @@ class MainMenu: NSObject, NSWindowDelegate {
self.update()
}
@objc public func openAbout() {
NSApplication.shared.activate(ignoringOtherApps: true)
NSApplication.shared.orderFrontStandardAboutPanel()
@objc func setBusyImage() {
DispatchQueue.main.async {
self.setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!)
}
}
@objc public func restartPhpFpm() {
self.waitAndExecute({
Actions.restartPhpFpm()
})
}
@objc public func restartNginx() {
self.waitAndExecute({
Actions.restartNginx()
})
}
@objc public func toggleXdebug() {
self.waitAndExecute({
Actions.toggleXdebug()
})
}
@objc public func forceRestartLatestPhp() {
Alert.present(
messageText: "alert.force_reload.title".localized,
informativeText: "alert.force_reload.info".localized
)
self.waitAndExecute({ Actions.fixMyPhp() }, {
Alert.present(
messageText: "alert.force_reload_done.title".localized,
informativeText: "alert.force_reload_done.info".localized
)
})
}
@objc public func openActiveConfigFolder() {
if (App.shared.currentVersion!.error) {
// php version was not identified
Actions.openGenericPhpConfigFolder()
} else {
// php version was identified
Actions.openPhpConfigFolder(version: App.shared.currentVersion!.short)
}
}
@objc public func openValetConfigFolder() {
Actions.openValetConfigFolder()
}
@objc public func switchToPhpVersion(sender: AnyObject) {
self.setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!)
self.setBusyImage()
let index = sender.tag!
let version = App.shared.availablePhpVersions[index]
App.shared.busy = true
@ -161,19 +192,17 @@ class MainMenu: NSObject, NSWindowDelegate {
}
}
@objc public func toggleXdebug() {
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
DispatchQueue.main.async {
self.setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!)
}
Actions.toggleXdebug()
DispatchQueue.main.async {
self.updatePhpVersionInStatusBar()
self.update()
}
@objc public func openAbout() {
NSApplication.shared.activate(ignoringOtherApps: true)
NSApplication.shared.orderFrontStandardAboutPanel()
}
@objc public func terminateApp() {
NSApplication.shared.terminate(nil)
}
// MARK: - Cleanup when window closes
func windowWillClose(_ notification: Notification) {
App.shared.windowController = nil
Shell.user.delegate = nil

View File

@ -40,9 +40,9 @@ class Shell {
}
/// Runs a shell command and returns the output.
public func pipe(_ command: String) -> String {
public func pipe(_ command: String, shell: String = "/bin/sh") -> String {
let task = Process()
task.launchPath = "/bin/bash"
task.launchPath = shell
task.arguments = ["--login", "-c", command]
let pipe = Pipe()
@ -51,13 +51,7 @@ class Shell {
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output: String = NSString(
data: data,
encoding: String.Encoding.utf8.rawValue
)!.replacingOccurrences(
of: "\u{1B}(B\u{1B}[m",
with: ""
) as String
let output: String = NSString(data: data, encoding: String.Encoding.utf8.rawValue)! as String
let historyItem = ShellHistoryItem(command: command, output: output)

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="15702" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="16096" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="15702"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="16096"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>