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プログラミングの初心者の間で非常によくある間違いであり、次の例のように、元のインスタンスがオブジェクトの外部で共有されないようにゲッターとセッターを修正することですばやく解決できます。

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

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

これにより、DoctrineORMの問題も修正されます。

また、これによりロジックで発生する可能性のあるリークが修正されることに注意してください。たとえば、次のコードはバグが多く、デバッグが困難です(現在壊れているゲッター/セッターを適用する場合)。

$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

過去の日付のエンティティを挿入しようとすると、まったく同じ問題が発生します(古いデータベースを、そのデータを含む新しいスキーマに移行しようとしています)。

セッターとゲッターの両方でオブジェクトのクローンを作成しようとしましたが、役に立ちません。Doctrine2は現在の日付を保存します。スキーマを確認しました。フィールドはタイムスタンプではなく日時であり、デフォルトはnullです。

どうすればいいの?

編集:

私の注意の欠如を許してください、私の同僚の開発者はprePersistイベントを追加しました:

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