在普通表单类上使用奏鸣曲字段类型

Posted

技术标签:

【中文标题】在普通表单类上使用奏鸣曲字段类型【英文标题】:Use Sonata Field Type on normal Form Class 【发布时间】:2015-01-03 08:33:02 【问题描述】:

我正在尝试在首页插入自定义奏鸣曲表单字段类型,而不是在 SonataAdmin 中,如下所示:

 $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(); 

但是当我执行它时,它会抛出一个错误:

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

如果 Sonata 表单字段类型是服务,我不明白为什么 Symfony 会抛出该错误。

【问题讨论】:

【参考方案1】:

感谢@gabtzi 的回答,我在 Sonata Admin 的源代码中四处寻找,并提出了一个非常相似的解决方案。假设我们有两个实体MovieGenre,它们之间是多对多关系(Movie 是拥有方),Symfony 4 和 Sonata Admin 3.x 中的解决方案将如下所示:

<?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,
        ));
    

这是一个非常基本的示例,但应该给出如何进一步进行的想法。重要的是:

如果您使用自动装配,则不必将表单类型注册为服务。检查autowire 是否在您的config/services.yaml 中设置为true。更多详细信息请阅读官方documentation;

ContainerInterface传递给构造函数获取容器;

你不再使用sonata_type_model。你必须使用ModelType::class。注意使用说明;

M2M关系可以设置mutipletrue,否则默认为false

您必须将实体类传递给class——在本例中为Movie::class

您可以指定property 以使用Genre 的某些属性。如果您在实体类中定义了__toString 方法,则不必声明这一点。然后会使用这个方法的返回值;

最重要的是:现在你有了容器,获取服务sonata.admin.manager.orm 并将它传递给model_manager。没有这个,一切都会落入水中。

然而,我还没有设法显示按钮 + 添加新。值得一提的是,相关属性的管理类必须存在并且可以访问(设置了适当的权限)——在这种情况下,GenreAdmin 是必需的,否则理论上该按钮甚至无法工作。

【讨论】:

【参考方案2】:

我刚刚遇到了与您相同的问题,但由于这是我在 Google 中遇到的第一个热门问题,因此我只是发布了我为解决该问题所做的工作。 我假设您在控制器中动态创建表单。如果不是,您需要将您的类声明为服务并向其注入sonata.admin.manager.orm 服务。

$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')
    ))
;

之后它为我正确呈现,就像在管理上下文中一样。

【讨论】:

你更喜欢$this-&gt;container-&gt;get而不是$this-&gt;get?假设 ContainerInterface $this-&gt;container 是在构造函数中创建的。 自从我发布这个答案已经有 2 年了,我真的不记得为什么我没有$this-&gt;container-&gt;get 但你可能是对的,所以我会编辑答案。跨度> 是的,这是旧答案,但非常有价值。我只是用一些扩展的解释写了我的版本。功劳归于您,因为您提示了 model_manager 的用途。 看到你的代码我顿悟了。这对我来说是正确的,因为正如我提到的,我在控制器中使用它。当时在我的 symfony 版本上的控制器可以使用$this-&gt;get('service') 通过它的别名直接检索服务。所以这个用例非常有效。阅读您的答案,我可以看到它在 FormType 类中。实际上,您可以在 Type 上注入容器,然后使用$this-&gt;container-&gt;get('service'),但如果我没记错的话,symfony 不鼓励这样做,因为它会产生紧密耦合。他们总是建议只注入需要的服务 是的,我的答案是如何在 FormType 类中使用它。这就是问题的标题。这个解决方案是我现在能想到的最好的解决方案。另一方面,在控制器中创建表单不是 DRY。如果您在其他地方需要它,则必须重复代码,因为它不可重复使用。【参考方案3】:

sonata_type_model 需要知道什么样的实体与你的领域相关。 如果您在管理类中定义它,奏鸣曲使用自己的内部方法来检查关系。 因此,如果您在 admin 之外定义它,则将其设置为 null 而不是 entity

【讨论】:

好的,那我怎么指定类呢?

以上是关于在普通表单类上使用奏鸣曲字段类型的主要内容,如果未能解决你的问题,请参考以下文章

使用更多表单字段扩展奏鸣曲用户包,获取无法加载类型“Application\Sonata\UserBundle\Form\RegistrationType”

使用 Sonata 字段类型在 Controller 中创建表单

多个嵌套集合字段和奏鸣曲类型集合

如何在奏鸣曲中获取字段类型的自定义值?

如何在奏鸣曲编辑表单中显示字段值?

在 XML 之外使用 Joomla 的表单字段类型