Sometimes we have to apply dynamic data (fetched from database) to form fields. Good example is to deliver an option to choose a preferred language during user registration. Let say we have two available languages on the begining, but we add next language versions later on. So in that case we want to add new language option to our form without rebuilding form and deployment of application.

In such situation we should read current list of available languages from database and deliver it as the options to form select field.

First of all lets build simple form.

class RegisterForm extends Form {

    /**
     * @param array $languages
     */
    public function __construct($languages) {
        $this->languages = $languages;
        parent::__construct();
    }

    public function init()
    {
        $this->setAttribute('method', 'post');

        $this->add(array(
            'type' => Csrf::class,
            'name' => 'secret',
            'options' => array(
                'csrf_options' => array(
                    'timeout' => 600
                )
            )
        ));

        $this->add([
            'type' => 'text',
            'name' => 'email',
            'options' => [
                'label' => 'E-mail address',
            ],
            'attributes' => [
                'required' => true,
            ],
        ]);

        $this->add([
            'type' => 'password',
            'name' => 'password',
            'options' => [
                'label' => 'Password',
            ],
            'attributes' => [
                'required' => true,
            ],
        ]);

        $language = new Select('language');
        $language->setLabel('Language');
        $language->setDisableInArrayValidator(true);
        $language->setAttributes([
            'required' => true,
        ]);
        $language->setEmptyOption('-- select --');
        $language->setValueOptions($this->languages);
        $this->add($language);

        $this->add([
            'type' => 'submit',
            'name' => 'submit',
            'attributes' => [
                'value' => 'Login',
            ],
        ]);
	}
}

As you can see we have four fields here: csrf, e-mail address, password and language. For the language field we set $this->languages as the value options. This field we are setting in the constructor of RegisterForm. So each time we want to create a RegisterForm object, we should fetch a list of languages and pass it in constructor.

But wait… Why we are not using dependency injection here? So… Let’s build a factory.

class RegisterFormFactory implements FactoryInterface {
    /**
     * Create an object
     *
     * @param  ContainerInterface $container
     * @param  string $requestedName
     * @param  null|array $options
     * @return RegisterForm
     * @throws ServiceNotFoundException if unable to resolve the service.
     * @throws ServiceNotCreatedException if an exception is raised when
     *     creating a service.
     * @throws ContainerException if any other error occurs
     */
    public function __invoke(ContainerInterface $container, $requestedName, array $options = null) {
        /** @var LanguageMapper $mapper */
        $mapper = $container->get(LanguageMapper::class);
        $languages = $mapper->findAllAsArray();

        return new RegisterForm($languages);
    }
}

To build factory we have used FactoryInterface. First of all we are fetching LanguageMapper and call method findAllAsArray() which is going to return us a key-value array with languages. It’s a perfect option for our form select field, which options base on key-value pairs. As the final option we create and return RegisterForm object with passed languages array.

To have this factory as the fully valid factory we have to notify our application about it. We do this in the module.config.php file, where we add following section for forms:

'form_elements' => [
        'factories' => [
            RegisterForm::class => RegisterFactory::class,
        ]
    ],

That’s it. Now we can inject it via constructor to our controller and we can use it. One small hint at the end.

How to inject form to controller using factory?

class RegisterControllerFactory implements FactoryInterface {
    /**
     * Create an object
     *
     * @param  ContainerInterface $container
     * @param  string $requestedName
     * @param  null|array $options
     * @return RegisterController
     * @throws ServiceNotFoundException if unable to resolve the service.
     * @throws ServiceNotCreatedException if an exception is raised when
     *     creating a service.
     * @throws ContainerException if any other error occurs
     */
    public function __invoke(ContainerInterface $container, $requestedName, array $options = null) {

        $formManager = $container->get('FormElementManager');

        return new RegisterController(
            $formManager->get(RegisterForm::class),
        );
    }
}

Usually in factory interface we are fetching object by $container->get() method. But for the forms we have to fetch FormElementManager first and then call method get on this object to fetch required form.

Zend Framework 3: select form field with options fetched from database
Tagged on:             

Leave a Reply

Your email address will not be published. Required fields are marked *

Social Widgets powered by AB-WebLog.com.