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:             

4 thoughts on “Zend Framework 3: select form field with options fetched from database

  • 2017-08-03 at 01:56
    Permalink

    Hello,
    I’, following the walkthrough tutorial on the new ZF3 website. I’m specifically on the Blog section and one item in particular is giving me a headache.
    I do everything but this guy ” $formManager = $container->get(‘FormElementManager’);” is not working.
    It says “Unable to resolve service “FormElementManager” to a factory; are you certain you provided it during configuration?”

    I’d really appreciate if you help me.

    Thanks

    Reply
  • 2017-08-21 at 13:02
    Permalink

    Bez sensu, łamiesz zasadę SRP i DRY. Czemu nie można zwyczajnie utworzyć elementu select, który by się populował danymi z bazy?

    Reply
  • 2017-09-14 at 06:08
    Permalink

    Dzięki!

    2 uwagi: (ja akurat wrzucałem kategorie produktu, a nie język)
    1. Jeśli ktoś używa Doctrine – musi zamienić obiekty z zapytania na tablicę, np:
    $categories = $entityManager
    ->getRepository(Category::class)
    ->createQueryBuilder(‘e’)
    ->select(‘e’)
    ->getQuery()
    ->getResult(\Doctrine\ORM\Query::HYDRATE_ARRAY);

    2. U mnie nie udało się tak łatwo ustawić opcji dla i musiałem zrobić: (w formularzu – ProductForm)
    $cats = [];
    foreach ($this->categories as $cat) {
    $cats[$cat[‘id’]] = $cat[‘name’];
    }
    $category->setValueOptions($cats);

    Reply
  • 2019-03-03 at 04:41
    Permalink

    RegisterForm not recive injection from RegisterFormFactory
    In Zend 3, not woking!
    Tks!

    Reply

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Social Widgets powered by AB-WebLog.com.