我认为这个问题应该比较容易解决,但我无法在任何地方找到答案。我正在使用Symfony 2.3,我有两个相互依赖的类,我们分别称它们为Parent
和Child
类。
Child
的生命周期取决于存在Parent
,所以在构造函数中我声明:
public function __construct(Parent $parent, $foo, $bar)
{
$this->parent = $parent;
$this->foo = $foo;
$this->bar = $baz;
}
以这种方式,我使用工厂方法模式,以便在给定两个类的依赖性的情况下创建Child
。在Parent
我有这个方法:
public function createChild($foo, $bar)
{
$c = new Child($this, $foo, $bar);
$this->addChildren($c); //The children are stored in a Doctrine's ArrayCollection
return $c;
}
现在我正在创建表单以创建Parent
和几个Child
。 ParentType
有:
class ParentType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('baz', 'text') //Merely a property of Parent
->add('children', 'collection', array(
'type' => 'childrentype',
'allow_add' => true,
'label' => false
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'My\Bundle\Parent',
'empty_data' => function (FormInterface $form) {
return new \My\Bundle\Parent(
$form->get('baz')->getData()
);
}
));
}
//...
}
而且ChildType
有:
class ChildType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('foo', 'text', array('label' => false)
)->add('bar', 'text', array('label' => false));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'My\Bundle\Child',
'empty_data' => function (FormInterface $form) {
return $form->getParent()->getData()->createChild(
$form->get('foo')->getData(),
$form->get('bar')->getData()
);
}
));
}
//...
}
理论上这应该有效,但我有以下问题:
addChildren
和removeChildren
。没什么大不了的,我做到了。FatalErrorException:错误:在(route)\ ChildType.php第22行中的非对象上调用成员函数createChild()
有什么我做错了或者我应该考虑到为了不破坏正确的OOP编程并使表单有效吗?谢谢。
答案 0 :(得分:1)
让我们首先回顾一下设计模式。 SourceMaking has a good article on this,但我会写一个摘要。简而言之,Factory Method设计模式允许我们在父类中创建一个方法来创建另一个类的对象,并且可以扩展和覆盖,因此子类可以有自己的实现。这是创建模式,因此处理创建对象。因此,工厂方法允许我们为任何意图封装对象的创建。
在我的问题中,我对Factory Method的使用来自事实Child
的生命周期紧紧依赖于Parent
。这是,没有Parent
,没有Child
,Child
无法独立于父级。因此,Parent
有一个工厂方法createChild
,它将创建一个立即绑定到它的Child
。此外,Child
没有setParent
,以免破坏正确的数据封装。
在示例中,无论何时或如何调用getParent
,我在尝试null
时都会getData
。这是因为在SUBMIT
之前没有数据,此时我无法添加更多表单字段。这也是我无法在任何时候取得任何成果的原因。
让我们来看看Child
的构造函数方法。
public function __construct(Parent $parent, $foo, $bar)
{
$this->parent = $parent;
$this->foo = $foo;
$this->bar = $baz;
}
正如您所看到的,__construct
具有紧密耦合(反模式,我知道,但业务规则要求我实际强制执行,因此我不能忽略这一点)Parent
。所以,让我们给它它所要求的东西 - 我们稍后会解决这个问题。
在ChildType
:
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'My\Bundle\Child',
'empty_data' => function (FormInterface $form) {
return new My\Bundle\Child(
new My\Bundle\Parent(null),
$form->get('foo')->getData(),
$form->get('bar')->getData()
);
}
));
}
不,我不是在开玩笑。我基本上是在为我们正在构建的Parent
设置一个完全不相关的Child
。不用担心,我们很快就会处理这个问题。
当我在一个集合中工作时,Symfony表单强迫我在addChildren
中声明removeChildren
和Parent
。 removeChildren
很容易实现,但存在一个问题:我无法addChildren
,因为我无法在Parent
中设置Child
,将其与Parent
相结合{1}}创造了它!所以,我在一个小工作中完成了整个魔术!
在Parent
:
public function createChild($foo, $bar)
{
$child = new Child($this, $foo, $bar);
$this->getChildren()->add($child);
return $child;
}
public function addChild(Child $child)
{
return $this->createChild($child->getFoo(), $child->getBar());
}
我无法摆脱getFoo
和getBar
,但请记住,在任何其他情况下,这些都是必要的,目前我正在让对象成为只读对象。
尽管如此,我在这里做的是根据Child
中的规范$child
制作新的addChild
,就像克隆整个对象一样。所以我并没有真正添加传入的Child
,而是将其内容复制到新的制作中。
这样,我设法不破坏数据封装,在Child
中创建ChildType
并给它一个不相关的Parent
,然后在表单调用{{1}时复制数据}。这就是我如何设法不破坏良好的面向对象编程和设计实践,即使Symfony表单在这个问题上可能看起来有点限制。
要想了解如何在Symfony中使用addChild
,请阅读William Durand's article on this matter.