v0.5.1 See what is new in this release

A CI pipeline that fits your PHP project

AxonPHP CLI reads your composer.json, works out which PHP versions and tools you use, and writes the pipeline for your CI service. No config file, no questions.

$composer require --dev maxim/axonphp-cli
  • GitHub Actions
  • GitLab CI
  • Bitbucket Pipelines
  • CircleCI
$ vendor/bin/axonphp ci:init github

AxonPHP CLI · GitHub Actions
============================

  Project          /home/you/acme-library
  PHP versions     8.2, 8.3, 8.4, 8.5 (from "php": "^8.2")
  Extensions       intl
  Tests            PHPUnit
  Static analysis  PHPStan
  Code style       PHP-CS-Fixer
  Branches         main
  Extras           coverage (at least 90%), lowest dependencies

 [OK] Created .github/workflows/ci.yml
The command lists what it detected in the project and reports that the workflow file was created.
  • 4CI services
  • 21tools detected
  • 0config files needed
  • 100%line coverage, enforced
How it works

Your composer.json already says what CI should run

Most pipelines start as a copy of the one from the last project, fixed up by hand. AxonPHP derives it from what the project declares.

1 Read

{
    "name": "acme/library",
    "require": {
        "php": "^8.2",
        "ext-intl": "*"
    },
    "require-dev": {
        "phpunit/phpunit": "^12.0",
        "phpstan/phpstan": "^2.0",
        "friendsofphp/php-cs-fixer": "^3.0"
    },
    "extra": {
        "axonphp": {
            "min-coverage": 90,
            "lowest": true
        }
    }
}

The php constraint, every ext-* requirement and the packages you require.

2 Derive

PHP matrix
8.28.38.48.5
Tools
PHPUnitPHPStanPHP-CS-Fixer
Extras
coverage ≥ 90%lowest dependencies

Which PHP versions to test, which extensions to install and which tools to run.

3 Write

  • GitHub Actions.github/workflows/ci.yml
  • GitLab CI.gitlab-ci.yml
  • Bitbucket Pipelinesbitbucket-pipelines.yml
  • CircleCI.circleci/config.yml

One file for your CI service. It is plain YAML and yours to edit.

Playground

Pick a project, see its pipeline

This is the real output, rendered by the same code the command runs. Nothing here is written by hand.

A package that supports four PHP versions, measures coverage and tests its lowest dependencies.

{
    "name": "acme/library",
    "require": {
        "php": "^8.2",
        "ext-intl": "*"
    },
    "require-dev": {
        "phpunit/phpunit": "^12.0",
        "phpstan/phpstan": "^2.0",
        "friendsofphp/php-cs-fixer": "^3.0"
    },
    "extra": {
        "axonphp": {
            "min-coverage": 90,
            "lowest": true
        }
    }
}
AxonPHP derives PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5 PHPUnit PHPStan PHP-CS-Fixer
.github/workflows/ci.yml
# Generated by AxonPHP CLI (https://github.com/maximilianfeix/AxonPHPCLI). Edit freely.

name: CI

on:
  push:
    branches: ['main']
  pull_request:

permissions:
  contents: read

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  quality:
    name: Code quality
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v7

      - name: Set up PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.5'
          extensions: intl
          coverage: none

      - name: Install dependencies
        uses: ramsey/composer-install@v4

      - name: Static analysis (PHPStan)
        run: vendor/bin/phpstan analyse --no-progress

      - name: Code style (PHP-CS-Fixer)
        run: vendor/bin/php-cs-fixer check --diff

  tests:
    name: Tests (PHP ${{ matrix.php }}, ${{ matrix.dependencies }} dependencies)
    runs-on: ubuntu-latest

    strategy:
      fail-fast: false
      matrix:
        php: ['8.2', '8.3', '8.4', '8.5']
        dependencies: ['highest']
        include:
          - php: '8.2'
            dependencies: 'lowest'

    steps:
      - name: Checkout
        uses: actions/checkout@v7

      - name: Set up PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: ${{ matrix.php }}
          extensions: intl
          coverage: none

      - name: Validate composer.json
        run: composer validate --strict

      - name: Install dependencies
        uses: ramsey/composer-install@v4
        with:
          dependency-versions: ${{ matrix.dependencies }}

      - name: Tests (PHPUnit)
        run: vendor/bin/phpunit

  coverage:
    name: Code coverage
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v7

      - name: Set up PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.5'
          extensions: intl
          coverage: pcov

      - name: Install dependencies
        uses: ramsey/composer-install@v4

      - name: Code coverage (PHPUnit)
        run: vendor/bin/phpunit --coverage-text --coverage-clover=coverage.xml

      - name: Upload coverage report
        uses: actions/upload-artifact@v7
        with:
          name: coverage
          path: coverage.xml

      - name: Require 90% line coverage
        run: php -r '$m = simplexml_load_file("coverage.xml")->project->metrics; $c = 100 * (int) $m["coveredstatements"] / max(1, (int) $m["statements"]); printf("Line coverage %.2f%%, required 90%%\n", $c); exit((int) ($c < 90));'
Features

Everything a good pipeline has, none of the copy and paste

A matrix that follows your constraint

"php": "^8.2" becomes a test matrix of 8.2, 8.3, 8.4 and 8.5. Narrow the constraint and the matrix narrows with it, on every provider.

The tools you already use

21 tools across tests, static analysis, code style and dependency checks. Installed means it runs.

A pipeline that stays in sync

ci:check fails when the committed file drifts from the project. ci:update brings it back.

Coverage with a floor

A coverage job on the newest PHP version, and a minimum the job has to reach.

Careful defaults

Dependency caching, least-privilege permissions, cancelled superseded runs and no duplicate runs for pull requests.

Plain YAML that belongs to you

No plugin, no runtime, no lock-in. The result is one readable file for your CI service. Nothing is overwritten unless you confirm it, and --dry-run prints the result first.

Stays in sync

The pipeline changes when the project does

Add a tool or drop a PHP version and the committed pipeline is out of date. One command tells you, the other fixes it.

$ composer require --dev phpstan/phpstan
$ vendor/bin/axonphp ci:check
 ✗ .github/workflows/ci.yml is out of date

   - in your file  + expected
     jobs:
   +   quality:
   +     name: Code quality
   +     runs-on: ubuntu-latest
   …
   +       - name: Static analysis (PHPStan)
   +         run: vendor/bin/phpstan analyse --no-progress

 [ERROR] The pipeline does not match the project.
ci:check exits with 1 and prints the lines that are missing from the committed workflow.
$ vendor/bin/axonphp ci:update
 ✓ .github/workflows/ci.yml updated
 ✓ .gitlab-ci.yml updated
 ✓ bitbucket-pipelines.yml is up to date

 [OK] Updated 2 pipelines. Review the changes and commit them.

$ vendor/bin/axonphp ci:check
 ✓ .github/workflows/ci.yml is up to date
 ✓ .gitlab-ci.yml is up to date
 ✓ bitbucket-pipelines.yml is up to date
ci:update rewrites the outdated pipelines, after which ci:check passes.
Detection

21 tools, found by the packages you require

Tests run on every PHP version in the matrix. Everything else runs once, on the newest version.

ToolTypePackageCommand
Pest Tests pestphp/pest vendor/bin/pest
Codeception Tests codeception/codeception vendor/bin/codecept run
ParaTest Tests brianium/paratest vendor/bin/paratest
PHPUnit Tests phpunit/phpunit vendor/bin/phpunit
PHPUnit Bridge Tests symfony/phpunit-bridge vendor/bin/simple-phpunit
PHPSpec Tests phpspec/phpspec vendor/bin/phpspec run --no-interaction
Behat Tests behat/behat vendor/bin/behat --no-interaction
PHPStan Static analysis phpstan/phpstan
larastan/larastan
nunomaduro/larastan
vendor/bin/phpstan analyse --no-progress
Psalm Static analysis vimeo/psalm vendor/bin/psalm --no-progress
Rector Static analysis rector/rector vendor/bin/rector process --dry-run
Deptrac Static analysis deptrac/deptrac
qossmic/deptrac-shim
vendor/bin/deptrac analyse --no-progress
PHPArkitect Static analysis phparkitect/phparkitect vendor/bin/phparkitect check
PHP-CS-Fixer Code style friendsofphp/php-cs-fixer
php-cs-fixer/shim
vendor/bin/php-cs-fixer check --diff
Pint Code style laravel/pint vendor/bin/pint --test
ECS Code style symplify/easy-coding-standard vendor/bin/ecs check
PHP_CodeSniffer Code style squizlabs/php_codesniffer vendor/bin/phpcs
Twig-CS-Fixer Code style vincentlanglet/twig-cs-fixer vendor/bin/twig-cs-fixer lint
Composer Normalize Dependencies ergebnis/composer-normalize composer normalize --dry-run
Composer Require Checker Dependencies maglnet/composer-require-checker vendor/bin/composer-require-checker check
Composer Unused Dependencies icanhazstring/composer-unused vendor/bin/composer-unused --no-progress
Composer Dependency Analyser Dependencies shipmonk/composer-dependency-analyser vendor/bin/composer-dependency-analyser
Commands

Five commands, one job each

ci:init

Generates the pipeline. Leave the provider out and it asks which one you use.

$ axonphp ci:init gitlab
$ axonphp ci:init github --dry-run
$ axonphp ci:init circleci --min-coverage 90

ci:check

Fails when a committed pipeline no longer matches the project. Made for CI.

$ axonphp ci:check
$ axonphp ci:check --format github
$ axonphp ci:check --format json

ci:update

Rewrites the pipelines that are out of date and leaves the others alone.

$ axonphp ci:update
$ axonphp ci:update --dry-run
$ axonphp ci:update gitlab

inspect

Shows what was detected without writing anything. JSON output for scripts.

$ axonphp inspect
$ axonphp inspect --format json | jq '.php.versions'

tools

Lists every tool and provider the installed version supports.

$ axonphp tools
$ axonphp tools --format json
Configuration

Optional, and in the file you already have

AxonPHP needs no configuration. When you want choices to stick, put them under extra.axonphp in composer.json. Command line options still win.

  • php pins the versions to test.
  • branches lists the branches whose pushes trigger the pipeline.
  • coverage, lowest and audit switch on the extra jobs.
  • min-coverage is the line coverage the coverage job has to reach.
{
  "extra": {
    "axonphp": {
      "branches": ["main", "develop"],
      "min-coverage": 90,
      "lowest": true,
      "audit": true
    }
  }
}
Install

Three commands to your first green run

Requires PHP 8.2 or newer. Install it as a development dependency of the project you want a pipeline for.

  1. Add the repository

    $composer config repositories.axonphp vcs https://github.com/maximilianfeix/AxonPHPCLI
  2. Require the package

    $composer require --dev maxim/axonphp-cli
  3. Generate your pipeline

    $vendor/bin/axonphp ci:init

The package is not on Packagist yet, which is why the repository is added first. A PHAR is attached to every release.

Stop copying pipelines between repositories