Rumah > Artikel > rangka kerja php > Penjelasan terperinci tentang cara menetapkan tahap pengesahan tertinggi PHPStan dalam Laravel
Sejak beberapa tahun lalu, analisis statik dalam PHP, dan lebih khusus lagi Laravel, telah menjadi semakin popular. Memandangkan semakin ramai orang mengamalkannya dalam pembangunan perisian mereka, saya fikir sekarang adalah masa yang sesuai untuk menulis tutorial tentang cara menambahkannya pada projek Laravel anda.
Pada tahun 2019, Nuno Maduro mengeluarkan pakej yang dipanggil Larastan, satu set peraturan PHPStan untuk projek Laravel yang saya sangat teruja. Setakat ini saya bergelut untuk mendapatkan liputan analisis statik yang baik dalam Laravel menggunakan sama ada PHPStan atau Psalm. Peraturan Larastans membenarkan saya mula menggunakan lebih banyak analisis statik pada pangkalan kod saya dan, seterusnya, lebih yakin dengan kod saya. Kini menggunakan PHP 8.1 dan Laravel 9 - Saya berasa lebih yakin berbanding sebelum ini dalam kod yang saya tulis terima kasih kepada banyak alat yang menakjubkan yang saya gunakan.
Dalam tutorial ini, saya akan menambah Larastan pada projek Laravel baharu, menetapkan tahap kepada tertinggi.
Mula-mula buat projek Laravel baharu bernama larastan-test:
laravel new larastan-test
Selepas mencipta projek baharu, pasang Larastan dengan menjalankan arahan komposer berikut:
composer require nunomaduro/larastan --dev
Sebab kami mahukannya sebagai pergantungan pembangunan adalah kerana dalam pengeluaran kami tidak sepatutnya menjalankan sebarang analisis statik - ia hanya untuk tujuan pembangunan untuk memastikan kod anda seaman mungkin. PHPStan menggunakan format konfigurasi yang dipanggil neon, yang agak serupa dengan yaml. Oleh itu, kami akan mencipta fail baharu yang dipanggil ./phpstan.neon dalam direktori akar aplikasi keluar - jika anda sedang membina pakej, pendekatan yang disyorkan ialah menambah .dist pada penghujung fail konfigurasi ini. Dalam fail ini kita akan mula mentakrifkan konfigurasi yang diperlukan untuk phpstan dijalankan dan peraturan yang mungkin ingin kita kenakan Dengan menambahkan kod berikut pada fail konfigurasi kita boleh memahami maksudnya:
includes: - ./vendor/nunomaduro/larastan/extension.neon parameters: paths: - app level: 9 ignoreErrors: excludePaths:
kami Bermula. dengan includes
, ini biasanya peraturan yang ingin kami sertakan dalam pakej dalam set peraturan phpstan asas kami. Bahagian parameter konfigurasi ini, pilihan pertama paths
membolehkan kami menentukan tempat yang kami mahu PHPStan semak - dalam kes ini, kami hanya perlu menumpukan pada direktori app
di mana kod aplikasi terletak. Anda boleh melanjutkan ini untuk merangkumi berbilang direktori jika anda mahu, tetapi berhati-hati dengan skop yang anda perkenalkan, kerana semuanya akan menjadi serius! Seterusnya, parameter level
PHPStan menentukan pelbagai peringkat yang boleh disemak, dengan 0 adalah yang terendah dan 9 pada masa ini adalah yang tertinggi.
Seperti yang anda lihat kami telah menetapkan tahap kepada 9, saya akan mengesyorkan melakukan ini pada apl sedia ada kerana hanya idealnya anda akan mencapai tahap ini - tetapi memandangkan ini adalah projek serba baharu, Kami boleh berasa sangat selesa pada 9 (hutang teknikal tidaklah banyak mana pun).
Seterusnya, dua pilihan ignoreErrors
dan excludePaths
membolehkan kami memberitahu PHPStan supaya mengabaikan fail yang kami tidak minati atau ralat khusus yang tidak dapat kami kawal atau betulkan pada peringkat ini. Mungkin anda sedang memfaktorkan semula perniagaan dan menghadapi pepijat. Anda mungkin memfaktorkan semula kod ini untuk analisis statik kemudian, kemudian anda boleh menggunakan konfigurasi ini untuk membenarkan PHPStan mengabaikan ralat berkaitan sebelum anda menyelesaikan pemfaktoran semula.
includes
Mengandungi peraturan phpstan asas. parameters
parameter konfigurasi, pilihan pertama paths
Konfigurasikan direktori yang phpstan semak - dalam contoh saya, saya hanya menyemak direktori app
di mana kod aplikasi terletak, sudah tentu anda juga boleh mengkonfigurasi direktori lain. level
Tahap konfigurasi, PHPStan boleh dikonfigurasikan dengan pelbagai peringkat, 0 adalah yang paling rendah, dan 9 adalah yang tertinggi pada masa ini. Seperti yang anda lihat, saya telah menetapkan tahap kepada 9 dan saya mengesyorkan menetapkan tahap kepada 9. Seterusnya terdapat ignoreErrors
dan excludePaths
yang memberitahu PHPStan untuk mengabaikan fail atau ralat khusus yang tidak mahu dikesan, atau fail dan ralat yang tidak perlu dikesan pada masa ini. Sebagai contoh, kod sedang difaktorkan semula dan anda mahu mengabaikan ralat sehingga ia selesai, dan kemudian melakukan analisis statik selepas ia selesai.
Jadi mari jalankan phpstan terhadap aplikasi Laravel lalai dan lihat ralat yang kita hadapi, jika ada. Jalankan arahan berikut dalam terminal:
./vendor/bin/phpstan analyse
Output yang kami dapat daripada aplikasi Laravel lalai kelihatan seperti ini:
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
Seperti yang anda lihat, kami ada Kami hanya mendapat satu ralat dalam aplikasi walaupun kami menetapkan tahap semakan ke tahap yang paling ketat.
Bagus, kan? Sudah tentu, anda mungkin melihat hasil yang berbeza jika anda menambahkan ini pada projek sedia ada, dan dengan mengikuti tutorial ini, anda akan belajar cara menyelesaikan isu tersebut supaya anda mempunyai aliran kerja yang baik untuk diikuti.
Jalankan phpstan dalam aplikasi Laravel anda jika ralat berlaku. Jalankan arahan berikut dalam terminal:
./vendor/bin/phpstan analyse
Output akan kelihatan seperti ini
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('api', 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('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', ); } }
从上面可以看出,我们在这里唯一关心的是名称、url,如果用户想要加星标/收藏书签并且该书签属于用户。现在我们可以把它留在这里,但我个人喜欢将类型定义添加到我的模型属性中——因为目前在 Laravel 9 中我无法输入提示它们。因此,重构你的模型,使其如下所示:
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', ); } }
我们在这里所做的只是告诉 PHP 和我们的 IDE,可填充数组是一个没有键的字符串数组——这意味着它将默认为整数。然后我们的 casts 数组是一个带键的字符串数组,其中的键也是字符串。现在,即使在没有类型定义的情况下运行静态分析,它也不会失败 - 但这是一个很好的实践,以便你的 IDE 在你工作时拥有尽可能多的信息。
让我们继续处理路由和控制器,以便我们可以继续运行静态分析检查。现在我是可调用控制器的忠实粉丝——我发现它们非常适合我的代码风格,但是你可能不喜欢它们或有不同的偏好,所以如果你是的话,下一部分可以随意偏离我的编码风格,会让你更舒服。
我们现在将创建一个控制器,运行以下 artisan 命令来为书签创建索引控制器:
php artisan make:controller Bookmarks/IndexController --invokable
这是我们路由所需的索引控制器,所以我们可以去添加一个新的路由组在 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 中间件中,以便我们控制作者对书签的访问,我们还希望在 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: 'bookmarks.list', data: [ 'bookmarks' => Bookmark::query() ->where('user_id', $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('user_id', 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视频教程】
Atas ialah kandungan terperinci Penjelasan terperinci tentang cara menetapkan tahap pengesahan tertinggi PHPStan dalam Laravel. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!