Я использую встроенную форму Symfony для добавления и удаления объектов Tag
прямо из редактора статей. Article
является обладающей стороной ассоциации:
class Article { /** * @ManyToMany(targetEntity="Tags", inversedBy="articles", cascade={"persist"}) */ private $tags; public function addTag(Tag $tags) { if (!$this->tags->contains($tags)) // It is always true. $this->tags[] = $tags; } }
Условие здесь не помогает, так как оно всегда верно, и если бы оно не было, никаких новых тегов не было бы вообще сохранено в базе данных. Вот объект Tag
:
class Tag { /** * @Column(unique=true) */ private $name /** * @ManyToMany(targetEntity="Articles", mappedBy="tags") */ private $articles; public function addArticle(Article $articles) { $this->articles[] = $articles; } }
Я задал $name
уникальным, потому что я хочу использовать один и тот же тег каждый раз, когда я вношу одно и то же имя в форму. Но это не работает, и я получаю исключение:
Нарушение ограничения целостности: 1062 Дубликат записи
Что мне нужно изменить, чтобы использовать article_tag
, таблицу соединений по умолчанию при отправке имени тега, который уже находится в таблице Tag
?
Два основных решения
Используйте трансформатор данных
class TagsTransformer implements DataTransformerInterface { /** * @var ObjectManager */ private $om; /** * @param ObjectManager $om */ public function __construct(ObjectManager $om) { $this->om = $om; } /** * used to give a "form value" */ public function transform($tag) { if (null === $tag) { //do proper actions } return $issue->getName(); } /** * used to give "a db value" */ public function reverseTransform($name) { if (!$name) { //do proper actions } $issue = $this->om ->getRepository('YourBundleName:Tag') ->findOneBy(array('name' => $name)) ; if (null === $name) { //create a new tag } return $tag; } }
Используйте обратный вызов жизненного цикла. В частности, вы можете использовать триггер prePersist
для своей article
? Таким образом, вы можете проверить уже существующие tags
и позволить своему entity manager
управлять ими для вас (поэтому ему не нужно пытаться упорствовать в возникновении ошибок).
Вы можете узнать больше о prePersist здесь
Создайте собственный метод репозитория для поиска и получения старых тегов (если есть)
Я много месяцев борюсь с подобной проблемой и, наконец, нашел решение, которое, похоже, очень хорошо работает в моем приложении. Это сложное приложение с довольно многими ассоциациями «многие-ко-многим», и мне нужно с максимальной эффективностью обрабатывать их.
Решение объясняется здесь частично: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/faq.html#why-do-i-get-exceptions-about-unique- ограничительный отказы-во-эм-флеш
Вы уже были на полпути с кодом:
public function addTag(Tag $tags) { if (!$this->tags->contains($tags)) // It is always true. $this->tags[] = $tags; }
В основном, что я добавил к этому, нужно установить indexedBy = "name" и fetch = "EXTRA_LAZY" на стороне владельца отношения, которое в вашем случае является объектом Article (вам может потребоваться прокрутить блок кода по горизонтали, чтобы увидеть дополнение ) :
class Article { /** * @ManyToMany(targetEntity="Tags", inversedBy="articles", cascade={"persist"}, indexedBy="name" fetch="EXTRA_LAZY") */ private $tags;
Вы можете прочитать о опции fetch = "EXTRA_LAZY" здесь: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/tutorials/extra-lazy-associations.html
Вы можете прочитать информацию о параметре indexBy = «name», выполнив поиск в Google для «Doctrine + indexed association» без кавычек и посмотрите результат, который является частью документов Doctrine (не может размещать другую ссылку, потому что моя репутация недостаточно высока) ,
Затем я изменил свои версии вашего метода 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 coalesce требует PHP7 +.
Установив стратегию выборки для тегов EXTRA_LAZY, следующий оператор заставляет Doctrine выполнять SQL-запрос, чтобы проверить, существует ли тег с тем же именем в БД (см. Ссылку EXTRA_LAZY выше для более):
$this->tags->contains($tags)
ПРИМЕЧАНИЕ. Это может возвращать true только в том случае, если поле PRIMARY KEY объекта, переданного ему, установлено. Doctrine может запрашивать только существующие объекты в карте базы данных / сущности на основе PRIMARY KEY этого объекта при использовании таких методов, как ArrayCollection :: contains () . Если свойство name объекта Tag является только УНИКАЛЬНЫМ КЛЮЧОМ , вероятно, поэтому он всегда возвращает false. Для правильного использования методов, таких как contains (), вам понадобится PRIMARY KEY .
Остальная часть кода в методе addTag () после блока if создает ключ для ArrayCollection из тегов либо значением в свойстве PRIMARY KEY (предпочтительным, если не null), либо хэшем сущности объекта (поиск Google для «PHP + spl_object_hash ", используемый Doctrine для индексирования сущностей). Таким образом, вы создаете индексированную ассоциацию, так что если вы добавите одну и ту же сущность дважды перед флешем, она будет просто добавлена в тот же ключ, но не будет дублироваться.