Samuel N.
Samuel N.

Reputation: 13

Zf3 populate select Element with data from Database

I know this topic was discussed 2 years ago. But I stuck on difficulty that I would like to solve.

I would like to create a zf3 form which contains more than two Select Element.

I would like to populate them with data coming from different repositories (options values of each Select item come from distinct repository).

First all, I tried to pass the service manager (from where I can access to my repositories) in the constructor of my form but I heard that this solution is not suitable at all.

So how can I include multiples repositories in my form to populate my Select Elements?

Upvotes: 1

Views: 609

Answers (1)

Ermenegildo
Ermenegildo

Reputation: 1308

Short answer:

  1. Create a class that extends the Select
  2. Create a factory for such class
  3. Add this custom element in your module configuration (module.config.php)
  4. Use this class as type for your form elements
  5. Retrieve the form through the form manager
  6. Example, for controllers, adapt the controller's factory

Detailed answer:

  1. Create a class that extends the Select, like BrandSelect
namespace MyModule\Form\Element;

use Laminas\Form\Element\Select;

class BrandSelect extends Select {

    protected $repository;

    public function __construct($repository, $name = null, $options = []) {
        parent::__construct($name, $options);
        $this->repository = $repository;
    }

    /**
     * Initialize the element
     *
     * @return void
     */
    public function init() {
        $valueOptions = [];
        foreach ($this->repository->fetchBrands() as $brand) {
            $valueOptions[$brand->getBrandId()] = $brand->getName();
        }
        asort($valueOptions);
        $this->setValueOptions($valueOptions);
    }

}
  1. Create a factory for such class
namespace MyModule\Form\Element;

use Laminas\ServiceManager\Factory\FactoryInterface;
use Interop\Container\ContainerInterface;
use MyModule\Db\Repository;

class BrandSelectFactory implements FactoryInterface {

    public function __invoke(ContainerInterface $container, $requestedName, $options = null): BrandSelect {
        $repository = $container->get(Repository::class);
        return new BrandSelect($repository);
    }

}
  1. Add this custom element in your module configuration (module.config.php)
namespace MyModule;

return [
    // ..
    // Other configs
    // ..
    'form_elements' => [
        'factories' => [
            Form\Element\BrandSelect::class => Form\Element\BrandSelectFactory::class
        ]
    ]
];
  1. Use this class as type for your form elements.
    It is really important to add all elements in the init() method, otherwise it will not work. I also added the InputFilterProviderInterface.
    In this case, form doens't require any other element its constructor. If needed, you must create a factory for the form and pass all params you need. The form factory must be added in module.config.php configuration, always under the form_elements key (as did for the BrandSelect):
namespace MyModule\Form;

use Laminas\Form\Form;
use Laminas\InputFilter\InputFilterProviderInterface;

class BrandForm extends Form implements InputFilterProviderInterface {

    public function __construct($name = null, $options = []) {
        parent::__construct($name, $options);
    }

    // IT IS REALLY IMPORTANT TO ADD ELEMENTS IN INIT METHOD!
    public function init() {
        parent::init();

        $this->add([
            'name' => 'brand_id',
            'type' => Element\BrandSelect::class,
            'options' => [
                'label' => 'Brands',
            ]
        ]);
    }

    public function getInputFilterSpecification() {
        $inputFilter[] = [
            'name' => 'brand_id',
            'required' => true,
            'filters' => [
                ['name' => 'Int']
            ]
        ];
        return $inputFilter;
    }

}
  1. Retrieve the form through the form manager
    Form must be retrieved using correct manager, which isn't the service manager, but the FormElementManager.
    For example, if you need the form inside BrandController:
<?php

namespace MyModule\Controller;

use Laminas\Form\FormElementManager;
use Laminas\Mvc\Controller\AbstractActionController;
use Laminas\View\Model\ViewModel;

class BrandController extends AbstractActionController {

    private $formManager;

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

    public function addBrandAction() {
        $form = $this->formManager->get(\MyModule\Form\BrandForm::class);

        // Do stuff

        return new ViewModel([
            'form' => $form
        ]);
    }

}
  1. Finally, you'll have to adapt the controller's factory:
namespace MyModule\Controller;

use Laminas\ServiceManager\Factory\FactoryInterface;
use Interop\Container\ContainerInterface;

class BrandControllerFactory implements FactoryInterface {

    public function __invoke(ContainerInterface $container, $requestedName, $options = null): BrandController {
        $formManager = $container->get('FormElementManager');
        return new BrandController($formManager);
    }

}

which must be configured under controllers key in module.config.php:

namespace MyModule;

return [
    // ..
    // Other configs
    // ..
    'controllers' => [
        'factories' => [
            Controller\BrandController::class => Controller\BrandControllerFactory::class
        ],
    ],
    'form_elements' => [
        'factories' => [
            Form\Element\BrandSelect::class => Form\Element\BrandSelectFactory::class
        ]
    ]
];

Upvotes: 2

Related Questions