Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 129 additions & 0 deletions .github/workflows/php-ci.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# php-ci

Reusable workflow that wires together the full PHP / Composer CI pipeline in a single call. The consuming repo controls **when** it runs; this workflow controls **how**.

> Replace `<current-sha>` with the current SHA from the [root README](../../README.md#current-sha).

## Pipeline shape

```
push / pull_request / workflow_dispatch
┌──────────┐
│ security │ gitleaks + composer-audit + guarddog (parallel)
└─────┬────┘
│ (gates everything below)
├──────────────┐
▼ ▼
┌──────┐ ┌──────┐
│ lint │ │ test │
└──┬───┘ └──┬───┘
└──────────────┘
│ (both must pass)
┌──────────────────┐
│vulnerability-scan│ Syft + Grype (code + Docker)
└──────────────────┘
```

## Usage

### Minimal — PHP service, no Dockerfile

```yaml
# .github/workflows/ci.yml (in your consuming repo)
name: CI

on:
push:
pull_request:
workflow_dispatch:

jobs:
ci:
uses: orangitfi/platform-tooling/.github/workflows/php-ci.yml@<current-sha>
with:
run-docker-scan: false
```

### PHP service with a Dockerfile

```yaml
jobs:
ci:
uses: orangitfi/platform-tooling/.github/workflows/php-ci.yml@<current-sha>
with:
image-name: my-app
```

### Custom PHPStan level and test suite

```yaml
jobs:
ci:
uses: orangitfi/platform-tooling/.github/workflows/php-ci.yml@<current-sha>
with:
phpstan-level: "6"
test-command: "vendor/bin/phpunit --testsuite=Unit"
```

### Full example with all options explicit

```yaml
jobs:
ci:
uses: orangitfi/platform-tooling/.github/workflows/php-ci.yml@<current-sha>
with:
working-directory: .
run-phpcs: true
phpcs-standard: PSR12
run-phpstan: true
phpstan-level: "5"
paths: src
dockerfile-path: Dockerfile
test-command: vendor/bin/phpunit
composer-args: ""
fail-on-severity: high
run-docker-scan: true
image-name: my-app
```

## Parameters

| Input | Default | Description |
|-------|---------|-------------|
| `working-directory` | `.` | Directory containing `composer.json` |
| `run-phpcs` | `true` | Run PHP_CodeSniffer in the lint job |
| `phpcs-standard` | `PSR12` | Coding standard when no `phpcs.xml` is present |
| `run-phpstan` | `true` | Run PHPStan in the lint job |
| `phpstan-level` | `5` | Analysis level 1–9 when no `phpstan.neon` is present |
| `paths` | `src` | Space-separated paths both lint tools scan (when no config file is present) |
| `dockerfile-path` | `Dockerfile` | Path to the Dockerfile (used by lint and vulnerability scan) |
| `test-command` | `vendor/bin/phpunit` | Full command to run tests |
| `composer-args` | `""` | Extra arguments passed to `composer install` in the test job |
| `fail-on-severity` | `high` | Minimum Grype severity for the vulnerability scan |
| `run-docker-scan` | `true` | Build and scan the Docker image in the vulnerability scan |
| `image-name` | *(repo name)* | Docker image name for the vulnerability scan |

## Jobs reference

| Job | Calls | Condition |
|-----|-------|-----------|
| `security` | `php-security-scan` | always |
| `lint` | `php-lint` | always (after security) |
| `test` | `php-test` | always (after security) |
| `vulnerability-scan` | `php-vulnerability-scan` | after lint and test pass |

## When it has value

- **Single line of CI**: one `uses:` line gives you secret scanning, dependency auditing, static analysis, unit tests, and vulnerability scanning — in the right order, with the right gates.
- **No Dockerfile?** Set `run-docker-scan: false` — the pipeline still runs all PHP-specific quality checks.
- **Scan before install**: composer-audit and guarddog run against `composer.lock` and `composer.json` before any package is installed. A compromised dependency is blocked before it executes.
- **Project config files respected**: if `phpcs.xml` or `phpstan.neon` are committed, the lint job uses them as-is. Workflow inputs only apply as fallbacks.

## Tips

- If phpcs or phpstan are not yet dev dependencies in the project, set `run-phpcs: false` and/or `run-phpstan: false` to skip them until the tools are added.
- Add this workflow as the sole required status check in branch protection rules: one check covers the entire pipeline.
- For nightly DAST scanning of the deployed service, use [`php-daily`](php-daily.md) in a separate scheduled workflow.
149 changes: 149 additions & 0 deletions .github/workflows/php-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# PHP CI
#
# Reusable workflow that wires together the full PHP / Composer CI pipeline:
#
# security — gitleaks + composer-audit + guarddog (gates everything below)
# lint — phpcs + phpstan + hadolint ┐
# test — composer install + phpunit ├─ parallel after security
# vulnerability-scan — Syft + Grype (after all above pass or are skipped)
#
# The consuming repo controls WHEN this runs. This workflow controls HOW.
#
# Usage in a consuming repository:
#
# on: [push, pull_request, workflow_dispatch]
#
# jobs:
# ci:
# uses: orangitfi/platform-tooling/.github/workflows/php-ci.yml@<sha>
# with:
# run-docker-scan: false
#
# See php-ci.md for full documentation and all parameter options.
#
# ─── SHA pinning ─────────────────────────────────────────────────────────────
# The `uses:` lines below reference platform-tooling workflows at a specific
# SHA. After committing this file, run the SHA bump process documented in the
# root README to replace the SHA with the new commit SHA.
# ─────────────────────────────────────────────────────────────────────────────

name: CI

on:
workflow_call:
inputs:
working-directory:
description: "Directory containing composer.json"
required: false
default: "."
type: string
# ── Lint inputs ──────────────────────────────────────────────────────
run-phpcs:
description: "Run PHP_CodeSniffer in the lint job"
required: false
default: true
type: boolean
phpcs-standard:
description: "phpcs coding standard when no phpcs.xml is present (e.g. PSR12)"
required: false
default: "PSR12"
type: string
run-phpstan:
description: "Run PHPStan in the lint job"
required: false
default: true
type: boolean
phpstan-level:
description: "PHPStan analysis level (1-9) when no phpstan.neon is present"
required: false
default: "5"
type: string
paths:
description: "Space-separated paths both lint tools scan when no tool config file is present"
required: false
default: "src"
type: string
dockerfile-path:
description: "Path to the Dockerfile relative to the repo root (used by lint and vulnerability scan)"
required: false
default: "Dockerfile"
type: string
# ── Test inputs ──────────────────────────────────────────────────────
test-command:
description: "Command to run tests (e.g. vendor/bin/phpunit --testsuite=Unit)"
required: false
default: "vendor/bin/phpunit"
type: string
composer-args:
description: "Extra arguments passed to composer install in the test job"
required: false
default: ""
type: string
# ── Vulnerability scan inputs ────────────────────────────────────────
fail-on-severity:
description: "Minimum Grype severity for the vulnerability scan: critical, high, medium, low, negligible"
required: false
default: "high"
type: string
run-docker-scan:
description: "Build and scan the Docker image in the vulnerability scan (set false for repos without a Dockerfile)"
required: false
default: true
type: boolean
image-name:
description: "Docker image name — used by the vulnerability scan"
required: false
default: ""
type: string

permissions:
contents: read

jobs:
# ───────────────────────────────────────────────────────────────────────────
# 1. Security audit — runs first, gates everything else
# ───────────────────────────────────────────────────────────────────────────
security:
name: Security Scan
uses: orangitfi/platform-tooling/.github/workflows/php-security-scan.yml@f877740fc9a519be6b06492ffd69017337b8ca86 # pt-sha
with:
working-directory: ${{ inputs.working-directory }}

# ───────────────────────────────────────────────────────────────────────────
# 2. Lint + Test — parallel after security passes
# ───────────────────────────────────────────────────────────────────────────
lint:
name: Lint
needs: security
uses: orangitfi/platform-tooling/.github/workflows/php-lint.yml@f877740fc9a519be6b06492ffd69017337b8ca86 # pt-sha
with:
working-directory: ${{ inputs.working-directory }}
run-phpcs: ${{ inputs.run-phpcs }}
phpcs-standard: ${{ inputs.phpcs-standard }}
run-phpstan: ${{ inputs.run-phpstan }}
phpstan-level: ${{ inputs.phpstan-level }}
paths: ${{ inputs.paths }}
dockerfile-path: ${{ inputs.dockerfile-path }}

test:
name: Test
needs: security
uses: orangitfi/platform-tooling/.github/workflows/php-test.yml@f877740fc9a519be6b06492ffd69017337b8ca86 # pt-sha
with:
working-directory: ${{ inputs.working-directory }}
test-command: ${{ inputs.test-command }}
composer-args: ${{ inputs.composer-args }}

# ───────────────────────────────────────────────────────────────────────────
# 3. Vulnerability scan — runs after all quality gates pass or are skipped
# ───────────────────────────────────────────────────────────────────────────
vulnerability-scan:
name: Vulnerability Scan
needs: [lint, test]
uses: orangitfi/platform-tooling/.github/workflows/php-vulnerability-scan.yml@f877740fc9a519be6b06492ffd69017337b8ca86 # pt-sha
with:
working-directory: ${{ inputs.working-directory }}
fail-on-severity: ${{ inputs.fail-on-severity }}
run-docker-scan: ${{ inputs.run-docker-scan }}
dockerfile-path: ${{ inputs.dockerfile-path }}
image-name: ${{ inputs.image-name }}
Loading
Loading