Подтвердить что ты не робот

Doctrine2 ORM не сохраняет изменения в поле DateTime

У меня есть пользовательский объект:

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;
    }
}

Когда я обновляю User service_expires_at следующим образом, обновленное значение service_expires_at NOT сохраняется в базе данных:

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

Почему это происходит?

4b9b3361

Ответ 1

Версии DateTime, возвращаемые ExampleBundle\Entity\User#getServiceExpiresAt(), - это те же объекты, которые хранятся в самой сущности, которая разбивает encapsulation.

UnitOfWork в Doctrine ORM применяет строгое сравнение для наборов изменений, что в основном означает, что в случае свойств объектов, содержащих объекты, если экземпляр объекта не изменился, 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

Это очень распространенная ошибка среди новичков в программировании ООП, и ее можно быстро решить, установив ваши геттеры и сеттеры, чтобы исходный экземпляр никогда не делился вне вашего объекта, как в следующем примере:

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.

Также обратите внимание, что это устраняет возможные утечки в вашей логике. Например, следующий код является ошибкой и трудно отлаживается (при применении ваших разбитых геттеров/сеттеров):

$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 в вопросе, пожалуйста, не разрушайте инкапсуляцию.

Ответ 2

У меня точно такая же проблема, когда я пытаюсь вставить объект с прошлой датой (я пытаюсь также перенести старую базу данных в новую схему с данными).

Я попытался клонировать объект как в сеттер, так и в getter, и это бесполезно. Doctrine 2 сохраняет текущую дату. Проверяемая схема, поле - это дата времени, а не отметка времени, а значение по умолчанию - null.

Как это может быть?

EDIT:

Пожалуйста, извините мое отсутствие внимания, мой коллега Dev добавил событие prePersist:

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

Ответ 3

Рассмотрите возможность использования DateTimeImmutable класса для ваших свойств даты/времени. Тем самым обратите внимание, что DateTimeImmutable не является экземпляром DateTime.