Use Sonata Field Type on normal Form Class

2020-06-22 06:15发布

问题:

I'm trying to insert custom sonata form field type on the front page, not in SonataAdmin, something like this:

 $form = $this->createFormBuilder($content)
            ->add('titleEs', 'text', array('required' => true, 'label' => 'label.title.spanish', 'attr' => array('class' => 'col-xs-12 form-control input-lg')))
            ->add('contentEs', 'ckeditor', array('required' => true,'label' => 'label.content.spanish', 'attr' => array('class' => 'col-xs-12')))
            ->add('titleEn', 'text', array('required' => true,'label' => 'label.title.english', 'attr' => array('class' => 'col-xs-12 form-control input-lg')))
            ->add('contentEn', 'ckeditor', array('required' => true, 'label' => 'label.content.english', 'attr' => array('class' => 'col-xs-12')))
            ->add('header', 'sonata_type_model', array('required' => true,'label' => 'label.content.headerImage'), array('link_parameters' => array('context' => 'content/front', 'size' => 'big')))
            //->add('coverImage', 'sonata_type_model_list', array('required' => true,'label' => 'label.content.coverImage'), array('link_parameters' => array('context' => 'content/front', 'size' => 'small')))
            //->add('sliderImage', 'sonata_type_model_list', array('required' => false,'label' => 'label.content.sliderImage'), array('link_parameters' => array('context' => 'content/slider', 'size' => 'normal')))
            ->getForm(); 

But when I execute that, it throws an error:

Catchable Fatal Error: Argument 1 passed to Sonata\AdminBundle\Form\ChoiceList\ModelChoiceList::__construct() must implement interface Sonata\AdminBundle\Model\ModelManagerInterface, null given

I can't understand why Symfony throws that error, if the Sonata Form Field Types are services.

回答1:

I just bumped into the same issue as you, but since it's the first hit I came across in Google, I'm just posting what I did to work around the issue. I'm assuming you were dynamically creating the form in a controller. If not you would need to declare your class as a service and inject the sonata.admin.manager.orm service to it.

$form = $this->createFormBuilder()
    ->add('<name_of_field>', 'sonata_type_model', array(
        'multiple' => true,
        'class' => <className>::class,
        'property' => '<propertyName>',
        'model_manager' => $this->get('sonata.admin.manager.orm')
    ))
;

After that it rendered correctly for me as it would in admin context.



回答2:

Thanks to @gabtzi's answer I poked around in the source code of Sonata Admin and came up with a very similar solution. Assuming that we have two entities Movie and Genre with a many-to-many relation between them (Movie is the owning side), the solution in Symfony 4 and Sonata Admin 3.x would look like this:

<?php

namespace App\Form\Type;

use App\Entity\Movie;
use App\Entity\Genre;
use Symfony\Component\Form\AbstractType;
use Sonata\AdminBundle\Form\Type\ModelType;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class MovieType extends AbstractType
{
    private $container;

    public function __construct(ContainerInterface $container)
    {
        $this->container = $container;
    }

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            // add fields
            ->add('genres', ModelType::class, [
                'multiple' => true,
                'class' => Genre::class,
                'property' => 'name',  // assuming Genre has property name
                'model_manager' => $this->container->get('sonata.admin.manager.orm'),
                'by_reference' => false
            ])
            // add more fields
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => Movie::class,
        ));
    }
}

This is a very basic example, but should give an idea how to proceed further. Important things are:

  • you don't have to register the form type as a service if you use autowiring. Check that autowire is set to true in your config/services.yaml. Read the official documentation for more detailed information;

  • pass ContainerInterface to the constructor to get the container;

  • you don't use sonata_type_model anymore. You have to use ModelType::class. Pay attention to the use statements;

  • you can set mutiple to true for a M2M relation, otherwise it defaults to false;

  • you have to pass the entity class to class - in this case Movie::class;

  • you can specify property to use certain property of Genre. You don't have to declare this if you have defined __toString method in the entity class. Then the return value of this method will be used;

  • the most important thing: now that you have the container, get the service sonata.admin.manager.orm and pass it to model_manager. Without this everything falls in water.

I haven't however managed to display the button + Add new. It's worth mentioning that admin class for the related property must exist and be accessible (proper permissions set) - in this case GenreAdmin would be required, otherwise the button couldn't even theoretically work.



回答3:

sonata_type_model need to know what kind of entity is related to your field. If you define this in admin class, sonata use own internal method to check the relation. So if you define it outside admin it is put null instead entity