GitHub And Open Source Collaboration

GitHub Actions Fundamentals For PHP Projects

GitHub Actions is GitHub's automation platform for repository workflows. It can run tests when a pull request opens, build assets after a push, publish packages when a release is created, label issues, upload artifacts, or deploy an application after protected checks pass. For a PHP developer, Actions is often the first remote quality gate that other contributors can see. A local test proves that code works on one machine. A well-designed workflow proves that the project can install dependencies, run checks, and produce expected output in a clean environment.

Use GitHub's official documentation for current syntax and platform behavior. GitHub documents the Actions model in Understanding GitHub Actions, detailed YAML options in Workflow syntax for GitHub Actions, workflow artifacts, dependency caching, and using secrets. Check those docs before teaching exact options, limits, retention behavior, or security details because Actions evolves over time.

The Core Model

A workflow is a YAML file in .GitHub/workflows/. It runs when an event triggers it. The workflow contains one or more jobs. Each job runs on a runner and contains ordered steps. A step can run a shell command or use an action. Jobs can run in parallel by default, or one job can depend on another. This vocabulary is the foundation for every useful Actions conversation.

The event answers when should this automation start? Common examples include pull_request, push, workflow_dispatch, schedule, and release-related events. The job answers what work should run together on one runner? The runner answers where does the job execute? The step answers what command or reusable action runs next?

A basic PHP continuous-integration workflow might check out the repository, set up PHP, install Composer dependencies, run static analysis, and run tests. The purpose is not to move every local command into GitHub. The purpose is to create shared evidence that every contributor and reviewer can trust.

name: PHP CI

on:
  pull_request:
  push:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install dependencies
        run: composer install --no-interaction --prefer-dist
      - name: Run tests
        run: vendor/bin/phpunit

This example is intentionally minimal. A real project may need PHP setup, extensions, environment variables, database services, frontend assets, static analysis, or coverage. The important lesson is that each step should have a reason reviewers understand.

Events And Pull Request Safety

Events are not interchangeable. A workflow that runs on push to main has different trust assumptions from one that runs on a pull request from a fork. Pull requests from external contributors may contain untrusted code. Running tests is normal; exposing production secrets to that code is dangerous. Actions workflows must be designed around the trust level of the event.

For ordinary PHP CI, pull_request is often appropriate because the workflow should test proposed changes before merge. For deployments, use events tied to protected branches, tags, releases, or approved environments. For manual operational work, workflow_dispatch can be useful because it records who started the workflow and which inputs were used. For scheduled work, remember that schedules are automation triggers, not precise business guarantees; long-running or critical jobs need monitoring and idempotency.

A safe workflow makes trust boundaries explicit. Do not put deployment credentials in a workflow that runs arbitrary pull-request code. Do not assume a branch name alone proves that code is safe. Use environment protection, required reviewers, narrow permissions, and separate workflows when the trust model changes.

Runners And Environments

A runner is the machine that executes a job. GitHub-hosted runners are fresh virtual machines provided by GitHub for supported operating systems. Self-hosted runners are machines you operate. A GitHub-hosted runner is convenient and isolated for many CI jobs. A self-hosted runner can access private networks or specialized hardware, but it creates operational and security responsibilities.

For PHP projects, runners must match the behavior you need to prove. If production runs Linux with PHP-FPM, a Linux runner is usually a better CI baseline than a macOS runner. If the package supports multiple PHP versions, use a matrix so the same job runs across those versions. If the application depends on PostgreSQL or Redis, use services or integration environments deliberately rather than pretending unit tests prove database behavior.

Do not treat runner labels as magic. ubuntu-latest is convenient, but it can move as GitHub updates hosted images. For sensitive compatibility checks, pin a more specific environment where the platform allows it, or document why latest is acceptable. For self-hosted runners, patching, isolation, cleanup, network access, and credential exposure become part of the system you own.

Permissions And The Workflow Token

Actions workflows can receive a GITHUB_TOKEN with permissions that depend on repository and workflow settings. A workflow should request only the permissions it needs. If a test job only reads repository contents, it should not need write access to issues, packages, deployments, or pull requests. Least privilege matters because workflow files are code. A vulnerable workflow can become an access path into repository automation.

Use the permissions key to make intent visible. A workflow that comments on pull requests may need pull-request or issue permissions. A workflow that publishes a package needs package permissions. A workflow that only runs tests can be more restrictive. Reviewers should treat permission changes as security-sensitive, just like changes to authentication code.

Avoid granting broad permissions because a command failed once. Diagnose the operation: is it reading code, writing a check result, uploading an artifact, publishing a package, creating a release, or deploying? Then grant the narrow permission for that operation if the platform supports it.

Secrets, Variables, And Configuration

Secrets are encrypted values made available to workflows according to repository, organization, or environment policy. They are for credentials and sensitive values, not ordinary configuration. Variables are better for non-secret settings. Environment protection can restrict when deployment secrets become available.

A PHP workflow might use secrets for deployment SSH keys, API tokens, signing keys, or package registry credentials. It might use variables for application environment names, package names, or non-sensitive feature flags. Do not print secrets. Do not pass secrets to commands that echo full environment state. Do not expose secrets to untrusted pull-request workflows. If a secret leaks, rotate it first, then investigate logs and history.

Secrets should also be owned. A repository with a PRODUCTION_SSH_KEY secret but no documentation for rotation is fragile. The next maintainer needs to know what the secret can access, who owns the corresponding account or key, how to replace it, and what workflow depends on it.

Artifacts And Caches

Artifacts and caches solve different problems. An artifact preserves output from a workflow run, such as test reports, built assets, coverage reports, logs, or packaged release files. A cache speeds up future runs by reusing dependencies or build output when a key matches. Mixing them up creates confusion: a cache is not a durable release artifact, and an artifact is not primarily a performance optimization.

For PHP, Composer dependencies are a common cache candidate. The cache key should include files that affect dependency resolution, such as composer.lock. If the key is too broad, stale dependencies can hide problems. If it is too narrow, the cache rarely helps. For artifacts, upload useful evidence: coverage HTML, test logs, built static assets, or deployment bundles that another job consumes.

Artifacts can also create privacy and retention concerns. Do not upload .env, database dumps with real data, secrets, or logs containing tokens. Treat artifacts as files someone may download later. Retention settings and access rules should match the sensitivity of the output.

Matrices, Dependencies, And Failure Meaning

A matrix runs a job across combinations such as PHP versions, operating systems, or dependency modes. A library might test PHP 8.2, 8.3, 8.4, and 8.5. An application might test only the production PHP version plus the next planned upgrade. More combinations are not automatically better. Each additional job costs time and attention. Choose the matrix from compatibility promises.

Job dependencies make workflows readable. A package job should depend on tests if packaging untested code is not useful. A deployment job should depend on build and test jobs. A notification job might run even after failure if it reports the result. The workflow graph should express the project policy: what can run in parallel, what must wait, and what failure stops the pipeline.

Failure messages should be actionable. A job named build that runs formatting, tests, packaging, and deployment makes failures harder to interpret. Split jobs when the distinction helps reviewers or maintainers respond. Keep jobs together when splitting would create noisy overhead without a decision benefit.

Workflow Review

Review workflow changes carefully because they are executable project policy. Ask which event triggers the workflow, what code can influence it, what permissions it receives, which secrets it can access, what runner it uses, what artifacts it uploads, and what happens on failure. If the workflow deploys, also ask how it is protected, how it is rolled back, and how partial failure is detected.

For a PHP project, a good first Actions workflow is often simple: install dependencies from the lock file, run formatting or linting, run static analysis if configured, run tests, and upload test evidence only when useful. Add deployment, package publishing, matrices, caches, and artifacts as the project needs them. Complexity in CI should buy reliability, speed, or clarity; otherwise it becomes another system to debug.

The learner should be able to read an Actions workflow, identify events, jobs, runners, steps, permissions, secrets, artifacts, and caches, explain what each part proves, and review workflow changes as both automation and security-sensitive code.

Practice

Read a workflow
name: CI
on: [pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: composer install --no-interaction
      - run: vendor/bin/phpunit

Identify the event, job, runner, and steps. Then explain what this workflow proves and what it does not prove.

Show solution

The event is pull_request, so the workflow runs for pull request activity. The single job is test. It runs on ubuntu-latest. The steps check out the repository, install Composer dependencies, and run PHPUnit.

This proves that the project can install dependencies and pass PHPUnit on the selected Ubuntu runner for the pull request code. It does not prove deployment works, production configuration is correct, database migrations are safe, frontend assets build, or secrets are available. It also does not prove behavior on other PHP versions unless the workflow explicitly sets and tests them.

Review permissions
permissions: write-all

Review the risk and propose a safer direction.

Acceptance criteria:

  • Explain why broad permissions are risky.
  • Identify what a test-only workflow usually needs.
  • Mention that exact permission names should be checked against current GitHub documentation.
Show solution

write-all is excessive for a test-only workflow. If untrusted or compromised workflow code can run with broad write permissions, the blast radius is much larger than necessary. A test job usually needs to read repository contents and publish a status/check result through the platform's normal workflow mechanism. It should not need broad write access to issues, packages, deployments, or repository administration.

A safer direction is to set explicit minimal permissions for the workflow or job, using the current GitHub Actions permissions documentation. If a later step needs to comment on a pull request or upload a package, grant that permission only to the job that needs it.

Choose artifact or cache
  1. Composer dependency files used to speed up later CI runs.
  2. A coverage HTML report reviewers may download from a workflow run.
  3. A production deployment SSH private key.
  4. The string staging used as a non-secret environment name.
  5. A .env file containing database credentials.

Explain each choice.

Show solution
  1. Cache. Composer dependencies can be cached using a key based on dependency files such as composer.lock.
  2. Artifact. A coverage report is output from a run that reviewers may inspect later.
  3. Secret. A private key is sensitive and should be stored only if the deployment design requires it and access is protected.
  4. Variable. staging is ordinary configuration, not a secret.
  5. Neither as an uploaded file. A real .env with credentials should not be committed, cached, or uploaded as an artifact. Store required sensitive values as secrets and use safe example files for documentation.