如果您是Laravel 的Web 應用程式建構者,並且碰巧使用PHPStan 進行靜態程式碼分析,那麼當您升級到Laravel 11.x.
使用PHPStan 全新安裝 Laravel 時,第一次執行 ./vendor/bin/phpstan 時會拋出以下錯誤:
------ ----------------------------------------------------------------------------------- Line app\Models\User.php ------ ----------------------------------------------------------------------------------- 13 Class App\Models\User uses generic trait Illuminate\Database\Eloquent\Factories\HasFactory but does not specify its types: TFactory ------ -----------------------------------------------------------------------------------
PHPDoc 和 @template 標籤,這是保留的泛型標籤之一。正如您可能已經猜到的,框架的許多部分都使用了泛型。
/** * @template TFactory of \Illuminate\Database\Eloquent\Factories\Factory */ trait HasFactory { ... }
parameters: ignoreErrors: - identifier: missingType.generics
什麼是泛型?
採用
Laravel 10 中的 IlluminateDatabaseConcernsBuildsQueries::first 方法,它可以傳回 Model 的實例、通用物件、像 IlluminateDatabaseEloquentBuilder 一樣使用它的類別的實例或 null。
/** * Execute the query and get the first result. * * @param array|string $columns * @return \Illuminate\Database\Eloquent\Model|object|static|null */ public function first($columns = ['*']) { return $this->take(1)->get($columns)->first(); }
PHPDocs 標籤 @template、@template-covariant、@template-contravariant、@extends、@implements 和@使用。
泛型類型的規則是使用型別參數定義的。 在PHPDocs中,我們使用@template標籤對它們進行註釋。類型參數名稱可以是任何名稱,只要不使用現有的類別名稱即可。您也可以使用 of 關鍵字限制可以使用哪些類型來取代帶有上限的類型參數。這稱為有界型參數。
<?php namespace Illuminate\Database\Eloquent; /** * @template TModel of \Illuminate\Database\Eloquent\Model * */ class Builder implements BuilderContract { }
以 IlluminateSupportValidatedInput::enum 方法為例:
------ ----------------------------------------------------------------------------------- Line app\Models\User.php ------ ----------------------------------------------------------------------------------- 13 Class App\Models\User uses generic trait Illuminate\Database\Eloquent\Factories\HasFactory but does not specify its types: TFactory ------ -----------------------------------------------------------------------------------
如果您隨後呼叫 $request→validated()→enum('status', OrderStatus::class),PHPStan 將知道您正在取得 OrderStatus 物件或 null!
泛型類別允許建立可以對任何資料類型進行操作的類,同時確保類型安全。它們允許使用特定類型的佔位符來定義類,稍後可以在類實例化時替換該佔位符。
Laravel 原始碼中的一個很好的例子是 IlluminateDatabaseEloquentBuilder 類別:
/** * @template TFactory of \Illuminate\Database\Eloquent\Factories\Factory */ trait HasFactory { ... }
類型參數 TModel 被定義並綁定到 IlluminateDatabaseEloquentModel 的任何子類別。相同的型別參數用作 make 方法的傳回類型。
另一個例子是,如果我們有一個訂單模型,它有一個本地範圍來根據訂單狀態過濾訂單。範圍方法應指定 TModel 類型
parameters: ignoreErrors: - identifier: missingType.generics
ℹ️ info:命名空間 IlluminateDatabaseEloquentRelations 中的所有 Eloquent 關係類別(例如 BelongsTo 和 HasOne)現在都是通用的。
通用介面並沒有那麼不同。 IlluminateContractsSupportArrayable 是通用介面的範例
/** * Execute the query and get the first result. * * @param array|string $columns * @return \Illuminate\Database\Eloquent\Model|object|static|null */ public function first($columns = ['*']) { return $this->take(1)->get($columns)->first(); }
此介面定義了兩個型別參數:array-key類型的TKey(可以是int或string)和TValue。這兩個參數用於定義 toArray 函數的傳回類型。這是一個例子:
<?php namespace Illuminate\Database\Eloquent; /** * @template TModel of \Illuminate\Database\Eloquent\Model * */ class Builder implements BuilderContract { }
使用者類別實作 Arrayable 接口,並指定 Tkey 類型為 int,TValue 型為 string。
我們在本文開頭的錯誤中遇到了 IlluminateDatabaseEloquentFactoriesHasFactory 特徵。讓我們仔細看看:
/** * @template TEnum * * @param string $key * @param class-string<TEnum> $enumClass * @return TEnum|null */ public function enum($key, $enumClass) { if ($this->isNotFilled($key) || ! enum_exists($enumClass) || ! method_exists($enumClass, 'tryFrom')) { return null; } return $enumClass::tryFrom($this->input($key)); }
HasFactory 定義了一個型別參數 TFactory,它綁定到 IlluminateDatabaseEloquentFactoriesFactory 的子類別。那麼要如何修復這個錯誤呢?
使用 Trait 時必須指定 TFactory 類型。因此,HasFactory 特徵的 use 語句需要使用 PHPDocs @use:
進行註釋
<?php namespace Illuminate\Database\Eloquent; /** * @template TModel of \Illuminate\Database\Eloquent\Model */ class Builder implements BuilderContract { /** * @param array $attributes * @return TModel */ public function make(array $attributes = []) { return $this->newModelInstance($attributes); } }
擴充類別、實作介面或使用特徵時,可以保持子類別中的通用性。
透過在子類別上方定義相同的類型參數並將其傳遞給 @extends、@implements 和 @use 標籤來實現保留通用性。
我們將使用 IlluminateDatabaseConcernsBuildsQueries 通用特徵作為範例,
它定義了一個型別參數TValue:
------ ----------------------------------------------------------------------------------- Line app\Models\User.php ------ ----------------------------------------------------------------------------------- 13 Class App\Models\User uses generic trait Illuminate\Database\Eloquent\Factories\HasFactory but does not specify its types: TFactory ------ -----------------------------------------------------------------------------------
IlluminateDatabaseEloquentBuilder 類別使用此特徵,但透過向其傳遞 TModel 參數類型來保持其通用性。現在由客戶端程式碼來指定 TModel 的類型,從而在 BuildsQueries 特徵中指定 TValue。
/** * @template TFactory of \Illuminate\Database\Eloquent\Factories\Factory */ trait HasFactory { ... }
總之,雖然 PHP 並不像其他程式語言那樣原生支援泛型,但引入高階類型提示和工具(例如 PHPStan)允許開發人員在程式碼中實現類似泛型的功能。透過利用 PHPDocs、參數化類別和接口,您可以創建更靈活和類型安全的應用程序,從而提高程式碼的可重用性和可維護性。隨著 PHP 的不斷發展,社群對類型安全和靜態分析的日益關注可能會帶來更強大的泛型實現解決方案。接受這些實踐不僅可以提高您的編碼技能,還有助於開發經得起時間考驗的高品質軟體。
以上是Laravel 11 中的 PHP 泛型的詳細內容。更多資訊請關注PHP中文網其他相關文章!