Я работаю над редактором статей в Symfony со встроенной функцией тегирования:
class MainController extends Controller { public function indexAction(Request $request, $id) { $em = $this->getDoctrine()->getManager(); // $article = ... $form = $this->createForm(new ArticleType(), $article); $form->handleRequest($request); if ($form->isValid()) { $em->persist($article); $em->flush(); return $this->redirect($this->generateUrl('acme_edit_success')); } return $this->render('AcmeBundle:Main:index.html.twig', array( 'form' => $form->createView() )); } }
Форма тега регистрируется как служба с аргументом @Doctrine
, поэтому я могу использовать диспетчер сущностей внутри класса. Форма тега внедряется внутри формы статьи.
class ArticleType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('content') ->add('tags', 'collection', array( 'type' => 'acme_bundle_tagtype', 'allow_add' => true, 'allow_delete' => true, 'by_reference' => false )) ->add('save', 'submit') ; } public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( 'data_class' => 'Acme\Bundle\Entity\Article', 'cascade_validation' => true )); } public function getName() { return 'acme_bundle_articletype'; } }
class TagType extends AbstractType { private $entityManager; public function buildForm(FormBuilderInterface $builder, array $options) { $transformer = new TagTransformer($this->entityManager); $builder->add( $builder->create('name') ->addModelTransformer($transformer) ); } function __construct(\Doctrine\Bundle\DoctrineBundle\Registry $doctrine) { $this->entityManager = $doctrine->getManager(); } public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( 'data_class' => 'Acme\Bundle\Entity\Tag' )); } public function getName() { return 'acme_bundle_tagtype'; } }
Я создал этот трансформатор данных, чтобы проверить, существует ли данный тег, а затем преобразовать объект тега в тот, который уже существует в базе данных:
class TagTransformer implements DataTransformerInterface { /** * @var ObjectManager */ private $om; /** * @param ObjectManager $om */ public function __construct(ObjectManager $om) { $this->om = $om; } public function transform($tag) { if (null === $tag) { return ''; } return $tag; } public function reverseTransform($name) { if (!$name) return null; $tag = $this->om ->getRepository('AcmeBundle:Tag') ->findOneByName($name) ; if (!$tag) { $tag = new Tag(); $tag->setName($name); } return $tag; } }
Когда я пытаюсь сохранить статью с уже существующим тегом, reverseTransform()
успешно возвращает исходные объекты тега, но DBAL преобразует объект обратно в строку с помощью метода __toString()
, а Doctrine все еще инициирует запрос INSERT
из UPDATE
, поэтому я получаю следующую ошибку:
Исключение произошло при выполнении «INSERT INTO Tag (имя) VALUES (?)» С параметрами [{}]:
SQLSTATE [23000]: Нарушение ограничения целостности: 1062 Дублирующая запись «Существующий тег» для ключа «UNIQ_0123456789ABCDE»
Как я могу это исправить? Когда я ввожу имя тега, которое уже используется, я хочу, чтобы Symfony использовал один и тот же тег в отношениях между тегами и тегами. Классы сущности появляются в моем предыдущем вопросе о том, как избежать дублирования записей в отношениях «многие ко многим» с «Доктриной» .
В трансформаторе есть одна ошибка. Вместо того, чтобы проверять, является ли имя нулевым, вы должны проверить, был ли возвращен тег:
if (!$tag) { $tag = new Tag(); $tag->setName($name); }
Вам также не нужно сохранять тег, поскольку по умолчанию доктрина будет каскадом сохраняться все связанные объекты.
Полный метод:
public function reverseTransform($name) { if (!$name) { return null; } $tag = $this->om ->getRepository('AcmeBundle:Tag') ->findOneByName($name) ; if (!$tag) { $tag = new Tag(); $tag->setName($name); } return $tag; }