24

我有一个用户实体:

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();

为什么会这样?

4

3 回答 3

87

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

Doctrine ORM 中的UnitOfWork 对变更集进行严格比较,这基本上意味着在包含对象的实体的属性的情况下,如果对象实例没有更改,则 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 部分如何,请不要破坏封装。

于 2013-03-18T22:31:53.857 回答
1

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

于 2014-07-30T09:10:18.007 回答
0

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

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

怎么会这样?

编辑:

请原谅我缺乏关注,我的同事开发添加了一个 prePersist 事件:

/**
 * @ORM\PrePersist
 */
function onPrePersist() {
    $this->created_at = new \DateTime('now');
}
于 2014-01-23T08:42:53.197 回答