FPNTagBundle
FPNTagBundle copied to clipboard
Handle forms
Which is the best practise to handle forms? I have a taggable Entity and i want a field how i can add tags to this entity in a form.
The best way is to use a datatransformer i work on that at the moment
@gimler Any progress on that datatransformer you could share?
:+1:
I looked into writing a "data transformer" class for this bundle, but I don't think this can work. Data transformers only convert one value to another value. Thus, they only get the value of a specific field and do not have a reference to the object itself. However, the way the TagBundle works would require such a reference to the object (for methods such as "addTag($fooTag, $object)", "saveTagging($object)" or "loadTagging($object)".).
I wrote a bundle to handle the "plain text"-to-"tag object" transformation in forms when using the SonataAdminBundle. https://github.com/mweimerskirch/MWTagAdminBundle It's just a prototype though. Depends on #13.
In order to make the transformation work I push the entire object through the transformer instead of just the "tags" field. I'm not sure this is the best solution, but it's the only one I got to work.
I now have an integration of FPNTagBundle with a CRUD form, using a DataTransformer and a custom form widget - a simple text field for now. This code is part of a showcase I prepared for a workshop, so please adapt the namespaces to your own bundle.
The DataTransformer looks like this:
<?php
/**
* @see http://symfony.com/doc/current/cookbook/form/data_transformers.html
*/
namespace FUxCon2013\ProjectsBundle\Form;
use Symfony\Component\Form\DataTransformerInterface;
class TagsTransformer implements DataTransformerInterface
{
private $tagManager;
public function __construct($tagManager)
{ $this->tagManager = $tagManager; }
public function transform($tags)
{ return join(', ', $tags->toArray()); }
public function reverseTransform($tags)
{
return $this->tagManager->loadOrCreateTags(
$this->tagManager->splitTagNames($tags)
);
}
}
With this, I implement a custom widget like so:
<?php
namespace FUxCon2013\ProjectsBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use DoctrineExtensions\Taggable\TagManager;
class TagsType extends AbstractType
{
public function __construct(TagManager $tagManager)
{ $this->tagManager = $tagManager; }
public function buildForm(FormBuilderInterface $builder, array $options)
{
$transformer = new TagsTransformer($this->tagManager);
$builder->addModelTransformer($transformer);
}
public function getParent()
{ return 'text'; }
public function getName()
{ return 'tags_entry'; }
}
Once you have registered this new type in app/config/config.yml:
services:
fuxcon2013.form.tags_entry:
class: FUxCon2013\ProjectsBundle\Form\TagsType
arguments: [ "@fpn_tag.tag_manager" ]
tags:
- { name: form.type, alias: tags_entry }
... you can use it in your edit form:
<?php
namespace FUxCon2013\ProjectsBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class ProjectType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('tags', 'tags_entry');
}
}
Good luck with tagging, Olav
This transformer is ok but you still need to invoke loadTagging($entity) before crating form and saveTagging($entity) after flushing entity. What is more, transformer require setTags method in entity:
public function setTags($tags) {
$this->tags->clear();
foreach($tags as $tag) {
$this->tags->add($tag);
}
}
I'm having a issue here where if I don't update the taggable entity the tags don't get updated. Say my entity has a name. If I leave name the same, but send a new tag with the entity when i persist the entity no update is done so postUpdate isn't run. If I do change name then the entity is updated, postUpdate called and the tags are updated correctly. @Matzz Is this what your setTags function on the entity is for? Where/When should I call it?
What you could do is to listen on the onFlush events to call the method saveTagging on the entity.
You could take a look at the wrapper of this bundle done by fogs : https://github.com/fogs/tagging-bundle and its doctrine event subscriber : https://github.com/fogs/tagging-bundle/blob/master/EventListener/TaggableSubscriber.php
Wish I'd seen your comment earlier @oschettler as I ended up writing exactly the same code, minus names and inconsequential style differences and then coming here to complain that the functionality wasn't part of the bundle already.
@Matzz The transformer itself that @oschettler posted doesn't require the setTags()
method. The entity and form components of symfony require the setTags()
method. @jbouzekri's solution is a great one for saving the tags after flushing automatically. Another idea for functionality that maybe ought to be part of the bundle by default.
@oschettler, your solution may work, but I don't think the data transformer should be responsable of creating new tags. What happens if there are some form errors? You have already persisted those tags.