Doctrine 3实体CURRENT_TIMESTAMP默认值不生效,如何正确设置?
问题分析与解决方案
错误原因
Doctrine ORM在持久化实体时,会把所有映射的字段都包含到INSERT语句中。虽然你给数据库列设置了DEFAULT CURRENT_TIMESTAMP,但实体的sent_at是未赋值的readonly属性,Doctrine会在INSERT时传递NULL值给该字段。数据库会优先使用显式传递的NULL,而非列默认值,这就触发了NOT NULL约束错误。而直接执行SQL时你没指定sent_at字段,数据库才会自动应用默认值。
无需手动设置sent_at的正确配置方法
方法1:用生命周期回调在应用层自动赋值
通过Doctrine的PrePersist生命周期事件,在实体持久化前自动设置sent_at的值。因为是readonly属性,需要用反射绕过初始化限制:
use Doctrine\ORM\Mapping\{Entity, Table, Column, HasLifecycleCallbacks, PrePersist}; use Doctrine\DBAL\Types\Types; use ReflectionProperty; #[Entity] #[Table(name: 'messages')] #[HasLifecycleCallbacks] class Message { #[Column( name: 'sent_at', type: Types::DATETIME_IMMUTABLE, updatable: false, options: ['default' => 'CURRENT_TIMESTAMP'] )] private readonly DateTimeImmutable $sent_at; public function __construct(private readonly string $message) { } #[PrePersist] public function setSentAtOnPersist(): void { $reflection = new ReflectionProperty(self::class, 'sent_at'); $reflection->setValue($this, new DateTimeImmutable()); } // 可选:添加getter方法 public function getSentAt(): DateTimeImmutable { return $this->sent_at; } }
优点:持久化后立即能获取sent_at的值,无需刷新实体;缺点:时间戳由应用服务器生成,可能和数据库服务器时间存在偏差。
方法2:让Doctrine忽略该字段的插入,依赖数据库默认值
配置Doctrine不在INSERT语句中包含sent_at字段,让数据库自动应用默认值。需要添加insertable: false和generated: Column::GENERATED_DEFAULT属性:
use Doctrine\ORM\Mapping\{Entity, Table, Column}; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\EntityManagerInterface; #[Entity] #[Table(name: 'messages')] class Message { #[Column( name: 'sent_at', type: Types::DATETIME_IMMUTABLE, updatable: false, insertable: false, generated: Column::GENERATED_DEFAULT, options: ['default' => 'CURRENT_TIMESTAMP'] )] private readonly DateTimeImmutable $sent_at; public function __construct(private readonly string $message) { } // 持久化后需要刷新实体才能获取数据库生成的sent_at public function refreshSentAt(EntityManagerInterface $em): void { $em->refresh($this); } public function getSentAt(): DateTimeImmutable { return $this->sent_at; } }
使用时,持久化后如果需要获取sent_at值,要调用refreshSentAt方法:
$message = new Message('test message'); $this->entity_manager->persist($message); $this->entity_manager->flush(); $message->refreshSentAt($this->entity_manager); // 现在可以获取到数据库生成的sent_at值 echo $message->getSentAt()->format('Y-m-d H:i:s');
优点:时间戳由数据库服务器生成,时间更准确;缺点:需要手动刷新实体才能拿到sent_at的值。
内容的提问来源于stack exchange,提问作者Unlucky
相关产品推荐
相关产品推荐

