【问题标题】:Symfony2 dependent dropdown ManyToManySymfony2 依赖下拉多对多
【发布时间】:2014-04-12 09:18:00
【问题描述】:

我有 3 个实体:公司、行业、类别 我想创建一个表单,用户可以在其中输入公司名称,然后从下拉列表中选择行业。每个行业都有类别。当用户选择一个行业时,我想填充类别列表。我读过以下文章:http://symfony.com/doc/current/cookbook/form/dynamic_form_modification.html#cookbook-form-events-submitted-data

我已经创建了表单,但是当触发 ajax 调用时出现以下错误:

Neither the property "categories" nor one of the methods "setCategories()", "__set()" or "__call()" exist and have public access in class "ebulucu\MainBundle\Entity\Company"

我现在已经很多天了,只是无法让它工作。我希望有人能给我一些提示。我需要一个带有公司名称输入字段的表单,两个下拉列表行业和类别,其中类别取决于所选行业。 Company 与 Category 有 ManyToMany 关系, Industry 也有 OneToMany 关系。到目前为止,这是我的代码:

编辑: 我已经尝试过使用 OneToMany 而不是 Company 和 Category 之间的 ManyToMany 关系的代码。 这很好用。但是万一ManyToMany Relation怎么办?如何管理加载和设置类别?

我的 3 个实体:

class Company
{
/**
 * @var integer
 * @ORM\Column(name="id", type="integer")
 * @ORM\Id
 * @ORM\GeneratedValue(strategy="AUTO")
 */
private $id;

/**
 * @var string
 *
 * @ORM\Column(name="name", type="string", length=255)
 */
private $name;


/**
 * @ORM\ManyToMany(targetEntity="Category", mappedBy="companies")
 */
private $categories;

/**
 * Constructor
 */
public function __construct()
{
    $this->categories = new \Doctrine\Common\Collections\ArrayCollection();
}

... setters and getters

class Industry
{
/**
 * @var integer
 *
 * @ORM\Column(name="id", type="integer")
 * @ORM\Id
 * @ORM\GeneratedValue(strategy="AUTO")
 */
private $id;

/**
 * @var string
 *
 * @ORM\Column(name="name", type="string", length=80)
 * @Assert\NotBlank()
 */
private $name;

/**
 * @var
 *
 * @ORM\OneToMany(targetEntity="Category",mappedBy="industry")
 */
private $categories;

public function __construct()
{
    $this->categories = new ArrayCollection();
}

...setters and getters

class Category
{
/**
 * @var integer
 *
 * @ORM\Column(name="id", type="integer")
 * @ORM\Id
 * @ORM\GeneratedValue(strategy="AUTO")
 */
private $id;

/**
 * @var string
 *
 * @ORM\Column(name="name", type="string", length=80)
 * @Assert\NotBlank()
 */
private $name;

/**
 * @ORM\ManyToOne(targetEntity="Industry", inversedBy="categories");
 * @ORM\JoinColumn(name="industry_id", referencedColumnName="id",nullable=false)
 */
private $industry;

/**
 * @ORM\ManyToMany(targetEntity="Company", inversedBy="categories");
 * @ORM\JoinTable(name="categories_companies")
 */
private $companies;

... setters and getters

我的公司表格类:

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use ebulucu\MainBundle\Entity\Industry;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormInterface;
use Doctrine\ORM\EntityRepository;

class CompanyRegistrationFormType extends AbstractType
{

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder->add('name');
    $builder->add('industry', 'entity', array(
        'mapped'    => false,
        'class' => 'ebulucuMainBundle:Industry',
        'property' => 'name',
        'empty_value' => 'Choose industry',
    ));

    $formModifier = function(FormInterface $form, $industry_id) {

        if($industry_id) {
            $form->add('categories', 'entity', array(
                    'class' => 'ebulucuMainBundle:Category',
                    'query_builder' => function(EntityRepository $er) use ($industry_id) {
                            $query = $er->createQueryBuilder('i')
                                ->select(array('i'))
                                ->where('i.industry_id = :industry_id')
                                ->setParameter('industry_id', $industry_id)
                                ->orderBy('i.name', 'ASC');

                            return $query;
                        },
                    'empty_value' => 'Choose category'
                )
            );
        }
    };

    $builder->addEventListener(
        FormEvents::PRE_SET_DATA,
        function(FormEvent $event) use ($formModifier) {
            $formModifier($event->getForm(), null);
        }
    );

    //** Checks for Industry that is submitted and adds categories based on industry selection **//
    $builder->get('industry')->addEventListener(
        FormEvents::POST_SUBMIT, function(FormEvent $event) use ($formModifier) {
            $industry_id = $event->getData();
            $formModifier($event->getForm()->getParent(), $industry_id);
        }
    );
}

public function setDefaultOptions(OptionsResolverInterface $resolver)
{
    $resolver->setDefaults(array(
            'data_class' => 'ebulucu\MainBundle\Entity\Company',
        ));
}

public function getName()
{
    return 'company';
}

}

控制器:

use ebulucu\MainBundle\Entity\Company;
use ebulucu\MainBundle\Form\Type\CompanyRegistrationFormType;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use ebulucu\MainBundle\Entity\Industry;
use ebulucu\MainBundle\Entity\Category;
use Symfony\Component\HttpFoundation\Request;

class MainController extends Controller
{

 /**
 * @Route("/", name="homepage")
 * @Template()
 */
public function indexAction(Request $request)
{
    $company = new Company();
    $form = $this->createForm(new CompanyRegistrationFormType(), $company);

    $form->handleRequest($request);

    if ($form->isValid()) {
        return $this->redirect($this->generateUrl('fos_user_security_login'));
    }

    return array(
        'form' => $form->createView(),
    );
}

/**
 * @Route("/", name="loadIndustryCategories")
 * @Template()
 */
public function loadIndustryCategories(Request $request)
{
    $company = new Company();
    $form = $this->createForm(new CompanyRegistrationFormType(), $company);
    $form->handleRequest($request);

    return array(
        'form' => $form->createView(),
    );
}

}

带有表单和ajax调用的Twig模板:

{% block content %}
<div>Homepage</div>
{{ form_start(form, {'attr': {'id': 'form_industry'}}) }}
{{ form_end(form) }}
{% endblock %}


{% block js%}
<script>
$('#company_industry').change( function() {

    var postData = $("#form_industry").serializeArray();
    var formURL = {{ path('loadIndustryCategories') }};
    $.ajax(
            {
                url : formURL,
                type: "POST",
                data : postData,
                success:function(data, textStatus, jqXHR)
                {
                    //data: return data from server
                },
                error: function(jqXHR, textStatus, errorThrown)
                {
                    //if fails
                }
            });
    e.preventDefault(); //STOP default action
    e.unbind(); //unbind. to stop multiple form submit.

});
</script>
{% endblock %}

【问题讨论】:

  • 您的公司实体中有 setCategories 方法吗?
  • 不,我有 add-、remove- 和 getCategories
  • 您的公司实体中需要一个 setCategories 函数。表单处理程序基本上会为您编译集合,然后尝试将其分配给实体字段。
  • 现在我已将公司和类别之间的关系从 ManyToMany 更改为 OneToMany,并且代码确实有效。但是我如何使用 ManyToMany Relation 和 arrayCollection 来管理呢?

标签: ajax forms symfony dynamic


【解决方案1】:

Company:categories 将是实体的集合,而不是单个实体,因此它不应该有 setCategory 方法,否则您可能会无意中删除整个集合。相反,您将拥有addCategoriesremoveCategoriesgetCategories(如果学说由于其“非复数化”而生成,则可能是 addCategorieremoveCategorie)。

要解决此问题,您需要将categories 表单元素更改为collection 类型,而不是CompanyRegistrationFormType 中的entity

只是检查一下,一家公司应该有一个类别还是多个类别?目前,您的代码似乎有点两者兼而有之?

【讨论】:

  • 公司应该有多个分类。但是在这个表格上应该可以只分配一个类别。
  • 这听起来有点不稳定。设置此类别会删除所有当前关联,还是将其添加到当前集合中?
  • 这就是问题所在,当我使用公司和类别之间的多对多关系时,我不知道如何设置类别。使用集合类型时如何填充类别?当我更改与 OneToMany 的关系时,上面的代码可以正常工作。
  • 对不起,我的意思是,如果用户在这个表单中设置了一个类别,这会清除公司已经存在的所有类别,还是添加新的类别?我认为从 UI 的角度来看这会令人困惑。要在使用集合类型时填充类别,您需要遵循this 页面的Adding and Removing items 部分
  • 此表格适用于新公司,您可以在其中添加您的第一个类别。
猜你喜欢
  • 2014-07-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多