Heim  >  Artikel  >  PHP-Framework  >  Ausführliche Erklärung, wie man die höchste Verifizierungsstufe von PHPStan in Laravel festlegt

Ausführliche Erklärung, wie man die höchste Verifizierungsstufe von PHPStan in Laravel festlegt

青灯夜游
青灯夜游nach vorne
2022-10-25 21:39:491869Durchsuche

Ausführliche Erklärung, wie man die höchste Verifizierungsstufe von PHPStan in Laravel festlegt

In den letzten Jahren ist die statische Analyse in PHP und insbesondere in Laravel immer beliebter geworden. Da immer mehr Leute es in ihre Softwareentwicklung übernehmen, dachte ich, dass jetzt ein guter Zeitpunkt wäre, ein Tutorial zu schreiben, wie man es zu seinem Laravel-Projekt hinzufügt.

Im Jahr 2019 veröffentlichte Nuno Maduro ein Paket namens Larastan, eine Reihe von PHPStan-Regeln für Laravel-Projekte, auf die ich mich riesig gefreut habe. Bisher hatte ich Mühe, mit PHPStan oder Psalm eine gute statische Analyseabdeckung in Laravel zu erhalten. Die Larastans-Regeln ermöglichten es mir, mehr statische Analysen auf meine Codebasis anzuwenden und dadurch mehr Vertrauen in meinen Code zu haben. Jetzt verwende ich PHP 8.1 und Laravel 9 – ich fühle mich sicherer als je zuvor in dem Code, den ich schreibe, dank der schieren Anzahl erstaunlicher Tools, die mir zur Verfügung stehen.

In diesem Tutorial werde ich Larastan Schritt für Schritt zu einem neuen Laravel-Projekt hinzufügen und dabei die Stufe auf den höchsten Wert einstellen.

Erstellen Sie zunächst ein neues Laravel-Projekt namens larastan-test:

laravel new larastan-test

Nachdem Sie das neue Projekt erstellt haben, installieren Sie Larastan, indem Sie den folgenden Composer-Befehl ausführen:

composer require nunomaduro/larastan --dev

Der Grund, warum wir es als Entwicklungsabhängigkeit wollen, ist, dass wir es in der Produktion nicht tun Es sollte keine statische Analyse durchgeführt werden – sie dient nur Entwicklungszwecken, um sicherzustellen, dass Ihr Code so sicher wie möglich ist. PHPStan verwendet ein Konfigurationsformat namens Neon, das Yaml etwas ähnelt. Daher erstellen wir eine neue Datei mit dem Namen ./phpstan.neon im Stammverzeichnis der Out-Anwendung – wenn Sie ein Paket erstellen, empfiehlt es sich, .dist am Ende dieser Konfigurationsdateien hinzuzufügen. In dieser Datei beginnen wir mit der Definition der für die Ausführung von phpstan erforderlichen Konfiguration und der Regeln, die wir möglicherweise anwenden möchten. Indem wir der Konfigurationsdatei den folgenden Code hinzufügen, können wir sehen, was das bedeutet:

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

Wir beginnen mit includes um zu beginnen, sind dies normalerweise die Regeln im Paket, die wir in unseren Basis-phpstan-Regelsatz aufnehmen möchten. Im Parameterabschnitt dieser Konfiguration können wir mit der ersten Option <code>paths die Speicherorte definieren, die PHPStan überprüfen soll – in diesem Fall müssen wir uns nur auf die app konzentrieren wo sich der Anwendungscode befindet > Verzeichnis. Wenn Sie möchten, können Sie dies auf mehrere Verzeichnisse ausdehnen. Seien Sie jedoch vorsichtig, welchen Bereich Sie einführen, denn bald wird es ernst! Als nächstes bestimmt der PHPStan-Parameter level die verschiedenen Ebenen, die überprüft werden können, wobei 0 die niedrigste und 9 derzeit die höchste ist. 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

Wie Sie sehen können, haben wir die Stufe auf 9 eingestellt. Ich würde empfehlen, dies in einer vorhandenen App zu tun, da Sie diese Stufe nur im Idealfall erreichen würden. Da es sich jedoch um ein brandneues Projekt handelt, können wir es bei 9 durchführen. Ich fühle mich sehr wohl ( (die technische Verschuldung ist doch nicht so hoch).

Als nächstes ermöglichen uns die beiden Optionen ignoreErrors und excludePaths, PHPStan anzuweisen, Dateien oder bestimmte Fehler zu ignorieren, an denen wir nicht interessiert sind, beispielsweise solche, die wir nicht kontrollieren können oder Fehler in dieser Phase beheben. Möglicherweise überarbeiten Sie ein Unternehmen und stoßen auf einen Fehler. Möglicherweise überarbeiten Sie diesen Code später für die statische Analyse. Anschließend können Sie diese Konfiguration verwenden, damit PHPStan zugehörige Fehler ignoriert, bevor Sie die Umgestaltung abschließen.

includes enthält grundlegende PHPSTAN-Regeln. parameters Konfigurationsparameter, die erste Option paths konfiguriert das Verzeichnis, das phpstan überprüft – in meinem Fall habe ich nur die appüberprüft, in der sich der Anwendungscode befindet code> Verzeichnis zum Überprüfen, natürlich können Sie auch andere Verzeichnisse konfigurieren. level Konfigurationsebene, PHPStan kann mit verschiedenen Ebenen konfiguriert werden, 0 ist die niedrigste und 9 ist derzeit die höchste. Wie Sie sehen, habe ich die Stufe auf 9 eingestellt und empfehle, die Stufe auf 9 einzustellen. Als nächstes gibt es ignoreErrors und excludePaths, die PHPStan anweisen, Dateien oder bestimmte Fehler, die nicht überprüft werden, oder Dateien und Fehler, die jetzt nicht überprüft werden müssen, zu ignorieren. Beispielsweise wird Code umgestaltet und Sie möchten Fehler ignorieren, bis er abgeschlossen ist, und dann eine statische Analyse durchführen, nachdem er abgeschlossen ist.

Also lassen Sie uns phpstan mit der Standardanwendung von Laravel ausführen und sehen, welche Fehler auftreten, falls vorhanden. Führen Sie den folgenden Befehl im Terminal aus:

./vendor/bin/phpstan analyse

Die Ausgabe, die wir von der Standard-Laravel-Anwendung erhalten, sieht folgendermaßen aus:

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

Wie Sie sehen können, erhalten wir in der Standard-Laravel-Anwendung nur eine Fehlermeldung, obwohl wir die Ebene überprüfen auf die strengste Stufe eingestellt.

Das ist gut, oder? Wenn Sie dies zu einem bestehenden Projekt hinzufügen, können natürlich andere Ergebnisse angezeigt werden. Wenn Sie diesem Tutorial folgen, erfahren Sie, wie Sie diese Probleme beheben können, sodass Sie einen guten Arbeitsablauf verfolgen können. 🎜🎜Führen Sie phpstan in Ihrer Laravel-Anwendung aus, wenn ein Fehler auftritt. Führen Sie im Terminal den folgenden Befehl aus: 🎜
./vendor/bin/phpstan analyse
🎜Die Ausgabe sieht so aus🎜
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视频教程

Das obige ist der detaillierte Inhalt vonAusführliche Erklärung, wie man die höchste Verifizierungsstufe von PHPStan in Laravel festlegt. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:learnku.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen