# PVM (PHP Version Manager)
[](https://github.com/WebProject-xyz/php-version-manager/actions/workflows/release.yml)
[](https://www.gnu.org/licenses/gpl-3.0)
[](https://crates.io/crates/php-version-manager)
Native, blazing fast, zero-configuration PHP version manager for Arch Linux and other Linux/macOS environments, heavily inspired by [fnm](https://github.com/Schniz/fnm).
PVM uses pre-compiled static PHP CLI binaries from [Static PHP CLI (SPC)](https://dl.static-php.dev/) to completely bypass compilation times and library dependency hell on Linux.
## Features
- 🚀 **Blazing Fast**: Written in Rust natively. Execution means zero overhead compared to Docker wrappers.
- ✨ **Zero Configuration**: Auto-switches PHP versions based on `.php-version` files.
- 📦 **Static Binaries**: No compilation needed. The `pvm install` command instantly downloads self-contained executables with most common extensions pre-baked.
- 🧩 **Multi-Package Selection**: Pick which packages to install per version — `cli`, `fpm`, and/or `micro` (`micro.sfx`) — via an interactive MultiSelect prompt. `cli` is the default.
- 🐘 **Native Composer Support**: Works out of the box with your system's global Composer without any explicit proxy or configuration.
- 🖱️ **Interactive TUI Menus**: Run `pvm` without arguments to launch a master selection menu. Or run commands like `pvm use` / `pvm ls-remote` / `pvm uninstall` without parameters to select actions via a visual UI.
- 🏷️ **Smart Aliasing**: Install and use patches cleanly by saying `pvm install 8.4`. PVM dynamically figures out the highest patch (`8.4.18`) underneath the hood.
- 🔄 **Patch Update Check**: `pvm use 8.4` notices when a newer patch (e.g. `8.4.19`) is available and offers to install and switch in one step.
- 📝 **`.php-version` Bootstrap**: `pvm init` interactively picks a major.minor and writes `.php-version` for the current directory.
- 🗑️ **Clean Uninstall**: `pvm uninstall` (alias `rm` / `remove`) removes a version's binaries; warns when removing the active one.
- 🐚 **Multi-Shell**: Bash, Zsh, and Fish wrappers generated by `pvm env`, with concurrency-safe per-PID env files locked via `fs4`.
- ⚡ **Cached Cloud Resolution**: Quickly check for new versions on `dl.static-php.dev` under lightning-fast 24-hour JSON caching.
## Installation
We provide an automatic install script that detects your platform exactly like `fnm` and downloads the pre-compiled native `pvm` binary directly from GitHub Releases into `~/.local/share/pvm/bin`, and then instructs you how to append the hook to your profile.
**Using a script (macOS/Linux)**
```bash
**Building from Source**
If you prefer to compile the application from scratch using Rust:
```bash
git clone git@github.com:WebProject-xyz/php-version-manager.git
cd php-version-manager
chmod +x build.sh
./build.sh
```
## Usage
```bash
# Enter the master interactive TUI menu
pvm
# Install a specific PHP version (by minor alias or fully-qualified).
# Opens a MultiSelect to pick packages: cli (default), fpm, micro.
pvm install 8.4 # alias: pvm i 8.4
# Install the absolute latest version available
pvm install latest
# Use a version in the current shell.
# Auto-prompts to install + switch if a newer patch exists upstream.
pvm use 8.4
# List all local installed versions alongside their specific aliases
pvm ls # alias for: pvm list
# Interactively view and install available cloud versions
pvm ls-remote # alias: pvm list-remote
# Print the currently active PHP version
pvm current
# Remove an installed version (interactive picker if no arg).
pvm uninstall 8.3 # aliases: pvm rm 8.3 / pvm remove 8.3
# Write a .php-version file for this directory (interactive picker)
pvm init
# Check for and apply updates to pvm itself
pvm self-update # optional: pvm self-update --apply to apply automatically
```
### Auto-Switching
If you run `pvm init` or manually create a `.php-version` file in a project directory containing `8.3`, PVM will automatically switch to your best local `8.3.x` patch when you `cd` into that folder. The `cd` hook is installed via `pvm env` (Bash, Zsh, or Fish — auto-detected from `$SHELL`).
## Packages
Each PHP version can ship up to three binaries; you pick which during `pvm install` via the MultiSelect prompt. All land under `$PVM_DIR/versions/<full-semver>/bin/`. Upstream reference for all three SAPIs: [static-php.dev — SAPI Reference](https://static-php.dev/en/guide/sapi-reference.html).
| `cli` (default) | `php` | Standard command-line PHP — runs scripts, REPL via `php -a`, drives Composer. See [SAPI Reference: CLI](https://static-php.dev/en/guide/sapi-reference.html#cli). |
| `fpm` | `php-fpm` | FastCGI Process Manager for serving PHP behind nginx/Caddy/Apache. Setup details in the next section + [SAPI Reference: FPM](https://static-php.dev/en/guide/sapi-reference.html#fpm). |
| `micro` | `micro.sfx` | [phpmicro](https://github.com/easysoft/phpmicro) self-contained executable stub — concat with a `.php` or `.phar` to ship a single-file PHP app. Combining requires the upstream [`spc`](https://static-php.dev/en/guide/getting-started.html) toolchain (`spc micro:combine app.phar --output=app`); pvm only delivers the stub. See [SAPI Reference: Micro](https://static-php.dev/en/guide/sapi-reference.html#micro). |
After `pvm use <version>`, every selected binary is on `$PATH` (CLI as `php`, FPM as `php-fpm`); `micro.sfx` stays at its absolute path since it's a build artifact, not something you invoke directly.
## Running PHP-FPM
> Upstream reference: [static-php.dev — SAPI Reference: FPM](https://static-php.dev/en/guide/sapi-reference.html#fpm) documents the binary's CLI flags (`-y`, `-c`, `-t`), a minimal `php-fpm.conf`, and an nginx FastCGI block. The guide below extends that with service wiring (systemd / launchd) and pvm-specific paths.
PVM downloads a static `php-fpm` binary alongside `php` when you tick the `fpm` package during `pvm install`. The static-php-cli tarball ships only the binary — no `php-fpm.conf`, no pool files, no init script — so you wire those up yourself. The binary lives next to the CLI at:
```text
$PVM_DIR/versions/<full-semver>/bin/php-fpm
```
`$PVM_DIR` defaults to `~/.local/share/pvm`. After running `pvm use 8.4` it is also on `$PATH` as plain `php-fpm`.
### 1. Install the fpm package
```bash
pvm install 8.4
# When the MultiSelect prompt appears, tick "fpm" (and "cli" if you want both).
pvm use 8.4
php-fpm -v # confirm it resolves to the pvm-managed binary
which php-fpm # → ~/.local/share/pvm/versions/8.4.x/bin/php-fpm
```
### 2. Create a minimal config
Put these under `~/.config/php-fpm/` (any path works — the binary takes `-y` and `-c`):
`~/.config/php-fpm/php-fpm.conf`:
```ini
[global]
pid = /tmp/php-fpm.pid
error_log = /tmp/php-fpm.log
daemonize = no
include = /home/YOU/.config/php-fpm/pool.d/*.conf
```
`~/.config/php-fpm/pool.d/www.conf`:
```ini
[www]
user = YOU
group = YOU
listen = 127.0.0.1:9000
; or a unix socket:
; listen = /tmp/php-fpm-www.sock
; listen.owner = YOU
; listen.group = YOU
; listen.mode = 0660
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
catch_workers_output = yes
clear_env = no
```
Replace `YOU` with your username (`whoami`).
### 3. Run it in the foreground
```bash
# Validate config first
php-fpm -y ~/.config/php-fpm/php-fpm.conf -t
# Foreground run, logs to stdout
php-fpm -y ~/.config/php-fpm/php-fpm.conf -F
# With a custom php.ini (the static binary has no compiled-in ini path)
php-fpm -c ~/.config/php-fpm/php.ini -y ~/.config/php-fpm/php-fpm.conf -F
```
Flag summary (matches upstream [SAPI Reference: FPM](https://static-php.dev/en/guide/sapi-reference.html#fpm)):
- `-y <file>` — `php-fpm.conf` path (required, no default for static builds)
- `-c <file>` — `php.ini` path (optional; without it, fpm runs with hard-coded defaults)
- `-t` — validate config and exit
- `-F` — stay in foreground (don't fork to daemon)
- `-v` — print version
- `-m` — list compiled-in extensions
### 4. Run it as a service
**systemd (Linux, user unit)** — `~/.config/systemd/user/php-fpm.service`:
```ini
[Unit]
Description=PHP-FPM (managed by pvm)
After=network.target
[Service]
Type=simple
ExecStart=%h/.local/share/pvm/versions/8.4.18/bin/php-fpm -y %h/.config/php-fpm/php-fpm.conf -F
Restart=on-failure
[Install]
WantedBy=default.target
```
```bash
systemctl --user daemon-reload
systemctl --user enable --now php-fpm
journalctl --user -u php-fpm -f
```
Pin the full semver in `ExecStart` (e.g. `8.4.18`) — symlinking to `versions/8.4` is not maintained by pvm, so a future `pvm install 8.4` that resolves to `8.4.19` will not move the service.
**launchd (macOS)** — `~/Library/LaunchAgents/dev.pvm.php-fpm.plist`:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key><string>dev.pvm.php-fpm</string>
<key>ProgramArguments</key>
<array>
<string>/Users/YOU/.local/share/pvm/versions/8.4.18/bin/php-fpm</string>
<string>-y</string>
<string>/Users/YOU/.config/php-fpm/php-fpm.conf</string>
<string>-F</string>
</array>
<key>RunAtLoad</key><true/>
<key>KeepAlive</key><true/>
<key>StandardOutPath</key><string>/tmp/php-fpm.out.log</string>
<key>StandardErrorPath</key><string>/tmp/php-fpm.err.log</string>
</dict>
</plist>
```
```bash
launchctl load ~/Library/LaunchAgents/dev.pvm.php-fpm.plist
```
### 5. Hook up nginx
```nginx
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
```
### Notes
- The static binary is self-contained — no system libphp / no extension `.so` files. Run `php-fpm -m` to list the extensions baked into your build.
- Switching the active CLI via `pvm use 8.3` does **not** restart your fpm service; the service runs whichever absolute path you wired into the unit/plist. Bump the path and reload when you upgrade.
- For multiple parallel versions (e.g. 8.3 + 8.4), run two services on different ports/sockets — `pvm` does not multiplex fpm for you.