Skip to content

Latest commit

 

History

History
378 lines (300 loc) · 25.1 KB

continuous-integration.md

File metadata and controls

378 lines (300 loc) · 25.1 KB

GitHub Reusable Workflow: Continuous integration

Continuous integration

Overview

Use continuous-integration.yml from both your pull request and default-branch workflows. Pin the same reviewed commit in each caller. An application can use lint and tests without enabling Composer package publication.

permissions: {}
jobs:
  checks:
    uses: hoverkraft-tech/ci-github-php/.github/workflows/continuous-integration.yml@<commit-sha>
    permissions:
      contents: read
      id-token: write

Define lint:ci and test:ci in composer.json. These are explicit project commands; they execute even when dependency installation scripts are disabled. An empty lint or test input disables that job; test: "false" also disables tests. Missing named scripts fail the job. lint also accepts a JSON object such as lint: '{"command":"lint:custom"}', or "false" to skip linting.

Use runs-on for a JSON array of Linux runner labels, for example runs-on: '["self-hosted", "linux"]'. Every job uses those labels. Docker must be available for the PHP version discovery action. Set checks: false to skip lint, tests, dependency review and distribution checks.

Runtime and dependencies

With no php-versions input, CI reads composer.json's require.php and discovers released stable versions from PHP.net using Composer's constraint parser. Discovery runs in its own container without changing the runner's PHP installation. It tests the newest compatible patch of every matching minor, including end-of-life versions still allowed by the project. Prereleases are excluded. Missing or invalid constraints and release-catalog failures stop discovery instead of choosing an arbitrary runtime. The highest discovered version runs lint and package checks and publishes coverage. Set php-versions explicitly to override the matrix, for example '["8.2", "8.5"]'. The last explicit entry is primary; an empty entry delegates to the setup action, which honors .php-version and Composer platform overrides before automatic discovery. See PHP version selection for local Docker builds.

Composer projects with or without a committed lockfile are supported. Each project is installed independently. By default, setup installs the root manifest and tools/composer.json when it exists. It does not search nested directories. Use composer-directories to override discovery, for example ["."] for the root only or [".", "dev-tools"] for another tooling directory. Only Composer's download cache is shared between runs; dependencies are installed and platform requirements checked each time.

Installation scripts and plugins are disabled by default. Use ignore-scripts: false when installation hooks are required. Use ignore-plugins: false for a project with explicit Composer allow-plugins configuration, such as PHPStan's extension installer. These options affect installation, not explicit lint or test commands.

For custom jobs, call actions/setup-php after checkout, then the individual lint or test actions. install-dependencies: false prepares the runtime without installing the project, for example before isolated package checks.

Reports and coverage

The lint and test actions reuse ci-github-common/actions/quality/parse-ci-reports for GitHub annotations and job summaries. The test action runs your Composer script unchanged. The script selects its test tool, preparation, configuration and report formats. For PHPUnit, keep the everyday test command and add a CI script:

{
  "scripts": {
    "test": "phpunit --configuration tests/phpunit.xml",
    "test:ci": "@test --log-junit=junit.xml --coverage-cobertura=cobertura.xml"
  }
}

The action sets CI=true and adds no test-runner arguments. JUnit reports are parsed even after the command fails. The same Composer command runs on every PHP version; only the last version publishes coverage. The coverage option controls publication, not collection. Set it to an empty string to skip coverage publication; omit coverage arguments from your script when collection is not needed.

For Codecov, use --coverage-clover=clover.xml in the script and configure:

with:
  test: '{"coverage":"codecov"}'

Codecov requires id-token: write and repository access. The action discovers Clover reports, falling back to Cobertura. For GitHub coverage summaries, generate Cobertura. Add generated report files to .gitignore. Clean any retained report files in your project command if it reuses an existing workspace.

The lint and test actions upload only parsed native reports, including after a failed command, and retain them for seven days. Their report-artifact-id output identifies the artifact; it is empty when no supported reports were found. Test artifacts include coverage only with coverage: github. Codecov upload remains separate. Console-only lint output creates no artifact; configure a supported file report such as PHPStan Checkstyle for consolidation.

Individual comments are disabled by default, keeping producer jobs read-only. When calling an action directly, set comment: true and grant pull-requests: write to opt in. Human and bot actors follow the same rules; fork PRs keep summaries, annotations and artifacts without commenting.

For custom producer jobs named lint and test, leave individual comments disabled and add one final job using the common reporting workflow:

reports:
  needs: [lint, test]
  if: always()
  uses: hoverkraft-tech/ci-github-common/.github/workflows/ci-reports.yml@d07ee49b460ae2f3815804b5153fe918092aa321 # 4.2.0
  permissions:
    contents: read
    pull-requests: write
  with:
    job-results: ${{ toJSON(needs) }}

This updates one comment each for tests, coverage and lint, with details collapsed. Keep producer job IDs aligned with the shared reporter's contract; when wrapping producers in reusable workflows, use matching caller and inner job IDs. The full CI workflow uses inner lint and test jobs; use the individual actions when you need to compose them with this final reporter. Keep producer jobs as required checks: report publication does not replace them.

Preparation belongs in the Composer script, with report arguments attached only to the test command:

{
  "scripts": {
    "test:ci": [
      "@test:load-fixtures",
      "@test --log-junit=junit.xml --coverage-clover=clover.xml"
    ]
  }
}

Pest and Codeception projects use the same contract; define their commands and report options in Composer. No test-runner selection is needed in the action. Use command for a script name other than test:ci. Group custom settings in one input, omitting fields that use defaults:

with:
  test: '{"coverage":"codecov","cache":".cache"}'

The test cache option can retain a directory of generated test fixtures or results. Do not cache vendor; Composer installation manages dependencies.

Dependency review runs only on pull requests. PHP CodeQL analysis is not part of the workflow because PHP is not a supported CodeQL language. Run your PHP static analyzer through the lint script.

Generated application files and documentation

Projects needing asset compilation or generated code keep those steps in their Composer commands or dedicated caller jobs. PHP libraries need no generic build step; Composer distributes committed source selected by Git export rules.

For API documentation, use documentation.yml, with optional runs-on labels for a Linux runner supporting Docker. It uses the project's phpDocumentor configuration when present, otherwise src/, and uploads a site containing phpdoc/. Run it during pull request validation and restore its artifact-id into _site for a separate deployment job using ci-github-publish actions. Site publishing and project-specific documentation transformations remain in the caller.

Package checks

Set package: "true" for a public Composer package at the Git repository root. The test action snapshots the checked-out commit using git archive, respecting .gitattributes export-ignore and export-subst. Uncommitted files and generated build output are not included.

The test action validates composer.json, verifies declared PSR autoload paths, autoload files, and binaries, installs the archive in an isolated consumer with production dependencies, checks platform requirements, and loads the autoloader. Installation hooks and plugins remain disabled in that consumer.

Distribution testing is disabled by default. Omit package or pass "false" to skip it. The input is a string, so boolean values must be quoted. A JSON object enables distribution testing with specific options; unknown options and non-string tests fail configuration validation.

Use tests only for behavior specific to your package, such as rendering a template or invoking a CLI:

with:
  package: '{"tests":"my-cli --help"}'

Commands execute in the consumer, with vendor/bin on PATH and PACKAGE_NAME, PACKAGE_INSTALL_PATH, and PACKAGE_ARCHIVE_PATH available. The temporary consumer is removed after success or failure. Modifying the temporary source archive fails validation. Nothing is uploaded: this tests the files GitHub will distribute, without adding a package publication step.

Package checks run on the primary PHP version. The source test matrix remains responsible for compatibility across every supported PHP version. Release callers should depend on successful validation with needs.

Permissions

  • contents: read
  • id-token: write

Usage

name: Continuous integration
on:
  push:
    branches:
      - main
permissions: {}
jobs:
  continuous-integration:
    uses: hoverkraft-tech/ci-github-php/.github/workflows/continuous-integration.yml@7b1303edc25e97da2a297dd285a54cf4b6ae76a1 # 1.1.2
    permissions:
      contents: read
      id-token: write
    with:
      # JSON array of Linux runner labels. Applies to every job, including the configuration job.
      # Default: `["ubuntu-latest"]`
      runs-on: '["ubuntu-latest"]'

      # Enable lint, tests, dependency review and optional distribution checks.
      # Default: `true`
      checks: true

      # Composer project directory relative to the checkout.
      # Default: `.`
      working-directory: .

      # Optional JSON array overriding Composer-based PHP discovery. Empty discovers the newest stable compatible patch per minor. The last version runs lint/package checks and publishes coverage. An empty entry uses the setup action default.
      php-versions: ""

      # PHP extensions forwarded to setup-php.
      # Default: `mbstring, dom, xml, xmlwriter, curl, zip, intl`
      extensions: mbstring, dom, xml, xmlwriter, curl, zip, intl

      # Optional JSON array of Composer directories relative to working-directory. Empty installs the root project and tools/composer.json when present, without recursive discovery.
      composer-directories: ""

      # Disable Composer installation scripts. Explicit project commands still run.
      # Default: `true`
      ignore-scripts: true

      # Disable Composer plugins during installation. When false, Composer allow-plugins configuration still applies.
      # Default: `true`
      ignore-plugins: true

      # Composer lint script name or a JSON object with `command` (default `lint:ci`). Pass "false", "null" or an empty string to disable.
      # Default: `true`
      lint: "true"

      # Run the Composer `test:ci` script unchanged, or provide another script name.
      # Pass "false", "null" or an empty string to disable tests.
      # A JSON object groups test options: `command` (default `test:ci`),
      # `coverage` (`github`, `codecov` or empty), and `cache` (one directory relative to working-directory).
      # Configure report/coverage generation in the Composer script itself.
      # Coverage is published on the last PHP version only; the command controls collection on every version. Codecov requires id-token: write.
      # Example: `{"coverage":"codecov","cache":".cache"}`.
      #
      # Default: `true`
      test: "true"

      # Validate a public Composer source distribution from the Git repository root.
      # Pass "false" (the default) to disable, "true" for archive and autoload checks,
      # or a JSON object with `tests` for additional commands in an isolated Composer consumer.
      # Quote booleans because this input has type string. Example: `{"tests":"my-cli --help"}`.
      # vendor/bin is on PATH; PACKAGE_NAME, PACKAGE_INSTALL_PATH and PACKAGE_ARCHIVE_PATH identify the package.
      # Unknown options and non-string tests are rejected before distribution checks.
      #
      # Default: `false`
      package: "false"

      # Review dependency changes on pull requests.
      # Default: `true`
      dependency-review: true

Inputs

Workflow Call Inputs

Input Description Required Type Default
runs-on JSON array of Linux runner labels. Applies to every job, including the configuration job. false string ["ubuntu-latest"]
checks Enable lint, tests, dependency review and optional distribution checks. false boolean true
working-directory Composer project directory relative to the checkout. false string .
php-versions Optional JSON array overriding Composer-based PHP discovery. Empty discovers the newest stable compatible patch per minor. The last version runs lint/package checks and publishes coverage. An empty entry uses the setup action default. false string -
extensions PHP extensions forwarded to setup-php. false string mbstring, dom, xml, xmlwriter, curl, zip, intl
composer-directories Optional JSON array of Composer directories relative to working-directory. Empty installs the root project and tools/composer.json when present, without recursive discovery. false string -
ignore-scripts Disable Composer installation scripts. Explicit project commands still run. false boolean true
ignore-plugins Disable Composer plugins during installation. When false, Composer allow-plugins configuration still applies. false boolean true
lint Composer lint script name or a JSON object with command (default lint:ci). Pass "false", "null" or an empty string to disable. false string true
test Run the Composer test:ci script unchanged, or provide another script name. false string true
Pass "false", "null" or an empty string to disable tests.
A JSON object groups test options: command (default test:ci),
coverage (github, codecov or empty), and cache (one directory relative to working-directory).
Configure report/coverage generation in the Composer script itself.
Coverage is published on the last PHP version only; the command controls collection on every version. Codecov requires id-token: write.
Example: {"coverage":"codecov","cache":".cache"}.
package Validate a public Composer source distribution from the Git repository root. false string false
Pass "false" (the default) to disable, "true" for archive and autoload checks,
or a JSON object with tests for additional commands in an isolated Composer consumer.
Quote booleans because this input has type string. Example: {"tests":"my-cli --help"}.
vendor/bin is on PATH; PACKAGE_NAME, PACKAGE_INSTALL_PATH and PACKAGE_ARCHIVE_PATH identify the package.
Unknown options and non-string tests are rejected before distribution checks.
dependency-review Review dependency changes on pull requests. false boolean true

Release License Stars PRs Welcome GitHub Verified Creator

Contributing

Contributions are welcome! Please see the contributing guidelines for more details.

License

This project is licensed under the MIT License.

SPDX-License-Identifier: MIT

Copyright © 2026 hoverkraft-tech

For more details, see the license.


This documentation was automatically generated by CI Dokumentor.