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
114 changes: 114 additions & 0 deletions .github/actions/setup-php/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
name: "Setup PHP"
description: "Select and configure the PHP the runner image provides"

# The org Actions policy admits GitHub-owned actions only — no Marketplace, no
# verified creators — so shivammathur/setup-php is unavailable here.
#
# That action's job was installing arbitrary PHP versions. This one cannot: the
# runner image carries exactly one PHP, so this verifies the requested version is
# the one present and applies the ini/coverage settings the matrix used to ask
# setup-php for. Requesting any other version fails loudly rather than silently
# testing the wrong runtime.
#
# Restoring the full 8.1–8.4 matrix is LIBRARIES-3169. Extending this action to
# install other versions is one of the options recorded there; it is not a
# drop-in and should not be attempted without reading the ticket first.

inputs:
php-version:
description: "MAJOR.MINOR required, e.g. '8.3'. Must match the runner image's PHP."
required: true
coverage:
description: "'none' disables Xdebug for speed; 'xdebug' enables coverage mode."
required: false
default: "none"
extensions:
description: "Comma-separated extensions to require. Verified, never installed."
required: false
default: ""
ini-values:
description: "Comma-separated php.ini directives, e.g. 'error_reporting=E_ALL, display_errors=On'."
required: false
default: ""

outputs:
php-version:
description: "Exact version present, e.g. '8.3.6'"
value: ${{ steps.verify.outputs.php-version }}

runs:
using: "composite"
steps:
- name: Verify the runner's PHP matches the requested version
id: verify
shell: bash
env:
REQUESTED: ${{ inputs.php-version }}
run: |
set -euo pipefail

if ! command -v php >/dev/null 2>&1; then
echo "::error::No php on PATH. The runner image is expected to ship one."
exit 1
fi

FULL="$(php -r 'echo PHP_VERSION;')"
SHORT="$(php -r 'echo PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION;')"

if [ "${SHORT}" != "${REQUESTED}" ]; then
echo "::error::Requested PHP ${REQUESTED} but the runner provides ${FULL}. Only ${SHORT} can be tested here — see LIBRARIES-3169 before widening the matrix."
exit 1
fi

echo "php-version=${FULL}" >> "${GITHUB_OUTPUT}"
echo "PHP ${FULL}"

- name: Verify required extensions
if: ${{ inputs.extensions != '' }}
shell: bash
env:
EXTENSIONS: ${{ inputs.extensions }}
run: |
set -euo pipefail
MISSING=""
IFS=','
for ext in ${EXTENSIONS}; do
ext="$(echo "${ext}" | tr -d '[:space:]')"
[ -z "${ext}" ] && continue
php -m | grep -qix "${ext}" || MISSING="${MISSING} ${ext}"
done
unset IFS
if [ -n "${MISSING}" ]; then
echo "::error::Missing PHP extension(s):${MISSING}. This action verifies but cannot install — see LIBRARIES-3169."
exit 1
fi

- name: Apply ini values and coverage mode
shell: bash
env:
INI_VALUES: ${{ inputs.ini-values }}
COVERAGE: ${{ inputs.coverage }}
run: |
set -euo pipefail

# A leading ':' keeps the image's own conf.d on the scan path, so this
# adds directives rather than replacing the distro configuration.
if [ -n "${INI_VALUES}" ]; then
INI_DIR="${RUNNER_TEMP}/php-ci-ini"
mkdir -p "${INI_DIR}"
: > "${INI_DIR}/99-ci.ini"
IFS=','
for kv in ${INI_VALUES}; do
kv="$(echo "${kv}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
[ -z "${kv}" ] && continue
echo "${kv}" >> "${INI_DIR}/99-ci.ini"
done
unset IFS
echo "PHP_INI_SCAN_DIR=:${INI_DIR}" >> "${GITHUB_ENV}"
fi

case "${COVERAGE}" in
none) echo "XDEBUG_MODE=off" >> "${GITHUB_ENV}" ;;
xdebug) echo "XDEBUG_MODE=coverage" >> "${GITHUB_ENV}" ;;
*) echo "::error::Unsupported coverage mode '${COVERAGE}' (expected 'none' or 'xdebug')"; exit 1 ;;
esac
209 changes: 209 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
name: CI

on:
push:
branches: [master, main]
pull_request:
workflow_dispatch:

permissions:
id-token: write
contents: read

env:
ARTIFACTORY_URL: ${{ vars.ARTIFACTORY_URL }}

# ──────────────────────────────────────────────────────────────────────────
# PHP version matrix — NARROWED, restore tracked by LIBRARIES-3169.
#
# The intended matrix is 8.1 / 8.2 / 8.3 / 8.4, matching the "^7.4 || ^8.0"
# that composer.json declares. It ran on shivammathur/setup-php, which the
# org Actions policy does not admit (GitHub-owned actions only), so every
# workflow using it failed at startup with no check reported.
#
# The runner image ships exactly one PHP, so CI can only prove that one.
# PHP_MATRIX below is the single source of truth for all three matrix jobs:
# restoring breadth is a one-line change here once LIBRARIES-3169 lands a
# way to install other versions. PHP_PRIMARY is the leg that also produces
# coverage; it must stay a member of PHP_MATRIX.
#
# Do NOT widen PHP_MATRIX on its own — .github/actions/setup-php verifies the
# runtime and fails loudly on a version the image lacks, by design.
# ──────────────────────────────────────────────────────────────────────────
PHP_MATRIX: '["8.3"]'
PHP_PRIMARY: "8.3"

jobs:
# Resolves the env-level matrix into an output, because `strategy.matrix`
# cannot read `env` directly.
config:
name: Resolve matrix
# Bare ubuntu-latest gets no runner on same-repo PRs in this org; only the
# fork path may use it.
runs-on: ${{ github.event.pull_request.head.repo.fork && 'ubuntu-latest' || 'ubuntu-x64' }}
outputs:
php-versions: ${{ steps.set.outputs.php-versions }}
php-primary: ${{ steps.set.outputs.php-primary }}
steps:
- id: set
shell: bash
run: |
set -euo pipefail
echo "php-versions=${PHP_MATRIX}" >> "${GITHUB_OUTPUT}"
echo "php-primary=${PHP_PRIMARY}" >> "${GITHUB_OUTPUT}"

coding-standard:
name: Coding Standards
needs: [config]
runs-on: ${{ github.event.pull_request.head.repo.fork && 'ubuntu-latest' || 'ubuntu-x64' }}

steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4

- name: Set up PHP
uses: ./.github/actions/setup-php
with:
php-version: ${{ needs.config.outputs.php-primary }}
coverage: none

- name: Configure Artifactory
if: ${{ !github.event.pull_request.head.repo.fork }}
uses: twilio/sdk-actions/artifactory-oidc@c94e420aa64ea686ff25bb03d4c66cdaf8e523e4 # main
with:
ecosystem: php
provider-name: github-actions-segmentio

- name: Install Composer dependencies
run: composer install --no-scripts --no-interaction --prefer-dist

# cs2pr came from setup-php's `tools:` and annotated PRs inline. Without
# it the report is only readable in the log and the artifact below —
# see LIBRARIES-3169.
- name: Check coding standards
continue-on-error: true
run: ./vendor/bin/phpcs -s --report-full --report-checkstyle=./phpcs-report.xml

- name: Upload PHPCS report
if: ${{ always() }}
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: phpcs-report
path: phpcs-report.xml
if-no-files-found: warn

lint:
name: "Lint: PHP ${{ matrix.php }}"
needs: [config]
runs-on: ${{ github.event.pull_request.head.repo.fork && 'ubuntu-latest' || 'ubuntu-x64' }}
strategy:
fail-fast: false
matrix:
php: ${{ fromJSON(needs.config.outputs.php-versions) }}

steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4

- name: Set up PHP
uses: ./.github/actions/setup-php
with:
php-version: ${{ matrix.php }}
coverage: none

- name: Configure Artifactory
if: ${{ !github.event.pull_request.head.repo.fork }}
uses: twilio/sdk-actions/artifactory-oidc@c94e420aa64ea686ff25bb03d4c66cdaf8e523e4 # main
with:
ecosystem: php
provider-name: github-actions-segmentio

- name: Install Composer dependencies
run: composer install --no-scripts --no-interaction --prefer-dist

- name: Lint against parse errors
run: composer lint

test:
name: "Test: PHP ${{ matrix.php }}"
needs: [config, coding-standard, lint]
runs-on: ${{ github.event.pull_request.head.repo.fork && 'ubuntu-latest' || 'ubuntu-x64' }}
strategy:
fail-fast: false
matrix:
php: ${{ fromJSON(needs.config.outputs.php-versions) }}

steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4

- name: Set up PHP
uses: ./.github/actions/setup-php
with:
php-version: ${{ matrix.php }}
coverage: xdebug
ini-values: error_reporting=E_ALL, display_errors=On
extensions: curl, json, mbstring

- name: Configure Artifactory
if: ${{ !github.event.pull_request.head.repo.fork }}
uses: twilio/sdk-actions/artifactory-oidc@c94e420aa64ea686ff25bb03d4c66cdaf8e523e4 # main
with:
ecosystem: php
provider-name: github-actions-segmentio

- name: Install Composer dependencies
run: composer install --no-scripts --no-interaction --prefer-dist

- name: Run tests
run: ./vendor/bin/phpunit --no-coverage

- name: Run tests with coverage
if: ${{ matrix.php == needs.config.outputs.php-primary }}
run: ./vendor/bin/phpunit

# codecov/codecov-action is not allow-listed either, so coverage is kept
# as an artifact rather than uploaded — see LIBRARIES-3169.
- name: Upload coverage report
if: ${{ success() && matrix.php == needs.config.outputs.php-primary }}
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: coverage-clover
path: build/logs/clover.xml
if-no-files-found: error

build-verification:
name: Build Verification
needs: [config, test]
runs-on: ${{ github.event.pull_request.head.repo.fork && 'ubuntu-latest' || 'ubuntu-x64' }}

steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4

- name: Set up PHP
uses: ./.github/actions/setup-php
with:
php-version: ${{ needs.config.outputs.php-primary }}
coverage: none

- name: Configure Artifactory
if: ${{ !github.event.pull_request.head.repo.fork }}
uses: twilio/sdk-actions/artifactory-oidc@c94e420aa64ea686ff25bb03d4c66cdaf8e523e4 # main
with:
ecosystem: php
provider-name: github-actions-segmentio

- name: Install Composer dependencies (production)
run: composer install --no-scripts --no-interaction --prefer-dist --no-dev --optimize-autoloader

- name: Verify autoload
run: php -r "require 'vendor/autoload.php'; echo 'Autoload OK' . PHP_EOL;"

- name: Verify package structure
run: |
# Ensure required files exist for Packagist
test -f composer.json
test -f LICENSE.md
test -d lib
echo "Package structure verified"
Loading
Loading