日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長(zhǎng)提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

詳解Laravel中怎么設(shè)置PHPStan最高驗(yàn)證級(jí)別


在過(guò)去的幾年里,PHP 中的靜態(tài)分析,更具體地說(shuō)是 Laravel,變得越來(lái)越流行。 隨著越來(lái)越多的人在他們的軟件開(kāi)發(fā)中采用它,我認(rèn)為現(xiàn)在是編寫一篇關(guān)于如何將它添加到 Laravel 項(xiàng)目中的教程的好時(shí)機(jī)。

早在 2019 年,Nuno Maduro 發(fā)布了一個(gè)名為 Larastan 的包,這是一組適用于 Laravel 項(xiàng)目的 PHPStan 規(guī)則,我非常興奮。 到目前為止,我一直在努力使用 PHPStan 或 Psalm 在 Laravel 中獲得良好的靜態(tài)分析覆蓋率。 Larastans 規(guī)則允許我開(kāi)始對(duì)我的代碼庫(kù)應(yīng)用更多的靜態(tài)分析,進(jìn)而對(duì)我的代碼更有信心。 在使用 PHP 8.1 和 Laravel 9 的現(xiàn)在 - 由于我可以使用大量令人驚嘆的工具,我對(duì)自己編寫的代碼感到前所未有的自信。

在本教程中,我會(huì)逐步將 Larastan 添加到新的 Laravel 項(xiàng)目中,將級(jí)別設(shè)置為最高。

先創(chuàng)建一個(gè)名為 larastan-test 的新 Laravel 項(xiàng)目:

laravel new larastan-test

新建項(xiàng)目后,安裝 Larastan,通過(guò)運(yùn)行以下 composer 命令:

composer require nunomaduro/larastan --dev

我們希望它作為開(kāi)發(fā)依賴項(xiàng)的原因是因?yàn)樵谏a(chǎn)中我們不應(yīng)該運(yùn)行任何靜態(tài)分析 - 它僅用于開(kāi)發(fā)目的,以確保您的代碼盡可能安全。 PHPStan 使用一種稱為 neon 的配置格式,在某種程度上類似于 yaml。 因此,我們將在 out 應(yīng)用程序的根目錄中創(chuàng)建一個(gè)名為 ./phpstan.neon 的新文件 - 如果您正在構(gòu)建一個(gè)包,推薦的方法是將 .dist 添加到這些配置文件的末尾。 在這個(gè)文件中,我們將開(kāi)始定義 phpstan 運(yùn)行所需的配置以及我們可能想要強(qiáng)加的規(guī)則,將以下代碼添加到配置文件中,我們可以了解它的含義:

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

我們從 includes 開(kāi)始,這些通常是我們希望包含在我們的基本 phpstan 規(guī)則集中的包中的規(guī)則。這個(gè)配置的參數(shù)部分,第一個(gè)選項(xiàng) paths 允許我們定義我們希望 PHPStan 檢查的位置——在案例中,我們只需要聚焦到應(yīng)用程序代碼所在的 app 目錄。如果你愿意,你可以將其擴(kuò)展到覆蓋多個(gè)目錄,但要小心你所引入的范圍,因?yàn)樗械氖虑榧磳⒆兊脟?yán)格(嚴(yán)謹(jǐn))!接下來(lái),PHPStan 的 level 參數(shù)決定了可以檢查的各種級(jí)別,0 是最低的,9 目前是最高的。

如你所見(jiàn),我們已將級(jí)別設(shè)置為 9,我建議在現(xiàn)有應(yīng)用程序上這樣做,因?yàn)橹挥欣硐肭闆r下你才達(dá)到這個(gè)級(jí)別 - 但由于這是一個(gè)全新的項(xiàng)目,我們可以在 9 時(shí)感到非常舒服(畢竟技術(shù)債務(wù)沒(méi)有那么多)。

接下來(lái),ignoreErrors 和 excludePaths 這兩個(gè)選項(xiàng)允許我們告訴 PHPStan 忽略我們不感興趣的文件或特定的錯(cuò)誤,比如現(xiàn)階段我們無(wú)法控制或修復(fù)的錯(cuò)誤。也許你正在重構(gòu)一些業(yè)務(wù)并且遇到了錯(cuò)誤。你可能正在重構(gòu)這段代碼,以便稍后進(jìn)行靜態(tài)分析,那你可以通過(guò)這個(gè)配置,讓 PHPStan 在你結(jié)束重構(gòu)前,忽略相關(guān)的錯(cuò)誤。

includes 包含基本的 phpstan 的規(guī)則。parameters 配置參數(shù),第一個(gè)選項(xiàng) paths 配置 phpstan 檢查的目錄——在我的例子中,我只對(duì)應(yīng)用程序代碼所在的 app 目錄進(jìn)行檢查,當(dāng)然您也可以配置其他目錄。 level 配置級(jí)別,PHPStan 可以配置各種級(jí)別,0 是最低的,9 目前是最高的。如您所見(jiàn),我已將級(jí)別設(shè)置為 9,我建議將級(jí)別設(shè)置為 9。接下來(lái)有 ignoreErrors 和 excludePaths 這兩個(gè)選項(xiàng)告訴 PHPStan 忽略不檢測(cè)的文件或特定錯(cuò)誤,或者現(xiàn)在不需要檢測(cè)的文件和錯(cuò)誤。例如正在重構(gòu)的代碼,您希望在完成之前忽略錯(cuò)誤,完成后再進(jìn)行靜態(tài)分析。

因此,讓我們針對(duì)默認(rèn)的 Laravel 應(yīng)用程序運(yùn)行 phpstan,看看我們遇到了什么錯(cuò)誤,如果有的話。在終端中運(yùn)行以下命令:

./vendor/bin/phpstan analyse

我們從默認(rèn) Laravel 應(yīng)用程序獲得的輸出如下所示:

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

正如你所看到的,我們?cè)谀J(rèn)的 Laravel 應(yīng)用程序中只得到一個(gè)錯(cuò)誤,即使我們將檢查的級(jí)別設(shè)置到了最嚴(yán)格的等級(jí)。

這很好,對(duì)吧?當(dāng)然,如果你將其添加到現(xiàn)有項(xiàng)目中,你可能會(huì)看到不同的結(jié)果,按照本教程,你將學(xué)習(xí)如何解決這些問(wèn)題,以便你有一個(gè)很好的工作流程可以遵循。

在 Laravel 應(yīng)用程序運(yùn)行 phpstan,如果發(fā)生錯(cuò)誤。 在終端中運(yùn)行以下命令:

./vendor/bin/phpstan analyse

輸出如下所示

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

現(xiàn)在,我們?cè)谧顕?yán)格的級(jí)別下,在默認(rèn)的 Laravel 應(yīng)用程序中也只得到一個(gè)錯(cuò)誤。 當(dāng)然,如果您將其添加到現(xiàn)有項(xiàng)目中,您可能會(huì)看到不同的結(jié)果,但是按照本教程,您將學(xué)習(xí)如何解決這些問(wèn)題。

如果您希望有一種簡(jiǎn)便的運(yùn)行方式,可以將腳本添加到您的composer文件中來(lái)運(yùn)行此命令,那么現(xiàn)在讓我們添加它,以便我們可以更輕松地運(yùn)行此命令,將以下代碼塊添加到你的 composer.json 文件中:

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

你的 composer 文件中有了 scripts 記錄 - 只需將 phpstan 腳本附加到塊的末尾即可。 現(xiàn)在我們可以再次運(yùn)行 PHPStan ,但這次使用 composer , 更容易輸入:

composer phpstan

所以當(dāng)我們有 1 個(gè)錯(cuò)誤時(shí),查看對(duì)應(yīng)的行,并且查看它當(dāng)前的樣子:

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

本節(jié)開(kāi)始,我們會(huì)聊聊靜態(tài)分析讓人抱怨的一些具體問(wèn)題:

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

當(dāng)我們想要獲取請(qǐng)求用戶,如果有的話返回ID,或者如果第一部分為空,則返回 IP 地址。在這個(gè)例子中,沒(méi)有真正的方法來(lái)確保這永遠(yuǎn)是一個(gè)字符串,用戶可能是空的,請(qǐng)求 IP 也可能是空的。

這是你想要消除錯(cuò)誤的情況,但因?yàn)樗莵?lái)自供應(yīng)商(第三方包)的代碼,你無(wú)法強(qiáng)制執(zhí)行此操作。在這種特定情況下,你可以做的最好的事情是告訴 PHPStan 忽略該錯(cuò)誤,但這不是全局性的。我們?cè)谶@里要做的是添加一個(gè)命令塊而不是設(shè)置規(guī)則,以告訴 PHPStan 在分析此代碼時(shí)忽略此特定行。將此方法重構(gòu)為如下所示:

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());
    });
}

我們?yōu)榉椒ㄌ砑恿朔祷仡愋停够卣{(diào)成為靜態(tài)閉包 - 并提示返回類型。但隨后我們?cè)诜祷刂瞪戏教砑用顗K,告訴 PHPStan 我們要忽略下一行。如果我們現(xiàn)在再次在命令行中運(yùn)行 PHPStan,你將看到以下輸出:

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

所以我們有默認(rèn)的 Laravel 應(yīng)用程序在 PHPStan 上運(yùn)行,現(xiàn)在我們需要開(kāi)始向我們的應(yīng)用程序添加一些實(shí)際的邏輯,以便我們?cè)谔砑庸δ芎瓦壿嫊r(shí)可以確保類型安全。為此,我們將創(chuàng)建一個(gè)簡(jiǎn)單的應(yīng)用程序來(lái)存儲(chǔ)書簽,這沒(méi)什么特別的。

讓我們開(kāi)始使用 artisan 添加模型,并使用 -mf 參數(shù)同時(shí)創(chuàng)建遷移任務(wù)和工廠模式:

php artisan make:model Bookmark -mf

其中,遷移任務(wù)的 up 方法如下所示:

Schema::create('bookmarks', static function (Blueprint $table): void {
    $table->id();
 
    $table->string('name');
    $table->string('url');
 
    $table->boolean('starred')->default(false);
 
    $table->foreignId('user_id')->index()->constrained()->cascadeOnDelete();
 
    $table->timestamps();
});

將以下代碼添加到我們的模型中:

class Bookmark extends Model
{
    use HasFactory;
 
    protected $fillable = [
        'name',
        'url',
        'starred',
        'user_id',
    ];
 
    protected $casts = [
        'starred' => 'boolean',
    ];
 
    /**
     * @return BelongsTo
     */
    public function user(): BelongsTo
    {
        return $this->belongsTo(
            related: User::class,
            foreignKey: 'user_id',
        );
    }
}

從上面可以看出,我們?cè)谶@里唯一關(guān)心的是名稱、url,如果用戶想要加星標(biāo)/收藏書簽并且該書簽屬于用戶。現(xiàn)在我們可以把它留在這里,但我個(gè)人喜歡將類型定義添加到我的模型屬性中——因?yàn)槟壳霸?Laravel 9 中我無(wú)法輸入提示它們。因此,重構(gòu)你的模型,使其如下所示:

class Bookmark extends Model
{
    use HasFactory;
 
    /**
     * @var array<int,string>
     */
    protected $fillable = [
        'name',
        'url',
        'starred',
        'user_id',
    ];
 
    /**
     * @var array<string,string>
     */
    protected $casts = [
        'starred' => 'boolean',
    ];
 
    /**
     * @return BelongsTo
     */
    public function user(): BelongsTo
    {
        return $this->belongsTo(
            related: User::class,
            foreignKey: 'user_id',
        );
    }
}

我們?cè)谶@里所做的只是告訴 PHP 和我們的 IDE,可填充數(shù)組是一個(gè)沒(méi)有鍵的字符串?dāng)?shù)組——這意味著它將默認(rèn)為整數(shù)。然后我們的 casts 數(shù)組是一個(gè)帶鍵的字符串?dāng)?shù)組,其中的鍵也是字符串。現(xiàn)在,即使在沒(méi)有類型定義的情況下運(yùn)行靜態(tài)分析,它也不會(huì)失敗 - 但這是一個(gè)很好的實(shí)踐,以便你的 IDE 在你工作時(shí)擁有盡可能多的信息。

讓我們繼續(xù)處理路由和控制器,以便我們可以繼續(xù)運(yùn)行靜態(tài)分析檢查。現(xiàn)在我是可調(diào)用控制器的忠實(shí)粉絲——我發(fā)現(xiàn)它們非常適合我的代碼風(fēng)格,但是你可能不喜歡它們或有不同的偏好,所以如果你是的話,下一部分可以隨意偏離我的編碼風(fēng)格,會(huì)讓你更舒服。

我們現(xiàn)在將創(chuàng)建一個(gè)控制器,運(yùn)行以下 artisan 命令來(lái)為書簽創(chuàng)建索引控制器:

php artisan make:controller Bookmarks/IndexController --invokable

這是我們路由所需的索引控制器,所以我們可以去添加一個(gè)新的路由組在 routes/web.php :

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

添加在在我們的 auth 中間件中,以便我們控制作者對(duì)書簽的訪問(wèn),我們還希望在 bookmarks 下為所有路由添加前綴,并將該組的命名策略設(shè)置為 bookmarks:*。 如果我們現(xiàn)在在我們的代碼庫(kù)上運(yùn)行我們的靜態(tài)分析,我們會(huì)看到一些錯(cuò)誤,但這主要是因?yàn)槲覀兊目刂破髦袥](méi)有內(nèi)容:

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

擺在我面前的第一個(gè)錯(cuò)誤是 Method App\Models\User::bookmarks() return type with generic class。現(xiàn)在我不想在這個(gè)應(yīng)用中過(guò)度依賴通用類型。這一錯(cuò)誤實(shí)際上告訴我們可以做什么,所以讓我們將checkGenericClassInNonGenericObjectType: false 添加到我們的 phpstan.neon 文件中:

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

現(xiàn)在,如果我們?cè)俅芜\(yùn)行分析,將只有 5 個(gè)錯(cuò)誤,這些錯(cuò)誤都和控制器相關(guān) - 讓我們從 IndexController 開(kāi)始,看看我們能做些什么。像這樣重構(gòu) IndexController:

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

如果我們現(xiàn)在對(duì)我們的代碼進(jìn)行靜態(tài)分析,并且只關(guān)注正在使用的控制器,我們將看到如下問(wèn)題:

------ -------------------------------------------------------------------------------------------------
  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.
 ------ -------------------------------------------------------------------------------------------------

那么我們對(duì)這兩個(gè)錯(cuò)誤能做些什么呢?第一個(gè)相對(duì)容易修復(fù),我們可以添加返回類型:

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

我們可以對(duì)此約束起個(gè)別名,使之看起來(lái)更為美觀:

public function __invoke(Request $request): ViewContract

然而下一個(gè)問(wèn)題,Cannot access property $id on App\Models\User|null.,類似于我們?cè)谀J(rèn) Laravel 應(yīng)用中,在請(qǐng)求的用戶可以為空的情況下去獲取ID時(shí)會(huì)碰到的問(wèn)題。因此我用以解決此問(wèn)題的方法是,使用 Auth 的輔助函數(shù)直接從 Auth 守衛(wèi)中獲取 ID。重構(gòu)查詢?nèi)缦拢?/p>

Bookmark::query()
    ->where('user_id', auth()->id())
    ->paginate()

使用 Auth 的 ID 方法,我們直接從認(rèn)證守衛(wèi)中獲取 ID,而不是從可能是 null 的請(qǐng)求(request)中獲取。需要記住的一點(diǎn)是,如果路由沒(méi)有使用認(rèn)證中間件,那么 id 方法會(huì)出現(xiàn)“正在嘗試獲取 null 的屬性ID(you are trying to get the property ID of null)”的報(bào)錯(cuò)。因此,請(qǐng)記得為該路由設(shè)置對(duì)應(yīng)中間件。

現(xiàn)在,如果我們?cè)俅芜\(yùn)行靜態(tài)分析,我們應(yīng)該已經(jīng)消除了這些錯(cuò)誤:

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

既然 IndexController 已經(jīng)沒(méi)有錯(cuò)誤了。下一步我們要做的是遍歷我們的應(yīng)用,確保在重要的節(jié)點(diǎn)中都運(yùn)行靜態(tài)分析檢查。我們最不想做的事情就是等到 sprint 格式化打印結(jié)束,或者在添加新功能來(lái)運(yùn)行它時(shí),才發(fā)現(xiàn)我們必須花費(fèi)無(wú)數(shù)個(gè)小時(shí)來(lái)修復(fù)靜態(tài)分析問(wèn)題。無(wú)論如何,到最后 - 你將擁有可信任的代碼了,這也是我通常喜歡使用靜態(tài)分析的一個(gè)重要原因。如果你可以配合好的測(cè)試套件進(jìn)行靜態(tài)分析,那么就沒(méi)有理由不信任你的代碼。

你的項(xiàng)目使用了 Larastan 嗎? 你敢把驗(yàn)證級(jí)別提高到最高嗎? 在推特上告訴我們, 或者讓我們知道你的恐怖故事!

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

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


分享到:
標(biāo)簽:Laravel設(shè)置PHPStan PHPStan最高驗(yàn)證級(jí)別
用戶無(wú)頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過(guò)答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫(kù),初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定