Skip to content

Latest commit

 

History

101 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Shepherd

Per-project PHP version switching on Windows — automatic, instant, zero-config.

Drop a .phpversion file in your project, and php / composer use the right version. No manual switching, no batch scripts, no broken PATH.

CI License: MIT

# Inside project A
~/projects/api $ php -v
PHP 8.3.12 (cli) ...

# Just change directory — Shepherd handles the rest automatically
~/projects/api $ cd ../frontend
~/projects/frontend $ php -v
PHP 8.5.7 (cli) ...

Why

Laravel Herd installs multiple PHP versions side by side, but doesn't let you pin a version per project from the CLI. The usual workaround is batch scripts wrapping php.exe — fragile, slow, and broken across terminals.

Shepherd replaces that with a single compiled binary (~2 MB) that acts as a transparent shim for php and composer. It reads .phpversion, resolves the right php.exe from Herd's installations, syncs nginx, and gets out of the way.

Shepherd does not alter your Herd installation or replace its binaries. It sits in your user profile PATH, layers on top of Herd, and falls back to Herd's default behavior when no project configuration is found.

No subshells. No recursion. No race conditions.

Requirements

Quick start

  1. Download the latest release from the releases page
  2. Run shp.exe — it detects it's not installed and offers to set everything up:
Shepherd is not installed yet. Install now? [Y/n]
  1. Restart your terminal, then:
shp use 8.4      # writes .phpversion
php -v           # → PHP 8.4.x
composer install # → uses PHP 8.4

That's it. The installer places shims (php.exe, composer.exe, shp.exe) in %USERPROFILE%\.config\shepherd\bin, prepends it to your PATH, and broadcasts the change.

CI / non-interactive: Use shp install in scripts. Interactive prompts are auto-skipped when stdin is not a terminal.

How it works

  1. Reads .phpversion from the current directory, walking up the tree (like .nvmrc)
  2. Resolves the matching php.exe from Herd's installs (~/.config/herd/bin/phpXX/)
  3. Falls back to herd.phar which-php when no dotfile is found
  4. Syncs Herd's nginx config so your .test domain matches the CLI version
  5. Execs the real php.exe — transparent, no wrapper overhead

Commands

Command Description
shp use [version] Set the PHP version for the current project (latest, auto)
shp run <ver> -- … Run a command with a specific PHP version
shp which Show resolved PHP path and source
shp current Print the resolved PHP version
shp list, ls List available PHP versions
shp status Show configuration overview
shp xdebug <cmd> Manage xdebug (on, off, toggle, profile, trace, run)
shp ext add <name> Install a PHP extension (DLL + deps + ini)
shp ext list List installed extensions (alias ls)
shp ext remove <n> Remove an installed extension (alias rm)
shp coverage <cmd> Manage the code coverage driver (status, install, run)
shp reverb [cmd] Show Laravel Reverb status (status, env)
shp doctor Diagnose common setup issues
shp self-update Update to the latest release (SHA256-verified)
shp install Install shims and configure PATH
shp guard <cmd> Manage the optional PATH Guard (enable, disable, status)
shp uninstall Remove shims and restore PATH
shp version Show current version (--version, -v)
shp help Show built-in help (-h, --help)

Global flags

Flag Description
--verbose Extra diagnostic output
--quiet, -q Suppress non-essential output
--json Machine-readable JSON output
--no-interactive Skip prompts (auto-detected in CI)

Command flags

Flag Applies to Description
--force, -f install Reinstall, killing running shim processes first
--enable-path-guard install Enable the PATH Guard without prompting
--php=X.Y, --php=all ext * Target a specific PHP version, or all of them
--ext-version=V ext add Pin the extension version (default: latest PECL)
--ts ext add Use the Thread Safe build (default: NTS)
--vs=vsNN ext add Visual Studio build tag (default: derived from the PHP version)
--port=PORT reverb Reverb listen port (default: 8443)

Xdebug management

Toggle xdebug without editing php.ini manually:

shp xdebug on          # enable (mode=debug)
shp xdebug coverage    # switch to coverage mode
shp xdebug profile     # enable profiling (sets output_dir to .)
shp xdebug trace       # enable function tracing (sets output_dir to .)
shp xdebug off         # disable
shp xdebug toggle      # quick on/off

Works on the PHP version resolved for the current project.

Stateless xdebug

Profile or trace a single command without touching your config — one-off, no side effects:

shp xdebug run trace -- php artisan migrate
shp xdebug run profile -- php artisan test
shp xdebug run debug -- php script.php

Xdebug is injected for that invocation only, then gone.

Code coverage

Coverage has two drivers, and php-code-coverage picks one on its own — preferring pcov, which is several times faster than xdebug. shp coverage resolves the driver per command instead of leaving both enabled in php.ini:

shp coverage install                                    # add pcov (all versions: --php=all)
shp coverage status                                      # which driver would be used
shp coverage run -- php artisan test --coverage
shp coverage run -- vendor/bin/phpunit --coverage-text

pcov is installed with pcov.enabled=0, so it costs nothing on normal runs; coverage run enables it for that invocation only. Nothing is written to php.ini.

pcov reports line coverage only. For branch or path coverage, use shp xdebug coverage.

Running with a different version

Test compatibility with another PHP version without modifying .phpversion:

shp run 8.3 -- php artisan test
shp run 8.3 -- composer install
shp run latest -- php -v

The resolved version applies only to that command. Your .phpversion stays untouched.

With --json, both shp run and shp xdebug run capture and return structured output:

shp --json run 8.3 -- php -v
# {"exitCode":0,"phpVersion":"8.3","stdout":"PHP 8.3.12 ...","stderr":""}

Auto-detection from composer.json

Don't know which version to pin? Let Shepherd figure it out:

shp use auto

Reads require.php from composer.json, picks the highest installed version that satisfies the constraint, and writes .phpversion. Useful for onboarding onto an existing project.

Extension management

Install extensions not bundled with Herd — no manual DLL download:

shp ext add redis
shp ext add imagick
shp ext add sqlsrv --php=all   # all installed versions

Supported: brotli, igbinary, imagick, memcached, pcov, pdo_sqlsrv, redis, sqlsrv.

Handles PECL lookup, DLL download, system deps (winget), ini registration, and verification.

List what's installed and remove what you don't need:

shp ext list               # show extensions for the current PHP version
shp ext list --php=8.3     # or for a specific one
shp ext remove redis       # remove DLL and php.ini directive
shp ext remove redis --php=all

Pin a build when the defaults don't fit:

shp ext add redis --ext-version=6.0.2   # instead of the latest PECL release
shp ext add redis --ts                  # Thread Safe build (default: NTS)
shp ext add redis --vs=vs16             # older Visual Studio build tag

Laravel Reverb

Reverb serves WebSockets directly over TLS using Herd's certificates, so no nginx reverse proxy is needed.

shp reverb              # connectivity status for the current project
shp reverb env          # the .env variables to copy
shp reverb --port=9000  # non-default listen port (default: 8443)

Self-update

shp self-update

Downloads the latest release, verifies SHA256, and replaces all shims. Releases are signed with cosign — see SECURITY.md.

PATH Guard (opt-in)

Herd updates can silently reorder your User PATH, putting its own bin directory back before Shepherd. The PATH Guard is a lightweight background process that watches HKCU\Environment\Path and restores the correct order automatically.

shp guard enable    # create a per-user scheduled task (runs at login)
shp guard status    # check if the guard is active
shp guard disable   # remove the task

The guard:

  • Runs with standard user privileges, no admin required
  • Uses no network access
  • Is proposed during shp install (default: No)
  • Can be enabled non-interactively: shp install --enable-path-guard
  • Is automatically stopped and removed by shp uninstall
  • Is refreshed when you run shp self-update

Multicall binary

The binary detects how it was invoked:

Invoked as Behavior
php / php.exe Transparent PHP shim
composer / composer.exe Runs composer.phar via resolved PHP
shp.exe Management commands

One binary, three names — shp install sets them all up.

IDE integration

Point your IDE's PHP interpreter to the Shepherd shim so static analysis matches your terminal:

PhpStorm: Settings → PHP → CLI Interpreter → ... → Add Local → path:

%USERPROFILE%\.config\shepherd\bin\php.exe

VS Code (Intelephense / PHP Intelephense): In .vscode/settings.json:

{
  "php.validate.executablePath": "${env:USERPROFILE}/.config/shepherd/bin/php.exe"
}

The shim resolves the correct PHP version per project, so the IDE always uses the same binary as your terminal.

Troubleshooting

shp doctor

Checks: Herd presence, .phpversion validity, shim installation, PATH order, shell aliases, Developer Mode, CA certificate, nginx config, PHP-CGI ports.

Common fixes:

  • Wrong PHP versionshp status to check PATH order, then shp install + restart terminal
  • Version not found → install it from the Herd UI
  • nginx errorsshp doctor will report the file and line
  • Antivirus flags shp.exe → expected on an unsigned binary that copies itself and edits your PATH; see SECURITY.md

Build from source

go build -ldflags="-s -w -X main.version=dev" -o shp.exe .
.\shp.exe install

License

MIT

About

Per-project PHP on Windows, done right.

Resources

Security policy

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages