Create CDN View Helper in Zend Framework 2
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.
Note: Zend Framework 2 has reached end of life. In 2019 the project moved to the Linux Foundation and was renamed Laminas, so the
Zend\namespaces below are nowLaminas\(for exampleZend\View\Helper\AbstractHelperbecomesLaminas\View\Helper\AbstractHelper). The view-helper mechanics are otherwise unchanged, so this post is kept as a historical walkthrough. For a real project, run the namespaces through the Laminas migration guide and follow the current custom view helper docs.
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
{
protected $request;
protected $serviceLocator;
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 = $this->getCdnDomain($filePath, $options);
return $this->getCdnUrl($cdnDomain, $filePath);
}
/**
* Use the file extension to pick the CDN domain for an asset.
* @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;
}
}
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.
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 is also implemented.
The implementation of the __invoke method means that the class can be used as if it were a method. It takes a single parameter, the relative path of the file to link to, and returns the CDN URL for that file. The logic is small but worth walking through: __invoke reads the cdn settings from the application config and returns the path unchanged when no CDN is configured; getCdnDomain matches the file’s extension against those settings to choose a host, falling back to the default entry; and getCdnUrl joins the host and path into a protocol-relative //host/path URL, so the asset loads over whatever scheme the page is served on. An example below shows it in action.
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 CDN servers themselves are configured in config/autoload/global.php, an example of which is shown below:
return [
"cdn" => [
"css|js" => "//assets.example.com/",
"jpg|jpeg|png|gif" => "//images.example.com/",
"default" => "//cdn.example.com/",
],
];
As you can see, you can add multiple CDN servers for different file types, and this configuration file is loaded when the application starts.
Note: The
defaultentry must exist; otherwisegetCdnDomainthrows a runtime exception for any file whose extension does not match a configured group.
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? That is what lets the helper be called like a method: the single argument is the path of the file to link to, and $this->cdn(...) returns the rewritten CDN URL that headLink() then renders.
Conclusion
You now have a reusable view helper that rewrites asset paths to your CDN, with the target host chosen per file type from a single config entry. The same recipe (extend AbstractHelper, implement __invoke, and wire up any dependencies through a factory) is how most custom view helpers are built, whether or not a CDN is involved. On a current Laminas application the only real change is the namespace: register the factory under the view_helpers config key (see the docs below) instead of the ZF2 Module::getViewHelperConfig() shown here.
References
- Migration to Laminas: the official guide and tooling for porting
Zend\code toLaminas\. - Advanced usage of helpers (laminas-view): writing and registering custom view helpers, including factory-injected dependencies.
- Laminas Project: the community-supported continuation of Zend Framework.
The Disqus comment system is loading ...
If the message does not appear, please check your Disqus configuration.