laravel-quality

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Laravel Quality

Laravel 代码质量保障

Testing, static analysis, and code quality enforcement.
Related guides:
  • code-style.md - Laravel Pint configuration and coding style
  • type-safety.md - Strict types and type hints
  • Testing - Comprehensive testing guide
测试、静态分析与代码质量管控。
相关指南:
  • code-style.md - Laravel Pint 配置与编码风格
  • type-safety.md - 严格类型与类型提示
  • Testing - 全面测试指南

Quality Stack

质量工具栈

bash
undefined
bash
undefined

composer.json scripts

composer.json 脚本

{ "test": "pest", "analyse": "phpstan analyse", "format": "pint", "quality": [ "@analyse", "@test" ] }

All files must have `declare(strict_types=1)` at top. Run quality checks before every commit.
{ "test": "pest", "analyse": "phpstan analyse", "format": "pint", "quality": [ "@analyse", "@test" ] }

所有文件顶部必须声明 `declare(strict_types=1)`。每次提交前需运行质量检查。

Architecture Tests (Pest)

架构测试(Pest)

Setup

设置

tests/Pest.php
php
pest()->extend(Tests\TestCase::class)->in('Feature', 'Unit');
tests/Pest.php
php
pest()->extend(Tests\TestCase::class)->in('Feature', 'Unit');

Core Architecture Tests

核心架构测试

tests/Architecture/ActionsTest.php
php
<?php

declare(strict_types=1);

arch('actions are invokable')
    ->expect('App\Actions')
    ->toHaveMethod('__invoke');

arch('actions live in Actions namespace')
    ->expect('App\Actions')
    ->toBeClasses()
    ->toOnlyBeUsedIn('App\Actions', 'App\Http', 'App\Jobs', 'App\Listeners');

arch('actions do not use models directly')
    ->expect('App\Actions')
    ->not->toUse('Illuminate\Database\Eloquent\Model');
tests/Architecture/DataTest.php
php
<?php

declare(strict_types=1);

arch('data objects extend base Data class')
    ->expect('App\Data')
    ->toExtend('App\Data\Data')
    ->ignoring('App\Data\Data');

arch('data objects use constructor property promotion')
    ->expect('App\Data')
    ->toHaveConstructor();
tests/Architecture/StrictTypesTest.php
php
<?php

declare(strict_types=1);

arch('app files declare strict types')
    ->expect('App')
    ->toUseStrictTypes();

arch('test files declare strict types')
    ->expect('Tests')
    ->toUseStrictTypes();
tests/Architecture/ControllersTest.php
php
<?php

declare(strict_types=1);

arch('controllers do not use DB facade')
    ->expect('App\Http')
    ->not->toUse('Illuminate\Support\Facades\DB');

arch('controllers do not use models directly')
    ->expect('App\Http\Web\Controllers')
    ->not->toUse('App\Models');
tests/Architecture/NamingTest.php
php
<?php

declare(strict_types=1);

arch('actions end with Action suffix')
    ->expect('App\Actions')
    ->toHaveSuffix('Action');

arch('data objects end with Data suffix')
    ->expect('App\Data')
    ->toHaveSuffix('Data')
    ->ignoring('App\Data\Data', 'App\Data\Concerns');

arch('exceptions end with Exception suffix')
    ->expect('App\Exceptions')
    ->toHaveSuffix('Exception')
    ->ignoring('App\Exceptions\Concerns');
tests/Architecture/ModelsTest.php
php
<?php

declare(strict_types=1);

arch('models use custom query builders')
    ->expect('App\Models')
    ->toHaveMethod('newEloquentBuilder');

arch('models do not use local scopes')
    ->expect('App\Models')
    ->not->toHaveMethod('scope*');
tests/Architecture/ActionsTest.php
php
<?php

declare(strict_types=1);

arch('actions are invokable')
    ->expect('App\Actions')
    ->toHaveMethod('__invoke');

arch('actions live in Actions namespace')
    ->expect('App\Actions')
    ->toBeClasses()
    ->toOnlyBeUsedIn('App\Actions', 'App\Http', 'App\Jobs', 'App\Listeners');

arch('actions do not use models directly')
    ->expect('App\Actions')
    ->not->toUse('Illuminate\Database\Eloquent\Model');
tests/Architecture/DataTest.php
php
<?php

declare(strict_types=1);

arch('data objects extend base Data class')
    ->expect('App\Data')
    ->toExtend('App\Data\Data')
    ->ignoring('App\Data\Data');

arch('data objects use constructor property promotion')
    ->expect('App\Data')
    ->toHaveConstructor();
tests/Architecture/StrictTypesTest.php
php
<?php

declare(strict_types=1);

arch('app files declare strict types')
    ->expect('App')
    ->toUseStrictTypes();

arch('test files declare strict types')
    ->expect('Tests')
    ->toUseStrictTypes();
tests/Architecture/ControllersTest.php
php
<?php

declare(strict_types=1);

arch('controllers do not use DB facade')
    ->expect('App\Http')
    ->not->toUse('Illuminate\Support\Facades\DB');

arch('controllers do not use models directly')
    ->expect('App\Http\Web\Controllers')
    ->not->toUse('App\Models');
tests/Architecture/NamingTest.php
php
<?php

declare(strict_types=1);

arch('actions end with Action suffix')
    ->expect('App\Actions')
    ->toHaveSuffix('Action');

arch('data objects end with Data suffix')
    ->expect('App\Data')
    ->toHaveSuffix('Data')
    ->ignoring('App\Data\Data', 'App\Data\Concerns');

arch('exceptions end with Exception suffix')
    ->expect('App\Exceptions')
    ->toHaveSuffix('Exception')
    ->ignoring('App\Exceptions\Concerns');
tests/Architecture/ModelsTest.php
php
<?php

declare(strict_types=1);

arch('models use custom query builders')
    ->expect('App\Models')
    ->toHaveMethod('newEloquentBuilder');

arch('models do not use local scopes')
    ->expect('App\Models')
    ->not->toHaveMethod('scope*');

Static Analysis (PHPStan)

静态分析(PHPStan)

Installation

安装

bash
composer require phpstan/phpstan --dev
composer require phpstan/phpstan-strict-rules --dev
composer require larastan/larastan --dev
bash
composer require phpstan/phpstan --dev
composer require phpstan/phpstan-strict-rules --dev
composer require larastan/larastan --dev

Configuration

配置

phpstan.neon
neon
includes:
    - vendor/larastan/larastan/extension.neon
    - vendor/phpstan/phpstan-strict-rules/rules.neon

parameters:
    level: 8
    paths:
        - app
        - tests
    excludePaths:
        - app/Providers/TelescopeServiceProvider.php
    checkMissingIterableValueType: true
    checkGenericClassInNonGenericObjectType: true
    reportUnmatchedIgnoredErrors: false
phpstan.neon
neon
includes:
    - vendor/larastan/larastan/extension.neon
    - vendor/phpstan/phpstan-strict-rules/rules.neon

parameters:
    level: 8
    paths:
        - app
        - tests
    excludePaths:
        - app/Providers/TelescopeServiceProvider.php
    checkMissingIterableValueType: true
    checkGenericClassInNonGenericObjectType: true
    reportUnmatchedIgnoredErrors: false

Run

运行

bash
./vendor/bin/phpstan analyse
bash
./vendor/bin/phpstan analyse

Code Style (Laravel Pint)

代码风格(Laravel Pint)

Installation

安装

bash
composer require laravel/pint --dev
bash
composer require laravel/pint --dev

Configuration

配置

pint.json
json
{
    "preset": "laravel",
    "rules": {
        "simplified_null_return": true,
        "no_unused_imports": true,
        "ordered_imports": {
            "sort_algorithm": "alpha"
        }
    }
}
pint.json
json
{
    "preset": "laravel",
    "rules": {
        "simplified_null_return": true,
        "no_unused_imports": true,
        "ordered_imports": {
            "sort_algorithm": "alpha"
        }
    }
}

Run

运行

bash
./vendor/bin/pint
./vendor/bin/pint --test  # Check only
bash
./vendor/bin/pint
./vendor/bin/pint --test  # 仅检查不修复

Test Coverage

测试覆盖率

Enable Coverage (Pest)

启用覆盖率(Pest)

phpunit.xml
xml
<coverage>
    <report>
        <html outputDirectory="coverage"/>
        <text outputFile="php://stdout"/>
    </report>
</coverage>
phpunit.xml
xml
<coverage>
    <report>
        <html outputDirectory="coverage"/>
        <text outputFile="php://stdout"/>
    </report>
</coverage>

Run with Coverage

带覆盖率运行测试

bash
./vendor/bin/pest --coverage
./vendor/bin/pest --coverage --min=80  # Enforce minimum
bash
./vendor/bin/pest --coverage
./vendor/bin/pest --coverage --min=80  # 强制最低覆盖率要求

CI/CD Checks

CI/CD 检查

GitHub Actions Example

GitHub Actions 示例

.github/workflows/tests.yml
yaml
name: Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: 8.4
          extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite
          coverage: xdebug

      - name: Install Dependencies
        run: composer install --prefer-dist --no-interaction

      - name: Code Style
        run: ./vendor/bin/pint --test

      - name: Static Analysis
        run: ./vendor/bin/phpstan analyse

      - name: Run Tests
        run: ./vendor/bin/pest --coverage --min=80
.github/workflows/tests.yml
yaml
name: Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: 8.4
          extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite
          coverage: xdebug

      - name: Install Dependencies
        run: composer install --prefer-dist --no-interaction

      - name: Code Style
        run: ./vendor/bin/pint --test

      - name: Static Analysis
        run: ./vendor/bin/phpstan analyse

      - name: Run Tests
        run: ./vendor/bin/pest --coverage --min=80

Pre-commit Hooks

提交前钩子

Installation

安装

bash
composer require brainmaestro/composer-git-hooks --dev
bash
composer require brainmaestro/composer-git-hooks --dev

Configuration

配置

composer.json
json
{
    "extra": {
        "hooks": {
            "pre-commit": [
                "./vendor/bin/pint",
                "./vendor/bin/phpstan analyse",
                "./vendor/bin/pest"
            ]
        }
    },
    "scripts": {
        "post-install-cmd": "vendor/bin/cghooks add --ignore-lock",
        "post-update-cmd": "vendor/bin/cghooks update"
    }
}
composer.json
json
{
    "extra": {
        "hooks": {
            "pre-commit": [
                "./vendor/bin/pint",
                "./vendor/bin/phpstan analyse",
                "./vendor/bin/pest"
            ]
        }
    },
    "scripts": {
        "post-install-cmd": "vendor/bin/cghooks add --ignore-lock",
        "post-update-cmd": "vendor/bin/cghooks update"
    }
}

Quality Metrics

质量指标

What to Track

需追踪的指标

  • Test coverage - Aim for 80%+
  • PHPStan level - Level 8 (max)
  • Architecture test pass rate - 100%
  • Code style violations - 0
  • 测试覆盖率 - 目标80%+
  • PHPStan 级别 - 级别8(最高)
  • 架构测试通过率 - 100%
  • 代码风格违规数 - 0

Regular Reviews

定期评审

  • Weekly - Check test coverage trends
  • Per PR - Run all quality checks
  • Monthly - Review architecture compliance
  • Release - Full quality audit
  • 每周 - 检查测试覆盖率趋势
  • 每个PR - 运行所有质量检查
  • 每月 - 评审架构合规性
  • 发布阶段 - 全面质量审计

Common Issues to Watch

需关注的常见问题

Anti-patterns

反模式

  • Domain logic in controllers
  • Using scopes instead of builders
  • Missing strict types declaration
  • Passing primitives instead of DTOs
  • Jobs/Listeners with domain logic
  • 控制器中包含领域逻辑
  • 使用作用域而非查询构建器
  • 缺失严格类型声明
  • 传递原始类型而非DTO
  • 任务/监听器中包含领域逻辑

Type Safety

类型安全

  • Missing return types
  • Missing parameter types
  • Missing property types
  • Untyped arrays/collections
  • 缺失返回类型
  • 缺失参数类型
  • 缺失属性类型
  • 未类型化的数组/集合

Testing

测试

  • Missing feature tests for endpoints
  • Missing unit tests for actions
  • Low coverage on critical paths
  • Brittle tests (too many mocks)
  • 端点缺失功能测试
  • 动作缺失单元测试
  • 关键路径覆盖率低
  • 脆弱测试(过多Mock)

Enforcement Strategy

执行策略

  1. Architecture tests - Automated checks
  2. PR reviews - Manual verification
  3. CI/CD gates - Block failing builds
  4. Team standards - Document + training
  5. Pair programming - Share knowledge
  1. 架构测试 - 自动化检查
  2. PR评审 - 人工验证
  3. CI/CD 门禁 - 阻止失败构建
  4. 团队标准 - 文档化+培训
  5. 结对编程 - 知识共享