Titouan Mathis

Developer & CTO at Studio Meta

A simple PHP version manager

15/06/2022 in #PHP #ZSH #scripts #terminal

I have multiple versions of PHP installed with Homebrew on my computer and it has always been a nightmare to use a specific version in the terminal for different commands. For example, installing Composer dependencies with a specific PHP version:

/usr/bin/local/php73 $(which composer) install

I ended up writing a small ZSH function to easily switch from one version to another à la nvm. It is quite simple under the hood as it simply changes the content of the $PATH variable so that the php command points to the desired version.

The function can be used as follows:

# Use a specific version
pvm use 7.4

# Use the system default version
pvm use system

# Try to guess a version from a `composer.json` file up in the folder tree
pvm use

# Display the current version
pvm current

# List available versions
pvm list
pvm ls

The pvm use command can be used without specifying a version, it will try to find a composer.json file up in the folder tree and guess a PHP version from its dependencies.

Disclaimer

This is a script created specificly to work on my local environment, I can not guarantee that it will work correctly on any other environment. Use it at your own risk.

The script

You will find the script below, to start using it, add it to your ~/.zshrc file or create a separate file and add the following to your ~/.zshrc:

source /path/to/pvm.zsh

You also will need to have multiple versions of PHP installed with Homebrew as well as the jq CLI:

brew install jq \
    shivammathur/php/php \
    shivammathur/php/[email protected] \
    shivammathur/php/[email protected] \
    shivammathur/php/[email protected] \
    shivammathur/php/[email protected] \
    shivammathur/php/[email protected] \
    shivammathur/php/[email protected] \
    shivammathur/php/[email protected]

Here is the script:

#!/bin/zsh

# Alias PHP version to Brew versions
function pvm() {
    zparseopts -D -E -a opts -verbose::=verbose v::=version -version::=version
    VERSION='0.0.2'
    brew_prefix=$(brew --prefix)

    function log() {
        if [[ $verbose ]]; then
            echo $1
        fi
    }

    function get_php_bin_path() {
        ls -d $brew_prefix/Cellar/php*/$1*/bin
    }

    function get_php_versions() {
        ls -d $brew_prefix/Cellar/php*/* | grep -E -o '/[0-9]+.[0-9]+' | sed -E 's#/##'
    }

    function set_php_version() {
        _current_php_version=$(pvm current)
        _current_path=$(get_php_bin_path "$_current_php_version")
        _old_path=$(php -r "echo str_replace('$_current_path:', '', '$PATH');")

        if [[ -z $1 ]]; then
            export PATH="$_old_path"
            return
        fi

        PHP_VERSIONS="$(get_php_versions)"

        _version_exists="$(php -r "echo preg_match('/^$1$/m', '$PHP_VERSIONS');")"

        if [[ $_version_exists == "0" ]]; then
            echo "Could not find PHP version $1, is it installed?";
            return
        fi

        _new_path=$(get_php_bin_path $1)
        _old_path=$(php -r "echo str_replace('$_new_path:', '', '$_old_path');")
        export PATH="$_new_path:$_old_path"
    }

    log "pvm@$VERSION"

    if [[ $1 == 'current' ]]; then
        local PHP_VERSION=$(php -v | grep -Eo 'PHP [0-9]+.[0-9]+')
        echo ${PHP_VERSION/PHP /};
        return;
    fi

    if [[ $1 == 'use' ]]; then
        if [[ $2 == 'system' ]]; then
            log 'use system'
            set_php_version
        elif [[ -n $2 ]]; then
            log "use $2"
            set_php_version $2
        else
            composerjson_path=$(_find_up composer.json | tr -d '[:space:]')
            if [[ -a $composerjson_path/composer.json ]]; then
                pvm_version=$(cat $composerjson_path/composer.json | jq '.require.php' | grep -Eo '[0-9](.[0-9])?' | head -n 1)
                log "use v$pvm_version from composer.json"
                if [[ -n $pvm_version ]]; then
                    set_php_version $pvm_version
                    return
                fi
            fi
        fi
        return
    fi

    if [[ $1 == 'list' || $1 == 'ls' ]]; then
        get_php_versions
        return
    fi

    if [[ $version == '--version' || $version == '-v' ]]; then
        echo $VERSION
        return
    fi;

    echo "pvm $VERSION
A fast and simple PHP manager

USAGE:
    pvm <SUBCOMMAND>

FLAGS:
    -h, --help
        Prints help information

    -v, --version
        Prints version information

SUBCOMMANDS:
    current       Print the current PHP version
    use [version] Change PHP version
    list          List all available PHP versions [alias: ls]
"
}