13

我有一个带有数组类型字段的 Doctrine 实体:

/**
 * @ORM\Table()
 */
class MyEntity
{
    (...)

    /**
     * @var array $items
     * 
     * @ORM\Column( type="array" ) 
     */
    private $items;

    /**
     * @param SomeItem $item 
     */
    public function addItem(SomeItem $item)
    {
        $this->items[] = $item;
    }

    (...)
}

如果我将元素添加到数组中,则此代码可以正常工作:

$myEntityObject->addItems(new SomeItem()); 
$EntityManager->persist($myEntityObject);
$EntityManager->flush();

$myEntityObject以正确的数据保存到数据库中(数组被序列化,在查询数据库时被反序列化)。

不幸的是,当我更改数组中的一个对象而不更改该数组的大小时,如果我试图将更改保存到数据库,Doctrine 什么也不做。

$items = $myEntityObject->getItems();
$items[0]->setSomething(123);
$myEntityObject->setItems($items);
$EntityManager->persist($myEntityObject);
$EntityManager->flush();
print_r($myEntityObject);

尽管print_r在该代码的最后一行显示更改的对象的数据,但如果数组大小未更改,Doctrine 不知道数组内部的某些内容已更改。有什么方法可以强制 Doctrine 保存在该字段中所做的更改(或轻轻告知它需要保存的该字段中的更改)?


刚刚在文档中找到了一种解决我的问题的方法:

http://docs.doctrine-project.org/en/latest/reference/change-tracking-policies.html

它需要对代码进行大量更改,但它可以工作。有人知道如何为其他字段保留默认跟踪策略并仅将 NotifyPropertyChanged 用于存储数组的字段吗?

4

3 回答 3

29

Doctrine 使用相同的运算符 (===) 来比较新旧值之间的变化。在具有不同数据的同一对象(或对象数组)上使用的运算符始终返回 true。还有另一种方法可以解决此问题,您可以克隆需要更改的对象。

$items = $myEntityObject->getItems();
$items[0] = clone $items[0];
$items[0]->setSomething(123);
$myEntityObject->setItems($items);

// ...

或者改变setItems()方法(我们只需要克隆一个对象来持久化整个数组)

public function setItems(array $items) 
{
    if (!empty($items) && $items === $this->items) {
        reset($items);
        $items[key($items)] = clone current($items);
    }
    $this->items = $items;
}

关于第二个问题:

有人知道如何为其他字段保留默认跟踪策略并仅将 NotifyPropertyChanged 用于存储数组的字段吗?

您不能只为一个字段设置跟踪策略。

于 2012-11-05T12:12:43.037 回答
1

我在代码上修复此问题的方法是使用 createQueryBuilder 并创建更新查询。这种方式教义没有办法说不:)

所以我从这个

$em          = $this->getDoctrine()->getManager();
$matchEntity = $em->getReference('MyBundleBundle:Match', $match_id);


$matchEntity->setElement($element);
$matchEntity->setTeamHomeColour($data['team_a_colour']);
$matchEntity->setTeamAwayColour($data['team_b_colour']);

对此:

$repository = $this->getDoctrine()->getRepository('MyBundleBundle:Match');
$query      = $repository->createQueryBuilder('u')
    ->update()
    ->set('u.element', ':element')
    ->set('u.teamHomeColour', ':thomecolour')
    ->set('u.teamAwayColour', ':tawaycolour')
    ->where('u.matchId = :match')

    ->setParameter('element', $element)
    ->setParameter('thomecolour', $data['team_a_colour'])
    ->setParameter('tawaycolour', $data['team_b_colour'])
    ->setParameter('match', $matchEntity)

    ->getQuery();

$query->execute();

它多了几行代码,但没有克隆或任何其他类型的魔法。只需直接告诉教义进行该死的更新!希望这可以帮助。

注意:在我的情况下,$element 没有被设置。我取消了之前查询中的所有匹配项,而学说只是没有看到它,因此拒绝更新元素。

于 2015-06-30T17:19:37.423 回答
0

我知道这是一个非常古老的问题,但仍然相关,我想在@VadimAshikhman 的答案中添加更多内容(顺便说一下,你为我节省了几个小时的工作时间......)。

因为我不想在控制器中做特定的事情,所以我在我的实体上添加了一个 Doctrine PreFlush 回调,在那里我克隆了未检测到更改的数组/对象:

/**
 * @ORM\Table()
 */
class MyEntity
{
    (...)

    /**
     * @var array $items
     * 
     * @ORM\Column( type="array" ) 
     */
    private $items;

    (...)

    /**
     * Always clone the array/object before flushing,
     * because changes are not detected inside the array/object.
     * It ensures changes are always persisted to the db.
     * 
     * @ORM\PreFlush()
     */
    public function cloneOnPreFlush() {
        $clone = clone $this->items;
        $this->items = $clone;
    }
}

在你的控制器中,一个正常的$em->flush()会触发 PreFlush 回调。唯一的小缺点是您的实体将始终在刷新时在数据库中更新,即使没有对其进行更改。

于 2020-01-24T14:41:14.420 回答