我有一个带有数组类型字段的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用于存储数组的字段吗?
答案 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回调。唯一的次要缺点是,即使没有更改,您的实体也会始终在数据库中进行刷新。