如何强制原则更新数组类型字段


How to force Doctrine to update array type fields?

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

/**
 * @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 不知道数组内部是否发生了更改。有没有办法迫使教义保存该领域所做的更改(或温和地告知它该领域需要保存的更改)?


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

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

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

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?

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

我在代码上解决此问题的方法是使用 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。我在之前的查询中取消设置了所有匹配项,而学说只是没有看到它,因此拒绝更新元素。

我知道

这是一个非常古老的问题,但仍然相关,我想在@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 回调。唯一的小缺点是,即使实体没有更改,您的实体也将始终在刷新时在数据库中更新。