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();
Почему это происходит?
Ответы
Ответ 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.