在使用Laravel应用程序之前找到错误是可能的,归功于Larastan,这是一个PHPStan的外壳,旨在支持静态分析中的所有Laravel魔法。
在此,我将引导您通过安装Larastan的步骤,直到达到规则的9级,而不忽略任何内容。
根据Larastan的README,安装它的操作如下
- 运行
composer require larastan/larastan:^2.0 --dev
- 在项目的根目录中添加一个
phpstan.neon
或phpstan.neon.dist
文件
includes:
- vendor/larastan/larastan/extension.neon
parameters:
paths:
- app/
# Level 9 is the highest level
level: 5
如您所见,默认设置为检查级别5,但我们将将其更改为级别0。
在继续之前,我们需要知道Larastan在每个级别上检查什么
- 基本检查,未知类,未知函数,对
$this
调用的未知方法,传递给这些方法和函数的参数数量不正确,始终未定义的变量 - 可能未定义的变量,具有
__call
和__get
的类上的未知魔法方法和属性 - 检查所有表达式上的未知方法(不仅仅是
$this
),验证PHPDocs - 返回类型,属性分配的类型
- 基本死代码检查 - 总是错误的
instanceof
和其他类型检查,死代码else
分支,返回后的不可达代码等。 - 检查传递给方法和函数的参数的类型
- 报告缺少类型提示
- 报告部分错误的联合类型 - 如果您在联合类型上调用仅存在于其中一些类型上的方法,级别7开始报告此类错误;其他可能不正确的情况
- 报告在可空类型上调用的方法和访问的属性
- 对
mixed
类型严格要求 - 您可以与另一个mixed
传递它的唯一操作
考虑到这些规则,假设我们有以下代码(为了简单起见,所有内容都在同一个文件中)
注意: 代码是为了能够得到所需的错误,以便向您展示如何操作而编写的,其中一些部分可以用更简单的方式来编写。
declare(strict_types=1);
use App\Http\Controllers\Controller;
use App\Models\Appointment;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
class User extends Model
{
public function appointments()
{
return $this->hasMany(Appointment::class);
}
}
class UserDTO
{
public function __construct(
public $name,
public $is_active,
) {
}
public function toArray()
{
return [
'name' => $this->name,
'is_active' => $this->is_active,
];
}
}
class ShowUserQuery
{
public function run($id)
{
$this->doSomething();
return User::query()
->with('appointments')
->find($id);
}
}
class UserController extends Controller
{
public function show($id, ShowUserQuery $query)
{
return response()->json($query->run($id)->toArray());
}
public function store(Request $request)
{
$request->validate([
'name' => ['required', 'max:250'],
'is_active' => ['required', 'boolean'],
]);
if (true) {
return;
}
$isActive = $request->input('is_active');
$userDTO = new UserDTO(
$request->input('name'),
$isActive
);
$user = User::create($userDTO->toArray());
return $user;
}
}
执行 ./vendor/bin/phpstan analyze
后,我们得到以下错误
22 Call to an undefined method ShowUserQuery::doSomething().
24 Relation 'appointments' is not found in User model.
为了修复这些错误,我们需要删除或定义未定义的方法,并为模型关系添加返回类型,得到以下代码
class User extends Model
{
public function appointments(): HasMany
{
return $this->hasMany(Appointment::class);
}
}
class ShowUserQuery
{
public function run($id)
{
return User::query()
->with('appointments')
->find($id);
}
}
直到第4级,我们没有得到任何错误,因为在这次操作中,我们没有违反第1级、第2级和第3级的规则。将级别改为4后,我们得到了以下错误
43 If condition is always true.
47 Unreachable statement - code above always terminates.
为了解决这个问题,我们需要删除控制器中 store 方法内的 if 语句,修复后得到的函数如下
public function store(Request $request)
{
$data = $request->validate([
'name' => ['required', 'max:250'],
'is_active' => ['required', 'boolean'],
]);
$isActive = $request->input('is_active');
$userDTO = new UserDTO(
$request->input('name'),
$isActive
);
$user = User::create($userDTO->toArray());
return $user;
}
对于第5级,我们一切顺利,但对于第6级,我们遇到了很多错误
13 Method User::appointments() 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.
21 Method ShowUserQuery::run() has no return type specified.
21 Method ShowUserQuery::run() has parameter $id with no type specified.
31 Method UserController::show() has no return type specified.
31 Method UserController::show() has parameter $id with no type specified.
36 Method UserController::store() has no return type specified.
让我们开始修复问题。在这个级别,我们需要指定代码中所有内容的返回类型和参数类型。对于第一个错误,它要求我们在关系定义中指定相关模型类型。
修复问题后,我们得到了以下代码
class User extends Model
{
/**
* @return HasMany<Appointment>
*/
public function appointments(): HasMany
{
return $this->hasMany(Appointment::class);
}
}
class UserDTO
{
public function __construct(
public string $name,
public bool $is_active,
) {
}
/**
* @return array{name: string, is_active: bool}
*/
public function toArray(): array
{
return [
'name' => $this->name,
'is_active' => $this->is_active,
];
}
}
class ShowUserQuery
{
public function run(int $id): ?User
{
return User::query()
->with('appointments')
->find($id);
}
}
class UserController extends Controller
{
public function show(int $id, ShowUserQuery $query): JsonResponse
{
return response()->json($query->run($id)->toArray());
}
public function store(Request $request): User
{
$request->validate([
'name' => ['required', 'max:250'],
'active' => ['required', 'boolean'],
]);
$isActive = $request->input('is_active');
$userDTO = new UserDTO(
$request->input('name'),
$isActive
);
$user = User::create($userDTO->toArray());
return $user;
}
}
对于第7级,我们很清楚,但对于第8级,很遗憾,我们已经不清楚
37 Cannot call method toArray() on User|null.
为了修复它,我们必须避免在可空类型上调用方法或属性。然后,这里的修复方法是
public function show(int $id, ShowUserQuery $query): JsonResponse
{
$userArray = $query->run($id)?->toArray() ?? [];
return response()->json($userArray);
}
最终,我们到达了最严格、最苛刻的第9级,其中有一些与混合值相关的错误。对于这个场景,我故意设置了变量 $isActive,以向您展示您可以通过两种方式修复相同的错误
- 使用
assert
方法和请求的string
方法
public function store(Request $request): User
{
$request->validate([
'name' => ['required', 'max:250'],
'active' => ['required', 'boolean'],
]);
$isActive = $request->input('is_active');
assert(is_bool($isActive));
$userDTO = new UserDTO(
$request->string('name')->toString(),
$isActive
);
$user = User::create($userDTO->toArray());
return $user;
}
- 使用请求的
string
和boolean
方法
public function store(Request $request): User
{
$request->validate([
'name' => ['required', 'max:250'],
'active' => ['required', 'boolean'],
]);
$userDTO = new UserDTO(
$request->string('name')->toString(),
$request->boolean('is_active')
);
$user = User::create($userDTO->toArray());
return $user;
}
修复了从第0级到第9级所有错误后,我们的最终代码将如下所示
<?php
declare(strict_types=1);
use App\Http\Controllers\Controller;
use App\Models\Appointment;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class User extends Model
{
/**
* @return HasMany<Appointment>
*/
public function appointments(): HasMany
{
return $this->hasMany(Appointment::class);
}
}
class UserDTO
{
public function __construct(
public string $name,
public bool $is_active,
) {
}
/**
* @return array{name: string, is_active: bool}
*/
public function toArray(): array
{
return [
'name' => $this->name,
'is_active' => $this->is_active,
];
}
}
class ShowUserQuery
{
public function run(int $id): ?User
{
return User::query()
->with('appointments')
->find($id);
}
}
class UserController extends Controller
{
public function show(int $id, ShowUserQuery $query): JsonResponse
{
$userArray = $query->run($id)?->toArray() ?? [];
return response()->json($userArray);
}
public function store(Request $request): User
{
$request->validate([
'name' => ['required', 'max:250'],
'active' => ['required', 'boolean'],
]);
$userDTO = new UserDTO(
$request->string('name')->toString(),
$request->boolean('is_active')
);
$user = User::create($userDTO->toArray());
return $user;
}
}
通过这个例子,我希望您能够了解如何在Larastan和PHPStan的帮助下,在代码被执行之前使其无错误,并达到第9级。
driesvints, alanmoe, rawphp, ricventu, sindhani, abdwhidd 喜欢了这篇文章