如何强制Doctrine更新数组类型字段?

时间:2012-06-18 13:44:20

标签: php arrays symfony doctrine doctrine-orm

我有一个带有数组类型字段的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用于存储数组的字段吗?

3 个答案:

答案 0 :(得分:22)

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用于存储数组的字段吗?

您无法仅为一个字段设置跟踪政策。

答案 1 :(得分: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();

它还有几行代码,但没有克隆或任何其他类型的魔法。直接告诉学说做一个该死的更新!希望这会有所帮助。

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

答案 2 :(得分:0)

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

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

FAILED (remote: Error calling AvbLoadAndVerifyBootImages Load Error)

在您的控制器中,正常的/** * @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; } } 将触发PreFlush回调。唯一的次要缺点是,即使没有更改,您的实体也会始终在数据库中进行刷新。