Doctrine 2 result caching in Symfony with form type entity - forms

I use APC result caching in docrine, and have filter form with type entity in all website pages and want cache this, but when I add useResultCache() to method I get exception
Entities passed to the choice field must be managed
example
...->getQuery()->useResultCache(true, null, 'someindex')->getResult()
but all action without form with entity type work normally.
Any ideas?

Don't know if You've figured out how to do it, but here's how I've done it (spent half a day figuring this out).
/* in FormType.php */
public function buildForm(FormBuilderInterface $builder, array $options)
{
$items = $options['entity_repository']
->findItems()
->useResultCache(true, 3600, 'my_cache')
->getResult();
$choice_list = new ObjectChoiceList($items, 'name', array(), null, 'id');
$builder->add('item', 'entity', array(
'class' => 'MyBundle:Items',
'multiple' => true,
'expanded' => true,
'choice_list' => $choice_list,
));
}

Related

Symfony 4 forms CollectionType: make FileType element required for new rows only

I have an array of images that I want to be able to add to/update/delete from in a Symfony 4 form.
To create a form for these images, I'm using a custom form with a FileType in it:
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('image', FileType::class, array(
'data_class' => null
))
;
}
I am then using a CollectionType filled with instances of the form described above to render a form for each of the images in the array, with 'allow_add' and 'allow_delete' so I can add/remove rows via JavaScript.
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('imagesets', CollectionType::class, array(
'entry_type' => ImageType::class,
'entry_options' => array('label' => false),
'allow_add' => true,
'allow_delete' => true
));
}
This works fine for adding new images, but when updating existing images, the FileType element shouldn't be required, it should only be required for the new rows.
Question: How can I make the FileType NOT required for existing images, yet required for all the new rows?
(Note, I will be passing plain arrays to these form objects, not Doctrine entities.)
You should add an EventListener to your ImageType form and modify the required attribute if the object is not new (or not null). Have in mind that adding the second element with the same name as the previous to the form, replaces it.
$builder
->add('image', FileType::class, array(
'data_class' => null,
'required' => true,
))
;
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
// get the form object
$form = $event->getForm();
// get the entity/object data
$image = $event->getData();
// if it is new, it will be null
if(null !== $image) {
// modify the input
$form->add('image', FileType::class, array(
'data_class' => null,
'required' => false,
))
;
});
}

Symfony: many to many relation with text fields

We have a many to many relation with two entity Product {properties: name, details } and Tag {properties: name}. Now when a user add product, he must be able to create tags as well. So I tried this
$builder->add('tags', 'entity', array(
'class' => 'AppBundle:Tag',
'multiple' => true,
'expanded' => true,
'property' => 'name',
'required' => true
'allow_add' => true ));
Unfortunately, this gives me a drop down list of tags. But what I really want is multiple text input fields where user can enter the name of the tags and then this should save in the database when user save the category form.
All solutions I found so far are all related to either drop down or checkbox choices. But in my case, I don't have a list of tags in the database and I want to create the tags when user create a product. So how can I do that?
The entity type field could be only select, checkboxes or radio buttons (depending on expanded and multiple options), so these are not option for you.
You can solve multiple text fields rendering by implementing Tag type and embed it to your form as a collection.
Alter ProductType by collection field:
...
$builder->add('tags', CollectionType::class, array(
'allow_add' => true,
'allow_delete' => true,
'entry_type' => TagType::class,
'entry_options' => array(
...
),
)
...
Create TagType itself:
class TagType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('tagName', FormType\TextType::class);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Entity\Tag',
)
);
}
}
But be aware of downsides - if there are not any tags assigned to the Product, the won't be any fields rendered. You have basically two options - 1) add few empty tags to the Product entity before rendering the form, 2) implement some kind of javascript method to add new tag fields.

Symfony2 form type nested too many queries

I am developing a webshop system and currently I am working at the admin tools. I've got 4 related entities
Articles
stores main article data (name, description)
ArticleSuppliers
stores variants data (articleNumber, price..)
ArticleAttributesValues
stores attributes for each variant (value e.g. red, 40cm)
ArticleAttributes
stores names of attributes (color, height...)
Since it's much easier to edit a product, I would like to merge the forms together which is working.
ArticlesType binds ArticleSuppliersType binds ArticleAttributesValuesType
My FormType: ArticleAttributesValues contains an entity choice of ArticleAttributes
This is working! But there is a huge problem. I display each variant with their attributes so there is a query for each attribute (imagine a product with 20 variants and 10 attributes).
The solution would be easy: I just need to give an array of attributeNames + id to my FormType, but I do not know how this is done.
I would be grateful for every other solution though.
Thank you in advance!
EDIT:
I will try to explain my problem with code:
// controller
$article = $em->getRepository('MyBundle:Articles')->find($id);
$form = $this->createForm(new ArticleType(), $article);
This is my article type:
// articleType
$builder->add('shortName', 'text',
array('label' => false))
->add('shortDescription', 'text',
array('label' => false))
->add('longDescription', 'textarea',
array('label' => false))
->add('variants', 'collection', array('type' => new VariantsType()))
->add('save', 'submit', array('label' => 'Save'));
This relates to VariantsType:
// variantsType
$builder->add('supplierArticleNumber', 'text',
array('label' => false))
->add('price', 'text',
array('label' => false))
->add('variantvalues', 'collection', array('type' => new VariantsvaluesType()));
This relates to VariantsvaluesType, where my choice field is.
// variantsvaluesType
$builder->add('attributeValue', 'text',
array('label' => false))
->add('attributeUnit', 'text',
array('label' => false, 'required' => false))
->add('attrName', 'entity', array(
'class' => 'MyBundle:ArticleAttributes',
'property' => 'attributeName',
));
This choice field is the same (of course there are changes sometimes), so it would be unnecessary to query it X-times...
My idea was to load all attributeNames in my controller and pass it via $options to variantsvaluesType, but this is not working...
I see, well maybe you can try the next idea. Create a service with a get function for each collection to load, then in the constructor of the service you can load all the list only one time. Then you can use that service wherever you needed. It must work like a singleton. The inconvenience is that all those list should be loaded all the time in memory, but nothing is for free. Will be something like this:
use Doctrine\ORM\EntityManager;
class CollectionsService
{
private $em;
private $collectionOne;
private $collectionTwo;
public function __construct(EntityManager $entityManager)
{
$this->em = $entityManager;
$this->collectionOne= $this->em->getRepository('AppBundle:CollectionOne')->findAll();
$this->collectionTwo= $this->em->getRepository('AppBundle:CollectionTwo')->findAll();
}
public function getCollectionOne(){
return $this->collectionOne;
}
public function getCollectionTwo(){
return $this->collectionTwo;
}
}
Also must work something in the functions like next one , and don't be necessary do the load in the constructor.
public function getCollectionOne(){
if($this->collectionOne == null){
$this->collectionOne= $this->em->getRepository('AppBundle:CollectionOne')->findAll();
}
return $this->collectionOne;
}
Then expose the class as a service in services.yml
parameters:
collection.controller: AppBundle\Services\CollectionsService
services:
collections.service:
class: "%collection.controller%"
arguments:
entityManager: "#doctrine.orm.entity_manager"
And finally just use the service in the controller or the form to update the data $options['data'].
$collectionOne = $this->get('collections.service')->getCollectionOne();
I hope this help you.

Symfony change form collection dropdown with propel model

Is it possible to change the contents of a form dropdown that is part of a form collection that is populated using propel but the data is not mapped. Example of code to get the data below:
AddressType:
public function buildForm(FormBuilderInterface $builder, array $options){
$builder->
->add("addressOne", new addressOneType()),
->add("addressTwo", new addressTwoType(), array(
"required" => false,
)),
}
addressOneType:
public function buildForm(FormBuilderInterface $builder, array $options){
$builder->
->setMethod('POST')
->add('Country', 'model', array(
'mapped' => 'bundle\nameBundle\Model\Countries',
'required' => true,
'multiple' => false,
'expanded' => false,
'property' => 'label',
'query' => CountryQuery::create()->find(),
))
->getForm();
}
This collection is used for a particular part of an application however in this part in need to call a service from the form itself. Would this be possible as I've tried to extend the ContainerInterface and declare this inside of the construct method however this just throws an error.
However, I beleive this to be due to the fact that the form builder is not declared as a service.
Is there an easier way of changing the data of the drop down menu by injecting a new model to override the original. For example:
$form = $this->createForm(new AddressType());
$newData = CountriesQuery::create()
->orderBy("different_field");
$form['collectionName']['fieldname']->setData($newData);
Doing the above doesn't change or override the original model that is changing the data. With or without the ->find() at the end of the $newData field.
Does anyone know of a way to overwrite the data set by the model?
A very simple way for pass specific options to form is in constructor ...
class addressOneType
{
protected $countryQuery;
public function __constructor( $countryQuery = null )
{
$this->countryQuery = $countryQuery;
}
public function buildForm(FormBuilderInterface $builder, array $options){
$query = $this->countryQuery ? $this->countryQuery :
CountryQuery::create();
$builder
->setMethod('POST')
->add('Country', 'model', array(
'mapped' => 'bundle\nameBundle\Model\Countries',
'required' => true,
'multiple' => false,
'expanded' => false,
'property' => 'label',
'query' => $query->find(),
))
->getForm();
}
}
... and you can call to form in this way ...
$cQuery = CountriesQuery::create()->orderBy("different_field");
$form = $this->createForm(new AddressType($cQuery));

Symfony2 form collection -

I am building a gallery of images which must allow for tagging each image with keywords. To handle the tags, I'm using FPN/TagBundle (https://github.com/FabienPennequin/FPNTagBundle).
I've already built the form, using the following:
// UserAlbumImageType.php
...
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('description', null, array('label' => 'Description'))
//TODO: add tags
->add('tags', null, array(
'label' => 'Tags',
'mapped' => false,
'required' => false,
'attr' => array(
'class' => 'tags',
),
))
->add('licenseType', 'entity', array(
'label' => 'License',
'class' => 'VoxCoreBundle:LicenseType',
))
->add('privacyType', null, array('label' => 'Privacy'))
;
}$builder
->add('images', 'collection', array(
'type' => new UserAlbumImageType(),
'label' => false,
))
;
break;
...
// UserAlbumType.php
...
$builder
->add('images', 'collection', array(
'type' => new UserAlbumImageType(),
'label' => false,
))
;
break;
...
As you can see, the tags property is NOT mapped. This is because I don't want to write the tags into a field in the database, but instead persist them to a central tag table. And that's where the problem lies.
When the form is submitted, I'm simply calling $em->persist($userAlbum) which then persists changes to the UserAlbumImage objects in the collection. At this time, I'd like to grab the tags that were submitted via the form, and set them using the tag manager. I'm unsure where to handle this. In a Doctrine postPersist listener? If so, I'll still need to save the tags to the entity at least temporarily, then parse them. Is there a better way?
If I were you, I'd follow (as I'm always trying to do) the MVC pattern with added repositories. I'd implement a saveGallery method in the repository for the gallery entity. This would get called from the controller (similar as mansolux recommended, but instead having the store functionality in the controller (bad practice, if you ask me), call the repository method for it). The method would receive all the submitted data. It would first store all the entities that need to be stored before the tags (gallery, images, whatnot). After that I'd get the tag repository:
$repo = $this->em->getRepository("FPNTagBundle:TagEntityName");
Now, the only thing left to do is to store the tags using this repository. You can add some sanity checks to make sure the tag bundle you're using actually exists, but that's something for you to decide.
Hope it helps.
Why not in your controller :
// ...
$tags = $form->getData()->getTags();
foreach($tags as $tag) {
$em->persist($tag);
}
// ...
$em->flush();