如何在 `firstOrCreate` 中使用Eloquent的类型转换?

后端开发 2026-07-08

我有一个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})

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章