#!/usr/bin/env bash # # Brew PHP Switcher # # Use to switch between PHP versions installed via Homebrew. # # Usage: php-switcher [--help|--installed|--current|--auto] # Example: php-switcher 7.4 # Example: php-switcher 8.0 # Example: php-switcher latest # Example: php-switcher --auto # # Created by Ismo Vuorinen (2025) # Licensed under the MIT License (https://opensource.org/licenses/MIT) set -euo pipefail # Add error handling # Configuration LATEST_VERSION_FORMULA="php" # The formula name for latest PHP version PHP_VERSION_FILE=".php-version" # File name to look for when auto-switching # Switch brew php version function check_dependencies() { if ! command -v brew > /dev/null 2>&1; then echo "Error: Homebrew is not installed" exit 1 fi } function usage() { echo "Brew PHP Switcher - Switch between PHP versions installed via Homebrew" echo "" echo "Usage: php-switcher [options]" echo "" echo "Options:" echo " --help Show this help message" echo " --installed List installed PHP versions" echo " --current Show currently active PHP version" echo " --auto Auto-switch based on .php-version file in current directory" echo "" echo "Examples:" echo " php-switcher 7.4" echo " php-switcher 8.0" echo " php-switcher latest" echo " php-switcher --auto" echo "" echo "Auto-switching:" echo " Create a .php-version file in your project directory with a PHP version" echo " Example .php-version content: 8.1" echo "" exit 0 } function list_php_versions() { # Check Homebrew's installation path for PHP versions local brew_cellar brew_cellar="$(brew --cellar)" local php_paths=() local versions=() local formulas=() local active=() # Look for all PHP installations in Homebrew Cellar if [[ -d "$brew_cellar/php" ]]; then php_paths+=("$brew_cellar/php") fi # Look for versioned PHP installations while IFS= read -r dir; do if [[ -d $dir ]]; then php_paths+=("$dir") fi done < <(find "$brew_cellar" \ -maxdepth 1 -name 'php@*' \ -type d 2> /dev/null || echo "") if [[ ${#php_paths[@]} -eq 0 ]]; then echo "No PHP versions installed through Homebrew." return 1 fi # Find which version is currently linked local current_bin current_bin=$(readlink -f \ "$(command -v php 2> /dev/null)" \ 2> /dev/null || echo "") # Collect data for each installed PHP version for path in "${php_paths[@]}"; do local formula formula=$(basename "$path") local version_label if [[ $formula == "php" ]]; then version_label="latest" else version_label="${formula#php@}" fi # Find the actual version from the directory structure local version_dir version_dir=$(find "$path" -maxdepth 1 -type d \ | grep -v "^$path$" | sort -V | tail -1) if [[ -n $version_dir && -d "$version_dir/bin" ]]; then local full_version full_version=$("$version_dir/bin/php" -v 2> /dev/null \ | grep -oE 'PHP [0-9]+\.[0-9]+\.[0-9]+' \ | head -1 \ | cut -d' ' -f2 \ || echo "$version_label.x") # Determine if this is the active version local is_active="No" if [[ -n $current_bin && $current_bin == "$version_dir/bin/php" ]]; then is_active="Yes" fi # Handle the 'latest' case - replace with actual version number local display_version if [[ $version_label == "latest" ]]; then display_version="${full_version%.*}" # Get major.minor version else display_version="$version_label" fi # Store data for table display versions+=("$display_version") formulas+=("$formula") active+=("$is_active") fi done # Calculate maximum column widths local max_version_width=7 # "Version" header length local max_formula_width=7 # "Formula" header length local max_active_width=6 # "Active" header length local count=${#versions[@]} for ((i = 0; i < count; i++)); do # Update max widths if needed if [[ ${#versions[i]} -gt $max_version_width ]]; then max_version_width=${#versions[i]} fi if [[ ${#formulas[i]} -gt $max_formula_width ]]; then max_formula_width=${#formulas[i]} fi done # Build header with correct widths local header_format="| %-${max_version_width}s | %-${max_formula_width}s | " header_format+="%-${max_active_width}s |" local separator_line="|" for ((i = 0; i < max_version_width + 2; i++)); do separator_line="${separator_line}-" done separator_line="${separator_line}|" for ((i = 0; i < max_formula_width + 2; i++)); do separator_line="${separator_line}-" done separator_line="${separator_line}|" for ((i = 0; i < max_active_width + 2; i++)); do separator_line="${separator_line}-" done separator_line="${separator_line}|" # Print table header # shellcheck disable=SC2059 printf "$header_format\n" "Version" "Formula" "Active" echo "$separator_line" # Print table rows local row_format="| %-${max_version_width}s | %-${max_formula_width}s | " row_format+="%-${max_active_width}s |" for ((i = 0; i < count; i++)); do # shellcheck disable=SC2059 printf "$row_format\n" "${versions[i]}" "${formulas[i]}" "${active[i]}" done } function get_php_formula_for_version() { local version="$1" # Handle "latest" as a special case if [[ $version == "latest" ]]; then echo "$LATEST_VERSION_FORMULA" return 0 fi # The regular version case (e.g., 7.4, 8.1) echo "php@$version" } function check_formula_installed() { local formula="$1" local brew_cellar brew_cellar="$(brew --cellar)" if [[ $formula == "php" ]]; then if [[ -d "$brew_cellar/php" ]]; then return 0 fi elif [[ -d "$brew_cellar/$formula" ]]; then return 0 fi return 1 } function unlink_current_php() { local current_formula="" # Find formulas more safely while IFS= read -r formula; do if [[ -n $formula ]]; then local linked linked=$(brew info --json=v1 "$formula" \ | grep -o '"linked_keg":"[^"]*"' \ | grep -v ':"null"') if [[ -n $linked ]]; then current_formula="$formula" break fi fi done < <(brew list --formula | grep -E '^php(@[0-9]+\.[0-9]+)?$' || echo "") # If we found a linked formula, unlink it if [[ -n $current_formula ]]; then echo "Unlinking current PHP version ($current_formula)..." brew unlink "$current_formula" > /dev/null 2>&1 || true fi } function link_php_version() { local formula="$1" if ! check_formula_installed "$formula"; then echo "Error: PHP formula '$formula' is not installed" echo "Available versions:" list_php_versions exit 1 fi echo "Linking $formula..." if ! brew link --force --overwrite "$formula" > /dev/null 2>&1; then echo "Error: Failed to link $formula. Try running manually:" echo " brew link --force --overwrite $formula" exit 1 fi # Verify the switch worked if ! command -v php > /dev/null 2>&1; then echo "Warning: PHP was linked but may not be working correctly" fi } function get_current_version() { if ! command -v php > /dev/null 2>&1; then echo "No PHP currently linked" return 1 fi local version version=$(php -v 2> /dev/null \ | grep -oE 'PHP [0-9]+\.[0-9]+\.[0-9]+' \ | head -1) if [[ -z $version ]]; then echo "Unable to determine PHP version" return 1 fi # Find the corresponding formula local current_version current_version=$(echo "$version" | cut -d' ' -f2) local major_minor major_minor=$(echo "$current_version" | cut -d'.' -f1,2) # Check if it's the latest version if check_formula_installed "php" \ && brew info --json=v1 php \ | grep -o '"linked_keg":"[^"]*"' \ | grep -v ':"null"' \ | grep -q .; then echo "Current PHP version: $current_version (latest)" else echo "Current PHP version: $current_version (php@$major_minor)" fi } function validate_version() { local version="$1" # Valid formats: x.y or latest if [[ ! $version =~ ^([0-9]+\.[0-9]+|latest)$ ]]; then echo "Error: Invalid PHP version format. Use x.y format (e.g., 7.4) or" echo " 'latest'" exit 1 fi } function find_php_version_file() { local dir="$PWD" # Look for .php-version file in current directory and all parent directories while [[ $dir != "/" ]]; do if [[ -f "$dir/$PHP_VERSION_FILE" ]]; then echo "$dir/$PHP_VERSION_FILE" return 0 fi dir=$(dirname "$dir") done # Check the root directory as well if [[ -f "/$PHP_VERSION_FILE" ]]; then echo "/$PHP_VERSION_FILE" return 0 fi return 1 } function auto_switch_php_version() { local version_file # Try to find a .php-version file version_file=$(find_php_version_file) || { echo "No .php-version file found in current directory or any parent" echo "directory. Create a $PHP_VERSION_FILE file with your desired" echo "PHP version (e.g., 8.1)" exit 1 } # Read the version from the file local version version=$(tr -d '[:space:]' < "$version_file") echo "Found $PHP_VERSION_FILE file at: $version_file" echo "Requested PHP version: $version" # Validate the version validate_version "$version" # Switch to the specified version switch_php_version "$version" } function switch_php_version() { local version="$1" # Get the formula name for the version local formula formula=$(get_php_formula_for_version "$version") # Check if the requested PHP version is installed if ! check_formula_installed "$formula"; then echo "Error: PHP version $version is not installed" echo "" list_php_versions exit 1 fi # Get the current version info for comparison local current_info current_info=$(get_current_version 2> /dev/null || echo "None") # Skip if we're already on the requested version if [[ $current_info == *"$version"* ]]; then echo "PHP version $version is already active" exit 0 fi # Perform the switch unlink_current_php link_php_version "$formula" # Verify the switch echo "" echo "Switched to:" get_current_version echo "" echo "PHP executable: $(command -v php)" } function main() { local version="" # Parse arguments case "${1:-}" in --help) usage ;; --installed) list_php_versions exit 0 ;; --current) get_current_version exit 0 ;; --auto) auto_switch_php_version exit 0 ;; "") usage ;; *) version="$1" ;; esac # Validate and switch to the specified version validate_version "$version" switch_php_version "$version" } # Run the script check_dependencies if [[ ${#@} -eq 0 ]]; then usage else main "$@" fi # vim: ft=bash sw=4 ts=4 et tw=80 cc=80 :