0

PHP version management (phpenv / phpbrew)

Beginner5 min read·php-01-007

Concept

PHP version management tools let you install, switch between, and isolate multiple PHP versions on a single machine. This is essential when you maintain legacy projects (PHP 7.4, 8.1) alongside new ones (PHP 8.4), or when testing whether your code is compatible across versions before deployment.

The two main tools are phpbrew (pure PHP, cross-platform) and phpenv (based on rbenv/pyenv conventions). On macOS, Homebrew with php@version formulas plus brew link is often simpler than either.

phpbrew

phpbrew compiles PHP from source with custom variant flags. This gives you full control over which extensions are compiled in, but compilation takes 5–20 minutes.

bash
# Install phpbrew
curl -L -O https://github.com/phpbrew/phpbrew/releases/latest/download/phpbrew.phar
chmod +x phpbrew.phar && sudo mv phpbrew.phar /usr/local/bin/phpbrew

# Initialize
phpbrew init
source ~/.phpbrew/bashrc   # Add to .bashrc/.zshrc

# List available versions
phpbrew known

# Install PHP 8.4 with common variants
phpbrew install 8.4 +default +pdo +mysql +mbstring +curl +zip

# Switch versions
phpbrew switch php-8.4.0

# Use a version only in current shell
phpbrew use php-8.1.0

Each installed version lives in ~/.phpbrew/php/php-x.y.z/ with its own php.ini, extensions, and binaries.

phpenv

phpenv follows the shims-based model from rbenv: a ~/.phpenv/shims/php binary intercepts all php calls and delegates to the correct version based on .php-version files in the project directory.

bash
git clone https://github.com/phpenv/phpenv.git ~/.phpenv
export PATH="$HOME/.phpenv/bin:$PATH"
eval "$(phpenv init -)"

# Uses php-build under the hood for compilation
phpenv install 8.4.0
phpenv global 8.4.0

# Per-project version
echo "8.4.0" > /path/to/project/.php-version
cd /path/to/project && php -v  # automatically uses 8.4.0

macOS Homebrew approach

The simplest approach on macOS: install multiple php@X.Y formulas and switch with brew link:

bash
brew install php@8.1 php@8.4
brew unlink php@8.1
brew link php@8.4 --force --overwrite

Add the active PHP to PATH in your shell config. This approach doesn't provide per-directory switching but works well when you consciously manage which version is active globally.

Docker as version management

For the strictest isolation, Docker sidesteps the version management problem entirely: each project's docker-compose.yml specifies image: php:8.4-fpm-alpine, and the container provides an isolated environment with exactly the right PHP version and extensions. This is the approach used in CI and production, and it's increasingly common for local development too (via Laravel Sail or custom Compose files).

Code Example

php
<?php
declare(strict_types=1);

// Check current PHP version programmatically and enforce minimum requirements
// Useful at the top of a library or framework bootstrap

const REQUIRED_PHP_VERSION = '8.4.0';

if (version_compare(PHP_VERSION, REQUIRED_PHP_VERSION, '<')) {
    $message = sprintf(
        "This application requires PHP %s or higher. Current: PHP %s\n",
        REQUIRED_PHP_VERSION,
        PHP_VERSION
    );
    fwrite(STDERR, $message);
    exit(1);
}

// Version comparison utilities
echo PHP_VERSION . "\n";                    // "8.4.1"
echo PHP_MAJOR_VERSION . "\n";             // 8
echo PHP_MINOR_VERSION . "\n";             // 4
echo PHP_RELEASE_VERSION . "\n";           // 1

// Check for specific feature availability (better than version checks)
if (!function_exists('array_find')) {
    // array_find() was added in PHP 8.4 — provide polyfill for older versions
    function array_find(array $array, callable $callback): mixed
    {
        foreach ($array as $value) {
            if ($callback($value)) {
                return $value;
            }
        }
        return null;
    }
}

// A .php-version file used by phpenv — just contains the version string
// Contents: 8.4.0
// Placed in project root alongside composer.json

// Composer also enforces PHP version via composer.json:
// "require": { "php": "^8.4" }
// Running `composer install` on PHP 8.1 will fail with a clear message

Interview Q&A

Q: How do you manage multiple PHP versions on a development machine and why does it matter?

The most common approaches are phpbrew (compiles from source, full control over extensions), phpenv (shims-based, per-directory .php-version files), or on macOS Homebrew with php@X.Y formulas. The practical choice depends on your workflow: if you frequently switch between projects using different PHP versions, phpenv's per-directory switching is invaluable — you cd into a project and PHP automatically switches. If you need custom compile-time options (specific OpenSSL version, custom extension), phpbrew gives more control. For CI and production, Docker images (php:8.4-fpm) provide the cleanest isolation, and many teams now use Docker for local development too to eliminate the "works on my machine" problem entirely.


Q: If you set php@8.4 as active with Homebrew but php artisan uses the wrong version, what would you check?

First, run which php and php -v in the same shell to confirm the active binary. Then check if your shell's PATH has an older PHP appearing before Homebrew's path — run echo $PATH. Homebrew's PHP should be at /opt/homebrew/opt/php@8.4/bin (Apple Silicon) or /usr/local/opt/php@8.4/bin. Also check if a phpenv shim is intercepting the php call (run which php — if it shows ~/.phpenv/shims/php, phpenv is overriding Homebrew). Finally, check if the project has a .php-version file that phpenv is respecting. The key insight is PATH order: the first php binary found in PATH wins.


Q: When would you use Docker instead of a PHP version manager for local development?

Docker becomes the right choice when: (1) your app has infrastructure dependencies (Redis, MySQL, Elasticsearch) that vary by project and you don't want them all running on your machine simultaneously, (2) you need exact parity with production (same PHP version, same extensions, same OS), (3) your team includes non-PHP developers who shouldn't need to manage PHP installations, (4) you use PHP extensions that are painful to compile locally (like swoole or imagick). Laravel Sail provides a pre-configured Docker Compose setup for exactly this scenario. The tradeoff is Docker adds startup time and file system performance overhead (on macOS, file I/O through the Docker VM is noticeably slower than native).