Ruthlessly fast
Whole projects installed in seconds, however cold the cache.
Public Beta
Phast resolves, downloads, and links your whole project in a fraction
of the time, then gets out of your way. Your registries, your vendor/,
an autoloader optimized by default.
curl -fsSL https://raw.githubusercontent.com/phast-sh/phast/main/install.sh | sh
❯ phast install -v Resolved 119 packages in 0.31s Locked 119 packages in 0.12s Installed 119 packages in 1.70s Autoloaded 119 packages in 0.13s Finished in 2.26s (119 packages) ❯
The manifest
/**
* You write phast.toml. phast lock derives phast.lock from it,
* pinning every package to an exact version, the repository it came
* from and the commit it was taken at.
*/
phast.toml
#:schema https://phast.sh/schema/v1/manifest.json [package] name = "acme/my-app" description = "My application" type = "project" license = "MIT" [require] php = "^8.2" "laravel/framework" = "^11.0" "psr/log" = "^3.0" [require-dev] "phpunit/phpunit" = "^11.0" [autoload.psr-4] "App\\" = ["src/"] [scripts] test = "@php vendor/bin/phpunit"
❯ phast lock
phast.lock
version = 1 [metadata] generated-by = "phast 0.1.0" generated-at = "2026-08-22T09:14:03.118762+00:00" [[packages]] name = "psr/log" version = "3.0.0" type = "library" description = "Common interface for logging libraries" license = ["MIT"] time = "2021-07-14T16:46:02+00:00" [packages.source] type = "git" url = "https://github.com/php-fig/log.git" reference = "fe5ea303b0887d5caefd3d431c3e61ad47037001" [packages.autoload.psr-4] 'Psr\Log\' = ["src"]
That first line on the left is not decoration. Point your editor at the schema
and you get completion and validation for every key, and
phast validate checks the same rules in CI.
Why Phast
/** * What changes on the first day: the wait, the file you edit, the * number of tools in the chain, and how much of it you have to * explain to whoever clones the repository next. */
Whole projects installed in seconds, however cold the cache.
Packagist, Private Packagist & Satis, with a standard vendor/autoload.php.
Import an existing project in one step, then phast install.
Workspaces, one lock file, one vendor/, no path-repo wiring.
phast audit plus a minimum package age against supply-chain attacks.
A deterministic lock file gives every machine byte-for-byte the same tree.
Benchmarks
/** * koel, a real music streaming app: 202 packages, one of them * installed straight from a git repository. Median of five runs on * one machine against Composer 2.8 on PHP 8.4, scripts disabled, * caches isolated, over a fast connection. */
no lock, no cache · lower is better
9.6× faster
Diagnostics
/** * A solver that records why every candidate was rejected can explain * itself. A conflict comes back in terms of your project: which * requirement, whose requirement, and the two ranges that cannot * overlap. */
❯ phast add symfony/polyfill-mbstring:^99.0 Phast could not find compatible versions for your requirements ╰─▶ symfony/polyfill-mbstring has incompatible constraints: · your project requires symfony/polyfill-mbstring[>=99.0, <100.0] · symfony/http-foundation[>=7.0, <8.0] requires symfony/polyfill-mbstring[>=1.1, <2.0]
phast.toml not found. Run phast init first.
'psr/log' is already in require. Remove it first with phast remove psr/log.
invalid package name 'psr log'. Expected vendor/package (e.g. psr/log), php, or ext-*.
--member requires a workspace. Add a [workspace] section to phast.toml first.
The same care goes into the ordinary ones. Every message names what went wrong and the command that fixes it.
Workspaces
/**
* Several packages in one repository, resolved together, sharing one
* phast.lock and one vendor/. No path repositories, no merge
* plugin, nothing to wire up per member.
*/
my-project/
phast.toml # the workspace root
phast.lock # one lock for everything
vendor/ # one directory, shared
packages/
├── http-client/
│ └── phast.toml
└── api-sdk/
└── phast.toml
tools/
└── codegen/
└── phast.toml
phast.toml
[workspace] members = ["packages/*", "tools/codegen"] exclude = ["packages/legacy"] [workspace.dependencies] "symfony/http-foundation" = "^7.0" "phpunit/phpunit" = "^11.0" # and in packages/api-sdk/phast.toml [require] "my-org/http-client" = "^1.0" # sibling "symfony/http-foundation" = { workspace = true }
Every member resolves against the same graph, so two packages in your repository can never disagree about a shared dependency.
A workspace root declares no dependencies of its own. Shared versions live
in [workspace.dependencies], and each member opts in.
Member code stays where it is. The generated autoloader points straight at each member's sources, so an edit is visible with no reinstall.
CLI tooling
/** * Fast feedback, readable output, and the commands you reach for * every day, right in your terminal. */
❯ phast install
Resolve, download, and link an entire project in a couple of seconds, no warm cache required.
❯ phast migrate
Import an existing composer.json in a single step and keep
shipping. Nothing else to rewire.
❯ phast audit
Scan every locked package against advisories, with a minimum-age guard against fresh supply-chain attacks.
❯ phast why
Trace exactly why a package is in your project, and which dependencies pull it in.
❯ phast install -v Resolved 119 packages in 0.31s Locked 119 packages in 0.12s Installed 119 packages in 1.70s Autoloaded 119 packages in 0.13s Finished in 2.26s (119 packages)
❯ phast migrate composer.json migrated to phast.toml. Run 'phast install' to install dependencies. ❯ phast install Finished in 3.19s (248 packages)
❯ phast audit Phast found 2 security advisories in your locked dependencies. facade/ignition 2.5.1 CVE-2021-3129 [CRITICAL] Unauthenticated remote code execution affected <2.5.2 guzzlehttp/psr7 2.4.4 CVE-2023-29197 [MEDIUM] Improper header parsing affected >=2.0.0,<2.4.5 Run `phast update` to attempt resolution.
❯ phast why psr/log --tree psr/log 3.0.0 ├── monolog/monolog 3.5.0 │ └── required by root ├── laravel/framework 11.0.3 │ └── required by root └── symfony/http-kernel 7.2.3 └── laravel/framework 11.0.3 └── required by root
FAQ
Yes. Phast reads your composer.json, resolves what Composer
resolves, and writes a vendor/ directory that Composer-era
tooling can still read, including the
\Composer\InstalledVersions runtime API.
No. phast migrate converts an existing
composer.json into phast.toml. Run
phast install after it and you are done.
Packagist, Private Packagist, Satis, and any other Composer-compatible registry. Phast speaks the same metadata protocol, so private repositories keep working as they do today.
On a real application of 202 packages with one installed straight from a git repository, a cold install takes 7.00s against Composer's 67.32s, installing from a lock file 5.20s against 17.17s, a warm cache 2.16s against 7.41s, and an install with nothing to do 0.53s against 3.12s. The run behind these numbers is published in the repository.
Yes. Phast generates a standard vendor/autoload.php, so a
framework loads its dependencies exactly as it did before. Nothing in
your application needs to change.
Linux and macOS, on both x86-64 and ARM. Releases ship prebuilt binaries for each, and the install script picks the right one.
Yes. Phast is open source and dual-licensed under MIT or Apache-2.0, the same pair of licenses most of the Rust ecosystem uses.
Phast is in public beta. Try it on real projects and report what breaks; the lock file is deterministic, so you can always keep Composer alongside while you evaluate.
Phast is built in the open and dual-licensed under MIT or Apache-2.0. Try it, star it, and let us know what you think!