Sonataadminbundle: Symfony 2.6.13 Sonata-Admin sonata_type_collection is breaking form

Created on 13 Mar 2016  Â·  59Comments  Â·  Source: sonata-project/SonataAdminBundle

I'm using sonata_type_collection to create multiple inline photos for page:

$formMapper
->add('title', 'text', array('label' => 'Page title'))
->add('images','sonata_type_collection', array('label' => 'Images'), array(
   'edit' => 'inline',
   'inline' => 'table',
))

When I'm saving form I have the following error:

Extra fields are not allowed

Probably, because this is OneToMany relationship

/**
* @ORM\OneToMany(targetEntity="(..)\Image", mappedBy="page", cascade={"all"}, orphanRemoval=true)
*/
protected $images;

Doctrine doesn't understand that something's changed and therefore it doesn't call prePersist/preUpdate.

public function preUpdate($product)
{
    die("Here"); // Doesn't die here.
    $product->setPhotos($product->getPhotos());
}

Maybe this is a reason? If so, how can I fix that?

When I'm trying to save form without any photos attached, it works flawlessly.

All 59 comments

preUpdate is supposed to be called after validation, but only if validation passes, which is not the case. Look at the profiler, in the form tab. You might get more details about which field is the extra field exactly.

@greg0ire, thanks. Though preValidate is not working either. I tried postUpdate, but still no luck. I'm trying to fix my inline photo list for product admin. Maybe I'm doing something wrong? Still have "Extra fields are not allowed" error.

What type of event is preValidate? Not doctrine, because validation is done with Symfony.

I found these events right here — https://sonata-project.org/bundles/admin/master/doc/reference/saving_hooks.html

When a SonataAdmin is submitted for processing, there are some events called. One is before any persistence layer interaction and the other is afterward. Also between submitting and validating for edit and create actions preValidate event called. The events are named as follows:

new object : preValidate($object) / prePersist($object) / postPersist($object)
edited object : preValidate($object) / preUpdate($object) / postUpdate($object)
deleted object : preRemove($object) / postRemove($object)

Okay, they're Sonata events, not doctrine events.

Is preValidate called when you save without photos?

@greg0ire, no, not really. I this is how it look like:

    public function preValidate($product)
    {
        die('x');
    }   

But again nothing happens and I'm returned back to admin page with same error.

How can you get the "same error" if you save without photos? I thought it was flawless…

Oh, sorry, I read you wrong! Yes, I just checked, now it even goes to preUpdate when I'm saving product without photo and doesn't go to preValidate anyway.

So it works fine without photos and actually calls preUpdate, but skips everything and gives "Extra fields are not allowed" when new photos are added.

Looks like this feature was added in 1e6bc7d97f401f0d4b543566f4bee1f1384dbc0e, which is the last commit on the branch, so I think you simply do not have this commit.

Look at the profiler, in the form tab. You might get more details about which field is the extra field exactly.

Ok, so it's still image field:

Origin: image
Cause: Object(Symfony\Component\Form\Form).children[images].children[0].children[image] = [file => Object(Symfony\Component\HttpFoundation\File\UploadedFile)]

Should I handle this separately? sonata_type_collection should be handled automatically as far as I understand.

I think it should work too. Apparently, the form you are binding your data to does not have the good structure. Does the structure you see in the profiler at least look like it a bit (is the title field here ?). I think you should debug the CRUDController, try to see why the form is not the same when you GET and when you POST.

Yes, it's absolutely the same, actually.

Here are form fields:

s56e6c34f73ae2 [form]
title [text]
featured [checkbox]
sort_order [number]
shortDescription [text]
description [textarea]
cost [number]
lab [entity]
category [entity]
images [sonata_type_collection]
_token [hidden]

and here is information passed to form in POST:

[title => '...', featured => 1, sort_order => 7, shortDescription => '...', description => '...', cost => '...', lab => '...', category => '...', _token => '...']

Though there are no images field, which seems to be an array in form debugger.

Also images field doesn't exist in table, because it's OneToMany connection:

    /**
    * @ORM\OneToMany(targetEntity="Izone\Bundle\ContentBundle\Entity\Image", mappedBy="product", cascade={"all"}, orphanRemoval=true)
    */
    protected $images; 

Also images field doesn't exist in table, because it's OneToMany connection:

Not a problem.

Maybe the error is not about images, but rather about images[0].image or images[0]

Well, yes, but it happens somewhere under the hood. I don't have any calls like that in my code.

@meesix This is typically the kind of scenario in which I end up using a debugger...

@meesix For example put a breakpoint somewhere in CRUDController::editAction and start stepping

@ju1ius well...this is something that I'm doing for a couple of days now. But I thought that people here know how SonataAdmin works and can suggest something.

Now I see that form doesn't pass validation, but even if I will comment out validation, it will save data... except for images.

this is something that I'm doing for a couple of days now.

@meesix Wow, strange... This kind of stuff with prePersist+associations happened to me quite a few times, but I always got something out of a few debugging sessions.

Have you tried setting 'cascade_validation' => true ?

$formMapper->add('images','sonata_type_collection', array(
   'label' => 'Images',
   'required' => false,
   'cascade_validation' => true
), array(
   'edit' => 'inline',
   'inline' => 'table',
))

Don't know if this is related, or if it helps at all, but for the to many associations, I've taken the habit of writing setters like this

public function setImages($images)
{
  $this->images = new ArrayCollection();
  foreach($images as $image) {
    $this->addImage($image);
  }
}

public function addImage(Image $image) {
  $image->setProduct($this);
  $this->images->add($image);
}

Can't exactly remember why, but I think that in some cases, starting from a fresh ArrayCollection helps Doctrine being aware of the change set ...
You still have to do the

public function prePersist($product)
{
  $product->setImages($product->getImages());
}

dance in the admin class though.

@ju1ius thanks. I have same setters, but when seems like this code is not executed as well. Somehow even __construct seems to be not executed.

Have you tried setting 'cascade_validation' => true ?

even __construct seems to be not executed.

Doctrine never calls your constructors.

oh, thanks. I'm new in Symfony.
cascade_validation, unfortunately, did nothing.

Problem is that it actually worked, but at some point it broke down. Probably because of upgrade.

After an upgrade ? app/console cache:clear ? No, that would be silly...

Unfortunately it's more complicated :)

At that point a gist would be useful

If you think it is after an upgrade, you could try pin pointing the faulty commit with git bisect

Also cache:clear has proven to be not so useless w/ sonata (to me at least)

I think the error message indicates validation does cascade

@greg0ire well, I went to CRUDController, commented out validation

$isFormValid = TRUE || $form->isValid();

And then form was saved without any problems, but no images were saved at all!

And here is gist with entity and admin class: https://gist.github.com/meesix/7d764efadaea754e7db6

$isFormValid = TRUE

did you mean == ?

@greg0ire no, it is not comparison, but assignment. Comparison goes next, but for test reasons I wanted $isFormValid to be always True.

Ok, so the || afterwards is just a way of commenting the rest of the statement?

Yes, I wanted to have old statement so I won't loose it by mistake.

What does the Image entity look like ?

It is pretty straightforward https://gist.github.com/meesix/7d764efadaea754e7db6#file-gistfile3-txt

Does it work without the @FileStore\UploadableField(mapping="image") mapping ?

I mean if the image field is just a string, are the entities saved ?

I will have to re-migrate entity and lose data from database. Maybe there is easier way to test that?

Don't know. Can't you get by with just removing this particular annotation ?
If not consider making a new symfony project with the minimal things needed to reproduce the issue.

Can you post your ImageAdmin class ?

When I remove annotation there is empty div in sonata admin inline :(

Here we go:

``` namespace Izone\Bundle\ContentBundle\Admin;

use Sonata\AdminBundle\Admin\Admin;
use Sonata\AdminBundleDatagridListMapper;
use Sonata\AdminBundleDatagridDatagridMapper;
use Sonata\AdminBundleFormFormMapper;

class ImageAdmin extends Admin
{
// Fields to be shown on create/edit forms
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('image', 'iphp_file')
;
}

// Fields to be shown on filter forms
protected function configureDatagridFilters(DatagridMapper $datagridMapper)
{
    $datagridMapper
        // ->add('image')
    ;
}

// Fields to be shown on lists
protected function configureListFields(ListMapper $listMapper)
{
    $listMapper
        ->addIdentifier('image')
    ;
}

}
```

@meesix The right way to color code a block is ```php
And naming file *.php in gist

Yeah, sorry, it's kinda too late :) Fixed that.

When I remove annotation there is empty div in sonata admin inline :(

But does this remove the error when saving ?

There doesn't seem to be anything wrong with your association related code, so it might well be that the 'iphp_file' form type is somehow causing the offending "extra fields".
Maybe a faulty data transformer or sthg like that, can't tell.

@meesix Maybe save yourself some trouble and don't use a bundle that hasn't seen a commit for 4 years...

@ju1ius yeah, this is the best idea. I wish I heard this advice before :)
Anyway, thanks for your suggestions. I'll keep on digging, maybe to iphp_file direction.

You should have a look at StofDoctrineExtensionsBundle. There's an uploadable extension that works pretty well.

Thanks, I'll try it out.

Or the SonataMediaBundle, of course.

Yeah, I guess I need something easily configurable to work with Sonata-Admin.

Yeah, I guess I need something easily configurable to work with Sonata-Admin.

Although you will have to write some code to make it store files in db if that's what you want.

No, I'm saving files into filesystem.

@meesix If it fixes your issue, please say it so the issue can be closed.

@ju1ius yes, I ended up using SonataMediaBundle and it fixed my issue. Thank you for help!

Was this page helpful?
0 / 5 - 0 ratings