PHP项目门禁检测如何合并前执行代码校验

wen PHP项目 31

本文目录导读:

PHP项目门禁检测如何合并前执行代码校验

  1. 使用Git Hooks实现本地或服务器端校验
  2. 使用CI/CD工具实现门禁检测
  3. 自定义门禁检测脚本
  4. 集成到Git工作流
  5. 最佳实践建议
  6. 门禁检测流程总结

在PHP项目中,实现“门禁检测”并在合并前执行代码校验,通常指的是代码审查流程中的Pre-merge Checks(合并前检查),这可以通过以下方式实现:

使用Git Hooks实现本地或服务器端校验

pre-receive hook(服务端钩子)

在远程仓库的 hooks/pre-receive 中添加校验逻辑:

#!/bin/bash
# .git/hooks/pre-receive
# 读取推送的引用
while read oldrev newrev refname; do
    # 跳过非分支推送
    if [[ $refname != refs/heads/* ]]; then
        continue
    fi
    branch=${refname#refs/heads/}
    # 检查目标分支是否为受保护分支(如 master, main)
    if [[ "$branch" == "master" || "$branch" == "main" ]]; then
        echo "禁止直接推送到 $branch 分支"
        exit 1
    fi
    # 执行代码校验
    # 获取要校验的文件
    changed_files=$(git diff --name-only $oldrev $newrev | grep '\.php$')
    if [ -n "$changed_files" ]; then
        # PHP语法检查
        for file in $changed_files; do
            if ! php -l "$file" 2>/dev/null; then
                echo "PHP语法错误: $file"
                exit 1
            fi
        done
        # 运行PHPUnit测试(可选)
        # php artisan test --coverage || exit 1
    fi
done
exit 0

pre-commit hook(本地钩子)

#!/bin/bash
# .git/hooks/pre-commit
# 获取暂存区的PHP文件
staged_files=$(git diff --cached --name-only --diff-filter=ACM | grep '\.php$')
if [ -n "$staged_files" ]; then
    echo "执行提交前代码校验..."
    # 1. PHP语法检查
    for file in $staged_files; do
        if ! php -l "$file" 2>/dev/null; then
            echo "❌ 语法错误: $file"
            exit 1
        fi
    done
    echo "✅ PHP语法检查通过"
    # 2. PHP CodeSniffer 代码规范检查
    if command -v phpcs &> /dev/null; then
        if ! phpcs --standard=PSR12 $staged_files; then
            echo "❌ 代码规范检查失败"
            exit 1
        fi
        echo "✅ 代码规范检查通过"
    fi
    # 3. PHPStan 静态分析(可选)
    if [ -f "phpstan.neon" ] || [ -f "phpstan.neon.dist" ]; then
        if ! phpstan analyse --level=max $staged_files; then
            echo "❌ 静态分析失败"
            exit 1
        fi
        echo "✅ 静态分析通过"
    fi
fi
exit 0

使用CI/CD工具实现门禁检测

GitHub Actions 示例

# .github/workflows/php-checks.yml
name: PHP Code Checks
on:
  pull_request:
    branches: [ main, develop ]
  push:
    branches: [ main ]
jobs:
  php-checks:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    - name: Setup PHP
      uses: shivammathur/setup-php@v2
      with:
        php-version: '8.2'
        extensions: mbstring, xml, curl
        tools: composer, phpcs, phpstan
    - name: Install dependencies
      run: composer install --prefer-dist --no-progress
    - name: PHP Syntax Check
      run: |
        find . -name "*.php" -not -path "./vendor/*" -exec php -l {} \;
    - name: PHP CodeSniffer
      run: phpcs --standard=PSR12 --extensions=php --ignore=vendor/ .
    - name: PHPStan Analysis
      run: phpstan analyse --level=max --memory-limit=2G
    - name: Run Tests
      run: php artisan test --coverage
    - name: Security Check
      run: php artisan security:check || true

GitLab CI 示例

# .gitlab-ci.yml
stages:
  - code-quality
  - test
  - security
php-lint:
  stage: code-quality
  script:
    - find . -name "*.php" -not -path "./vendor/*" -exec php -l {} \;
php-cs:
  stage: code-quality
  script:
    - phpcs --standard=PSR12 --extensions=php --ignore=vendor/ .
phpstan:
  stage: code-quality
  script:
    - phpstan analyse --level=max --memory-limit=2G
phpunit:
  stage: test
  script:
    - php artisan test --coverage
  coverage: '/^\s*Methods:\s*\d+.\d+%/'
security-check:
  stage: security
  script:
    - php artisan security:check
  allow_failure: true

自定义门禁检测脚本

创建独立的门禁检测脚本 gate-check.php

<?php
// gate-check.php
class GateCheck
{
    private array $errors = [];
    private array $warnings = [];
    public function run(): bool
    {
        $this->checkPhpSyntax();
        $this->checkCodeStyle();
        $this->checkStaticAnalysis();
        $this->checkUnitTests();
        $this->checkSecurity();
        $this->report();
        return empty($this->errors);
    }
    private function checkPhpSyntax(): void
    {
        $files = $this->getChangedPhpFiles();
        foreach ($files as $file) {
            exec("php -l {$file} 2>&1", $output, $returnCode);
            if ($returnCode !== 0) {
                $this->errors[] = "语法错误: {$file} - " . implode("\n", $output);
            }
        }
    }
    private function checkCodeStyle(): void
    {
        exec("phpcs --standard=PSR12 --report=full " . implode(' ', $this->getChangedPhpFiles()), $output, $returnCode);
        if ($returnCode !== 0) {
            $this->errors[] = "代码规范问题:\n" . implode("\n", $output);
        }
    }
    private function checkStaticAnalysis(): void
    {
        exec("phpstan analyse --level=max " . implode(' ', $this->getChangedPhpFiles()), $output, $returnCode);
        if ($returnCode !== 0) {
            $this->errors[] = "静态分析问题:\n" . implode("\n", $output);
        }
    }
    private function checkUnitTests(): void
    {
        exec("php artisan test --coverage 2>&1", $output, $returnCode);
        if ($returnCode !== 0) {
            $this->warnings[] = "测试失败:\n" . implode("\n", $output);
        }
    }
    private function checkSecurity(): void
    {
        exec("composer audit 2>&1", $output, $returnCode);
        if ($returnCode !== 0) {
            $this->warnings[] = "安全漏洞警告:\n" . implode("\n", $output);
        }
    }
    private function getChangedPhpFiles(): array
    {
        exec("git diff --name-only --cached", $output);
        $files = array_filter($output, function($file) {
            return pathinfo($file, PATHINFO_EXTENSION) === 'php';
        });
        return array_values($files);
    }
    private function report(): void
    {
        if (!empty($this->errors)) {
            echo "❌ 门禁检测失败:\n";
            foreach ($this->errors as $error) {
                echo "  - {$error}\n";
            }
        }
        if (!empty($this->warnings)) {
            echo "⚠️  警告:\n";
            foreach ($this->warnings as $warning) {
                echo "  - {$warning}\n";
            }
        }
        if (empty($this->errors) && empty($this->warnings)) {
            echo "✅ 门禁检测通过\n";
        }
    }
}
$check = new GateCheck();
exit($check->run() ? 0 : 1);

使用方式:

php gate-check.php

集成到Git工作流

使用husky(Node.js工具)管理Git hooks

// .husky/pre-commit
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npm run lint-staged
// package.json
{
  "lint-staged": {
    "*.php": [
      "php -l",
      "phpcs --standard=PSR12",
      "phpstan analyse --level=max"
    ]
  }
}

最佳实践建议

  1. 分层校验

    • 本地:语法检查、代码规范
    • 服务器:单元测试、集成测试
    • CI/CD:安全扫描、性能测试
  2. 配置门禁规则

    • 关键分支(master/main)必须有PR
    • 所有检查必须通过才能合并
    • 代码覆盖率不低于80%
  3. 使用门禁服务器

    # 使用Jenkins/GitLab CI等工具
    pipeline {
     agent any
     stages {
         stage('门禁检测') {
             parallel {
                 stage('语法检查') {
                     steps {
                         sh 'find . -name "*.php" -not -path "./vendor/*" -exec php -l {} \;'
                     }
                 }
                 stage('代码规范') {
                     steps {
                         sh 'phpcs --standard=PSR12 --extensions=php --ignore=vendor/ .'
                     }
                 }
                 stage('安全扫描') {
                     steps {
                         sh 'composer audit'
                     }
                 }
             }
         }
     }
    }

门禁检测流程总结

graph TD
    A[开发者提交代码] --> B{本地pre-commit检查}
    B -->|通过| C[推送到远程]
    B -->|失败| A
    C --> D{服务器pre-receive检查}
    D -->|通过| E[创建合并请求]
    D -->|失败| F[拒绝推送]
    E --> G{CI/CD门禁检查}
    G -->|通过| H[允许合并]
    G -->|失败| I[标记失败并通知]
    H --> J[代码合并到目标分支]

通过以上方法,你可以建立一个完整的PHP项目门禁检测系统,确保代码在合并前经过充分的校验,提高代码质量和项目稳定性。

抱歉,评论功能暂时关闭!