Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Subdomain Linking in a Zend Framework 2 Web Application

DZone's Guide to

Subdomain Linking in a Zend Framework 2 Web Application

· Web Dev Zone
Free Resource

Should you build your own web experimentation solution? Download this whitepaper by Optimizely to find out.

Recently, at work, I had a requirement for serving part of the ZF2-powered website from a different, non-www sub-domain. Problem was in fact that such requirement carries need of making all the other routes absolute, because when some link to a page on the default subdomain appears on a sub-domain page, it should be directed to the default (www) sub-domain, and not the current one. So here is the overview of a solution that I came up with in order to resolve this situation.

Zend Framework 2 introduces powerful routing mechanism, providing various strategies for defining application end-points. Among others, there's a Hostname route type, which allows matching of the request hostname.

In order to setup subdomain linking for the purpose of that separate site area/module, be it a Blog, its top route should be defined as a route of a hostname type:

'router' => array(
    'routes' => array(
        'blog' => array(
            'type' => 'hostname',
            'options' => array(
                'route' => 'blog.example.com',
                'defaults' => array(
                    'controller' => 'Application\Controller\BlogController',
                    'action' => 'index',
                ),
            ),
            'may_terminate' => true,
            'child_routes' => array(
                'post' => array(
                    'type' => 'segment',
                    'options' => array(
                        'route' => '/[:slug]',
                        'constraints' => array(
                            'slug' => '[a-zA-Z0-9_-]+'
                        ),
                        'defaults' => array(
                            'action' => 'view'
                        )
                    )
                )
            )
        )
    )
)

As a result, blog and it's posts will be accessible at the blog.example.com base URL. But some other links, like for example login/register links, should still point to the default domain, for example www.example.com/login, when rendered on some blog page. In order to achieve this, my idea was to dynamically override every non-hostname routes in the system in a way that they are prefixed with the default hostname route. In order to accomplish this, first, I defined abstract route, and named it "defaultDomain":

//...
'defaultDomain' => array(
    'type' => 'hostname',
    'options' => array(
        'route' => 'www.example.com',
    ),
),
//...

Then for the override part itself, I created a delegator, a wrapper around a factory for creating Router service, which intercepts routes configuration and inflects every non-hostname route into a chain route, consisting of a default domain route and the actual route:

public function getServiceConfig()
{
    return array(
        //...
        'delegators' => array(
            'Router' => array(
                function(ServiceLocatorInterface $sm, $name, $requestedName, $callback) {
                    if (!Console::isConsole()) {
                        $config = $sm->get('Config');
                        $routes = $config['router']['routes'];

                        $defaultDomain = $routes['defaultDomain'];
                        foreach ($routes as $name => $route) {
                            if ($route['type'] != 'hostname') {
                                $routes[$name] = array(
                                    'type' => 'chain',
                                    'options' => array(
                                        'routes' => array($defaultDomain, $route),
                                        'route_plugins' => $sm->get('RoutePluginManager')
                                    )
                                );
                            }
                        }
                        unset($routes['defaultDomain']);

                        //Save changes
                        $config['router']['routes'] = $routes;
                        $sm->setAllowOverride(true);
                        $sm->setService('Config', $config);
                        $sm->setAllowOverride(false);
                    }

                    return $callback();
                }
            ),
        ),
        //...
    );
}

In practice, I coded this a little bit differently, in a separate delegator factory class, but the point should be clear. By doing this, when, for example, user/login route is requested, it will always be assembled as an absolute URL: www.example.org/user/login on every page where it is requested, because it is now defined as a chain of the default domain route and the original route.

What I liked about this approach is that it is unobtrusive, meaning that if for ever reason that Blog module should be returned to the default subdomain, there will be no need for altering any other route except one for blog pages, because name/definition of that user/login route remained the same, and was not affected by the introduction of that root, base URL route.


Implementing an Experimentation Solution: Choosing whether to build or buy?

Topics:

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}