Symfony2 - 采集形式的工厂方法

时间:2014-06-11 22:43:40

标签: php symfony

我认为这个问题应该比较容易解决,但我无法在任何地方找到答案。我正在使用Symfony 2.3,我有两个相互依赖的类,我们分别称它们为ParentChild类。

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和几个ChildParentType有:

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

   //...
}

理论上这应该有效,但我有以下问题:

  1. Symfony强迫我宣布addChildrenremoveChildren。没什么大不了的,我做到了。
  2. 在我这样做之后,我收到以下错误:
  3.   

    FatalErrorException:错误:在(route)\ ChildType.php第22行中的非对象上调用成员函数createChild()

    有什么我做错了或者我应该考虑到为了不破坏正确的OOP编程并使表单有效吗?谢谢。

1 个答案:

答案 0 :(得分:1)

哇,我实际上可以解决这个问题。这是非常黑客,但我设法在这种情况下没有打破正确的数据封装。在这里,我将解释,希望这对任何人都有用。

如何在Symfony 2表单中使用工厂方法模式而不是尝试

工厂方法模式

让我们首先回顾一下设计模式。 SourceMaking has a good article on this,但我会写一个摘要。简而言之,Factory Method设计模式允许我们在父类中创建一个方法来创建另一个类的对象,并且可以扩展和覆盖,因此子类可以有自己的实现。这是创建模式,因此处理创建对象。因此,工厂方法允许我们为任何意图封装对象的创建。

在我的问题中,我对Factory Method的使用来自事实Child的生命周期紧紧依赖于Parent。这是,没有Parent,没有ChildChild无法独立于父级。因此,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中声明removeChildrenParentremoveChildren很容易实现,但存在一个问题:我无法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());
}

我无法摆脱getFoogetBar,但请记住,在任何其他情况下,这些都是必要的,目前我正在让对象成为只读对象。

尽管如此,我在这里做的是根据Child中的规范$child制作新的addChild,就像克隆整个对象一样。所以我并没有真正添加传入的Child,而是将其内容复制到新的制作中。

结论

这样,我设法不破坏数据封装,在Child中创建ChildType并给它一个不相关的Parent,然后在表单调用{{1}时复制数据}。这就是我如何设法不破坏良好的面向对象编程和设计实践,即使Symfony表单在这个问题上可能看起来有点限制。

要想了解如何在Symfony中使用addChild,请阅读William Durand's article on this matter.