Skip to content

chore(deps): Bump the actions group across 1 directory with 6 updates… #41

chore(deps): Bump the actions group across 1 directory with 6 updates…

chore(deps): Bump the actions group across 1 directory with 6 updates… #41

Workflow file for this run

# SPDX-License-Identifier: MPL-2.0
name: PHP Lint & Analysis
on:
push:
branches: [main, master]
paths:
- 'src/**'
- 'tests/**'
- 'composer.json'
- 'composer.lock'
- 'phpstan.neon'
- 'phpunit.xml'
- '.php-cs-fixer.dist.php'
- '.github/workflows/php-lint.yml'
pull_request:
paths:
- 'src/**'
- 'tests/**'
- 'composer.json'
- 'composer.lock'
- 'phpstan.neon'
- 'phpunit.xml'
- '.php-cs-fixer.dist.php'
- '.github/workflows/php-lint.yml'
permissions:
contents: read
env:
PHP_VERSION: '8.3'
jobs:
# ============================================================
# Syntax Check - Fast fail on parse errors
# ============================================================
syntax:
name: PHP Syntax Check
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Setup PHP
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2.37.2
with:
php-version: ${{ env.PHP_VERSION }}
tools: none
coverage: none
- name: Check PHP syntax
run: |
echo "Checking PHP syntax..."
find src -name "*.php" -print0 | xargs -0 -n1 php -l
echo "✅ All PHP files have valid syntax"
# ============================================================
# Code Style - PSR-12 compliance via PHP-CS-Fixer
# ============================================================
code-style:
name: Code Style (PHP-CS-Fixer)
runs-on: ubuntu-latest
needs: syntax
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Setup PHP
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2.37.2
with:
php-version: ${{ env.PHP_VERSION }}
tools: php-cs-fixer:3
coverage: none
- name: Run PHP-CS-Fixer
run: |
php-cs-fixer fix --dry-run --diff --verbose --config=.php-cs-fixer.dist.php
# ============================================================
# Static Analysis - PHPStan at maximum strictness
# ============================================================
phpstan:
name: Static Analysis (PHPStan Level 9)
runs-on: ubuntu-latest
needs: syntax
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Setup PHP
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2.37.2
with:
php-version: ${{ env.PHP_VERSION }}
tools: phpstan:1
coverage: none
- name: Install Composer dependencies
run: composer install --no-progress --prefer-dist --no-interaction
- name: Run PHPStan
run: |
phpstan analyse --configuration=phpstan.neon --error-format=github
# ============================================================
# SPDX License Headers - Ensure all files have headers
# ============================================================
license-headers:
name: SPDX License Headers
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Check SPDX headers
run: |
MISSING=""
for file in $(find src -name "*.php"); do
if ! grep -q "SPDX-License-Identifier" "$file"; then
MISSING="$MISSING\n - $file"
fi
done
if [ -n "$MISSING" ]; then
echo "::error::Missing SPDX-License-Identifier in:$MISSING"
exit 1
fi
echo "✅ All PHP files have SPDX license headers"
# ============================================================
# Strict Types - Ensure all files use strict_types
# ============================================================
strict-types:
name: Strict Types Declaration
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Check strict_types
run: |
MISSING=""
for file in $(find src -name "*.php"); do
if ! grep -q "declare(strict_types=1)" "$file"; then
MISSING="$MISSING\n - $file"
fi
done
if [ -n "$MISSING" ]; then
echo "::error::Missing declare(strict_types=1) in:$MISSING"
exit 1
fi
echo "✅ All PHP files declare strict_types=1"
# ============================================================
# Security Patterns - Check for dangerous code patterns
# ============================================================
security-patterns:
name: Security Pattern Check
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Check dangerous functions
run: |
FOUND=0
# Dangerous execution functions.
# Word boundaries (\b) are required so legitimate functions whose
# names merely END in a dangerous token are not flagged, e.g.
# curl_exec() (HTTP, not command execution) must NOT match \bexec\(.
DANGEROUS=$(grep -rEn '\beval\s*\(|\bexec\s*\(|\bsystem\s*\(|\bpassthru\s*\(|\bshell_exec\s*\(|\bproc_open\s*\(|\bpopen\s*\(' \
--include="*.php" src/ 2>/dev/null || true)
if [ -n "$DANGEROUS" ]; then
echo "::error::Dangerous execution functions found:"
echo "$DANGEROUS"
FOUND=1
fi
# Backtick (shell) execution with variable interpolation.
# Anchor on the OPENING backtick of a shell-exec operator, then drop
# lines where the backtick is a literal character inside a quoted
# string/regex (this security library escapes the backtick char in
# its Turtle/URI validators, which is legitimate, not shell exec).
BACKTICKS=$(grep -rEn '(=|\(|\breturn\b|\becho\b)\s*`[^`]*\$[^`]*`' --include="*.php" src/ 2>/dev/null \
| grep -vE "['\"][^'\"]*\`" || true)
if [ -n "$BACKTICKS" ]; then
echo "::error::Backtick execution with variables found:"
echo "$BACKTICKS"
FOUND=1
fi
# preg_replace with /e modifier (deprecated but check anyway)
PREG_E=$(grep -rEn "preg_replace\s*\([^)]*'/[^']*e[^']*'" --include="*.php" src/ 2>/dev/null || true)
if [ -n "$PREG_E" ]; then
echo "::error::preg_replace with /e modifier found:"
echo "$PREG_E"
FOUND=1
fi
# assert() with string (can execute code)
ASSERT_STR=$(grep -rEn "assert\s*\(['\"]" --include="*.php" src/ 2>/dev/null || true)
if [ -n "$ASSERT_STR" ]; then
echo "::error::assert() with string argument found:"
echo "$ASSERT_STR"
FOUND=1
fi
# create_function (deprecated, can execute code)
CREATE_FUNC=$(grep -rEn 'create_function\s*\(' --include="*.php" src/ 2>/dev/null || true)
if [ -n "$CREATE_FUNC" ]; then
echo "::error::create_function() found (use closures instead):"
echo "$CREATE_FUNC"
FOUND=1
fi
if [ "$FOUND" -eq 0 ]; then
echo "✅ No dangerous function patterns found"
else
exit 1
fi
- name: Check weak cryptography
run: |
FOUND=0
# MD5 for security (allow md5_file for checksums)
MD5=$(grep -rEn 'md5\s*\(' --include="*.php" src/ 2>/dev/null | grep -v 'md5_file' || true)
if [ -n "$MD5" ]; then
echo "::warning::MD5 usage found (ensure not used for security):"
echo "$MD5"
fi
# SHA1 for security
SHA1=$(grep -rEn 'sha1\s*\(' --include="*.php" src/ 2>/dev/null || true)
if [ -n "$SHA1" ]; then
echo "::warning::SHA1 usage found (ensure not used for security):"
echo "$SHA1"
fi
# Insecure random functions
RAND=$(grep -rEn '\brand\s*\(|\bmt_rand\s*\(|\buniqid\s*\(' --include="*.php" src/ 2>/dev/null || true)
if [ -n "$RAND" ]; then
echo "::warning::Potentially insecure random functions found (use random_int/random_bytes):"
echo "$RAND"
fi
echo "✅ Weak cryptography check completed"
- name: Check SQL injection patterns
run: |
# Direct variable interpolation in queries
SQLI=$(grep -rEn '(mysql_query|mysqli_query|pg_query|->query)\s*\([^)]*\$' \
--include="*.php" src/ 2>/dev/null || true)
if [ -n "$SQLI" ]; then
echo "::warning::Potential SQL injection pattern (use prepared statements):"
echo "$SQLI"
fi
echo "✅ SQL injection pattern check completed"
# ============================================================
# Composer Audit - Check for dependency vulnerabilities
# ============================================================
composer-audit:
name: Dependency Audit
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Setup PHP
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2.37.2
with:
php-version: ${{ env.PHP_VERSION }}
tools: composer:2
coverage: none
- name: Install dependencies
run: composer install --no-progress --prefer-dist --no-interaction
- name: Run Composer audit
run: |
composer audit --format=plain || echo "::warning::Vulnerabilities found in dependencies"
# ============================================================
# Unit Tests - PHPUnit with coverage
# ============================================================
tests:
name: Unit Tests (PHPUnit)
runs-on: ubuntu-latest
needs: syntax
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Setup PHP
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2.37.2
with:
php-version: ${{ env.PHP_VERSION }}
extensions: xdebug
coverage: xdebug
tools: phpunit:10
- name: Install Composer dependencies
run: composer install --no-progress --prefer-dist --no-interaction
- name: Run PHPUnit tests
run: |
vendor/bin/phpunit --coverage-text --coverage-clover=coverage/clover.xml
- name: Check coverage threshold
run: |
# Extract coverage percentage from clover.xml
if [ -f coverage/clover.xml ]; then
COVERAGE=$(php -r "
\$xml = simplexml_load_file('coverage/clover.xml');
\$metrics = \$xml->project->metrics;
\$elements = (int)\$metrics['elements'];
\$covered = (int)\$metrics['coveredelements'];
if (\$elements > 0) {
echo round((\$covered / \$elements) * 100, 2);
} else {
echo '0';
}
")
echo "Code coverage: ${COVERAGE}%"
echo "## Test Coverage: ${COVERAGE}%" >> $GITHUB_STEP_SUMMARY
# Fail if coverage is below 70%
if (( $(echo "$COVERAGE < 70" | bc -l) )); then
echo "::warning::Coverage is below 70% threshold"
fi
fi
# ============================================================
# Multi-version PHP Test - Ensure compatibility
# ============================================================
php-compat:
name: PHP ${{ matrix.php }} Compatibility
runs-on: ubuntu-latest
needs: [syntax, code-style, phpstan]
permissions:
contents: read
strategy:
fail-fast: false
matrix:
php: ['8.1', '8.2', '8.3', '8.4']
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Setup PHP ${{ matrix.php }}
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2.37.2
with:
php-version: ${{ matrix.php }}
coverage: none
- name: Install dependencies
run: composer install --no-progress --prefer-dist --no-interaction
- name: Check syntax on PHP ${{ matrix.php }}
run: find src -name "*.php" -print0 | xargs -0 -n1 php -l
- name: Run PHPStan on PHP ${{ matrix.php }}
run: vendor/bin/phpstan analyse --configuration=phpstan.neon --no-progress
continue-on-error: ${{ matrix.php == '8.4' }} # Allow failures on newest PHP
# ============================================================
# Summary - Aggregate all check results
# ============================================================
lint-summary:
name: Lint Summary
runs-on: ubuntu-latest
needs: [syntax, code-style, phpstan, tests, license-headers, strict-types, security-patterns, composer-audit, php-compat]
if: always()
permissions:
contents: read
steps:
- name: Check results
run: |
echo "## PHP Lint & Analysis Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Check | Status |" >> $GITHUB_STEP_SUMMARY
echo "|-------|--------|" >> $GITHUB_STEP_SUMMARY
echo "| Syntax | ${{ needs.syntax.result == 'success' && '✅ Pass' || '❌ Fail' }} |" >> $GITHUB_STEP_SUMMARY
echo "| Code Style | ${{ needs.code-style.result == 'success' && '✅ Pass' || '❌ Fail' }} |" >> $GITHUB_STEP_SUMMARY
echo "| PHPStan | ${{ needs.phpstan.result == 'success' && '✅ Pass' || '❌ Fail' }} |" >> $GITHUB_STEP_SUMMARY
echo "| Unit Tests | ${{ needs.tests.result == 'success' && '✅ Pass' || '❌ Fail' }} |" >> $GITHUB_STEP_SUMMARY
echo "| License Headers | ${{ needs.license-headers.result == 'success' && '✅ Pass' || '❌ Fail' }} |" >> $GITHUB_STEP_SUMMARY
echo "| Strict Types | ${{ needs.strict-types.result == 'success' && '✅ Pass' || '❌ Fail' }} |" >> $GITHUB_STEP_SUMMARY
echo "| Security Patterns | ${{ needs.security-patterns.result == 'success' && '✅ Pass' || '❌ Fail' }} |" >> $GITHUB_STEP_SUMMARY
echo "| Composer Audit | ${{ needs.composer-audit.result == 'success' && '✅ Pass' || '❌ Fail' }} |" >> $GITHUB_STEP_SUMMARY
echo "| PHP Compatibility | ${{ needs.php-compat.result == 'success' && '✅ Pass' || '⚠️ Partial' }} |" >> $GITHUB_STEP_SUMMARY
# Fail if any critical check failed
if [ "${{ needs.syntax.result }}" != "success" ] || \
[ "${{ needs.code-style.result }}" != "success" ] || \
[ "${{ needs.phpstan.result }}" != "success" ] || \
[ "${{ needs.tests.result }}" != "success" ] || \
[ "${{ needs.strict-types.result }}" != "success" ] || \
[ "${{ needs.security-patterns.result }}" != "success" ]; then
echo ""
echo "::error::One or more critical checks failed"
exit 1
fi
echo ""
echo "✅ All critical checks passed!"