Symfony CMF with multiple images on Product entity - symfony

I'm currently trying to make Symfony CMF and Sonata allow the admin user to add multiple images to a product through the same view panel. I have my Product and Image entities setup and working individually with their ORM relationships too.
Product:
class Product extends AbstractEntity
{
/**
* #ORM\OneToMany(targetEntity="Image", mappedBy="product", cascade={"persist"})
*/
protected $images;
// ...
Image:
class Image extends AbstractEntity
{
/**
* #ManyToOne(targetEntity="Product", inversedBy="image", cascade={"persist"})
* #JoinColumn(name="product_id", referencedColumnName="id")
**/
private $product;
// ...
Rather than having the user add all the images first and then link to them, I want it to be achievable from the same view. So in my ProductAdmin class I have added the following:
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->with('Product')
->add('name', 'text', array('required' => true))
// everything else ...
->add('images', 'sonata_type_collection', array(
'type_options' => array('delete' => false),
), array(
'edit' => 'inline',
'inline' => 'table',
))
->end();
}
Currently this allows me to upload images from the product management page. However it does not create a link between the product and image and I'm not sure what I need to do to get it to do that.
UPDATE
Having read countless articles across StackOverflow and the wider web the common response I seem to be seeing is that the relation is only stored when the sonata_type_collection is used for the Admin page of the Entity which holds the relationship (in my case that would be the image). I can understand why it is that way, but it would be much better from a user perspective to work the other way round also (as I'd expect to add images to a product, and not add images and then add a product to them).
I'll leave this question open in case anyone has/does figure out a workaround.
WORKING
I've managed to get it working. Firstly I have changed my relationship to a Many-to-Many (so that images can be re-used for other products as my system contains versions), however I do not believe this is the fix itself, but worth noting none the less.
What I think has made it work is the inclusion of the by_reference attribute:
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->with('Product')
->add('name', 'text', array('required' => true))
// everything else ...
->add('images', 'sonata_type_collection', array(
'by_reference' => false,
'type_options' => array('delete' => false),
), array(
'edit' => 'inline',
'inline' => 'table',
))
->end();
}

So my issue was because I was missing 'by_reference' => false: http://symfony.com/doc/current/reference/forms/types/collection.html#by-reference
Similarly, if you're using the collection form type where your underlying collection data is an object (like with Doctrine's ArrayCollection), then by_reference must be set to false if you need the adder and remover (e.g. addAuthor() and removeAuthor()) to be called.
Or in my case addImage() and removeImage().

Related

Sonata - Use the repository of an entity in admin

I am working on a Symfony project, using Sonata.
Context:
I got different entities:
Product (ID, categories (relation), characteristicValues (relation))
Category (ID, characteristics (relation))
Characteristic (ID, id_category (relation), label).
CharacteristicValue (ID, id_product (relation), id_characteristic (relation), value)
Relations:
Product --OneToMany--> CharacteristicValue
Category -->OneToMany--> Characteristic
Characteristic -->OneToMany--> CharacteristicValue
Product --ManyToMany--> Category
Problem:
I need to get all characteristics of a the categories of a product (and their values if they're set) in the ProductAdmin, and show an input for each of them (like Characteristic1 : value1).
What I did:
I tried to call a function the CharacteristicValueRepository in the ProductAdmin, but the repository was not instantiated.
The code of ProductAdmin is really basic:
final class ProductAdmin extends AbstractAdmin
{
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->with('Product information', ['class' => 'col-md-6'])
->add('name', TextType::class, [
'label' => 'Name of the product'
])
->add('categories', EntityType::class, [
'class' => Category::class,
'choice_label' => 'name',
'multiple' => true,
'label' => 'Categories of the product'
])
->end();
}
protected function configureDatagridFilters(DatagridMapper $datagridMapper)
{
$datagridMapper->add('name');
$datagridMapper->add('categories');
}
protected function configureListFields(ListMapper $listMapper)
{
$listMapper->add('id');
$listMapper->addIdentifier('name');
$listMapper->addIdentifier('categories');
}
}
Notes:
I am using the last version of everything (Symfony, Sonata, ...)
If someone knows how to help me, I would be really grateful!
You need to configure custom form type for example ProductCharacteristicsType. While using Form Event listeners you could fetch all characteristics and form a proper collection. What you have here is EAV (Entity attribute value) model. It may cause confusion for Symfony, but it is manageable. On SonataAdmin you must use that custom type of yours.

SonataAdmin sonata_type_model with lot of rows throw OutOfMemoryException

I have a simple one to many relation Listing -> Booking with many thousands listings.
When i add the following SonataAdmin class :
class BookingAdmin extends Admin
{
...
$formMapper
->add(
'listing',
null,
array(
'disabled' => true,
)
),
...
An OutOfMemoryException is thrown due to the lot of numbers of listings.
I would like to know how to avoid this error by displaying the listing title in the form without using the choice list.
You could use a 'sonata_type_model_autocomplete' form type for these cases (Ref.):
class BookingAdmin extends AbstractAdmin
{
protected function configureFormFields(FormMapper $formMapper)
{
// the dropdown autocomplete list will show only Booking
// entities that contain specified text in "title" attribute
$formMapper->add('listing', 'sonata_type_model_autocomplete', array(
'property' => 'title'
));
}
}
This one avoids to query all rows to populate the widget.
I found an another solution than Yonel one.
In this solution we only get the current Listing of the persisted Booking entity in the choice widget. It is only useful if the Listing must not be changed.
class BookingAdmin extends Admin
{
...
protected function configureFormFields(FormMapper $formMapper)
{
$listing= $this->getSubject();
$formMapper
->add(
'listing',
'sonata_type_model',
array(
'query' => $this->modelManager
->getEntityManager('Bundle:Listing')
->getRepository('Bundle:Listing')
->find(
$listing->getId()
),
'disabled' => true,
)
);
...

Symfony2 Sonata Admin IvoryCKEditor wrong render for some reason

I am trying to implement IvoryCKEditor Bundle to my SonataAdmin entities and I am witnessing some very strange errors/bugs/mistakes... I dont even know..
So when I want to render a simple textarea field and add some longtext to it I simply do something like this:
/**
* #param FormMapper $formMapper
*/
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
// ->add('id', 'hidden')
->add('name')
->add('contentEn', 'ckeditor', array(
'attr' => array('cols' => '8', 'rows' => '8')))
->add('contentEs', 'ckeditor', array(
'attr' => array('cols' => '8', 'rows' => '8')))
->add('status')
;
}
This works like a charm.. However if I have mapped entities and I want to show its fields I use sonata collection:
->add('translations', 'sonata_type_collection',
array(
'required' => false,
'label' => false,
),
array(
'edit' => 'inline',
'inline' => 'standard',
)
)
And in the mapped entities admin I do this again:
$formMapper
->with('Item Info')
//->add('id')
->add('product_name')
->add('description_for_lbi', 'ckeditor')
->add('short_description', 'ckeditor')
->add('long_description', 'ckeditor')
->add('conditions', 'ckeditor')
->add('language', null, array('required' => true))
->end()
;
Now here is the problem. Its seems the ckeditor form the collection is rendering in a completely different way.
The first example renders an iframe and makes the ckeditor look "clean".
In the collection ckeditor is rendering in a completely different way, no iframes.. And for the editor to show up I have to click on the field first.. And the field has no borders... I really dont know how to explain this.
So I guess my question is, why the ckeditor is rendering completely different when I am using it in a collection. Am I doing something wrong?
If you guys dont understand what I mean I can post some screens...
UPDATE
I thing the problem is here:
'edit' => 'inline',
'inline' => 'standard',
this makes the editor look bad. However if I remove these lines I get error:
Error: Maximum function nesting level of '100' reached, aborting!
This error is when I am trying to edit an object
You are completely right with your edit. inline editing in ckeditor breaks if you have 2 or more instances of the ckeditor config.
My suggestion is to drop inline editing.
For the second part, the error comes from xdebug, which cannot follow your object nesting level because it exceeds this limit.
In order to fix this (which is recommended, as limit = 100 is way too low for symfony2), please see this
article.

Use sonata_type_collection inside custom type

What I want to do is add sonata_type_collection to my custom formType.
Normal way is add sonata_collection_type to $formMaper inside AdminClass like:
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper->add('elements, 'sonata_type_collection', array(
'some_options' => 'options'
))
}
It work perfect, but i have my custom form type, and when i defined it like:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$formMapper->add('elements, 'sonata_type_collection', array(
'some_options' => 'options'
))
}
It doesn't work (it appear only label of filed). Problem is wrong template, so I tried to set formAdminTemplate
I made it by set template in view
{% form_theme formElement 'SonataDoctrineORMAdminBundle:Form:form_admin_fields.html.twig' %}
Problem is sonata_admin variable inside this 'formTheme'. This variable doesn't exist in my form.
Of course my form type is related to admin class but i don't know how could I I tell symfony about this relation
You need an admin class for your collection child :
$formMapper->add('customizations', 'sonata_type_collection',
array(
'required' => true,
'type_options' => array('delete' => true),
'by_reference' => false,
'mapped' => true
),
array(
'edit' => 'inline',
'inline' => 'table',
'sortable' => 'position',
'targetEntity' => '/path/to/Entity/Customization',
'admin_code' => 'my.service.customization_admin'
)
);
I find solution. Instead using my custom type, I defined form using admin class. I need this form outside admin so it was little difficult.
First of all in my controller i get admin class from service. Inside admin class I override 3 methods which are use to create form
public function getFormBuilder()
public function defineFormBuilder(FormBuilder $formBuilder)
public function buildForm()
then i had to save my entity by sonata admin way. using create method instead handleRequest.

How to properly configure 'sonata_type_collection' field in Sonata Admin

In a nutshell:
When I am using 'sonata_type_collection' in OneToMany relationship I have to specify the other side of the relation, which in the "create action" still does not exist and in "update action" could be set, but it is also possible to specify entirely different parent.
More detailed explanation:
I am using Sonata Admin Bundle for the CRUD operations and lets say that I have only Post(id, title, content) and Tag(id, post_id, title) entities.
I would like to be able to add and remove tag entities while I am editing the Post entity, so I use 'sonata_type_collection' field.
This is the configureFormFields method from the PostAdmin class:
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('title')
->add('content')
->add('tags', 'sonata_type_collection', array(), array(
'edit' => 'inline',
'inline' => 'table'
))
))
;
}
The problem is that in the create form, when I add new tag I have to specify both post and title, but the Post still does not exist, so I am not able to add tags.
While I am editing the post I could add new tags, but for every one of them I have to explicitly set a post, and I am able for example to add a tag for entirely different post.
Could you tell me how to solve this problem?
You might want to set the by_reference option to false.
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('title')
->add('content')
->add('tags', \Sonata\CoreBundle\Form\Type\CollectionType::class,
array('by_reference' => false),
array('edit' => 'inline',
'inline' => 'table'
)
);
}
[edit]
So it looks like the problem was coming from the Post entity which had to call tags' setPost() method from the addTag() method.
public function addTag($tag)
{
$tag->setPost($this);
$this->tags->add($tag);
return $this;
}

Resources