如何在 `firstOrCreate` 中使用Eloquent的类型转换?
我有一个PostgreSQL数据库表 tasks,其中包含一列 eligible_status_ids BIGINT[]。我还有一个Laravel Eloquent的类型转换 PostgresArrayCast,它在PostgreSQL的数组格式(例如 {1,2,3})与Laravel集合之间进行转换:
<?php
namespace App\Casts;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Contracts\Database\Eloquent\SerializesCastableAttributes;
use Illuminate\Support\Collection;
class PostgresArrayCast implements CastsAttributes, SerializesCastableAttributes
{
public function get($model, string $key, $value, array $attributes)
{
if ($value === null) { return null; }
$value = \substr($value, 1, -1);
return collect(explode(',', $value))
->filter()
->map(function ($val) {
if ($val[0] === '"' && $val[-1] === '"') {
return \substr($val, 1, -1);
}
if (is_numeric($val)) {
return $val + 0; // implicit type conversion to number
}
return $val;
});
}
public function set($model, string $key, $value, $attributes)
{
if ($value === null) { return null; }
$imploded = ($value instanceof Collection) ? $value->join(',') : implode(',', $value);
$encodedArray = '{'.$imploded.'}';
$escapedQuotes = \str_replace("'", "''", $encodedArray);
return $escapedQuotes;
}
public function serialize($model, string $key, $value, array $attributes)
{
return json_encode($value);
}
}
该表还包含一列 initial_duration INT,其中存放一个数字。
我想编写代码——给定一个已存在的任务和一个新的 initial_duration 值——将找到一个已匹配的现有任务,或者创建一个新的任务,使其除了这个新的目标 initial_duration 值之外完全相同。我以为可以这样做:
$newTask = Task::firstOrCreate([
'initial_duration' => $desiredValue,
...$existingTask->only(['name', 'description', 'eligible_status_ids'])
]);
当我运行时,PostgreSQL服务器返回一个错误:
SQLSTATE[08P01]: <<Unknown error>>: 7 ERROR: bind message supplies 3 parameters, but prepared statement "pdo_stmt_0000054c" requires 4 (Connection: pgsql, Host: postgres, Port: 5432, Database: develop, SQL: select * from "tasks" where ("initial_duration" = 120 and "name" = Close and "description" = Testing and "eligible_status_ids" = ?) and "tasks"."deleted_at" is null limit 1)
如果我重新排列属性名,我发现数组属性根本没有被提供,后面的属性都往前移动了一位:
$newTask = Task::firstOrCreate([
'initial_duration' => $desiredValue,
...$existingTask->only(['eligible_status_ids', 'name', 'description'])
]);
SQLSTATE[08P01]: <<Unknown error>>: 7 ERROR: bind message supplies 3 parameters, but prepared statement "pdo_stmt_0000054c" requires 4 (Connection: pgsql, Host: postgres, Port: 5432, Database: develop, SQL: select * from "tasks" where ("initial_duration" = 120 and "eligible_status_ids" = Close and "name" = Testing and "description" = ?) and "tasks"."deleted_at" is null limit 1)
如果我 dump($existingTask->only(['eligible_status_ids', 'name', 'description'])),我看到这个:
= [
"name" => "Close",
"description" => "Testing",
"eligible_status_ids" => Illuminate\Support\Collection {#10115
all: [
1,
2,
3
],
}
]
看起来查询构建器完全忽略了这个经过强制转换的数组参数,根本没有把它包含在查询中。如果我关闭强制转换,它会按预期把原始的数组表示形式('{1,2,3}')放入查询。
为什么强制转换会阻止该值包含在查询参数中?
我该如何修复它,同时仍然保留强制转换?
解决方案
使用原始的未转换值。为此,请使用getRawOriginal() 方法
$newTask = Task::firstOrCreate([
'initial_duration' => $desiredValue,
'name' => $existingTask->name,
'description' => $existingTask->description,
'eligible_status_ids' => $existingTask->getRawOriginal('eligible_status_ids'),
]);
getRawOriginal() 会绕过强制转换,直接从数据库返回原始值({1,2,3})