Appendix: Continuous Integration

This project’s CI runs entirely on GitHub Actions — no separate CI service to configure. A reference map of what runs on every push/PR and what it checks, for the CI configuration itself, not the framework’s own code. Five workflow files, .github/workflows/: ci.yml, semgrep.yml, integration.yml, infection.yml, sonarqube.yml, plus .github/dependabot.yml.

ci.yml — static checks and unit tests, per package

One job per package (core plus every satellite package, 14 in total), each running against the exact same Docker images used for local development:

  • composer validate --strict

  • composer install

  • composer audit — checks every installed dependency against the FriendsOfPHP security advisory database.

  • PHPUnit — every package’s own existing, fake-backed unit test suite. Skipped for kinetis/skeleton, which has none by design (see Appendix: Satellite Packages).

  • PHPStan, level 8.

  • Psalm, --taint-analysis — data-flow analysis for injection-style bugs (SQL injection, XSS, …), a different lens than PHPStan’s type-correctness checking. Results upload to GitHub’s code-scanning tab as SARIF.

A separate job builds the Sphinx documentation with -W (warnings fail the build), so a broken docs page can’t merge silently.

semgrep.yml — pattern-based security scanning

p/php, p/security-audit, and p/secrets rulesets, scanning the whole repository once (not per package — Semgrep doesn’t need to know about Composer package boundaries). Same SARIF upload as Psalm’s.

Two findings are suppressed in .semgrepignore, with the reasoning recorded there:

  • docs/_templates/page.html — mirrors the Furo Sphinx theme’s own footer template verbatim; the flagged template variables are Sphinx-internal, build-time navigation values, never live request input, since this renders to static HTML.

  • packages/auth-jwt/tests/JwtAuthMiddlewareTest.php — a synthetic RSA key pair generated solely to test RS256 JWT verification, not a real credential.

integration.yml — real backends, not fakes

Several classes across this project are tested only against real service containers, not mocks — a mocked “was this method called with X” test can’t prove backend-specific correctness (a reliable queue’s ack/release mechanics, FOR UPDATE SKIP LOCKED, priority-queue fallthrough). Each is a standalone PHP script (tests-integration/run.php, or a descriptively-named file at the relevant package root), not a disguised PHPUnit test.

  • query-builder (MySQL 8.4, MariaDB 11.4, Postgres 16) — Query::get()/first()/count()/insertGetId()/update()/ delete()/join()/paginate()/cursorPaginate().

  • queue (Redis 7, MySQL 8.4, MariaDB 11.4) — RedisQueue/ SqlQueue: push/pop/ack/release/fail, attempts, priority queues.

  • core (MySQL 8.4, MariaDB 11.4, Postgres 16, Redis 7) — TransactionGuard: commit/rollback/rollbackDangling(); RedisSimpleCache: the full PSR-16 surface, TTL expiry.

  • mailer (Mailpit) — MailerFactory: a real SMTP send, read back through the mail server’s own API.

  • migrations (MySQL 8.4, MariaDB 11.4, Postgres 16) — MigrationRunner/SqlMigrationRepository: migrate/status/rollback against a real fixture migration file.

  • localstack (LocalStack: SQS + S3) — SqsQueue: push/pop/ack/ release/fail, maxAttempts, priority queues; S3FilesystemFactory: write/read/exists/list/delete.

query-builder, queue, core, and migrations each run twice — once against MySQL, once against MariaDB — via a matrix over the database image, not separate jobs or duplicated scripts. Only the service container’s image and health-check command (mysqladmin vs. mariadb-admin) differ between the two matrix entries.

infection.yml — mutation testing

Mutates source code (flipping a comparison, removing a statement, incrementing a constant, …) and re-runs the covering tests per mutant — a mutant the suite doesn’t catch (“escaped”) is a gap in assertion rigor, not just a coverage gap.

One matrix job per package, core plus every satellite package except kinetis/skeleton (no PHPUnit tests, nothing to mutate against). Each: composer install, then Infection with PCOV as the coverage driver, informational only (--min-msi=0 --min-covered-msi=0, not gating the build on a mutation-score threshold).

queue/queue-sqs/queue-rabbitmq/storage-s3 score lower here: their real backend-specific logic (RedisQueue/SqlQueue/SqsQueue/ RabbitMqQueue) has no PHPUnit coverage (see Appendix: Satellite Packages), so Infection there only scores the thin config-parsing factory classes that are unit-tested.

sonarqube.yml — SonarQube Cloud

A repo-wide static-analysis and coverage pass via the official SonarSource/sonarqube-scan-action, configured by sonar-project.properties at the repo root (source paths spanning core’s src/ plus every satellite package’s own src/).

Runs PHPUnit with PCOV coverage for core and every satellite package with a PHPUnit suite, feeding the resulting Clover reports into the scan via sonar.php.coverage.reportPaths. RedisQueue, SqlQueue, SqsQueue (and its SqsQueueException), and RabbitMqQueue are excluded from the coverage calculation via sonar.coverage.exclusions, matching their real-backend-only testing in integration.yml.

Requires a SONAR_TOKEN repository secret from the project’s own SonarCloud dashboard; analysis method is “With GitHub Actions.”

dependabot.yml

One composer ecosystem entry per package directory, plus one github-actions entry for the workflow files themselves. A dependency bump opens a real pull request, which re-runs every workflow above against it before a human ever looks at it.

See also