PHP version management (phpenv / phpbrew)
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.
# 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.0Each 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.
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.0macOS Homebrew approach
The simplest approach on macOS: install multiple php@X.Y formulas and switch with brew link:
brew install php@8.1 php@8.4
brew unlink php@8.1
brew link php@8.4 --force --overwriteAdd 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
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 messageInterview 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).