From ceb168c6cf3581a5c4afe2ebe3c66718a000786d Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 21 Dec 2021 16:00:27 +0100 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Rework=20various=20common?= =?UTF-8?q?=20classes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 54 +++++-- README.md | 4 +- phpmon-cli/main.swift | 4 +- .../Core/Actions.swift | 76 +-------- phpmon-common/{ => Core}/Command.swift | 2 +- .../Core}/Constants.swift | 0 phpmon-common/{ => Core}/Paths.swift | 2 +- phpmon-common/{ => Core}/Shell.swift | 2 +- .../PHP/ActivePhpInstallation.swift | 24 +-- .../PHP}/HomebrewPackage.swift | 0 .../PHP/PhpExtension.swift | 0 .../PHP/PhpInstallation.swift | 0 phpmon-common/PHP/PhpSwitcher.swift | 146 ++++++++++++++++++ phpmon-common/PhpSwitcher.swift | 60 ------- phpmon/Domain/Core/App.swift | 4 - phpmon/Domain/Core/AppDelegate.swift | 23 +-- phpmon/Domain/Core/Startup.swift | 40 ++--- .../Homebrew/HomebrewDiagnostics.swift | 4 +- phpmon/Domain/Menu/MainMenu.swift | 2 +- phpmon/Domain/Menu/StatusMenu.swift | 8 +- .../PHP/ActivePhpInstallation+Checks.swift | 33 ++++ 21 files changed, 272 insertions(+), 216 deletions(-) rename {phpmon/Domain => phpmon-common}/Core/Actions.swift (67%) rename phpmon-common/{ => Core}/Command.swift (98%) rename {phpmon => phpmon-common/Core}/Constants.swift (100%) rename phpmon-common/{ => Core}/Paths.swift (98%) rename phpmon-common/{ => Core}/Shell.swift (99%) rename {phpmon/Domain => phpmon-common}/PHP/ActivePhpInstallation.swift (85%) rename {phpmon/Domain/Integrations/Homebrew => phpmon-common/PHP}/HomebrewPackage.swift (100%) rename {phpmon/Domain => phpmon-common}/PHP/PhpExtension.swift (100%) rename {phpmon/Domain => phpmon-common}/PHP/PhpInstallation.swift (100%) create mode 100644 phpmon-common/PHP/PhpSwitcher.swift delete mode 100644 phpmon-common/PhpSwitcher.swift create mode 100644 phpmon/Domain/PHP/ActivePhpInstallation+Checks.swift diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 89d4e01..39cbd70 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -27,6 +27,14 @@ C40C7F1E2772136000DDDCDC /* PhpSwitcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F1D2772136000DDDCDC /* PhpSwitcher.swift */; }; C40C7F1F2772136000DDDCDC /* PhpSwitcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F1D2772136000DDDCDC /* PhpSwitcher.swift */; }; C40C7F202772136000DDDCDC /* PhpSwitcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F1D2772136000DDDCDC /* PhpSwitcher.swift */; }; + C40C7F2227721F8200DDDCDC /* PhpInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F2E4392752F7D00020E974 /* PhpInstallation.swift */; }; + C40C7F2327721F8200DDDCDC /* ActivePhpInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4A22B019FF00E7CF16 /* ActivePhpInstallation.swift */; }; + C40C7F2427721F8200DDDCDC /* PhpExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4ACA38E25C754C100060C66 /* PhpExtension.swift */; }; + C40C7F2527721F9800DDDCDC /* HomebrewPackage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */; }; + C40C7F2627721FA200DDDCDC /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE188322D3386B00E126E5 /* Constants.swift */; }; + C40C7F2827721FF600DDDCDC /* ActivePhpInstallation+Checks.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F2727721FF600DDDCDC /* ActivePhpInstallation+Checks.swift */; }; + C40C7F2927721FF600DDDCDC /* ActivePhpInstallation+Checks.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C7F2727721FF600DDDCDC /* ActivePhpInstallation+Checks.swift */; }; + C40C7F2B2772201C00DDDCDC /* Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415D3B62770F294005EF286 /* Actions.swift */; }; C412E5FC25700D5300A1FB67 /* HomebrewPackage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */; }; C415D3B72770F294005EF286 /* Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415D3B62770F294005EF286 /* Actions.swift */; }; C415D3B82770F294005EF286 /* Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415D3B62770F294005EF286 /* Actions.swift */; }; @@ -180,6 +188,7 @@ C405A4CE24B9B9130062FAFA /* InternetAccessPolicy.strings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; path = InternetAccessPolicy.strings; sourceTree = ""; }; C405A4CF24B9B9140062FAFA /* InternetAccessPolicy.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = InternetAccessPolicy.plist; sourceTree = ""; }; C40C7F1D2772136000DDDCDC /* PhpSwitcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpSwitcher.swift; sourceTree = ""; }; + C40C7F2727721FF600DDDCDC /* ActivePhpInstallation+Checks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ActivePhpInstallation+Checks.swift"; sourceTree = ""; }; C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomebrewPackage.swift; sourceTree = ""; }; C415D3B62770F294005EF286 /* Actions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Actions.swift; sourceTree = ""; }; C415D3D62770F341005EF286 /* phpmon-cli */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "phpmon-cli"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -298,6 +307,8 @@ 54B20EDF263AA22C00D3250E /* PHP */ = { isa = PBXGroup; children = ( + C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */, + C40C7F1D2772136000DDDCDC /* PhpSwitcher.swift */, C41C1B4A22B019FF00E7CF16 /* ActivePhpInstallation.swift */, C4F2E4392752F7D00020E974 /* PhpInstallation.swift */, C4ACA38E25C754C100060C66 /* PhpExtension.swift */, @@ -335,6 +346,26 @@ path = "Test Files"; sourceTree = ""; }; + C40C7F2127721F7300DDDCDC /* Core */ = { + isa = PBXGroup; + children = ( + C415D3B62770F294005EF286 /* Actions.swift */, + C4EE188322D3386B00E126E5 /* Constants.swift */, + C4B5853D2770FE3900DA4FBE /* Command.swift */, + C4B5853B2770FE3900DA4FBE /* Paths.swift */, + C4B5853C2770FE3900DA4FBE /* Shell.swift */, + ); + path = Core; + sourceTree = ""; + }; + C40C7F2C2772204700DDDCDC /* PHP */ = { + isa = PBXGroup; + children = ( + C40C7F2727721FF600DDDCDC /* ActivePhpInstallation+Checks.swift */, + ); + path = PHP; + sourceTree = ""; + }; C415D3D72770F341005EF286 /* phpmon-cli */ = { isa = PBXGroup; children = ( @@ -373,7 +404,6 @@ C41C1B3522B0097F00E7CF16 /* phpmon */ = { isa = PBXGroup; children = ( - C4EE188322D3386B00E126E5 /* Constants.swift */, C41E181722CB61EB0072CF09 /* Domain */, C41C1B3F22B0098000E7CF16 /* Info.plist */, C4232EE42612526500158FC6 /* Credits.html */, @@ -398,7 +428,7 @@ children = ( C4AF9F6B275445D300D44ED0 /* Integrations */, C4B13B1D25C4915000548C3A /* Core */, - 54B20EDF263AA22C00D3250E /* PHP */, + C40C7F2C2772204700DDDCDC /* PHP */, C47331A0247093AC009A0597 /* Menu */, C464ADAA275A7A25003FCD53 /* SiteList */, 5420395726135DB800FB00FA /* Preferences */, @@ -480,7 +510,6 @@ isa = PBXGroup; children = ( C4F2E4362752F0870020E974 /* HomebrewDiagnostics.swift */, - C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */, ); path = Homebrew; sourceTree = ""; @@ -497,7 +526,6 @@ C4B97B77275CF1B5003F3378 /* App+ActivationPolicy.swift */, C4B97B7A275CF20A003F3378 /* App+GlobalHotkey.swift */, C4D8016522B1584700C6DA1B /* Startup.swift */, - C415D3B62770F294005EF286 /* Actions.swift */, ); path = Core; sourceTree = ""; @@ -505,10 +533,8 @@ C4B5853A2770FE2500DA4FBE /* phpmon-common */ = { isa = PBXGroup; children = ( - C4B5853D2770FE3900DA4FBE /* Command.swift */, - C4B5853B2770FE3900DA4FBE /* Paths.swift */, - C4B5853C2770FE3900DA4FBE /* Shell.swift */, - C40C7F1D2772136000DDDCDC /* PhpSwitcher.swift */, + C40C7F2127721F7300DDDCDC /* Core */, + 54B20EDF263AA22C00D3250E /* PHP */, ); path = "phpmon-common"; sourceTree = ""; @@ -710,10 +736,16 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + C40C7F2427721F8200DDDCDC /* PhpExtension.swift in Sources */, + C40C7F2627721FA200DDDCDC /* Constants.swift in Sources */, C4B585402770FE3900DA4FBE /* Paths.swift in Sources */, C415D3E62770F540005EF286 /* main.swift in Sources */, + C40C7F2227721F8200DDDCDC /* PhpInstallation.swift in Sources */, C4B585432770FE3900DA4FBE /* Shell.swift in Sources */, + C40C7F2327721F8200DDDCDC /* ActivePhpInstallation.swift in Sources */, C4B585462770FE3900DA4FBE /* Command.swift in Sources */, + C40C7F2527721F9800DDDCDC /* HomebrewPackage.swift in Sources */, + C40C7F2B2772201C00DDDCDC /* Actions.swift in Sources */, C415D3E12770F34D005EF286 /* AllowedArguments.swift in Sources */, C40C7F202772136000DDDCDC /* PhpSwitcher.swift in Sources */, ); @@ -737,6 +769,7 @@ C4F2E43A2752F7D00020E974 /* PhpInstallation.swift in Sources */, C41E871A2763D42300161EE0 /* SiteListVC+ContextMenu.swift in Sources */, C48D0CA325CC992000CC7490 /* StatsView.swift in Sources */, + C40C7F2827721FF600DDDCDC /* ActivePhpInstallation+Checks.swift in Sources */, C4EE55A927708B9E001DF387 /* PMHeaderView.swift in Sources */, C4F2E4372752F0870020E974 /* HomebrewDiagnostics.swift in Sources */, C4CCBA6C275C567B008C7055 /* PMWindowController.swift in Sources */, @@ -794,6 +827,7 @@ C4F780CC25D80B75000DBC97 /* ActivePhpInstallation.swift in Sources */, C4F780B125D80B4D000DBC97 /* PhpExtension.swift in Sources */, C4F780CE25D80B75000DBC97 /* LocalNotification.swift in Sources */, + C40C7F2927721FF600DDDCDC /* ActivePhpInstallation+Checks.swift in Sources */, C4FBFC532616485F00CDB8E1 /* PhpVersionDetectionTest.swift in Sources */, C43A8A2425D9D20D00591B77 /* BrewJsonParserTest.swift in Sources */, C4F780CA25D80B75000DBC97 /* HomebrewPackage.swift in Sources */, @@ -1020,7 +1054,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 200; + CURRENT_PROJECT_VERSION = 220; DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = phpmon/Info.plist; @@ -1045,7 +1079,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 200; + CURRENT_PROJECT_VERSION = 220; DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = phpmon/Info.plist; diff --git a/README.md b/README.md index a3e5c3c..68bc7de 100644 --- a/README.md +++ b/README.md @@ -301,9 +301,7 @@ The switcher will disable all PHP-FPM services not belonging to the version you ### Want to know more? -If you want to know more about how this works, I recommend you check out the source code. - -This app isn't very complicated after all. In the end, this just (conveniently) executes some shell commands. +If you want to know more about how this works, I recommend you check out the source code. ## 🔧 Build instructions diff --git a/phpmon-cli/main.swift b/phpmon-cli/main.swift index 64ece82..72fab89 100644 --- a/phpmon-cli/main.swift +++ b/phpmon-cli/main.swift @@ -31,9 +31,11 @@ if !AllowedArguments.has(argument) { let action = AllowedArguments.init(rawValue: argument) +let switcher = PhpSwitcher.shared +PhpSwitcher.detectPhpVersions() + switch action { case .use: - // Read the PHP value let version = CommandLine.arguments[2] print("Switching to PHP \(version)...") break diff --git a/phpmon/Domain/Core/Actions.swift b/phpmon-common/Core/Actions.swift similarity index 67% rename from phpmon/Domain/Core/Actions.swift rename to phpmon-common/Core/Actions.swift index 19773f6..0921845 100644 --- a/phpmon/Domain/Core/Actions.swift +++ b/phpmon-common/Core/Actions.swift @@ -10,74 +10,6 @@ import AppKit class Actions { - // MARK: - Detect PHP Versions - - public static func detectPhpVersions() -> [String] - { - let files = Shell.pipe("ls \(Paths.optPath) | grep php@") - var versionsOnly = Self.extractPhpVersions(from: files.components(separatedBy: "\n")) - - // Make sure the aliased version is detected - // The user may have `php` installed, but not e.g. `php@8.0` - // We should also detect that as a version that is installed - let phpAlias = PhpSwitcher.shared.brewPhpVersion - - // Avoid inserting a duplicate - if (!versionsOnly.contains(phpAlias) && Shell.fileExists("\(Paths.optPath)/php/bin/php")) { - versionsOnly.append(phpAlias); - } - - print("The PHP versions that were detected are: \(versionsOnly)") - - PhpSwitcher.shared.availablePhpVersions = versionsOnly - Actions.extractPhpLongVersions() - - return versionsOnly - } - - /** - This method extracts the PHP full version number after finding the php installation folders. - To be refactored at some later point, I'd like to cache the `PhpInstallation` objects instead of just the version number at some point. - */ - public static func extractPhpLongVersions() - { - var mappedVersions: [String: PhpInstallation] = [:] - PhpSwitcher.shared.availablePhpVersions.forEach { version in - mappedVersions[version] = PhpInstallation(version) - } - - PhpSwitcher.shared.cachedPhpInstallations = mappedVersions - } - - /** - Extracts valid PHP versions from an array of strings. - This array of strings is usually retrieved from `grep`. - */ - public static func extractPhpVersions( - from versions: [String], - checkBinaries: Bool = true - ) -> [String] { - var output : [String] = [] - - versions.filter { (version) -> Bool in - // Omit everything that doesn't start with php@ - // (e.g. something-php@8.0 won't be detected) - return version.starts(with: "php@") - }.forEach { (string) in - let version = string.components(separatedBy: "php@")[1] - // Only append the version if it doesn't already exist (avoid dupes), - // is supported and where the binary exists (avoids broken installs) - if !output.contains(version) - && Constants.SupportedPhpVersions.contains(version) - && (checkBinaries ? Shell.fileExists("\(Paths.optPath)/php@\(version)/bin/php") : true) - { - output.append(version) - } - } - - return output - } - // MARK: - Services public static func restartPhpFpm() @@ -137,7 +69,7 @@ class Actions { group.enter() DispatchQueue.global(qos: .userInitiated).async { - let formula = (available == PhpSwitcher.shared.brewPhpVersion) + let formula = (available == PhpSwitcher.brewPhpVersion) ? "php" : "php@\(available)" brew("unlink \(formula)") @@ -151,7 +83,7 @@ class Actions { print("All versions have been unlinked!") print("Linking the new version!") - let formula = (version == PhpSwitcher.shared.brewPhpVersion) ? "php" : "php@\(version)" + let formula = (version == PhpSwitcher.brewPhpVersion) ? "php" : "php@\(version)" brew("link \(formula) --overwrite --force") brew("services start \(formula)", sudo: true) @@ -204,8 +136,8 @@ class Actions { { brew("services restart dnsmasq", sudo: true) - detectPhpVersions().forEach { (version) in - let formula = (version == PhpSwitcher.shared.brewPhpVersion) ? "php" : "php@\(version)" + PhpSwitcher.shared.detectPhpVersions().forEach { (version) in + let formula = (version == PhpSwitcher.brewPhpVersion) ? "php" : "php@\(version)" brew("unlink php@\(version)") brew("services stop \(formula)") brew("services stop \(formula)", sudo: true) diff --git a/phpmon-common/Command.swift b/phpmon-common/Core/Command.swift similarity index 98% rename from phpmon-common/Command.swift rename to phpmon-common/Core/Command.swift index 941172a..8da8bd2 100644 --- a/phpmon-common/Command.swift +++ b/phpmon-common/Core/Command.swift @@ -1,6 +1,6 @@ // // Command.swift -// phpmon-common +// PHP Monitor // // Copyright © 2021 Nico Verbruggen. All rights reserved. // diff --git a/phpmon/Constants.swift b/phpmon-common/Core/Constants.swift similarity index 100% rename from phpmon/Constants.swift rename to phpmon-common/Core/Constants.swift diff --git a/phpmon-common/Paths.swift b/phpmon-common/Core/Paths.swift similarity index 98% rename from phpmon-common/Paths.swift rename to phpmon-common/Core/Paths.swift index 5231893..202e790 100644 --- a/phpmon-common/Paths.swift +++ b/phpmon-common/Core/Paths.swift @@ -1,6 +1,6 @@ // // Paths.swift -// phpmon-common +// PHP Monitor // // Copyright © 2021 Nico Verbruggen. All rights reserved. // diff --git a/phpmon-common/Shell.swift b/phpmon-common/Core/Shell.swift similarity index 99% rename from phpmon-common/Shell.swift rename to phpmon-common/Core/Shell.swift index 11c6730..59aa4b9 100644 --- a/phpmon-common/Shell.swift +++ b/phpmon-common/Core/Shell.swift @@ -1,6 +1,6 @@ // // Shell.swift -// phpmon-common +// PHP Monitor // // Copyright © 2021 Nico Verbruggen. All rights reserved. // diff --git a/phpmon/Domain/PHP/ActivePhpInstallation.swift b/phpmon-common/PHP/ActivePhpInstallation.swift similarity index 85% rename from phpmon/Domain/PHP/ActivePhpInstallation.swift rename to phpmon-common/PHP/ActivePhpInstallation.swift index 10e2b35..6111ffe 100644 --- a/phpmon/Domain/PHP/ActivePhpInstallation.swift +++ b/phpmon-common/PHP/ActivePhpInstallation.swift @@ -25,7 +25,7 @@ class ActivePhpInstallation { // MARK: - Computed var formula: String { - return (version.short == PhpSwitcher.shared.brewPhpVersion) ? "php" : "php@\(version.short)" + return (version.short == PhpSwitcher.brewPhpVersion) ? "php" : "php@\(version.short)" } // MARK: - Initializer @@ -122,26 +122,6 @@ class ActivePhpInstallation { return (match == nil) ? "⚠️" : "\(value)B" } - /** - It is always possible that the system configuration for PHP-FPM has not been set up for Valet. - This can occur when a user manually installs a new PHP version, but does not run `valet install`. - In that case, we should alert the user! - - - Important: The underlying check is `checkPhpFpmStatus`, which can be run multiple times. - This method actively presents a modal if said checks fails, so don't call this method too many times. - */ - public func notifyAboutBrokenPhpFpm() { - if !self.checkPhpFpmStatus() { - DispatchQueue.main.async { - Alert.notify( - message: "alert.php_fpm_broken.title".localized, - info: "alert.php_fpm_broken.info".localized, - style: .critical - ) - } - } - } - /** Determine if PHP-FPM is configured correctly. @@ -149,7 +129,7 @@ class ActivePhpInstallation { versions of PHP, we can just check for the existence of the `valet-fpm.conf` file. If the check here fails, that means that Valet won't work properly. */ - private func checkPhpFpmStatus() -> Bool { + func checkPhpFpmStatus() -> Bool { if self.version.short == "5.6" { // The main PHP config file should contain `valet.sock` and then we're probably fine? let fileName = "\(Paths.etcPath)/php/5.6/php-fpm.conf" diff --git a/phpmon/Domain/Integrations/Homebrew/HomebrewPackage.swift b/phpmon-common/PHP/HomebrewPackage.swift similarity index 100% rename from phpmon/Domain/Integrations/Homebrew/HomebrewPackage.swift rename to phpmon-common/PHP/HomebrewPackage.swift diff --git a/phpmon/Domain/PHP/PhpExtension.swift b/phpmon-common/PHP/PhpExtension.swift similarity index 100% rename from phpmon/Domain/PHP/PhpExtension.swift rename to phpmon-common/PHP/PhpExtension.swift diff --git a/phpmon/Domain/PHP/PhpInstallation.swift b/phpmon-common/PHP/PhpInstallation.swift similarity index 100% rename from phpmon/Domain/PHP/PhpInstallation.swift rename to phpmon-common/PHP/PhpInstallation.swift diff --git a/phpmon-common/PHP/PhpSwitcher.swift b/phpmon-common/PHP/PhpSwitcher.swift new file mode 100644 index 0000000..213a237 --- /dev/null +++ b/phpmon-common/PHP/PhpSwitcher.swift @@ -0,0 +1,146 @@ +// +// PhpSwitcher.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 21/12/2021. +// Copyright © 2021 Nico Verbruggen. All rights reserved. +// + +import Foundation + +protocol PhpSwitcherDelegate: AnyObject { + func switcherDidStartSwitching() + func switcherDidCompleteSwitch() +} + +class PhpSwitcher { + + // MARK: - Initializer + + init() { + self.currentInstall = ActivePhpInstallation() + + let brewPhpAlias = Shell.pipe("\(Paths.brew) info php --json"); + + self.homebrewPackage = try! JSONDecoder().decode( + [HomebrewPackage].self, + from: brewPhpAlias.data(using: .utf8)! + ).first! + + print("When on your system, the `php` formula means version \(homebrewPackage.version)!") + } + + // MARK: - Properties + + /** The delegate that is informed of updates. */ + weak var delegate: PhpSwitcherDelegate? + + /** The static app instance. Accessible at any time. */ + static let shared = PhpSwitcher() + + /** Whether the switcher is busy performing any actions. */ + var isBusy: Bool = false + + /** All available versions of PHP. */ + var availablePhpVersions: [String] = [] + + /** Cached information about the PHP installations. */ + var cachedPhpInstallations: [String: PhpInstallation] = [:] + + /** Information about the currently linked PHP installation. */ + var currentInstall: ActivePhpInstallation + + /** + The version that the `php` formula via Brew is aliased to on the current system. + + If you're up to date, `php` will be aliased to the latest version, + but that might not be the case since not everyone keeps their + software up-to-date. + + As such, we take that information from Homebrew. + */ + static var brewPhpVersion: String { + return Self.shared.homebrewPackage.version + } + + /** + The currently linked and active PHP installation. + */ + static var phpInstall: ActivePhpInstallation { + return Self.shared.currentInstall + } + + /** + Information we were able to discern from the Homebrew info command. + */ + var homebrewPackage: HomebrewPackage! = nil + + // MARK: - Methods + + public static func detectPhpVersions() -> Void { + _ = Self.shared.detectPhpVersions() + } + + /** + Detects which versions of PHP are installed. + */ + public func detectPhpVersions() -> [String] + { + let files = Shell.pipe("ls \(Paths.optPath) | grep php@") + + var versionsOnly = extractPhpVersions(from: files.components(separatedBy: "\n")) + + // Make sure the aliased version is detected + // The user may have `php` installed, but not e.g. `php@8.0` + // We should also detect that as a version that is installed + let phpAlias = homebrewPackage.version + + // Avoid inserting a duplicate + if (!versionsOnly.contains(phpAlias) && Shell.fileExists("\(Paths.optPath)/php/bin/php")) { + versionsOnly.append(phpAlias) + } + + print("The PHP versions that were detected are: \(versionsOnly)") + + availablePhpVersions = versionsOnly + + var mappedVersions: [String: PhpInstallation] = [:] + + availablePhpVersions.forEach { version in + mappedVersions[version] = PhpInstallation(version) + } + + cachedPhpInstallations = mappedVersions + + return versionsOnly + } + + /** + Extracts valid PHP versions from an array of strings. + This array of strings is usually retrieved from `grep`. + */ + public func extractPhpVersions( + from versions: [String], + checkBinaries: Bool = true + ) -> [String] { + var output : [String] = [] + + versions.filter { (version) -> Bool in + // Omit everything that doesn't start with php@ + // (e.g. something-php@8.0 won't be detected) + return version.starts(with: "php@") + }.forEach { (string) in + let version = string.components(separatedBy: "php@")[1] + // Only append the version if it doesn't already exist (avoid dupes), + // is supported and where the binary exists (avoids broken installs) + if !output.contains(version) + && Constants.SupportedPhpVersions.contains(version) + && (checkBinaries ? Shell.fileExists("\(Paths.optPath)/php@\(version)/bin/php") : true) + { + output.append(version) + } + } + + return output + } +} diff --git a/phpmon-common/PhpSwitcher.swift b/phpmon-common/PhpSwitcher.swift deleted file mode 100644 index 17a824c..0000000 --- a/phpmon-common/PhpSwitcher.swift +++ /dev/null @@ -1,60 +0,0 @@ -// -// PhpSwitcher.swift -// PHP Monitor -// -// Created by Nico Verbruggen on 21/12/2021. -// Copyright © 2021 Nico Verbruggen. All rights reserved. -// - -import Foundation - -protocol PhpSwitcherDelegate: AnyObject { - func switcherDidStartSwitching() - func switcherDidCompleteSwitch() -} - -class PhpSwitcher { - - init() { - self.currentInstall = ActivePhpInstallation() - } - - /** The delegate that is informed of updates. */ - weak var delegate: PhpSwitcherDelegate? - - /** The static app instance. Accessible at any time. */ - static let shared = PhpSwitcher() - - /** Whether the switcher is busy performing any actions. */ - var isBusy: Bool = false - - /** All available versions of PHP. */ - var availablePhpVersions: [String] = [] - - /** Cached information about the PHP installations. */ - var cachedPhpInstallations: [String: PhpInstallation] = [:] - - /** Static accessor for `PhpSwitcher.shared.currentInstall`. */ - static var phpInstall: ActivePhpInstallation { - return Self.shared.currentInstall - } - - /** Information about the currently linked PHP installation. */ - var currentInstall: ActivePhpInstallation - - /** - The version that the `php` formula via Brew is aliased to on the current system. - - If you're up to date, `php` will be aliased to the latest version, - but that might not be the case. - */ - var brewPhpVersion: String { - return homebrewPackage.version - } - - /** - Information we were able to discern from the Homebrew info command. - */ - var homebrewPackage: HomebrewPackage! = nil - -} diff --git a/phpmon/Domain/Core/App.swift b/phpmon/Domain/Core/App.swift index 2a578d1..1743377 100644 --- a/phpmon/Domain/Core/App.swift +++ b/phpmon/Domain/Core/App.swift @@ -15,10 +15,6 @@ class App: PhpSwitcherDelegate { /** The static app instance. Accessible at any time. */ static let shared = App() - init() { - PhpSwitcher.shared.delegate = self - } - /** Retrieve the version number from the main info dictionary, Info.plist. */ static var version: String { let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as! String diff --git a/phpmon/Domain/Core/AppDelegate.swift b/phpmon/Domain/Core/AppDelegate.swift index ecd5420..6729135 100644 --- a/phpmon/Domain/Core/AppDelegate.swift +++ b/phpmon/Domain/Core/AppDelegate.swift @@ -20,16 +20,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele */ let sharedShell: Shell - /** - The PhpSwitcher singleton that handles PHP version - detection, as well as switching. - - - Note: It is important to initialize the switcher - before the `App` singleton, so that the delegate - is set correctly. - */ - let switcher: PhpSwitcher - /** The App singleton contains information about the state of the application and global variables. @@ -54,6 +44,13 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele */ let valet: Valet + /** + The PhpSwitcher singleton that handles PHP version + detection, as well as switching. It is initialized + when the app is ready and passed all checks. + */ + var switcher: PhpSwitcher! = nil + // MARK: - Initializer /** @@ -65,7 +62,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele print("Version \(App.version)") print("==================================") self.sharedShell = Shell.user - self.switcher = PhpSwitcher.shared self.state = App.shared self.menu = MainMenu.shared self.paths = Paths.shared @@ -73,6 +69,11 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele super.init() } + func initializeSwitcher() { + self.switcher = PhpSwitcher.shared + self.switcher.delegate = self.state + } + // MARK: - Lifecycle /** diff --git a/phpmon/Domain/Core/Startup.swift b/phpmon/Domain/Core/Startup.swift index 747a0d2..987cf45 100644 --- a/phpmon/Domain/Core/Startup.swift +++ b/phpmon/Domain/Core/Startup.swift @@ -6,6 +6,7 @@ // import Foundation +import AppKit class Startup { @@ -75,37 +76,30 @@ class Startup { ) if (!failed) { - determineBrewAliasVersion() + initializeSwitcher() + print("PHP Monitor has determined the application has successfully passed all checks.") success() } } /** - * In order to avoid having to hard-code which version of PHP is aliased to what specific subversion, - * PHP Monitor now determines the alias by checking the user's system. + Because the Switcher requires various environment guarantees, the switcher is only + initialized when it is done working. */ - private func determineBrewAliasVersion() - { - 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.pipe("\(Paths.brew) info php --json"); - - PhpSwitcher.shared.homebrewPackage = try! JSONDecoder().decode( - [HomebrewPackage].self, - from: brewPhpAlias.data(using: .utf8)! - ).first! - - print("When on your system, the `php` formula means version \(PhpSwitcher.shared.brewPhpVersion)!") + private func initializeSwitcher() { + DispatchQueue.main.async { + let appDelegate = NSApplication.shared.delegate as! AppDelegate + appDelegate.initializeSwitcher() + } } - + /** - * Perform an environment check. Will cause the application to terminate, if `breaking` is set to true. - * - * - 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 + Perform an environment check. Will cause the application to terminate, if `breaking` is set to true. + + - 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 */ private func performEnvironmentCheck( _ condition: Bool, diff --git a/phpmon/Domain/Integrations/Homebrew/HomebrewDiagnostics.swift b/phpmon/Domain/Integrations/Homebrew/HomebrewDiagnostics.swift index a41695c..b473c6f 100644 --- a/phpmon/Domain/Integrations/Homebrew/HomebrewDiagnostics.swift +++ b/phpmon/Domain/Integrations/Homebrew/HomebrewDiagnostics.swift @@ -46,12 +46,12 @@ class HomebrewDiagnostics { from: tapAlias.data(using: .utf8)! ).first! - if tapPhp.version != PhpSwitcher.shared.brewPhpVersion { + if tapPhp.version != PhpSwitcher.brewPhpVersion { print("The `php` formula alias seems to be the different between the tap and core. This could be a problem!") print("Determining whether both of these versions are installed...") let bothInstalled = PhpSwitcher.shared.availablePhpVersions.contains(tapPhp.version) - && PhpSwitcher.shared.availablePhpVersions.contains(PhpSwitcher.shared.brewPhpVersion) + && PhpSwitcher.shared.availablePhpVersions.contains(PhpSwitcher.brewPhpVersion) if bothInstalled { print("Both conflicting aliases seem to be installed, warning the user!") diff --git a/phpmon/Domain/Menu/MainMenu.swift b/phpmon/Domain/Menu/MainMenu.swift index 899bb94..3d6ccfa 100644 --- a/phpmon/Domain/Menu/MainMenu.swift +++ b/phpmon/Domain/Menu/MainMenu.swift @@ -41,7 +41,7 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate { When the environment is all clear and the app can run, let's go. */ private func onEnvironmentPass() { - _ = Actions.detectPhpVersions() + PhpSwitcher.detectPhpVersions() if HomebrewDiagnostics.shared.errors.contains(.aliasConflict) { DispatchQueue.main.async { diff --git a/phpmon/Domain/Menu/StatusMenu.swift b/phpmon/Domain/Menu/StatusMenu.swift index 7326462..8c8a5a7 100644 --- a/phpmon/Domain/Menu/StatusMenu.swift +++ b/phpmon/Domain/Menu/StatusMenu.swift @@ -40,14 +40,14 @@ class StatusMenu : NSMenu { servicesMenu.addItem(NSMenuItem(title: "mi_help".localized, action: nil, keyEquivalent: "")) - if !PhpSwitcher.shared.availablePhpVersions.contains(PhpSwitcher.shared.brewPhpVersion) { + if !PhpSwitcher.shared.availablePhpVersions.contains(PhpSwitcher.brewPhpVersion) { servicesMenu.addItem(NSMenuItem( - title: "mi_force_load_latest_unavailable".localized(PhpSwitcher.shared.brewPhpVersion), + title: "mi_force_load_latest_unavailable".localized(PhpSwitcher.brewPhpVersion), action: nil, keyEquivalent: "f" )) } else { servicesMenu.addItem(NSMenuItem( - title: "mi_force_load_latest".localized(PhpSwitcher.shared.brewPhpVersion), + title: "mi_force_load_latest".localized(PhpSwitcher.brewPhpVersion), action: #selector(MainMenu.forceRestartLatestPhp), keyEquivalent: "f")) } @@ -140,7 +140,7 @@ class StatusMenu : NSMenu { let versionString = long ? longVersion : shortVersion let action = #selector(MainMenu.switchToPhpVersion(sender:)) - let brew = (shortVersion == PhpSwitcher.shared.brewPhpVersion) ? "php" : "php@\(shortVersion)" + let brew = (shortVersion == PhpSwitcher.brewPhpVersion) ? "php" : "php@\(shortVersion)" let menuItem = PhpMenuItem( title: "\("mi_php_switch".localized) \(versionString) (\(brew))", action: (shortVersion == PhpSwitcher.phpInstall.version.short) ? nil : action, keyEquivalent: "\(shortcutKey)" diff --git a/phpmon/Domain/PHP/ActivePhpInstallation+Checks.swift b/phpmon/Domain/PHP/ActivePhpInstallation+Checks.swift new file mode 100644 index 0000000..c1b3124 --- /dev/null +++ b/phpmon/Domain/PHP/ActivePhpInstallation+Checks.swift @@ -0,0 +1,33 @@ +// +// ActivePhpInstallation.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 21/12/2021. +// Copyright © 2021 Nico Verbruggen. All rights reserved. +// + +import Foundation + +extension ActivePhpInstallation { + + /** + It is always possible that the system configuration for PHP-FPM has not been set up for Valet. + This can occur when a user manually installs a new PHP version, but does not run `valet install`. + In that case, we should alert the user! + + - Important: The underlying check is `checkPhpFpmStatus`, which can be run multiple times. + This method actively presents a modal if said checks fails, so don't call this method too many times. + */ + public func notifyAboutBrokenPhpFpm() { + if !self.checkPhpFpmStatus() { + DispatchQueue.main.async { + Alert.notify( + message: "alert.php_fpm_broken.title".localized, + info: "alert.php_fpm_broken.info".localized, + style: .critical + ) + } + } + } + +}