Maison  >  Article  >  cadre php  >  Explication détaillée de la façon de définir le niveau de vérification le plus élevé de PHPStan dans Laravel

Explication détaillée de la façon de définir le niveau de vérification le plus élevé de PHPStan dans Laravel

青灯夜游
青灯夜游avant
2022-10-25 21:39:491869parcourir

Explication détaillée de la façon de définir le niveau de vérification le plus élevé de PHPStan dans Laravel

Depuis quelques années, l'analyse statique en PHP, et plus particulièrement Laravel, est devenue de plus en plus populaire. Alors que de plus en plus de personnes l'adoptent dans leur développement logiciel, j'ai pensé que ce serait le bon moment pour écrire un tutoriel sur la façon de l'ajouter à votre projet Laravel.

En 2019, Nuno Maduro a publié un package appelé Larastan, un ensemble de règles PHPStan pour les projets Laravel qui m'excitaient énormément. Jusqu'à présent, j'ai eu du mal à obtenir une bonne couverture d'analyse statique dans Laravel en utilisant PHPStan ou Psalm. Les règles de Larastan m'ont permis de commencer à appliquer davantage d'analyses statiques à ma base de code et, par conséquent, d'avoir plus confiance en mon code. J'utilise désormais PHP 8.1 et Laravel 9 - je me sens plus confiant que jamais dans le code que j'écris grâce au grand nombre d'outils incroyables à ma disposition.

Dans ce tutoriel, j'ajouterai étape par étape Larastan à un nouveau projet Laravel, en définissant le niveau au plus haut.

Commencez par créer un nouveau projet Laravel appelé larastan-test :

laravel new larastan-test

Après avoir créé le nouveau projet, installez Larastan en exécutant la commande composer suivante :

composer require nunomaduro/larastan --dev

La raison pour laquelle nous le voulons comme dépendance de développement est parce qu'en production, nous ne Toute analyse statique doit être exécutée - elle est uniquement à des fins de développement pour garantir que votre code est aussi sécurisé que possible. PHPStan utilise un format de configuration appelé neon, qui est quelque peu similaire à yaml. Par conséquent, nous allons créer un nouveau fichier appelé ./phpstan.neon dans le répertoire racine de l'application out. Si vous créez un package, l'approche recommandée consiste à ajouter .dist à la fin de ces fichiers de configuration. Dans ce fichier, nous allons commencer à définir la configuration requise pour que phpstan s'exécute et les règles que nous pouvons vouloir imposer. En ajoutant le code suivant au fichier de configuration, nous pouvons voir ce que cela signifie :

includes:
    - ./vendor/nunomaduro/larastan/extension.neon
parameters:
    paths:
        - app
    level: 9
    ignoreErrors:
    excludePaths:

Nous commençons par includes<.> pour commencer, ce sont généralement les règles du package que nous souhaitons inclure dans notre ensemble de règles de base phpstan. Dans la section paramètres de cette configuration, la première option <code>paths nous permet de définir les emplacements que nous voulons que PHPStan vérifie - dans ce cas, nous devons juste nous concentrer sur l'app où se trouve le code de l'application > Répertoire. Vous pouvez étendre cela pour couvrir plusieurs répertoires si vous le souhaitez, mais faites attention à la portée que vous introduisez, car tout est sur le point de devenir sérieux ! Ensuite, le paramètre level de PHPStan détermine les différents niveaux qui peuvent être vérifiés, 0 étant le plus bas et 9 étant actuellement le plus élevé. includes 开始,这些通常是我们希望包含在我们的基本 phpstan 规则集中的包中的规则。这个配置的参数部分,第一个选项 paths 允许我们定义我们希望 PHPStan 检查的位置——在案例中,我们只需要聚焦到应用程序代码所在的 app 目录。如果你愿意,你可以将其扩展到覆盖多个目录,但要小心你所引入的范围,因为所有的事情即将变得严格(严谨)!接下来,PHPStan 的 level 参数决定了可以检查的各种级别,0 是最低的,9 目前是最高的。

如你所见,我们已将级别设置为 9,我建议在现有应用程序上这样做,因为只有理想情况下你才达到这个级别 - 但由于这是一个全新的项目,我们可以在 9 时感到非常舒服(毕竟技术债务没有那么多)。

接下来,ignoreErrorsexcludePaths 这两个选项允许我们告诉 PHPStan 忽略我们不感兴趣的文件或特定的错误,比如现阶段我们无法控制或修复的错误。也许你正在重构一些业务并且遇到了错误。你可能正在重构这段代码,以便稍后进行静态分析,那你可以通过这个配置,让 PHPStan 在你结束重构前,忽略相关的错误。

includes 包含基本的 phpstan 的规则。parameters 配置参数,第一个选项 paths 配置 phpstan 检查的目录——在我的例子中,我只对应用程序代码所在的 app 目录进行检查,当然您也可以配置其他目录。 level 配置级别,PHPStan 可以配置各种级别,0 是最低的,9 目前是最高的。如您所见,我已将级别设置为 9,我建议将级别设置为 9。接下来有 ignoreErrorsexcludePaths

Comme vous pouvez le voir, nous avons fixé le niveau à 9, je recommanderais de le faire sur une application existante car ce n'est qu'idéalement que vous atteindriez ce niveau - mais comme il s'agit d'un tout nouveau projet, nous pouvons le faire à 9. Je me sens très à l'aise ( la dette technique n'est pas si grande que ça après tout).

Ensuite, les deux options ignoreErrors et excludePaths nous permettent de dire à PHPStan d'ignorer les fichiers ou les erreurs spécifiques qui ne nous intéressent pas, comme celles que nous ne pouvons pas contrôler ou corriger à ce stade l'erreur. Peut-être que vous êtes en train de refactoriser une entreprise et que vous rencontrez un bug. Vous refactoriserez peut-être ce code pour une analyse statique plus tard, vous pourrez alors utiliser cette configuration pour permettre à PHPStan d'ignorer les erreurs associées avant de terminer la refactorisation.

includes contient les règles de base de phpstan. parameters paramètres de configuration, la première option paths configure le répertoire que phpstan vérifie - dans mon cas, j'ai uniquement vérifié l'app où se trouve le code de l'application > répertoire à vérifier, bien sûr vous pouvez également configurer d'autres répertoires. <code>level Niveau de configuration, PHPStan peut être configuré avec différents niveaux, 0 est le plus bas et 9 est actuellement le plus élevé. Comme vous pouvez le voir, j'ai réglé le niveau sur 9 et je recommande de régler le niveau sur 9. Ensuite, il y a ignoreErrors et excludePaths qui indiquent à PHPStan d'ignorer les fichiers ou les erreurs spécifiques qui ne sont pas vérifiés, ou les fichiers et les erreurs qui n'ont pas besoin d'être vérifiés maintenant. Par exemple, le code est en cours de refactorisation et vous souhaitez ignorer les erreurs jusqu'à ce qu'il soit terminé, puis effectuer une analyse statique une fois l'opération terminée.

Alors exécutons phpstan sur l'application Laravel par défaut et voyons quelles erreurs nous rencontrons, le cas échéant. Exécutez la commande suivante dans le terminal :

./vendor/bin/phpstan analyse

Le résultat que nous obtenons de l'application Laravel par défaut ressemble à ceci :

Note: Using configuration file /Users/steve/code/sites/larastan-test/phpstan.neon.
 18/18 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%

 ------ ----------------------------------------------------------------------------------------------------------------------------
  Line   Providers/RouteServiceProvider.php
 ------ ----------------------------------------------------------------------------------------------------------------------------
  49     Parameter #1 $key of method Illuminate\Cache\RateLimiting\Limit::by() expects string, int<min, -1>|int<1, max>|string|null
         given.
 ------ ----------------------------------------------------------------------------------------------------------------------------

 [ERROR] Found 1 error

Comme vous pouvez le voir, nous obtenons uniquement une erreur dans l'application Laravel par défaut même si nous vérifierons que le niveau est réglé au niveau le plus strict.

C'est bien, non ? Bien sûr, vous pouvez voir des résultats différents si vous l'ajoutez à un projet existant, et en suivant ce didacticiel, vous apprendrez comment résoudre ces problèmes afin de disposer d'un bon flux de travail à suivre. 🎜🎜Exécutez phpstan dans votre application Laravel si une erreur se produit. Exécutez la commande suivante dans le terminal : 🎜
./vendor/bin/phpstan analyse
🎜Le résultat ressemblera à ceci🎜
Note: Using configuration file /Users/steve/code/sites/larastan-test/phpstan.neon.
 18/18 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%

 ------ ----------------------------------------------------------------------------------------------------------------------------
  Line   Providers/RouteServiceProvider.php
 ------ ----------------------------------------------------------------------------------------------------------------------------
  49     Parameter #1 $key of method Illuminate\Cache\RateLimiting\Limit::by() expects string, int<min, -1>|int<1, max>|string|null
         given.
 ------ ----------------------------------------------------------------------------------------------------------------------------

 [ERROR] Found 1 error

现在,我们在最严格的级别下,在默认的 Laravel 应用程序中也只得到一个错误。 当然,如果您将其添加到现有项目中,您可能会看到不同的结果,但是按照本教程,您将学习如何解决这些问题。

如果您希望有一种简便的运行方式,可以将脚本添加到您的composer文件中来运行此命令,那么现在让我们添加它,以便我们可以更轻松地运行此命令,将以下代码块添加到你的 composer.json 文件中:

"scripts": {
  "phpstan": [
    "./vendor/bin/phpstan analyse"
  ]
},
"scripts-descriptions": {
  "phpstan": "Run PHPStan static analysis against your application."
},

你的 composer 文件中有了 scripts 记录 - 只需将 phpstan 脚本附加到块的末尾即可。 现在我们可以再次运行 PHPStan ,但这次使用  composer , 更容易输入:

composer phpstan

所以当我们有 1 个错误时,查看对应的行,并且查看它当前的样子:

protected function configureRateLimiting()
{
    RateLimiter::for(&#39;api&#39;, function (Request $request) {
        return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
    });
}

本节开始,我们会聊聊静态分析让人抱怨的一些具体问题:

$request->user()?->id ?: $request->ip()

当我们想要获取请求用户,如果有的话返回ID,或者如果第一部分为空,则返回 IP 地址。在这个例子中,没有真正的方法来确保这永远是一个字符串,用户可能是空的,请求 IP 也可能是空的。

这是你想要消除错误的情况,但因为它是来自供应商(第三方包)的代码,你无法强制执行此操作。在这种特定情况下,你可以做的最好的事情是告诉 PHPStan 忽略该错误,但这不是全局性的。我们在这里要做的是添加一个命令块而不是设置规则,以告诉 PHPStan 在分析此代码时忽略此特定行。将此方法重构为如下所示:

protected function configureRateLimiting(): void
{
    RateLimiter::for('api', static function (Request $request): Limit {
        /** @phpstan-ignore-next-line  */
        return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
    });
}

我们为方法添加了返回类型,使回调成为静态闭包 - 并提示返回类型。但随后我们在返回值上方添加命令块,告诉 PHPStan 我们要忽略下一行。如果我们现在再次在命令行中运行 PHPStan,你将看到以下输出:

Note: Using configuration file /Users/steve/code/sites/larastan-test/phpstan.neon.
 18/18 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%

 [OK] No errors

所以我们有默认的 Laravel 应用程序在 PHPStan 上运行,现在我们需要开始向我们的应用程序添加一些实际的逻辑,以便我们在添加功能和逻辑时可以确保类型安全。为此,我们将创建一个简单的应用程序来存储书签,这没什么特别的。

让我们开始使用 artisan 添加模型,并使用 -mf 参数同时创建迁移任务和工厂模式:

php artisan make:model Bookmark -mf

其中,迁移任务的 up 方法如下所示:

Schema::create(&#39;bookmarks&#39;, static function (Blueprint $table): void {
    $table->id();

    $table->string(&#39;name&#39;);
    $table->string(&#39;url&#39;);

    $table->boolean(&#39;starred&#39;)->default(false);

    $table->foreignId(&#39;user_id&#39;)->index()->constrained()->cascadeOnDelete();

    $table->timestamps();
});

将以下代码添加到我们的模型中:

class Bookmark extends Model
{
    use HasFactory;

    protected $fillable = [
        &#39;name&#39;,
        &#39;url&#39;,
        &#39;starred&#39;,
        &#39;user_id&#39;,
    ];

    protected $casts = [
        &#39;starred&#39; => &#39;boolean&#39;,
    ];

    /**
     * @return BelongsTo
     */
    public function user(): BelongsTo
    {
        return $this->belongsTo(
            related: User::class,
            foreignKey: &#39;user_id&#39;,
        );
    }
}

从上面可以看出,我们在这里唯一关心的是名称、url,如果用户想要加星标/收藏书签并且该书签属于用户。现在我们可以把它留在这里,但我个人喜欢将类型定义添加到我的模型属性中——因为目前在 Laravel 9 中我无法输入提示它们。因此,重构你的模型,使其如下所示:

class Bookmark extends Model
{
    use HasFactory;

    /**
     * @var array<int,string>
     */
    protected $fillable = [
        &#39;name&#39;,
        &#39;url&#39;,
        &#39;starred&#39;,
        &#39;user_id&#39;,
    ];

    /**
     * @var array<string,string>
     */
    protected $casts = [
        &#39;starred&#39; => &#39;boolean&#39;,
    ];

    /**
     * @return BelongsTo
     */
    public function user(): BelongsTo
    {
        return $this->belongsTo(
            related: User::class,
            foreignKey: &#39;user_id&#39;,
        );
    }
}

我们在这里所做的只是告诉 PHP 和我们的 IDE,可填充数组是一个没有键的字符串数组——这意味着它将默认为整数。然后我们的 casts 数组是一个带键的字符串数组,其中的键也是字符串。现在,即使在没有类型定义的情况下运行静态分析,它也不会失败 - 但这是一个很好的实践,以便你的 IDE 在你工作时拥有尽可能多的信息。

让我们继续处理路由和控制器,以便我们可以继续运行静态分析检查。现在我是可调用控制器的忠实粉丝——我发现它们非常适合我的代码风格,但是你可能不喜欢它们或有不同的偏好,所以如果你是的话,下一部分可以随意偏离我的编码风格,会让你更舒服。

我们现在将创建一个控制器,运行以下 artisan 命令来为书签创建索引控制器:

php artisan make:controller Bookmarks/IndexController --invokable

这是我们路由所需的索引控制器,所以我们可以去添加一个新的路由组在 routes/web.php

Route::middleware([&#39;auth&#39;])->prefix(&#39;bookmarks&#39;)->as(&#39;bookmarks:&#39;)->group(static function (): void {
    Route::get(&#39;/&#39;, App\Http\Controllers\Bookmarks\IndexController::class)->name(&#39;index&#39;);
});

添加在在我们的 auth 中间件中,以便我们控制作者对书签的访问,我们还希望在 bookmarks 下为所有路由添加前缀,并将该组的命名策略设置为 bookmarks:*。 如果我们现在在我们的代码库上运行我们的静态分析,我们会看到一些错误,但这主要是因为我们的控制器中没有内容:

composer phpstan
Note: Using configuration file /Users/steve/code/sites/larastan-test/phpstan.neon.
 20/20 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%

 ------ -------------------------------------------------------------------------------------------------
  Line   Http/Controllers/Bookmarks/IndexController.php
 ------ -------------------------------------------------------------------------------------------------
  15     Method App\Http\Controllers\Bookmarks\IndexController::__invoke() has no return type specified.
 ------ -------------------------------------------------------------------------------------------------

 ------ -----------------------------------------------------------------------------------------------------------------------------
  Line   Models/Bookmark.php
 ------ -----------------------------------------------------------------------------------------------------------------------------
  33     Method App\Models\Bookmark::user() return type with generic class Illuminate\Database\Eloquent\Relations\BelongsTo does not
         specify its types: TRelatedModel, TChildModel
         ? You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your
         phpstan.neon.
 ------ -----------------------------------------------------------------------------------------------------------------------------

 ------ ----------------------------------------------------------------------------------------------------------------------------
  Line   Models/User.php
 ------ ----------------------------------------------------------------------------------------------------------------------------
  49     Method App\Models\User::bookmarks() return type with generic class Illuminate\Database\Eloquent\Relations\HasMany does not
         specify its types: TRelatedModel
         ? You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your
         phpstan.neon.
 ------ ----------------------------------------------------------------------------------------------------------------------------

 [ERROR] Found 3 errors

摆在我面前的第一个错误是 Method App\Models\User::bookmarks() return type with generic class。现在我不想在这个应用中过度依赖通用类型。这一错误实际上告诉我们可以做什么,所以让我们将checkGenericClassInNonGenericObjectType: false 添加到我们的 phpstan.neon 文件中:

includes:
    - ./vendor/nunomaduro/larastan/extension.neon
parameters:
    paths:
        - app
    level: 9
    ignoreErrors:
    excludePaths:
    checkGenericClassInNonGenericObjectType: false

现在,如果我们再次运行分析,将只有 5 个错误,这些错误都和控制器相关 - 让我们从 IndexController 开始,看看我们能做些什么。像这样重构 IndexController:

class IndexController extends Controller
{
    public function __invoke(Request $request)
    {
        return View::make(
            view: &#39;bookmarks.list&#39;,
            data: [
                &#39;bookmarks&#39; => Bookmark::query()
                    ->where(&#39;user_id&#39;, $request->user()->id)
                    ->paginate(),
            ]
        );
    }
}

如果我们现在对我们的代码进行静态分析,并且只关注正在使用的控制器,我们将看到如下问题:

------ -------------------------------------------------------------------------------------------------
  Line   Http/Controllers/Bookmarks/IndexController.php
 ------ -------------------------------------------------------------------------------------------------
  15     Method App\Http\Controllers\Bookmarks\IndexController::__invoke() has no return type specified.
  21     Cannot access property $id on App\Models\User|null.
 ------ -------------------------------------------------------------------------------------------------

那么我们对这两个错误能做些什么呢?第一个相对容易修复,我们可以添加返回类型:

public function __invoke(Request $request): \Illuminate\Contracts\View\View

我们可以对此约束起个别名,使之看起来更为美观:

public function __invoke(Request $request): ViewContract

然而下一个问题,Cannot access property $id on App\Models\User|null.,类似于我们在默认 Laravel 应用中,在请求的用户可以为空的情况下去获取ID时会碰到的问题。因此我用以解决此问题的方法是,使用 Auth 的辅助函数直接从 Auth 守卫中获取 ID。重构查询如下:

Bookmark::query()
    ->where(&#39;user_id&#39;, auth()->id())
    ->paginate()

使用 Auth 的 ID 方法,我们直接从认证守卫中获取 ID,而不是从可能是 null 的请求(request)中获取。需要记住的一点是,如果路由没有使用认证中间件,那么 id 方法会出现“正在尝试获取 null 的属性ID(you are trying to get the property ID of null)”的报错。因此,请记得为该路由设置对应中间件。

现在,如果我们再次运行静态分析,我们应该已经消除了这些错误:

composer phpstan
Note: Using configuration file /Users/steve/code/sites/larastan-test/phpstan.neon.
 20/20 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%

 [OK] No errors

既然 IndexController 已经没有错误了。下一步我们要做的是遍历我们的应用,确保在重要的节点中都运行静态分析检查。我们最不想做的事情就是等到 sprint 格式化打印结束,或者在添加新功能来运行它时,才发现我们必须花费无数个小时来修复静态分析问题。无论如何,到最后 - 你将拥有可信任的代码了,这也是我通常喜欢使用静态分析的一个重要原因。如果你可以配合好的测试套件进行静态分析,那么就没有理由不信任你的代码。

你的项目使用了 Larastan 吗? 你敢把验证级别提高到最高吗? 在推特上告诉我们, 或者让我们知道你的恐怖故事!

原文地址:https://laravel-news.com/running-phpstan-on-max-with-laravel

译文地址:https://learnku.com/laravel/t/69412

【相关推荐:laravel视频教程

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer