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_Form i elementy typu hidden
- Zend_Form - legend dla fieldset
- Przekazywanie zmiennych do formularza
- Zend_Form_Element_Select - wstawianie wartości za pomocą AJAX i błąd "'value' was not found in the haystack"
- Zend Framework 2 - pole Select i walidator InArray
- Google reCAPTCHA - use with PHP
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
Bez sensu, łamiesz zasadę SRP i DRY. Czemu nie można zwyczajnie utworzyć elementu select, który by się populował danymi z bazy?
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);
RegisterForm not recive injection from RegisterFormFactory
In Zend 3, not woking!
Tks!