How to get a Dynamic PHP Version Matrix in GitHub Actions

Do you want to run your tests on each PHP version you support? PHP 7.3, 7.4 and 8.0? Instead of 3 workflows with copy-paste steps, you can define just one with a matrix for PHP versions.

But PHP is released every year. The version constraints are already defined in composer.json. Hmm, how could we use this knowledge to provide a list of PHP version for a dynamic matrix?

Do you know memory locks? An "if this then that" code smell. E.g., you always have to put keys into your left pocket, after you lock your office door.

If we have tests, we want them run on all PHP versions we support. To be sure, no PHP 8 features are running on PHP 7.4.


When new PHP version once a year, then we have to:

This is utterly useless operation = double the work and double the maintenance—no gain, except the fear of control.

Memory Lock is Expensive

Could you guess how much time it took to send both commits? 2 minutes? 10 minutes?

Almost 2 hours. I had to send a new commit based on feedback in code-review. That's a hidden cost of memory lock code smell. Hidden cost in attention, work and slow feedback loop.

Trust composer.json

What PHP version should be tested? The information is already in composer.json:

{
    "require": {
        "php": "^7.2|^8.0"
    }
}

You're right. It's this list:

So why should we duplicate this information in the GitHub Actions workflow config?

We can do better with dynamic matrix.

EasyCI to the Rescue

Symplify 9 is coming with a brand new package called EasyCI. This package helps you with CI maintenance, like in our case above:

composer require symplify/easy-ci --dev

vendor/bin/easy-ci php-json
# "[7.2, 7.3, 7.4, 8.0]"

Now we have a problem, the command to provide JSON data and GitHub Actions. Let's put them together.

Workflow with Dynamic PHP Versions

There are 2 steps in our unit tests workflow:

The First Step

jobs:
    # first step
    provide_php_versions_json:
        runs-on: ubuntu-latest
        steps:
            -   uses: actions/[email protected]

            -   uses: shivammathur/[email protected]
                with:
                    # this is the only place we have to use PHP to avoid the lock to bash scripting
                    php-version: 8.0

            -   run: composer install --no-progress --ansi

            # to see the provided output, just to be sure
            -   run: vendor/bin/easy-ci php-versions-json

            # here we create the json, we need the "id:" so we can use it in "outputs" bellow
            -
                id: output_data
                run: echo "::set-output name=matrix::$(vendor/bin/easy-ci php-versions-json)"

        # here, we save the result of this 1st phase to the "outputs"
        outputs:
            matrix: ${{ steps.output_data.outputs.matrix }}

The Second Step

    # continue from above
    unit_tests:
        needs: provide_php_versions_json

        runs-on: ubuntu-latest

        strategy:
            fail-fast: false
            matrix:
                php: ${{ fromJson(needs.provide_php_versions_json.outputs.matrix) }}

        # continue with tests
        steps:
            # ...
            -   run: vendor/bin/phpunit


This workflow is not just a theory. It's tested for last week on Symplify repository and it works :).

Here is full .github/workflows/unit_tests.yaml worfklow to explore.


Happy coding!