In this article, you will learn how to create a custom view helper in Zend Framework 2. A concrete example will be used; a helper which generates links for a subdomain, intended for storing static files. This is especially useful if you wish to use a Content Delivery Network (CDN). With very little modification, the helper can be made generic to support links to subdomains for all purposes.

The Helper Class

Let us begin by creating the helper class. It can be added within any module, but a suitable place would be within the Application module, provided that you made use of the Skeleton Application. Create a file CdnHelper.php in zf2-tutorial\module\Application with the following subdirectories:

zf2-tutorial/
/module
/Application
/src
/Application
/View
/Helper

where the file content of CdnHelper.php is listed below.

namespace Application\View\Helper;

use Zend\Http\Request;
use Zend\ServiceManager\ServiceManager;
use Zend\View\Helper\AbstractHelper;

class CdnHelper extends AbstractHelper
{
    public function __construct(
        Request $request,
        ServiceManager $serviceLocator
    ) {
        $this->request = $request;
        $this->serviceLocator = $serviceLocator;
    }

    /**
     * Get URL on CDN servers.
     * @param String $filePath - the relative path of assets file path
     * @return the url of assets file path
     */
    public function __invoke($filePath)
    {
        $config = $this->serviceLocator->get("config");
        if (!array_key_exists("cdn", $config)) {
            return $filePath;
        }

        $options = $config["cdn"];
        $cdnDomain = self::getCdnDomain($filePath, $options);
        return self::getCdnUrl($cdnDomain, $filePath);
    }

    /**
     * Use File Extension to get the domain of CDN.
     * @param String $filePath - the relative path of assets file path
     * @return the domain of the CDN server
     */
    public function getCdnDomainUrl($filePath)
    {
        $cdnDomain = self::getCdnDomain($filePath);
        return "//" . rtrim($cdnDomain, "/");
    }

    /**
     * Use File Extension to get the domain of CDN.
     * @param String $filePath - the relative path of assets file path
     * @param Array $options - CDN service settings
     * @return the domain of the CDN server
     */
    private function getCdnDomain($filePath, $options)
    {
        $assetName = basename($filePath);
        foreach ($options as $fileExt => $cdnDomain) {
            if (preg_match("/^.*\.(" . $fileExt . ')$/i', $assetName)) {
                return $cdnDomain;
            }
        }
        $cdnDomain = $options["default"];
        return $cdnDomain;
    }

    /**
     * Get the url of assets files.
     * @param String $cdnDomain - the domain of CDN server
     * @param String $filePath - the relative path of assets file path
     * @return the url of assets file path
     */
    private function getCdnUrl($cdnDomain, $filePath)
    {
        return "//" . rtrim($cdnDomain, "/") . "/" . ltrim($filePath, "/");
    }

    public function getRequest()
    {
        return $this->request;
    }

    protected $request;

    protected $serviceLocator;
}

There are a few things to discuss about the above code. Firstly, the class extends the AbstractHelper class, which implements HelperInterface. All the class does is to provide a property for the view object and a getter and setter method. Optionally, one can simply implement HelperInterface, but normally the functionality provided within the abstract class is sufficient.

A series of constants are defined within the above class. The purpose of the constants is to provide an easy way to access directory names for the various types of resources. This way, one can make use of these constants when using the helper instead of hard coding directory names for every use of the helper.

Because we need access to the hostname, the Zend\Http\Request object will be injected into the helper’s constructor. As a result, a little more work has to be done before the helper is ready for use, but we will get back to that in a moment. The request object is stored as a field variable and a getter and setter is also implemented.

The implementation of the __invoke method means that the class can be used as if it were a method. The method takes three parameters where two are optional; the name of the file to link to, a subfolder, and an array of subdirectories. The subfolder – if any – is appended to the hostname, whereafter any subdirectories are appended. Lastly, the file name is appended. After configuring the helper, there will be an example that further explains the meaning of each parameter. The method’s logic itself is quite straightforward and is not of importance in order to learn how to create custom view helpers, and thus it will not be discussed.

Configuring the Helper

Before using the helper, it must be registered. Because we have a dependency in the form of a Zend\Http\Request object, we will be injecting this dependency by using a factory. If we did not have any dependencies, a simply invokable should be used because no initialization would be required. While there are various ways to configure the helper, we will be using a configuration file, which is the most common approach. Even in regards to configuration files, there are several ways to go about it; one can add a view_manager key with a nested factories key either to the main configuration file located at config/application.config.php or to a module’s config/module.config.php. It can also be configured within theModuleclass’ getViewHelperConfig method by returning an array.

The way we will do it, though, is to use a separate configuration file that only configures view helpers. For this purpose, the Zend\ModuleManager\Feature\ViewHelperProviderInterface defines a getViewHelperConfig method. While implementing this interface in the Module class is not strictly necessary, it is good practice. The module manager will check to see if the class either implements the interface or simply provides a method with that name.

To keep the class simple, we will not simply return an array from the getViewHelperConfig method. Rather, the configuration will be stored in a global config folder. This file will then be read by getAutoloaderConfig() and returned. Please consider the code below.

namespace Application;

use Zend\ModuleManager\Feature\ConfigProviderInterface;
use Zend\ModuleManager\Feature\AutoloaderProviderInterface;
use Zend\ModuleManager\Feature\ViewHelperProviderInterface;

class Module implements
    ConfigProviderInterface,
    AutoloaderProviderInterface,
    ViewHelperProviderInterface
{
    public function getConfig()
    {
        return include __DIR__ . "/config/module.config.php";
    }

    public function getAutoloaderConfig()
    {
        return [
            "Zend\Loader\StandardAutoloader" => [
                "namespaces" => [
                    __NAMESPACE__ => __DIR__ . "/src/" . __NAMESPACE__,
                ],
            ],
        ];
    }

    public function getViewHelperConfig()
    {
        return [
            "factories" => [
                /* CDN Service */
                "cdn" => function ($sm) {
                    $request = $sm->getServiceLocator()->get("Request");
                    $serviceLocator = $sm->getServiceLocator();
                    return new CdnHelper($request, $serviceLocator);
                },
            ],
        ];
    }
}

The getViewHelperConfig method returns the below configuration file (config/autoload/global.php).

As you can see, you can add multiple CDN servers for different file types. Please NOTE that the default entry must exist, otherwise, you’ll get a runtime exception.
This configuration file will be included when the Zend Application starts.

Using the View Helper

The view helper is now ready for use from within view scripts.
You can use it as follows:

echo $this->headLink()->appendStylesheet($this->cdn('/css/home/homepage.css'));

Remember the __invoke magic method? Its magic is demonstrated above where the class is used as a method. The only parameter is the name of the file to link to.
Quite easy isn’t it?

The Disqus comment system is loading ...
If the message does not appear, please check your Disqus configuration.