A composite action for the four steps we copy everywhere

Checkout, set up PHP, restore the Composer cache, install — repeated in nine jobs across four workflows, with three of them a version behind.

# .github/actions/php-setup/action.yml
name: PHP setup
inputs:
  php-version: { default: '8.2' }
runs:
  using: composite
  steps:
    - uses: shivammathur/setup-php@v2
      with: { php-version: ${{ inputs.php-version }}, coverage: none }
    - uses: actions/cache@v3
      with:
        path: ~/.composer/cache
        key: composer-${{ hashFiles('composer.lock') }}
    - run: composer install --no-interaction --no-progress
      shell: bash

The shell key on every run step is mandatory in a composite action and omitting it is the first error everybody hits. A composite action runs in the caller’s workspace, which is what makes it right for setup steps and wrong for anything that wants isolation — for that you want a reusable workflow, which is a different mechanism with different inputs and its own job.