4
votes

I am trying to build a form in ZF2. The problem comes when I want to populate the options array of a Select input element from a database table. A response to this question Zend FrameWork 2 Get ServiceLocator In Form and populate a drop down list by @timdev pointed me to the ZF2 docs where the 'correct' method is described. I followed this carefully but I suspect that they must have left obvious code out assuming I could fill in the gaps as I cannot get it to work. Can anyone see what I am doing wrong?

I start with a form to which I add a fieldset:

namespace Ctmm\Form;
use Zend\Form\Form;

class AddPropertyForm extends Form {

public function __construct() {

    parent::__construct('AddProperty');

    $this->setName('addProperty');
    $this->setAttribute('method', 'post');

    $this->add(array(
        'name' => 'property',
        'type' => 'PropertyFieldset'
    ));

} }

I then create the fieldset:

namespace Ctmm\Form;
use Ctmm\Model;
use Zend\Form\Fieldset;

class PropertyFieldset extends Fieldset { 

public function __construct(PropertyType $property_type) {
    $this->add(array(
        'name' => 'property_type',
        'type' => 'Zend\Form\Element\Select',
        'attributes' => array(
            'required' => true,
        ),
        'options' => array(
            'label' => 'Property Type',
            'value_options' => array(
                0 => 'Detached house',
                1 => 'Semi-detached house',
                2 => 'Terraced house',
                3 => 'Bungalow',
                4 => 'Maisonette',
                5 => 'Flat',
                6 => 'Land',
                7 => 'Development Opportunity',
            ),
        ),
    ));

}

}

As you can see I inject PropertyType dependency into the fieldset. At this stage, I have not even used this to generate the options array. I have hard coded the array values to avoid adding another source of possible errors. Once I get the form to render I will then try to pull the array data from the PropertyType table.

Now I set up the form element manager in my Module.php:

namespace Ctmm;
use Ctmm\Form\PropertyFieldset;
use Zend\ModuleManager\Feature\FormElementProviderInterface;

class Module implements FormElementProviderInterface {

public function getFormElementConfig() {
    return array(
        'factories' => array(
            'PropertyFieldset' => function($sm) {
                $serviceLocator = $sm->getServiceLocator();
                $property_type = $serviceLocator->get('Ctmm\Model\PropertyType');
                $fieldset = new PropertyFieldset($property_type);
            }
        )
    );
}

}

This code is straight from the docs. I have tried adding

return $fieldset;

to the PropertyFieldset factory, and I have even tried adding

'invokables' => array(
'PropertyFieldset' => 'Ctmm\Form\PropertyFieldset'
)

to the getFormElementConfig array, as well as replacing the factory with the invokable.

The last step is to create the form in my controller action using the form element manager:

public function addAction() {        
$formManager = $this->serviceLocator->get('FormElementManager');
    $form        = $formManager->get('Ctmm\Form\AddPropertyForm');
}

Whatever I do, I get an error saying the Servicemanager cannot create the PropertyFieldset:

Zend\ServiceManager\Exception\ServiceNotFoundException

File:

/home/mike/public_html/ctmm/vendor/zendframework/zendframework/library/Zend/ServiceManager/ServiceManager.php:456

Message:

Zend\ServiceManager\ServiceManager::get was unable to fetch or create an instance for PropertyFieldset

Stack trace:

#0 /home/mike/public_html/ctmm/vendor/zendframework/zendframework/library/Zend/ServiceManager/AbstractPluginManager.php(103): Zend\ServiceManager\ServiceManager->get('PropertyFieldse...', true)
#1 /home/mike/public_html/ctmm/vendor/zendframework/zendframework/library/Zend/Form/Factory.php(110): Zend\ServiceManager\AbstractPluginManager->get('PropertyFieldse...')
#2 /home/mike/public_html/ctmm/vendor/zendframework/zendframework/library/Zend/Form/Form.php(145): Zend\Form\Factory->create(Array)
#3 /home/mike/public_html/ctmm/module/Ctmm/src/Ctmm/Form/AddPropertyForm.php(33): Zend\Form\Form->add(Array)

Line 33 in AddPropertyForm.php is where I try to add my custom PropertyFieldset. Clearly I have an error in the fieldset itself or in the way I am declaring it. I have tried without injecting the PropertyType dependency, but that makes no difference. For completeness, the code for my PropertyType model is:

namespace Ctmm\Model;

class PropertyType {
public $id;
public $property_type;
protected $adapter;

public function __construct($adapter) {
    $this->adapter = $adapter;
}

public function exchangeArray($data) {
    $this->id = (isset($data['id'])) ? $data['id'] : null;
    $this->property_type = (isset($data['property_type'])) ? $data['property_type'] : null;
}

public function getPropertyType() {
    return $this->property_type;
}

public function fetchAll() {

    $sql_query = "SELECT id, property_type from property_type";
    $statement = $this->adapter->createStatement($sql_query);
    $results = $statement->execute();
    return $results;
}

}

Edit:

I don't have an answer but I have done some more investigation. I created a fieldset directly in the controller to test my PropertyFieldset class and it's dependent model.

$property_type = $this->getServiceLocator()->get('Ctmm\Model\PropertyType');
$fieldset = new PropertyFieldset($property_type);

This did not work immediately. First, I had to take the hinting out of the Fieldset contructor

public function __construct(PropertyFieldset $property_type) {

became

public function __construct($property_type) {

I then had to add

parent::__construct('propertyfieldset');

before it would allow me to add an element.

Once I had added these changes I was able to create a PropertyFieldset object in the controller. I could test this by var_dump()ing it.

Unfortunately, these changes to the PropertyFieldset class did not fix the basic problem, so that when I try to create the form in the controller, it generates the same error as before. I have, at least exonerated the PropertyFieldset class and it's dependent model, which says to me that I have something wrong in the getFormElementConfig() in my Module.php class

1
What does your AddPropertyForm factory look like? As regards to the type hinting. I think you need to use the full namespace, so: use Ctmm\Model\PropertyType; or put the full path in your constructorAydin Hassan
I don't use a factory to create the AddPropertyForm. The form is created in the controller using the new FormElementManager. The docs insist on using this to create the form so that it picks up elements defined in the getFormElementConfig().Mike Kelly
Just had a mess around with it and I think if you change the __construct method to init in the AddPropertyForm it should work. You will also need to return the fieldset from the factory. I will double check it all and post an answer.Aydin Hassan

1 Answers

6
votes

So I got this to work with a few minor changes:

As you noted the PropertyFieldSet should call the parents construct like so:

parent::__construct('propertyfieldset'); 

ElementConfig should be like so:

public function getFormElementConfig() {
    return array(
        'factories' => array(
            'PropertyFieldset' => function($sm) {
                $serviceLocator = $sm->getServiceLocator();
                $property_type = $serviceLocator->get('Ctmm\Model\PropertyType');
                $fieldset = new PropertyFieldset($property_type);
                return $fieldset;
            },
        )
    );
}

And the AddPropertyForm should be like so:

namespace Ctmm\Form;
use Zend\Form\Form;

class AddPropertyForm extends Form {

    public function init() {

        parent::__construct('AddProperty');

        $this->setName('addProperty');
        $this->setAttribute('method', 'post');

        $this->add(array(
            'name' => 'addproperty',
            'type' => 'PropertyFieldset',
        ));
    }
}

Instead of using __construct we use init(). This function is apparently called when instantiated by factory: http://framework.zend.com/apidoc/2.1/classes/Zend.Form.Form.html#init

Regarding building the select, I would pass a TableGateway object to the fieldSet instead of a model. Then using a fetchAll function we could do the following in the form:

class PropertyFieldset extends Fieldset {

    public function __construct(PropertyTypeTable $propertyTypeTable) {
        parent::__construct('propertyfieldset');


        $propertyValOpts = array();
        foreach($propertyTypeTable->fetchAll() as $propertyRow) {
            array_push($propertyValOpts,$propertyRow->property_type);
        }

        $this->add(array(
            'name' => 'property_type',
            'type' => 'Zend\Form\Element\Select',
            'attributes' => array(
                'required' => true,
            ),
            'options' => array(
                'label' => 'Property Type',
                'value_options' => $propertyValOpts
            ),
        ));
    }
}

Hope this helps :)