GeezerVison is showing alchemy.leafphp.dev — Alchemy: Your entire QA pipeline. One file.
app:
- src
tests:
engine: pest
env:
APP_ENV: testing
lint:
preset: PSR12
refactor:
php: '8.3'
sets: [dead-code, code-quality]
analyse:
level: 6
actions:
provider: [github, gitlab, circleci]
run: [lint, tests, refactor, analyse]
➜ composer run test ✓ 42 passed
➜ composer run lint ✓ style clean
➜ composer run analyse ✓ no errors
➜ composer run ci ✓ pipelines written
➜
composer require leafs/alchemy --dev
➜
composer run test
Setting up tests with pest...
✓
42 passed
(0.31s)
➜
composer run ci
✓
Pipelines written from alchemy.yml
➜
vendor/bin/alchemy init
Detected Laravel project
✓
Imported phpunit.xml — suites, env, coverage
✓
Imported .php-cs-fixer.dist.php — rules, preset
✓
alchemy.yml created
➜
vendor/bin/alchemy switch gitlab --clean
✓
.gitlab-ci.yml written from the same alchemy.yml
✓
Removed .github/workflows
Works with PHP everywhere
Leaf
Laravel
Symfony
Slim
Plain PHP
Pest
PHPUnit
PHP CS Fixer
Rector
PHPStan
GitHub Actions
GitLab CI
CircleCI
Leaf
Laravel
Symfony
Slim
Plain PHP
Pest
PHPUnit
PHP CS Fixer
Rector
PHPStan
GitHub Actions
GitLab CI
CircleCI
alchemy init detects
your framework from composer.json and configures sensible defaults for
it, no framework required.
Intro
Alchemy is the QA layer for PHP.
Instead of babysitting phpunit.xml, php-cs-fixer, rector and phpstan
configs (and hand-writing the CI that runs them), you describe your
policy once in alchemy.yml. Alchemy
installs the tools, generates their config, runs them with
honest exit codes, and writes your
pipelines. It works with
Leaf, Laravel, Symfony, Slim or no
framework at all. And your AI assistant only needs to understand
one QA file, not five unrelated
formats.
One file
One config to run your entire pipeline
Everything below is generated, run, and cleaned up for you. Your
repo keeps exactly one QA file.
alchemy.yml
app:
- src
tests:
engine: pest
env:
APP_ENV: testing
lint:
preset: PSR12
refactor:
php: '8.3'
sets: [dead-code, code-quality]
analyse:
level: 6
actions:
provider: [github, gitlab]
run: [lint, tests, analyse]
What it replaces
-
→
phpunit.xml
suites, env vars, coverage: near 1:1, minus the XML
-
→
.php-cs-fixer.dist.php
every fixer rule works as-is
-
→
rector.php
prepared sets, PHP upgrade targets, skips
-
→
phpstan.neon
level, paths, ignores
-
→
.github/workflows/*.yml
+ .gitlab-ci.yml + .circleci/
matrices, caching, check-mode jobs, all generated
…and the clutter they drag in
-
.phpunit.result.cache
-
.php-cs-fixer.cache
-
.phpunit.cache/
-
rector cache · phpstan tmp
-
test-results, run logs, coverage output
All of it is swept into one gitignored
.alchemy/
folder. Your repo root stays exactly one QA file big.
Workflow
One command for every question your code answers
composer run test
Pest or PHPUnit, installed on first run. Parallel out of the box.
Sample tests scaffolded on empty projects.
composer run lint · fmt
lint checks and fails CI
without touching a file.
fmt rewrites. You always
know which one you're running.
composer run refactor · analyse
Rector refactors with prepared sets, PHPStan at your chosen level.
Opt-in, gated in CI, honest exit codes.
Pest 5 ready · any stack
Pest 5 just landed. Alchemy is the fastest way to actually run it.
Pest 5 ships Test Impact Analysis, time-balanced sharding, a
first-party PHPStan plugin and Rector rules, and still reads its
config from a phpunit.xml, next to your neon file, your rector.php and
your hand-written workflows. Alchemy folds all of it into the one file
you already have.
alchemy.yml
tests:
engine: pest
flags: [tia] # only re-run affected tests
analyse:
level: 9
refactor:
sets: [phpunit-code-quality]
➜ composer run analyse
Your analyse paths cover your tests — adding pest's phpstan
plugin...
✓ pest syntax analyses clean
— it(), expect(), $this
Day-one Pest 5 support
flags: [tia] turns on Test
Impact Analysis on every run.
shard=1/4 splits CI by
real timing. Pest's PHPStan plugin is installed and wired
automatically when your analysis covers your tests: no neon
editing, no extension-installer required.
Not a Leaf thing, a PHP thing
alchemy init detects
Laravel, Symfony, Slim, Leaf or plain composer projects, imports
the configs you already have (phpunit.xml, php-cs-fixer,
phpstan neon, rector.php) or pins them untouched. Your call,
recorded in the yml.
The file pile, retired
phpunit.xml, .php-cs-fixer.dist.php, phpstan.neon,
phpstan-baseline.neon, rector.php, four workflow files: one
alchemy.yml replaces the lot, generates configs out of sight in
.alchemy/, and
alchemy eject hands them
all back if you ever leave.
The matrix
Connect the tools behind your quality bar
Alchemy speaks every engine's config language so you don't have to.
Swap any of them without rewriting a thing.
Pest
PHPUnit
CS Fixer
Rector
PHPStan
GitHub
GitLab
CircleCI
Test engines
Pest · PHPUnit · paratest
Quality tools
CS Fixer · Rector · PHPStan
CI providers
GitHub · GitLab · CircleCI
Frameworks
Leaf · Laravel · Symfony · Slim
phpunit.xml
.php-cs-fixer.dist.php
rector.php
phpstan.neon
.github/workflows/tests.yml
.github/workflows/lint.yml
.gitlab-ci.yml
.circleci/config.yml
↓
alchemy.yml
1 file
for your whole QA setup, instead of five configs in five
formats.
➜ composer require leafs/alchemy --dev
✓ 1 package installed
➜ composer run test
Setting up tests with pest...
✓ pest installed on first use
➜ composer run refactor
Setting up refactoring with rector...
✓ rector installed on first use
➜ composer run analyse
Setting up static analysis with phpstan...
✓ phpstan installed on first use
0 deps
added to your project until the first command that needs an
engine runs.
Pricing
Free where it counts
Open source
$0forever
-
✓ Tests, lint/fmt, refactor,
analyse
-
✓ CI generation for GitHub,
GitLab, CircleCI
-
✓ Framework detection +
config import
-
✓ Provider & engine
switching
-
✓ Eject anytime, no lock-in
Alchemy Cloud
Coming soon
The chain after the commit
-
◇ Pipeline insights across
your repos
-
◇ Debugging that starts
where CI stops
-
◇ Everything in that
chain, done for you
FAQ
Answers to the questions that come up most
No. When
alchemy init
finds an existing config (phpunit.xml, php-cs-fixer, a phpstan
neon, a rector.php), it asks: port it into alchemy.yml, or keep
it. Keeping it is recorded as
tests: phpunit.xml
(a pinned file alchemy runs as-is, forever). Porting maps your
config into the yml so you can delete the original. Either way the
choice lives in alchemy.yml, not in alchemy's head.
No. Requiring alchemy adds nothing else to your dependency tree.
Each engine installs the first time its command runs: pest on your
first
test,
rector on your first
refactor, and so on.
No. Alchemy is framework-agnostic:
alchemy init
detects Leaf, Laravel, Symfony, Slim or plain PHP from your
composer.json and configures sensible defaults for each.
Yes, that's the point. Generated pipelines run every tool in
check mode with real exit codes: style violations, pending
refactors and analysis findings all fail the build. Locally,
fmt
and
refactor
apply the fixes.
alchemy eject
exports a standard phpunit.xml and .php-cs-fixer.dist.php, rewires
your composer scripts to call the engines directly, and steps out
of the way. Your tests were always plain Pest/PHPUnit tests.
"Somewhere along the way we accepted that every new tool deserved
another config file. I never thought that was progress."
Michael Darko · creator of Leaf PHP
Get started today
Your pipeline, brewed before the coffee is.
Read the docs
$ composer require leafs/alchemy
Copied to clipboard
|