在使用Spatie的模型状态库时,我遇到了一个问题。我不认为这是一个bug,但它的行为并不像预期的那样,尽管只是在我的一个控制器中。我们使用的是旧版本的代码,目前无法更新到最新版本。
问题是"state“字段没有被转换到Spatie\ModelStates\State派生对象,而是以字符串的形式返回。因此,当我尝试转换到一个新的状态时,我会得到一个异常:“调用字符串上的成员函数transitionTo()”。
但是,代码的其他部分使用相同的模型,并且转换工作正常,状态被转换为正确的类。我只是不明白为什么这个控制器会引起问题。
从我自己的抽象类派生出来的状态
<?php
namespace App\States\ShiftPattern;
use Spatie\ModelStates\State;
abstract class ShiftPatternBaseState extends State
{
public static array $states = [
Approved::class,
Draft::class,
PendingApproval::class,
Rejected::class,
];
}
即使我在基类中注册状态,它们也在同一个文件夹中。迁移将status
字段添加到数据库表中
public function up()
{
Schema::table('shift_patterns', function (Blueprint $table) {
$table->string('status')->default('draft')->after('booking_pay_rate_id');
});
}
我的模型实现了HasStates
class ShiftPattern extends Model
{
use HasStates, LogsActivity, UserPermissions;
...
public function registerStates(): void
{
$this->addState('status', ShiftPatternBaseState::class)
->default(Draft::class)
->allowTransition([Draft::class, Rejected::class], PendingApproval::class, ToPendingApproval::class)
->allowTransition(PendingApproval::class, Approved::class, PendingApprovalToApproved::class)
->allowTransition(PendingApproval::class, Rejected::class, ToRejected::class);
}
...
}
这个问题发生在我正在更新的一个控制器中。它目前处理一个API调用来创建一个新的ShiftPattern
并将它附加到一个Booking
模型上,这个模型可以工作。Booking
和ShiftPattern
之间有一对多的关系。
// CreateShiftPatternRequest has the Booking object ($request->record) and attributes ($request->attributes) to create the ShiftPattern
public function createShiftPattern(CreateShiftPatternRequest $request)
{
$this->authorize('editShiftPatterns', $request->record);
$shiftPattern = $request->record->shiftPatterns()->create($request->attributes());
return $this->reply()->content($shiftPattern, [], $this->getMeta('bookings.shift-pattern.create'));
}
新的ShiftPattern是在"Pending“状态下创建的,但是有些预订并不要求它们被”批准“,所以我想将它们直接移到”批准“状态。
public function createShiftPattern(CreateShiftPatternRequest $request)
{
...
$shiftPattern = $request->record->shiftPatterns()->create($request->attributes());
if (!$request->record->booking_must_be_approved) {
$shiftPattern->transitionTo(Approved::class);
}
return $this->reply()->content($shiftPattern, [], $this->getMeta('bookings.shift-pattern.create'));
}
但是,我一直收到错误“调用字符串上的成员函数transitionTo()”,在transitionTo
调用中,状态字段被库解析后就会发生这种错误。正如我所说的,在其他情况下,这很好,但是在这个控制器中,state字段不会自动转换为一个对象。
我认为模型可能没有正确地“引导”来设置类转换,所以我公开了一个函数,允许控制器调用bootIfNotBooted
,但是在里面它跳过初始化,因为它已经被引导了。然后,我尝试从数据库中刷新和重新加载ShiftPattern,看看这是否能解决这个问题:
...
$shiftPattern = $request->record->shiftPatterns()->create($request->attributes());
// Attempt 1 - refresh model
$shiftPattern->refresh();
// Attempt 2 - reload model
$newShiftPattern = ShiftPattern::find($shiftPattern->id);
...
这两种方法都不起作用,状态字段仍然作为字符串返回。
我还尝试创建模型,而不是将其与预订联系起来,但这也没有解决问题。
$shiftPattern = ShiftPattern::create($request->attributes());
if (!$request->record->booking_must_be_approved) {
$shiftPattern->transitionTo(Approved::class);
}
$request->record->shiftPatterns()->save($shiftPattern);
...
有人知道为什么会发生这种事吗?我真的不认为它是ModelStates库中的一个bug,因为它适用于tinker和我的代码的其他部分,它似乎是一个Laravel属性强制转换问题。
我也在包的github讨论上发布了这个问题。
发布于 2022-01-04 06:32:06
这个问题是由从传入的属性创建status
字段时填充的:
ShiftPattern::create($request->attributes());
当create
被调用时,Laravel构造一个新的对象,然后从传递的数组中填充属性。在这种情况下,API的调用方包含状态的名称(pending-approval
),因此在构造类并将status
字段设置为默认状态(作为对象)之后,属性将被字符串“未决-批准”覆盖--因此在尝试转换到新状态时出现错误。
我看到的解决办法是:
draft
状态(然后转换到pending-approval
)并直接跳转到pending-approval
而引起的,但是更改是在没有考虑其影响的情况下进行的。应该删除draft
状态并重构代码。对于解决方案3,mutator可以使用库函数resolveStateClass
public function setStatusAttribute($status)
{
if (is_string($status)) {
$stateClass = ShiftPatternBaseState::resolveStateClass($status);
$status = class_exists($stateClass)
? new $stateClass($this)
: (new ReflectionClass(self::getDefaultStateFor('status')))->newInstance($this);
}
$this->attributes['status'] = $status;
}
当状态使用字符串时,基态检查它是否可以解析为类(可以使用名称或类字符串)。解析函数返回的字符串要么是完全限定的类名,要么是与已知状态类不匹配的字符串,或者是传入的字符串。
检查字符串结果以查看类是否存在,意味着我们可以创建该状态类的实例,也可以创建默认状态。无论哪种方式,对象上的属性现在都是正确的,我们可以在它上调用transitionTo
。
我将使用这个mutator来快速修复我遇到的问题,但是接下来我需要返回并重构代码(选项2),以删除draft
状态,并在将来使用pending-approval
状态作为默认状态。
https://stackoverflow.com/questions/70465485
复制相似问题