I’m developing a project using Doctrine’s Class Table Inheritance mapping strategy which involves joining a parent table with one of a number of child tables depending on the value in a discriminator column in the parent table. I’ve got a working prototype in which the unique conglomerate fieldsets each contain duplicate copies of all of the code for the common elements from the parent entity. In order to ensure consistency and avoid excess code I want to change the fieldsets so that I have a single fieldset that’s related to the parent entity and all of the other fieldsets are simply extensions of the parent (a detailed explanation is included at How to Extend a Fieldset in ZF2). I run into problems when I separate the fieldsets and then try to get them to work with each other.
The first answer to the question How to Extend a Fieldset in ZF2 provides a clear explaination of how a single fieldset can extend another in ZF2. However, the answer uses init()
to put together the fieldsets, and for the Doctrine strategy we need to use __construct
. My first pass in developing the fieldsets was to build in the some of the code prescribed in the DoctrineModule/docs, which brings us to this:
class FieldsetParent extends Zend\Form\Fieldset
{
public function __construct(ObjectManager $objectManager) {
parent::__construct('parent-fieldset');
$this->setHydrator(new DoctrineHydrator($objectManager, 'MyModule\Entity\Parent'))
->setObject(new Parent());
$this->add(array('name' => 'fieldA'));
$this->add(array('name' => 'fieldB'));
$this->add(array('name' => 'fieldC'));
}
}
and this:
class FieldsetFoo extends FieldsetParent
{
public function __construct(ObjectManager $objectManager) {
parent::__construct('foo-fieldset');
$this->setHydrator(new DoctrineHydrator($objectManager, 'MyModule\Entity\Foo'))
->setObject(new Foo());
$this->add(array('name' => 'fieldD'));
$this->add(array('name' => 'fieldE'));
$this->add(array('name' => 'fieldF'));
$this->add(array('name' => 'fieldG'));
}
}
This doesn’t work because it attempts to add a fieldset from a string, giving the error message:
Catchable fatal error: Argument 1 passed to MyModuule\Form\ParentFieldset::__construct()
must implement interface Doctrine\Common\Persistence\ObjectManager, string given ...
The first answer to the question zend2 doctrine 2 form intgergration OneToOne explains how adding a fieldset from a string can be avoided in the case of a OneToOne strategy. However, I'm working with a different ORM strategy and I’m having difficulties working out how to solve the same problem.
EDIT
As requested, here is some more detailed information:
class FooController extends AbstractActionController
{
/**
* @var Doctrine\ORM\EntityManager
*/
protected $em;
public function setEntityManager(EntityManager $em)
{
$this->em = $em;
return $this;
}
public function getEntityManager()
{
if (null === $this->em) {
$this->em = $this->getServiceLocator()->get('Doctrine\ORM\EntityManager');
}
return $this->em;
}
// ... //
public function editAction()
{
$fooID = (int)$this->getEvent()->getRouteMatch()->getParam('fooID');
if (!$fooID) {
return $this->redirect()->toRoute('foo', array('action'=>'add'));
}
$foo = $this->getEntityManager()->find('MyModule\Entity\Foo', $fooID);
// Get your ObjectManager from the ServiceManager
$objectManager = $this->getServiceLocator()->get('Doctrine\ORM\EntityManager');
// Create the form and inject the ObjectManager
$form = new EditFooForm($objectManager);
$form->setBindOnValidate(false);
$form->bind($foo);
$form->get('submit')->setAttribute('label', 'Update');
$request = $this->getRequest();
if ($request->isPost()) {
$form->setData($request->getPost());
if ($form->isValid()) {
$form->bindValues();
$this->getEntityManager()->flush();
return $this->redirect()->toRoute('foo');
}
}
return array(
'foo' => $foo,
'form' => $form,
);
}
}
and
class EditFooForm extends Form
{
public function __construct(ObjectManager $objectManager)
{
parent::__construct('edit_foo_form');
$this->setHydrator(new DoctrineHydrator($objectManager, 'MyModule\Entity\Foo'));
$fooFieldset = new FooFieldset($objectManager);
$fooFieldset->setUseAsBaseFieldset(true);
$this->add($fooFieldset);
// submit elements
}
}
This fieldset works:
class FooFieldset extends Fieldset implements InputFilterProviderInterface
{
public function __construct(ObjectManager $objectManager)
{
parent::__construct('foo-fieldset');
$this->setHydrator(new DoctrineHydrator($objectManager, 'MyModule\Entity\Foo'))
->setObject(new Foo());
$this->add(array('name' => 'fieldA'));
$this->add(array('name' => 'fieldB'));
$this->add(array('name' => 'fieldC'));
$this->add(array('name' => 'fieldD'));
$this->add(array('name' => 'fieldE'));
$this->add(array('name' => 'fieldF'));
$this->add(array('name' => 'fieldG'));
}
public function getInputFilterSpecification()
{
return array('fieldA' => array('required' => false),);
return array('fieldB' => array('required' => false),);
return array('fieldC' => array('required' => false),);
return array('fieldD' => array('required' => false),);
return array('fieldE' => array('required' => false),);
return array('fieldF' => array('required' => false),);
return array('fieldG' => array('required' => false),);
}
}
These fieldsets give an error message:
class FoobarFieldset extends Fieldset implements InputFilterProviderInterface
{
public function __construct(ObjectManager $objectManager)
{
parent::__construct('foobar-fieldset');
$this->setHydrator(new DoctrineHydrator($objectManager, 'MyModule\Entity\Foobar'))
->setObject(new Foobar());
$this->add(array('name' => 'fieldA'));
$this->add(array('name' => 'fieldB'));
$this->add(array('name' => 'fieldC'));
}
public function getInputFilterSpecification()
{
return array('fieldA' => array('required' => false),);
return array('fieldB' => array('required' => false),);
return array('fieldC' => array('required' => false),);
}
}
and
use MyModule\Form\FoobarFieldset;
class FooFieldset extends FoobarFieldset implements InputFilterProviderInterface
{
public function __construct(ObjectManager $objectManager)
{
parent::__construct('foo-fieldset');
$this->setHydrator(new DoctrineHydrator($objectManager, 'MyModule\Entity\Foo'))
->setObject(new Foo());
$this->add(array('name' => 'fieldD'));
$this->add(array('name' => 'fieldE'));
$this->add(array('name' => 'fieldF'));
$this->add(array('name' => 'fieldG'));
}
public function getInputFilterSpecification()
{
return array('fieldD' => array('required' => false),);
return array('fieldE' => array('required' => false),);
return array('fieldF' => array('required' => false),);
return array('fieldG' => array('required' => false),);
}
}
The resulting error message is:
Catchable fatal error: Argument 1 passed to MyModule\Form\FoobarFieldset::__construct()
must implement interface Doctrine\Common\Persistence\ObjectManager, string given, called
in C:\xampp\htdocs\GetOut\module\MyModule\src\MyModule\Form\FooFieldset.php on line 17
and defined in C:\xampp\htdocs\GetOut\module\MyModule\src\MyModule\Form\FoobarFieldset.php
on line 14
I have tried to avoid adding a fieldset from a string by making these changes:
use MyModule\Form\FoobarFieldset;
class FooFieldset extends Fieldset implements InputFilterProviderInterface
{
public function __construct(ObjectManager $objectManager)
{
parent::__construct('foo-fieldset');
$this->setHydrator(new DoctrineHydrator($objectManager, 'MyModule\Entity\Foo'))
->setObject(new Foo());
$this->add(array('name' => 'fieldD'));
$this->add(array('name' => 'fieldE'));
$this->add(array('name' => 'fieldF'));
$this->add(array('name' => 'fieldG'));
$fieldset = new FoobarFieldset($objectManager);
$this->add($fieldset);
}
but this gives a Zend\Form\Exception\InvalidElementException
with the message, No element by the name of [fieldA] found in form
, indicating that the filedset is not getting added.
AN ALTERNATIVE
When it’s all said and done, all I’m really trying to do is keep all of the common statements in one place and draw them into the various unique fieldsets that need to include them. I can solve this without ZF2 by using include
statements as shown below:
// FoobarFieldset_fields.php
$this->add(array('name' => 'fieldA'));
$this->add(array('name' => 'fieldB'));
$this->add(array('name' => 'fieldC'));
and
// FoobarFieldset_filters.php
return array('fieldA' => array('required' => false),);
return array('fieldB' => array('required' => false),);
return array('fieldC' => array('required' => false),);
and
class FooFieldset extends Fieldset implements InputFilterProviderInterface
{
public function __construct(ObjectManager $objectManager)
{
parent::__construct('foo-fieldset');
$this->setHydrator(new DoctrineHydrator($objectManager, 'MyModule\Entity\Foo'))
->setObject(new Foo());
include 'FoobarFieldset_fields.php';
$this->add(array('name' => 'fieldD'));
$this->add(array('name' => 'fieldE'));
$this->add(array('name' => 'fieldF'));
$this->add(array('name' => 'fieldG'));
}
public function getInputFilterSpecification()
{
include 'FoobarFieldset_filters.php';
return array('fieldD' => array('required' => false),);
return array('fieldE' => array('required' => false),);
return array('fieldF' => array('required' => false),);
return array('fieldG' => array('required' => false),);
}
}
The issue you are having relates to the fieldset's
__construct
The 'parent'
In the 'child' however you are calling the parent
__construct
passing a string (which should be the$objectManager
)There are some additional things that could improve your code.
Currently you are creating the form using the new keyword.
This is fine (it will work) however you should really be loading it via the service manager to allow you to attach a factory to it.
Then you would have a factory registered to create the new form and fieldset (keeping all the construction code in one place)
Module.php
Lastly; allow the Zend\Form\Factory to create the fieldset by adding it via
$this->add()
(rather than creating the fieldset within the form usingnew
)Notice in the last example the form elements are added in
init()
this is because theFormElementManager
will callinit()
when you request it from the service manager. This is an important separation of concerns as it will allow you to provide the dependencies via__construct
injection (when the form is created) and then separately add the form elements after all the forms properties have been set.This is explained within the documentation:
and also: