欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

PHPStan和Psalm—查找php錯誤的靜態(tài)代碼分析工具

 更新時間:2025年10月03日 17:11:39   作者:墨舞青云  
使用PHPStan和Psalm發(fā)現(xiàn)在代碼運行前發(fā)現(xiàn)潛在錯誤,無需實際執(zhí)行程序即可檢查語法錯誤、類型錯誤及邏輯問題,工具能檢測類型錯誤、空指針等隱藏問題,分階段實施提升代碼質(zhì)量,團隊協(xié)作中需逐步引導,避免過載,最終實現(xiàn)更嚴謹?shù)拈_發(fā)思維和更安全的代碼交付

說起來有點丟人,我以前特別討厭靜態(tài)分析,覺得就是瞎折騰。直到有一次,PHPStan 救了我一命,差點讓我丟了飯碗的那種救命。

當時我給支付功能寫了一段代碼,自己覺得寫得挺好,手工測試也過了,單元測試也綠了,看起來沒毛病。結(jié)果同事非要我跑一下 PHPStan,我心想這不是多此一舉嗎?沒想到一跑就炸了,發(fā)現(xiàn)了一個類型錯誤,這玩意兒會讓支付金額算錯!

就這么一個 bug,徹底改變了我的想法。以前覺得 IDE 里那些紅色波浪線煩死了,現(xiàn)在覺得它們就是代碼的保鏢。現(xiàn)在讓我不用靜態(tài)分析寫 PHP,就像讓我不系安全帶開車一樣心慌。

靜態(tài)分析到底有啥用:不只是抓錯字

那次支付的事兒讓我想明白了,靜態(tài)分析不是用來抓拼寫錯誤的,而是用來抓那些你自己看不出來的邏輯問題。寫代碼的時候,你腦子里想的都是正常情況,PHPStan 想的是各種能出錯的地方。

靜態(tài)分析就像個特別較真的代碼審查員,什么都要質(zhì)疑一遍。類型對不上、空指針、死代碼,這些問題它都能揪出來。就好比有個強迫癥同事,專門盯著你累了或者飄了的時候?qū)懙臓€代碼。

PHPStan和Psalm定位與特性

  • PHPStan

    • 采用NEON配置文件,支持規(guī)則級別自定義(如level: 8表示嚴格模式)‌1
    • 提供多環(huán)境配置能力,可通過--release參數(shù)指定PHP版本兼容性檢查‌2
    • 典型配置示例:
    • level: 8
      paths: [src/, tests/]
      ignoreErrors: [
        {message: "Undefined method call", count: 3}
      ]
      

  • Psalm

    • 使用XML配置文件(.psalm.xml),支持類型推斷和PSR標準檢查‌3
    • 內(nèi)置對PHP 8+新特性的支持(如??運算符版本兼容性檢測)‌2
    • 基礎(chǔ)配置示例:
    • < psalm.xml >
        < project >
          < name >MyApp< /name >
          < autoloader >vendor/autoload.php< /autoloader >
        < /project >
      < / psalm.xml >
      

PHPStan:我的編程好幫手

自從那次支付的事兒之后,PHPStan 就成了我寫代碼的標配。一開始是被逼著用的,后來發(fā)現(xiàn)這玩意兒真香。最牛的地方是它懂 Laravel,Eloquent 關(guān)系、中間件這些 Laravel 的黑魔法它都認識,別的工具經(jīng)常搞不定。

第一次跑 PHPStan 的時候我差點崩潰——我以為挺干凈的代碼庫居然報了 847 個錯誤。不過修這些錯誤的過程中,我學到的 PHP 類型安全知識比之前幾年加起來都多。

安裝和基本設(shè)置

# 安裝 PHPStan
composer require --dev phpstan/phpstan

# 創(chuàng)建 phpstan.neon 配置文件
touch phpstan.neon
# phpstan.neon
parameters:
  level: 5
  paths:
    - app
    - tests
  excludePaths:
    - app/Console/Kernel.php
    - app/Http/Kernel.php
  checkMissingIterableValueType: false
  checkGenericClassInNonGenericObjectType: false
  ignoreErrors:
    - '#Unsafe usage of new static#'

分析級別:從 0 到 8 的血淚史

PHPStan 有 10 個級別,這玩意兒教會了我什么叫循序漸進。一開始我想裝逼,直接跳到級別 9,想證明自己是個"嚴肅的開發(fā)者"。結(jié)果級別 3 就把我整懵了,2000 多個錯誤,差點讓我懷疑人生。后來我老實了,按部就班來:

# 級別 0 - 基本檢查
vendor/bin/phpstan analyze --level=0

# 級別 5 - 嚴格性和實用性的良好平衡
vendor/bin/phpstan analyze --level=5

# 級別 9 - 非常嚴格,幾乎捕獲所有問題
vendor/bin/phpstan analyze --level=9

Laravel 集成

# 安裝 Laravel 擴展
composer require --dev nunomaduro/larastan
# 為 Laravel 更新的 phpstan.neon
# 更多 Laravel 特定配置,請參見:
# https://mycuriosity.blog/level-up-your-laravel-validation-advanced-tips-tricks
parameters:
  level: 5
  paths:
    - app
  includes:
    - ./vendor/nunomaduro/larastan/extension.neon

高級 PHPStan 配置

# phpstan.neon
parameters:
  level: 6
  paths:
    - app
    - tests

  # 忽略特定模式
  ignoreErrors:
    - '#Call to an undefined method Illuminate\\Database\\Eloquent\\Builder#'
    - '#Method App\\Models\\User::find\(\) should return App\\Models\\User\|null but returns Illuminate\\Database\\Eloquent\\Model\|null#'

  # 自定義規(guī)則
  rules:
    - PHPStan\Rules\Classes\UnusedConstructorParametersRule
    - PHPStan\Rules\DeadCode\UnusedPrivateMethodRule
    - PHPStan\Rules\DeadCode\UnusedPrivatePropertyRule

  # 類型別名
  typeAliases:
    UserId: 'int<1, max>'
    Email: 'string'

  # 前沿功能
  reportUnmatchedIgnoredErrors: true
  checkTooWideReturnTypesInProtectedAndPublicMethods: true
  checkUninitializedProperties: true

Psalm:另一個強大的選擇

Psalm 是另一個優(yōu)秀的靜態(tài)分析工具,有著不同的優(yōu)勢。它特別擅長發(fā)現(xiàn)復雜的類型問題,并且有出色的泛型支持。

安裝和設(shè)置

# 安裝 Psalm
composer require --dev vimeo/psalm

# 初始化 Psalm
vendor/bin/psalm --init
<!-- psalm.xml -->
<?xml version="1.0"?>
<psalm
    errorLevel="3"
    resolveFromConfigFile="true"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="https://getpsalm.org/schema/config"
    xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
>
    <projectFiles>
        <directory name="app" />
        <directory name="tests" />
        <ignoreFiles>
            <directory name="vendor" />
            <file name="app/Console/Kernel.php" />
        </ignoreFiles>
    </projectFiles>

    <issueHandlers>
        <LessSpecificReturnType errorLevel="info" />
        <MoreSpecificReturnType errorLevel="info" />
        <PropertyNotSetInConstructor errorLevel="info" />
    </issueHandlers>

    <plugins>
        <pluginClass class="Psalm\LaravelPlugin\Plugin"/>
    </plugins>
</psalm>

Psalm 的 Laravel 插件

# 安裝 Laravel 插件
composer require --dev psalm/plugin-laravel

# 啟用插件
vendor/bin/psalm-plugin enable psalm/plugin-laravel

血的教訓:那些差點要命的 Bug

類型錯誤 - 差點出大事的支付 Bug

就是下面這種寫法,當時我在算購物車總價,想當然地以為數(shù)組里都是數(shù)字。PHPStan 一眼就看出來了,數(shù)組里可能有各種亂七八糟的類型,這要是上線了,支付金額算錯了還得了?

// 我原來的危險代碼
function calculateTotal(array $items): float
{
    $total = 0;
    foreach ($items as $item) {
        $total += $item; // PHPStan: Cannot add array|string to int
    }
    return $total; // 可能返回完全錯誤的金額!
}

// PHPStan 強制我明確類型
function calculateTotal(array $items): float
{
    $total = 0.0;
    foreach ($items as $item) {
        if (is_numeric($item)) {
            $total += (float) $item;
        } else {
            throw new InvalidArgumentException('All items must be numeric');
        }
    }
    return $total;
}

空指針問題

// PHPStan 捕獲潛在的空指針
function getUserEmail(int $userId): string
{
    $user = User::find($userId); // 返回 User|null
    return $user->email; // 錯誤:無法訪問 null 上的屬性
}

// 修復版本
function getUserEmail(int $userId): ?string
{
    $user = User::find($userId);
    return $user?->email;
}

// 或者顯式空值檢查
function getUserEmail(int $userId): string
{
    $user = User::find($userId);
    if ($user === null) {
        throw new UserNotFoundException("User {$userId} not found");
    }
    return $user->email;
}

無法到達的代碼

// PHPStan 檢測無法到達的代碼
function processPayment(float $amount): bool
{
    if ($amount <= 0) {
        return false;
    }

    if ($amount > 1000000) {
        throw new InvalidArgumentException('Amount too large');
    }

    return true;
    echo "Payment processed"; // 無法到達的代碼
}

高級類型注解

泛型類型

/**
 * @template T
 * @param class-string<T> $className
 * @return T
 */
function createInstance(string $className): object
{
    return new $className();
}

// 使用
$user = createInstance(User::class); // PHPStan 知道這是 User

集合類型

/**
 * @param array<int, User> $users
 * @return array<int, string>
 */
function extractUserEmails(array $users): array
{
    return array_map(fn(User $user) => $user->email, $users);
}

/**
 * @param Collection<int, Product> $products
 * @return Collection<int, Product>
 */
function getActiveProducts(Collection $products): Collection
{
    return $products->filter(fn(Product $product) => $product->isActive());
}

復雜類型定義

/**
 * @param array{name: string, age: int, email: string} $userData
 * @return User
 */
function createUser(array $userData): User
{
    return new User($userData['name'], $userData['age'], $userData['email']);
}

/**
 * @param array<string, int|string|bool> $config
 * @return void
 */
function configure(array $config): void
{
    // 實現(xiàn)
}

自定義 PHPStan 規(guī)則

為你的特定需求創(chuàng)建自定義規(guī)則:

// CustomRule.php
use PHPStan\Rules\Rule;
use PHPStan\Analyser\Scope;
use PhpParser\Node;

class NoDirectDatabaseQueryRule implements Rule
{
    public function getNodeType(): string
    {
        return Node\Expr\StaticCall::class;
    }

    public function processNode(Node $node, Scope $scope): array
    {
        if ($node->class instanceof Node\Name &&
            $node->class->toString() === 'DB' &&
            $node->name instanceof Node\Identifier &&
            in_array($node->name->name, ['select', 'insert', 'update', 'delete'])) {

            return ['Direct database queries are not allowed. Use repositories instead.'];
        }

        return [];
    }
}

與 CI/CD 集成

GitHub Actions

# .github/workflows/static-analysis.yml
name: Static Analysis

on: [push, pull_request]

jobs:
  phpstan:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.2'

      - name: Install dependencies
        run: composer install --no-dev --optimize-autoloader

      - name: Run PHPStan
        run: vendor/bin/phpstan analyze --error-format=github

      - name: Run Psalm
        run: vendor/bin/psalm --output-format=github

Pre-commit 鉤子

# 安裝 pre-commit
pip install pre-commit
# .pre-commit-config.yaml
repos:
  - repo: local
    hooks:
      - id: phpstan
        name: phpstan
        entry: vendor/bin/phpstan analyze --no-progress
        language: system
        types: [php]
        pass_filenames: false

      - id: psalm
        name: psalm
        entry: vendor/bin/psalm --no-progress
        language: system
        types: [php]
        pass_filenames: false

代碼質(zhì)量工具集成

PHP CS Fixer

# 安裝 PHP CS Fixer
composer require --dev friendsofphp/php-cs-fixer
# .php-cs-fixer.php
<?php
return (new PhpCsFixer\Config())
    ->setRules([
        '@PSR12' => true,
        'array_syntax' => ['syntax' => 'short'],
        'ordered_imports' => true,
        'no_unused_imports' => true,
        'declare_strict_types' => true,
    ])
    // 遵循 PSR 標準提高代碼質(zhì)量:
    // https://mycuriosity.blog/php-psr-standards-writing-interoperable-code
    ->setFinder(
        PhpCsFixer\Finder::create()
            ->in('app')
            ->in('tests')
    );

PHPMD (PHP Mess Detector)

# 安裝 PHPMD
composer require --dev phpmd/phpmd
# phpmd.xml
<?xml version="1.0"?>
<ruleset name="Custom PHPMD ruleset">
    <rule ref="rulesets/cleancode.xml">
        <exclude name="StaticAccess" />
    </rule>
    <rule ref="rulesets/codesize.xml" />
    <rule ref="rulesets/controversial.xml" />
    <rule ref="rulesets/design.xml" />
    <rule ref="rulesets/naming.xml" />
    <rule ref="rulesets/unusedcode.xml" />
</ruleset>

性能優(yōu)化

靜態(tài)分析在大型代碼庫上可能很慢。以下是優(yōu)化方法:

基線文件

# 生成基線以忽略現(xiàn)有問題
vendor/bin/phpstan analyze --generate-baseline

# 這會創(chuàng)建 phpstan-baseline.neon
parameters:
  includes:
    - phpstan-baseline.neon

并行處理

# phpstan.neon
parameters:
  parallel:
    maximumNumberOfProcesses: 4
    processTimeout: 120.0

結(jié)果緩存

# phpstan.neon
parameters:
  tmpDir: var/cache/phpstan
  resultCachePath: var/cache/phpstan/resultCache.php

IDE 集成

PHPStorm

PHPStorm 對 PHPStan 和 Psalm 都有出色的內(nèi)置支持:

  1. 轉(zhuǎn)到 Settings > PHP > Quality Tools
  2. 配置 PHPStan 和 Psalm 路徑
  3. 在 Editor > Inspections 中啟用檢查

VS Code

// .vscode/settings.json
{
  "php.validate.enable": false,
  "php.suggest.basic": false,
  "phpstan.enabled": true,
  "phpstan.path": "vendor/bin/phpstan",
  "phpstan.config": "phpstan.neon"
}

實際實施策略 - 團隊采用的經(jīng)驗教訓

讓我的團隊采用靜態(tài)分析比我自己學習它更困難。開發(fā)者討厭被告知他們的代碼有 800+ 個錯誤,特別是當它"運行得很好"的時候。以下是真正有效的方法,遵循清潔代碼原則以獲得更好的團隊采用:

第一階段:基礎(chǔ)(第 1-2 周)

  • 在級別 0 安裝 PHPStan
  • 修復基本問題
  • 設(shè)置 CI/CD 集成

第二階段:漸進改進(第 3-4 周)

  • 提升到級別 3
  • 添加 Laravel/框架特定規(guī)則
  • 培訓團隊注解

第三階段:高級功能(第 5-6 周)

  • 達到級別 5-6
  • 添加自定義規(guī)則
  • 為遺留代碼實施基線

第四階段:精通(持續(xù)進行)

  • 新代碼達到級別 8-9
  • 添加 Psalm 以獲得額外覆蓋
  • 持續(xù)改進

常見陷阱和解決方案

過度抑制

// 不好 - 抑制過于寬泛
/** @phpstan-ignore-next-line */
$user = User::find($id);

// 好 - 具體抑制并說明原因
/** @phpstan-ignore-next-line User::find() can return null but we know ID exists */
$user = User::find($validatedId);

類型注解過載

// 不好 - 過度注解明顯類型
/** @var string $name */
$name = 'John';

// 好 - 注解復雜類型
/** @var array<string, mixed> $config */
$config = json_decode($jsonString, true);

衡量成功

跟蹤這些指標來衡量靜態(tài)分析的成功。理解 PHP 性能分析有助于將靜態(tài)分析改進與應(yīng)用程序性能相關(guān)聯(lián):

// 要跟蹤的指標
class StaticAnalysisMetrics
{
    public function getMetrics(): array
    {
        return [
            'phpstan_errors' => $this->countPhpStanErrors(),
            'psalm_errors' => $this->countPsalmErrors(),
            'code_coverage' => $this->getCodeCoverage(),
            'type_coverage' => $this->getTypeCoverage(),
            'bugs_prevented' => $this->getBugsPrevented(),
        ];
    }

    private function countPhpStanErrors(): int
    {
        // 解析 PHPStan 輸出
        $output = shell_exec('vendor/bin/phpstan analyze --error-format=json');
        $data = json_decode($output, true);
        return count($data['files'] ?? []);
    }
}

總結(jié):從黑粉到真香

PHPStan 抓到的那個支付 bug 徹底改變了我寫 PHP 的方式。從一開始的被迫使用,到后來的真心喜歡,這個過程挺有意思的。

最大的變化不是抓 bug,而是心態(tài)。以前上線代碼心里都沒底,祈禱別出事。現(xiàn)在上線前心里有數(shù),該抓的錯誤都抓了,踏實多了。

寫代碼的思路也變了:以前是寫完了碰運氣,現(xiàn)在是邊寫邊考慮類型安全。PHPStan 不光幫我找 bug,還教會我怎么更嚴謹?shù)厮伎即a邏輯。

給做 Laravel 的兄弟們幾個建議:

別急著裝逼:第一天就想跳級別 9?醒醒吧。老老實實從 0 → 3 → 5 → 8 這么來,一步一個腳印。

別怕報錯:看到 847 個錯誤別慌,這不是說你菜,而是給你學習的機會。每修一個錯誤,你對類型安全的理解就深一分。

讓團隊看到好處:光說靜態(tài)分析有用沒人信,得拿實際抓到的 bug 說話。一個具體的例子勝過千言萬語。

強制執(zhí)行:把靜態(tài)分析加到 CI/CD 里,讓它變成必須的步驟。代碼過不了靜態(tài)分析就別想合并,這樣大家就不會偷懶了。

靜態(tài)分析不只是讓代碼寫得更好,更重要的是讓你晚上睡得安穩(wěn)。知道有工具幫你把關(guān),用戶看到 bug 之前你就能發(fā)現(xiàn),這種踏實感一旦體驗過就回不去了。配合好的 PHP 內(nèi)存管理和安全認證,靜態(tài)分析就是寫出靠譜 PHP 應(yīng)用的基石。

PHPStan‌:啟用parallel參數(shù)加速多核分析;‌Psalm‌:配置cacheDir復用掃描結(jié)果,減少重復分析2。通過合理配置,兩者可協(xié)同使用:Psalm處理復雜類型與安全檢測,PHPStan作為基礎(chǔ)類型檢查層。

到此這篇關(guān)于PHPStan和Psalm—查找php錯誤的靜態(tài)代碼分析工具的文章就介紹到這了,更多相關(guān)php代碼分析工具PHPStan和Psalm內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論