Howto Handle External Form Element Dependencies with FormElementManager

Zend Framework 2, like all great PHP frameworks, provides thorough infrastructure for creating forms in your application. Whether that’s form objects, form elements, fieldsets, validation groups or that they interact with so many other components in the Zend Framework 2 default libraries. But how do you handle external dependencies?


Zend Framework 2, like all great PHP frameworks, provides thorough infrastructure for creating forms in your application. Whether that’s form objects, form elements, fieldsets, validation groups or that they interact with so many other components in the Zend Framework 2 default libraries. But how do you handle external dependencies?

That’s right, external dependencies? Frameworks can only go so far and shouldn’t be coupled to external dependencies, such as database, logging servers, cache servers or any other.

So what if you need a custom form element in your application, one which will render a list of articles from a database table?

You could bootstrap this when creating your form? Or you could instead create a custom element, which transparently handles the logic for you, by integrating with FormElementManager.

In today’s post, we’re going to look at how to create such a custom element, extending the existing select element. The form element will retrieve a list of records from an existing table as well as make use of the Zend\Validator\Db\RecordExists validator to ensure that only an existing record can be submitted. So we’ll go through how to implement these two requirements, by making use of FormElementManager.

NB: In a recent post on Zend Framework 2 forms, I went through how to make forms available throughout Zend Framework 2 applications. However, in #zftalk on IRC, there was a critique given, that this wasn’t the right way to go. Instead, the use of the FormElementManager was highlighted as the preferred approach. So that’s why I’m emphasising it here.

What’s great about this approach, is that it’s quite a straight-forward, logical approach. All we need to do is the following:

  1. Create a new form element which extends Zend\Form\Element\Select, implementing Zend\ServiceManager\ServiceLocatorAwareInterface
  2. Create a new form object, which also implements Zend\ServiceManager\ServiceLocatorAwareInterface, adding the custom element in its init method
  3. Implement the getFormElementConfig method in our modules Module class
  4. Instantiate the form via the FormElementManager

If you’re already familiar with forms in Zend Framework 2, this will be very straight-forward for you. If not, then have a read of the following posts to get you up to speed:

Let’s get underway.

1. Create a New Form Element

In this Gist, you can see the complete code used in today’s post. You can see, at the top, that it defines the custom element, ArticleList. This extends an example in the Zend Framework 2 manual.

Let’s start working through the code now.

namespace Forecaster\Form\Element;
use Zend\Form\Element\Select;
use Zend\InputFilter\InputProviderInterface;
use Zend\Validator\Db\RecordExists;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class ArticleList extends Select implements InputProviderInterface, ServiceLocatorAwareInterface
{
protected $validator;
protected $serviceLocator;

First, we create our new class, indicating that it extends the in-built Select element, and implements both InputProviderInterface and ServiceLocatorAwareInterface. InputProviderInterface is required for getInputSpecification and ServiceLocatorAwareInterface is required for setServiceLocator and getServiceLocator. As well as this, it is used by the ServiceManager to automatically inject the service locator when the element’s instantiated.

public function setServiceLocator(ServiceLocatorInterface $sl)
{
    $this->serviceLocator = $sl;
}

public function getServiceLocator()
{
    return $this->serviceLocator;
}

Here, we implement the setServiceLocator and getServiceLocator methods, so that the service locator is able to automatically set on our class.

Now we arrive at the key functionality we need, the init method. This is automatically called after the class is constructed and once all the dependencies are resolved. Let’s look at what it does.

public function init()
{
    $validator = new RecordExists(array(
        'table'   => 'articles',
        'field'   => 'id',
        'adapter' => $this->serviceLocator->getServiceLocator()->get(
            'Zend\Db\Adapter\Adapter'
        )
    ));
    $validator->setMessage(
        'Please choose a valid article!',
        RecordExists::ERROR_NO_RECORD_FOUND
    );

Firstly, we’ve created a new RecordExists validator, passing it the application’s default adapter as defined by the key Zend\Db\Adapter\Adapter. We also specify the table and field to use, when verifying that the record exists in our database, along with a custom error message, should the record specified not be found.

$articleTable = $this->serviceLocator->getServiceLocator()->get('Forecaster\Table\ArticleTable');
    $this->setValueOptions(array_merge(array(-1 => 'Please Choose'), $articleTable->getSelectData()));
    $this->validator = $validator;
}

Here, we’re retrieving a service from the serviceLocator, Forecaster\Table\ArticleTable and calling getSelectData on it, which will return an associative array which can be used in the setValueOptions method of the select object. The key will be the unique record id and the value will be the article name.

public function getValidator()
{
    return $this->validator;
}

public function setValidator(ValidatorInterface $validator)
{
    $this->validator = $validator;
    return $this;
}

Here, we’re implementing getValidator and setValidator so that validators can be set and retrieved on our custom element, in addition to what we have specified.

public function getInputSpecification()
{
    return array(
        'name' => $this->getName(),
        'required' => true,
        'filters' => array(
            array('name' => 'Zend\Filter\Int'),
        ),
        'validators' => array(
            $this->getValidator(),
        ),
    );
}

This method allows us to specify key aspects of the element, such as name, whether it’s required, default filters and adds the validator we’ve created, in init.

2. Implement the getFormElementConfig Method

Ok, now that the custom element’s created, we need to expose it to FormElementManager, so that it can be used.

use Zend\ModuleManager\Feature\FormElementProviderInterface;
class Module implements FormElementProviderInterface
{
    public function getFormElementConfig()
    {
        return array(
            'invokables' => array(
                'ArticleList' => 'Forecaster\Form\Element\ArticleList'
            )
        );
    }
}

In Module.php, we implement the getFormElementConfig method, as above. What this does is allow the class to be instantiated, or invoked, with the alias ArticleList.

3. Create a New Form Object

Right, now with our new custom element created and available to be used, let’s add it to a form.

namespace Forecaster\Form;
use Zend\Form\Form;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;

use Forecaster\InputFilter\ArticleInputFilter;

class AddRecordForm extends Form

implements ServiceLocatorAwareInterface {
    protected $serviceLocator;

    public function init() {
        $this->add(array(
            'name' => 'articleList',
            'type' => 'ArticleList',
            'options' => array( 'label' => 'Articles' )
        ));
    }
}

Here, in the init method, which is automatically called, as in the custom select element, we add the new element to our form, specifying the element’s name and type, which we defined in point 2, as well as some custom options. This is just like we may with a standard form element.

One thing I want to draw your attention to, is this catch in the manual:

If you are creating your form class by extending Zend\Form\Form, you must not add the custom element in the __construct-or (as we have done in the previous example where we used the custom element’s FCQN), but rather in the init() method.

Make sure you don’t get caught out by that.

4. Instantiate the Form Via the FormElementManager

Right, now that we’ve implemented all the plumbing required for the various parts of the puzzle, we retrieve it in a controller action.

As you can see, it’s really not much different from using the service locator to retrieve any other service. The only real difference, is that we first retrieve the FormElementManager, then we use it to retrieve (and instantiate) or form object, which we then set in the ViewModel which’s returned from the action.

public function addArticleAction()
{
    $formManager = $this->serviceLocator->get(
        'FormElementManager'
    );
    $form = $formManager->get(
        'Forecaster\Form\AddRecordForm'
    );

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

Wrapping Up

So, what did you think? Whilst I didn’t really have issues before, when I wasn’t using it, after playing around with it recently, I can see just how flexible it makes creating custom elements, especially ones which have an external dependency, such as this one, on a database.

I don’t know about you, but this is definitely becoming a regular part of my Zend Framework 2 applications.


You might also be interested in these tutorials too...

Tue, Jun 26, 2018

What Are Delegator Factories and Why You Should Use Them

Ever wanted to dynamically expand the functionality of an object which you retrieve from your dependency injection container, based on different needs, yet without creating messy, hard to maintain configurations? Then you’re going to want to know about a powerful new technique - called Delegator Factories.

Tue, Jul 25, 2017

How to Create a Zend Expressive Module

Ever thought of creating a Zend Expressive module, one that either scratches an itch or allows you to use a set of related functionality across multiple Zend Expressive projects? I created one recently and share how I did it with you in this three-part series.

Visual Debt. Really?
Wed, Jun 14, 2017

Visual Debt. Really?

Recently, Jeffrey Way dateed what’s become quite a controversial video in the PHP community on Laracasts, discussing a concept called Visual Debt.


Want more tutorials like this?

If so, enter your email address in the field below and click subscribe.

You can unsubscribe at any time by clicking the link in the footer of the emails you'll receive. Here's my privacy policy, if you'd like to know more. I use Mailchimp to send emails. You can learn more about their privacy practices here.

Join the discussion

comments powered by Disqus