Я боролся с подобной проблемой в течение нескольких месяцев и, наконец, нашел решение, которое, похоже, очень хорошо работает в моем приложении. Это сложное приложение с довольно большим количеством ассоциаций «многие ко многим», и мне нужно обрабатывать их с максимальной эффективностью.
Решение частично объясняется здесь: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/faq.html#why-do-i-get-exceptions-about-unique-constraint-failures-during-em-flush
Вы уже были на полпути со своим кодом:
public function addTag(Tag $tags)
{
if (!$this->tags->contains($tags)) // It is always true.
$this->tags[] = $tags;
}
По сути, я добавил к этому установку indexedBy=name и fetch=EXTRA_LAZY на стороне-владельце отношений, которой в вашем случае является Article. strong> сущность (вам может потребоваться прокрутить блок кода по горизонтали, чтобы увидеть добавление):
class Article
{
/**
* @ManyToMany(targetEntity="Tags", inversedBy="articles", cascade={"persist"}, indexedBy="name" fetch="EXTRA_LAZY")
*/
private $tags;
Вы можете прочитать о fetch=EXTRA_LAZY здесь.
Вы можете прочитать о опции indexBy=name здесь.
Затем я изменил свои версии вашего метода addTag() следующим образом:
public function addTag(Tag $tags)
{
// Check for an existing entity in the DB based on the given
// entity's PRIMARY KEY property value
if ($this->tags->contains($tags)) {
return $this; // or just return;
}
// This prevents adding duplicates of new tags that aren't in the
// DB already.
$tagKey = $tag->getName() ?? $tag->getHash();
$this->tags[$tagKey] = $tags;
}
ПРИМЕЧАНИЕ. Для оператора объединения ?? null требуется PHP7+.
Установив стратегию выборки для тегов на EXTRA_LAZY, следующий оператор заставляет Doctrine выполнить запрос SQL, чтобы проверить, существует ли тег с таким же именем в БД (см. EXTRA_LAZY ссылка выше для получения дополнительной информации):
$this->tags->contains($tags)
ПРИМЕЧАНИЕ. Это может вернуть true, только если установлено поле PRIMARY KEY объекта, переданного ему. Doctrine может запрашивать существующие объекты в базе данных/карте объектов только на основе ПЕРВИЧНОГО КЛЮЧА этого объекта при использовании таких методов, как ArrayCollection::contains(). Если свойство name объекта Tag является всего лишь UNIQUE KEY, возможно, именно поэтому оно всегда возвращает значение false. Вам понадобится PRIMARY KEY, чтобы эффективно использовать такие методы, как contains().
Остальная часть кода в методе addTag() после того, как блок if создает ключ для коллекции ArrayCollection тегов либо по значению в свойстве PRIMARY KEY (предпочтительно, если не null) или по хэшу сущности Tag (найдите в Google PHP + spl_object_hash, используемый Doctrine для индексации сущностей). Итак, вы создаете индексированную ассоциацию, так что если вы добавите один и тот же объект дважды перед сбросом, он будет просто повторно добавлен с тем же ключом, но не дублирован.
person
garethlawson
schedule
11.01.2017