You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Symfony/Doctrine迁移Types::JSON后无法持久化嵌入对象数组数据

问题原因与解决方案

核心原因

Doctrine的Types::ARRAY和Types::JSON序列化逻辑完全不同:

  • Types::ARRAY依赖PHP原生的serialize()/unserialize(),能完整保留嵌入类实例的所有结构(包括私有/受保护属性)
  • Types::JSON使用json_encode()/json_decode(),默认仅序列化对象的公开属性。你的ControlledKeywordEmbeddable嵌入类大概率只有私有/受保护属性,因此序列化后会变成空对象{},最终存储为{"1":{},"2":{}}

解决方法

方法1:给嵌入类实现JsonSerializable接口

在ControlledKeywordEmbeddable类中实现JsonSerializable接口,明确指定需要序列化的属性:

use JsonSerializable;

class ControlledKeywordEmbeddable implements JsonSerializable
{
    // 示例嵌入类属性
    private string $value;
    private string $authority;

    // 构造函数、getter/setter等逻辑...

    public function jsonSerialize(): mixed
    {
        return [
            'value' => $this->value,
            'authority' => $this->authority,
            // 按需添加其他需要序列化的属性
        ];
    }
}

实现后,json_encode()会按照你定义的结构序列化实例,持久化后就能得到正确的JSON数据。

方法2:配置字段的自定义序列化回调

如果不想修改嵌入类,可以在实体的字段属性中指定serialize和unserialize回调:

use Doctrine\ORM\Mapping as ORM;
use Doctrine\DBAL\Types\Types;

#[ORM\Entity]
class Work
{
    // 其他字段...

    #[ORM\Column(
        type: Types::JSON,
        serialize: [self::class, 'serializeControlledKeywords'],
        unserialize: [self::class, 'unserializeControlledKeywords']
    )]
    private array $controlledKeywords;

    // 自定义序列化方法
    public static function serializeControlledKeywords(array $keywords): string
    {
        return json_encode(array_map(function (ControlledKeywordEmbeddable $keyword) {
            return [
                'value' => $keyword->getValue(),
                'authority' => $keyword->getAuthority(),
            ];
        }, $keywords));
    }

    // 自定义反序列化方法
    public static function unserializeControlledKeywords(string $data): array
    {
        $decoded = json_decode($data, true);
        return array_map(function (array $item) {
            return new ControlledKeywordEmbeddable($item['value'], $item['authority']);
        }, $decoded);
    }
}

注:Doctrine ORM 2.10及以上版本支持在#[Column]属性中使用serialize和unserialize参数,你的版本刚好兼容。

方法3:创建Doctrine自定义类型

如果需要更通用的控制方案,可以创建专门处理该嵌入类的自定义JSON类型:

use Doctrine\DBAL\Types\JsonType;
use Doctrine\DBAL\Platforms\AbstractPlatform;

class ControlledKeywordJsonType extends JsonType
{
    public function convertToDatabaseValue($value, AbstractPlatform $platform)
    {
        if (!is_array($value)) {
            return parent::convertToDatabaseValue($value, $platform);
        }

        $serialized = array_map(function (ControlledKeywordEmbeddable $keyword) {
            return [
                'value' => $keyword->getValue(),
                'authority' => $keyword->getAuthority(),
            ];
        }, $value);

        return parent::convertToDatabaseValue($serialized, $platform);
    }

    public function convertToPHPValue($value, AbstractPlatform $platform)
    {
        $decoded = parent::convertToPHPValue($value, $platform);

        if (!is_array($decoded)) {
            return $decoded;
        }

        return array_map(function (array $item) {
            return new ControlledKeywordEmbeddable($item['value'], $item['authority']);
        }, $decoded);
    }

    public function getName()
    {
        return 'controlled_keyword_json';
    }
}

在config/packages/doctrine.yaml中注册自定义类型:

doctrine:
    dbal:
        types:
            controlled_keyword_json: App\Doctrine\Type\ControlledKeywordJsonType

最后在实体字段中使用该类型:

#[ORM\Column(type: 'controlled_keyword_json')]
private array $controlledKeywords;

额外检查

确保数据库对应字段的类型为JSON(如MySQL的JSON、PostgreSQL的jsonb),如果之前使用Types::ARRAY时字段是TEXT或VARCHAR,需要修改字段类型为JSON,避免序列化/反序列化异常。

内容的提问来源于stack exchange,提问作者Wolfone

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 05:30:48