chore(deps): Bump the actions group across 1 directory with 6 updates… #41
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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!" |