Symfony 2.2 と Doctrine を使用してフォーム ジェネレーターを構築しています。基本原則は、ユーザーが名前を入力し、選択メニューで使用したいウィジェットを選択して、新しいフォームを作成することです。
WidgetInputText、WidgetSelect、WidgetFile などが考えられます。
これが私のモデルの例です:
<?php
namespace Ineat\FormGeneratorBundle\Entity\Widget;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\ORM\Mapping as ORM;
/**
* Widget
*
* @ORM\Table(name="widget")
* @ORM\Entity
* @ORM\InheritanceType("SINGLE_TABLE")
* @ORM\DiscriminatorColumn(name="discr", type="string")
* @ORM\DiscriminatorMap({"widget_text" = "WidgetText", "widget_input_text" = "WidgetInputText", "widget_select" = "WidgetSelect"})
*/
abstract class Widget
{
/**
* @var integer
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @ORM\ManyToOne(targetEntity="Form", inversedBy="widgets")
*/
private $form;
/**
* @var integer
*
* @ORM\OneToOne(targetEntity="Question")
*/
private $question;
/**
* Get id
*
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set form
*
* @param \Ineat\FormGeneratorBundle\Entity\Form $form
* @return Widget
*/
public function setForm(\Ineat\FormGeneratorBundle\Entity\Form $form = null)
{
$this->form = $form;
return $this;
}
/**
* Get form
*
* @return \Ineat\FormGeneratorBundle\Entity\Form
*/
public function getForm()
{
return $this->form;
}
/**
* Set question
*
* @param \Ineat\FormGeneratorBundle\Entity\Question $question
* @return Widget
*/
public function setQuestion(\Ineat\FormGeneratorBundle\Entity\Question $question = null)
{
$this->question = $question;
return $this;
}
/**
* Get question
*
* @return \Ineat\FormGeneratorBundle\Entity\Question
*/
public function getQuestion()
{
return $this->question;
}
}
<?php
namespace Ineat\FormGeneratorBundle\Entity\Widget;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\ORM\Mapping as ORM;
/**
* Widget
*
* @ORM\Entity
* @ORM\Table(name="widget_text")
*/
class WidgetText extends Widget
{
/**
* @var string
*
* @ORM\Column(type="text")
*/
private $text;
/**
* Set text
*
* @param string $text
* @return WidgetText
*/
public function setText($text)
{
$this->text = $text;
return $this;
}
/**
* Get text
*
* @return string
*/
public function getText()
{
return $this->text;
}
}
<?php
namespace Ineat\FormGeneratorBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Doctrine\Common\Collections\ArrayCollection;
/**
* Form
*
* @ORM\Table(name="form")
* @ORM\Entity(repositoryClass="Ineat\FormGeneratorBundle\Entity\FormRepository")
* @UniqueEntity("name")
* @UniqueEntity("slug")
*/
class Form
{
/**
* @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;
/**
* @var string
*
* @ORM\Column(name="slug", type="string", length=255)
*/
private $slug;
/**
* @var ArrayCollection
*
* @ORM\OneToMany(targetEntity="Widget", mappedBy="form", cascade={"persist"})
*/
private $widgets;
public function __construct()
{
$this->widgets = new ArrayCollection();
}
/**
* Get id
*
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* @param string $name
* @return Form
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Set slug
*
* @param string $slug
* @return Form
*/
public function setSlug($slug)
{
$this->slug = $slug;
return $this;
}
/**
* Get slug
*
* @return string
*/
public function getSlug()
{
return $this->slug;
}
/**
* Add widgets
*
* @param \Ineat\FormGeneratorBundle\Entity\Widget\Widget $widget
* @return Form
*/
public function addWidget(\Ineat\FormGeneratorBundle\Entity\Widget\Widget $widget)
{
$this->widgets[] = $widget;
return $this;
}
/**
* Remove widgets
*
* @param \Ineat\FormGeneratorBundle\Entity\Widget\Widget $widget
*/
public function removeWidget(\Ineat\FormGeneratorBundle\Entity\Widget\Widget $widget)
{
$this->widgets->removeElement($widget);
}
/**
* Get widgets
*
* @return \Doctrine\Common\Collections\Collection
*/
public function getWidgets()
{
return $this->widgets;
}
/**
* Set widgets
*
* @param \Doctrine\Common\Collections\Collection $widgets
*/
public function setWidget(\Doctrine\Common\Collections\Collection $widgets)
{
$this->widgets = $widgets;
}
public function __set($name, $obj)
{
if (is_a($obj, '\Ineat\FormGeneratorBundle\Entity\Widget\Widget')) {
$this->addWidget($obj);
}
}
}
ご覧のとおり、フォームには複数のウィジェットを関連付けることができます。
すべてのウィジェットに共通のフィールドがあり、Widget 型であるため、抽象クラス Widget を作成しました。また、Form エンティティでは、Widget 型ごとに 1 つのコレクションを持つのは非常に悪いように思われるため (悪くて退屈です)。
このモデルは機能し、単体テストを行い、WidgetText をフォームに添付して取得することができました。
問題は、フォームを使用しようとしたときに発生します。
<?php
namespace Ineat\FormGeneratorBundle\Form;
use Ineat\FormGeneratorBundle\Entity\Widget\WidgetText;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class FormType extends AbstractType
{
protected $widgets;
public function __construct(array $widgets = array())
{
$this->widgets = $widgets;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', 'text')
->add('slug', 'text')
->add('WidgetText', 'collection', array(
'type' => new WidgetTextType(),
'allow_add' => true,
'attr' => array('class' => 'widget-text'),
'by_reference' => false
))
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Ineat\FormGeneratorBundle\Entity\Form',
));
}
public function getName()
{
return 'ineat_formgeneratorbundle_formtype';
}
}
<?php
namespace Ineat\FormGeneratorBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class WidgetTextType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('text', 'text')
;
}
public function getName()
{
return 'ineat_formgeneratorbundle_widgettexttype';
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Ineat\FormGeneratorBundle\Entity\Widget\WidgetText',
));
}
}
フォームを表示しようとすると、次のエラーが発生します。
クラス「Ineat\FormGeneratorBundle\Entity\Form」には、プロパティ「WidgetText」もメソッド「getWidgetText()」もメソッド「isWidgetText()」も存在しません。
私の WidgetText も Widget 型であることを Symfony が認識していないようです。
コントローラー (Symfony によって生成された) の場合、次の行を変更します。
$this->createForm(new FormType(), new Form())
に:
$this->createForm(new FormType())
フォームはうまく表示されますが、送信時にデータがバインドされていません。
OOP の観点からは、これでうまくいくと思いますが、Symfony で自分のやりたいことができるかどうかはわかりません。