Skip to main content
A matrix strategy lets you run the same workflow job across multiple PHP versions, operating systems, and configurations in parallel. This is the most effective way to ensure your code works everywhere your users run it.

Basic matrix example

The following workflow runs tests on Ubuntu, Windows, and macOS against PHP 8.2 through 8.5. Each combination becomes a separate job that runs in parallel.
jobs:
  run:
    runs-on: ${{ matrix.operating-system }}
    strategy:
      matrix:
        operating-system: ['ubuntu-latest', 'windows-latest', 'macos-latest']
        php-versions: ['8.2', '8.3', '8.4', '8.5']
        phpunit-versions: ['latest']
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: ${{ matrix.php-versions }}
          extensions: mbstring, intl
          ini-values: post_max_size=256M, max_execution_time=180
          coverage: xdebug
          tools: php-cs-fixer, phpunit:${{ matrix.phpunit-versions }}

      - name: Install dependencies
        run: composer install --prefer-dist

      - name: Run tests
        run: phpunit
This produces 12 parallel jobs (3 operating systems × 4 PHP versions).

Including specific PHP and PHPUnit version combinations

Use the include: key to add specific combinations that fall outside the main matrix. This is useful when you need to support an older PHP version that requires a pinned tool version.
jobs:
  run:
    runs-on: ${{ matrix.operating-system }}
    strategy:
      matrix:
        operating-system: ['ubuntu-latest', 'windows-latest', 'macos-latest']
        php-versions: ['8.2', '8.3', '8.4', '8.5']
        phpunit-versions: ['latest']
        include:
          - operating-system: 'ubuntu-latest'
            php-versions: '8.1'
            phpunit-versions: 10
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: ${{ matrix.php-versions }}
          extensions: mbstring, intl
          ini-values: post_max_size=256M, max_execution_time=180
          coverage: xdebug
          tools: php-cs-fixer, phpunit:${{ matrix.phpunit-versions }}

      - name: Install dependencies
        run: composer install --prefer-dist

      - name: Run tests
        run: phpunit
The include: entry adds one extra job: PHP 8.1 on Ubuntu with PHPUnit 10. All other combinations use the latest PHPUnit version.

Multi-arch setup

To run tests on both amd64 and arm64 architectures, use shivammathur/node container images. These images have compatible Node.js and PHP installed for setup-php on Ubuntu and Debian runners. PHP 5.6 to PHP 8.5 are supported on multiple architectures through this approach.
jobs:
  run:
    runs-on: ${{ matrix.os }}
    container: shivammathur/node:php-${{ matrix.php-versions }}-24.04-${{ matrix.arch }}
    strategy:
      matrix:
        arch: ["amd64", "arm64v8"]
        php-versions: [8.4, 8.5]
        include:
          - arch: "amd64"
            os: "ubuntu-24.04"
          - arch: "arm64v8"
            os: "ubuntu-24.04-arm"
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Install PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: ${{ matrix.php-versions }}

      - name: Install dependencies
        run: composer install --prefer-dist

      - name: Run tests
        run: phpunit
The container image tag format is shivammathur/node:php-{version}-{ubuntu-version}-{arch}. The include: entries map each architecture to the appropriate runner label.

Matrix configuration tips

By default, GitHub Actions cancels all in-progress jobs when one matrix job fails. Set fail-fast: false to let all jobs complete so you can see which combinations fail.
strategy:
  fail-fast: false
  matrix:
    operating-system: ['ubuntu-latest', 'windows-latest', 'macos-latest']
    php-versions: ['8.2', '8.3', '8.4', '8.5']
If certain combinations are not relevant (for example, PHP 5.x only on Linux), use exclude: to skip them and reduce job count.
strategy:
  matrix:
    operating-system: ['ubuntu-latest', 'windows-latest', 'macos-latest']
    php-versions: ['8.3', '8.4', '8.5']
    exclude:
      - operating-system: 'windows-latest'
        php-versions: '8.3'
Add a name: field to the job to make the matrix jobs easier to identify in the Actions UI.
jobs:
  run:
    name: PHP ${{ matrix.php-versions }} on ${{ matrix.operating-system }}
    runs-on: ${{ matrix.operating-system }}
Test both the minimum and stable versions of your Composer dependencies by adding a prefer dimension to your matrix. This is especially useful for libraries that need to support a range of dependency versions.
strategy:
  matrix:
    php-versions: ['8.2', '8.3', '8.4']
    prefer: ['prefer-lowest', 'prefer-stable']
steps:
  - name: Install dependencies
    run: composer update --${{ matrix.prefer }} --prefer-dist
The GitHub-hosted runner label ubuntu-latest currently maps to Ubuntu 24.04. Pin to a specific version like ubuntu-24.04 in production workflows to avoid unexpected changes when the latest label is updated.

Build docs developers (and LLMs) love