【问题标题】:Doctrine2 ORM does not save changes to a DateTime fieldDoctrine2 ORM 不保存对 DateTime 字段的更改
【发布时间】:2013-03-07 08:30:58
【问题描述】:

我有一个用户实体:

use Doctrine\ORM\Mapping as ORM;

/**
 * ExampleBundle\Entity\User
 *
 * @ORM\Entity()
 */
class User
{
    // ...

    /**
     * @ORM\Column(type="service_expires_at", type="date", nullable=true)
     */
    private $service_expires_at;

    public function getServiceExpiresAt()
    {
        return $this->service_expires_at;
    }

    public function setServiceExpiresAt(\DateTime $service_expires_at)
    {
        $this->service_expires_at = $service_expires_at;
    }
}

当我如下更新用户的service_expires_at 时,更新后的service_expires_at保存回数据库:

$date = $user->getServiceExpiresAt(); 

var_dump($date->format('Y-m-d')); // 2013-03-08

$date->modify('+10 days');

var_dump($date->format('Y-m-d')); // 2013-03-18

$user->setServiceExpiresAt($date);

$em->persist($user);
$em->flush();

但是,如果我将新的 DateTime 对象传递给 service_expires_at,则更新的值将正确保存:

$date = $user->getServiceExpiresAt(); 

$date->modify('+10 days');

$user->setServiceExpiresAt(new \DateTime($date->format('Y-m-d'));

$em->persist($user);
$em->flush();

为什么会这样?

【问题讨论】:

    标签: oop doctrine-orm encapsulation


    【解决方案1】:

    ExampleBundle\Entity\User#getServiceExpiresAt() 返回的DateTime 实例与存储在实体本身中的对象相同,这会破坏encapsulation

    UnitOfWork in Doctrine ORM applies strict comparison for changesets,这基本上意味着在包含对象的实体的属性的情况下,如果对象实例没有改变,ORM 不会检测到改变。

    严格比较,以下是正确的:

    $dateTime1 = new \DateTime('@0');
    $dateTime2 = new \DateTime('@0');
    $dateTime3 = $dateTime1;
    
    var_dump($dateTime1 !== $dateTime2); // true
    var_dump($dateTime1 === $dateTime3); // true
    
    $dateTime1->modify('+1 day');
    
    var_dump($dateTime1 === $dateTime3); // true
    

    这是 OOP 编程新手中非常常见的错误,可以通过修复 getter 和 setter 来快速解决,这样原始实例就不会在对象之外共享,如下例所示:

    public function getServiceExpiresAt()
    {
        return clone $this->service_expires_at;
    }
    
    public function setServiceExpiresAt(\DateTime $service_expires_at)
    {
        $this->service_expires_at = clone $service_expires_at;
    }
    

    这也将解决您的 Doctrine ORM 问题。

    另外,请注意,这可以修复您的逻辑中可能存在的漏洞。例如,以下代码有缺陷且难以调试(在应用当前损坏的 getter/setter 时):

    $bankTransaction1 = $someService->getTransaction(1);
    $bankTransaction2 = $someService->getTransaction(2);
    
    // leak! Now both objects reference the same DateTime instance!
    $bankTransaction2->setDateTime($bankTransaction1->getDateTime());
    
    // bug! now both your objects were modified!
    $bankTransaction1->getDateTime()->modify('+1 day');
    

    因此,无论问题中的 ORM 部分如何,请不要破坏封装。

    【讨论】:

    • 答案真的很详细,并指出了确切的问题!感谢您的努力。这让我发疯了!
    • 您应该考虑到这对所有领域都不是一个好主意。如果您在两个实体之间建立了关系,则一旦您触摸代理,克隆对象就会触发延迟加载。这可能会毁了你的表现
    • 在这些情况下,性能几乎总是次要的:如果您希望从此类操作中获得性能,请直接在数据库级别执行复制操作(通过INSERT ... SELECT ...
    • 我遇到了这个问题,并考虑在事务中包装持久刷新 (beginTransaction/commit) - 它奏效了。另外-本地环境(xampp)中没有问题,仅在prod linux中。难道这种行为取决于环境设置?
    【解决方案2】:

    当我尝试插入具有过去日期的实体时,我遇到了完全相同的问题(我也在尝试将旧数据库迁移到带有数据的新模式)。

    我试图在 setter 和 getter 中克隆对象,但它没用。 Doctrine 2 保存当前日期。检查架构,该字段是日期时间而不是时间戳,默认为空。

    这怎么可能?

    编辑:

    请原谅我的注意力不集中,我的同事开发添加了一个 prePersist 事件:

    /**
     * @ORM\PrePersist
     */
    function onPrePersist() {
        $this->created_at = new \DateTime('now');
    }
    

    【讨论】:

      【解决方案3】:

      考虑为您的日期/时间属性使用DateTimeImmutable 类。因此,请注意 DateTimeImmutable 不是 DateTime 的实例

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-03-17
        • 1970-01-01
        • 2013-04-06
        • 1970-01-01
        • 2012-10-09
        • 1970-01-01
        • 1970-01-01
        • 2011-06-02
        相关资源
        最近更新 更多