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
相关产品推荐
相关产品推荐

