This document describes how a system can be formed to set up modules in combination with a tree of pages stored in the database. The general idea is to have pages stored within a database in a tree structure, where every page is coupled to a module (1:n). Pages are queried and parsed to routes as a combination of the page route and the modules route(s).
Webapplications exist often with a frontend (for normal visitors) and backend (to manage the whole bunch). Also some pages might want to utilize the same module (two simple text pages as basic example). Thirdly, it should be easy for end users to create new pages, modify them or delete pages. To accomplish this, a robust system ("content management framework") should be made on top of zf2 to accomodate this.
A module registers the routes to the Application\Router\Listener
, aliased as "router":
'di' => array(
'instance' => array(
'router' => array(
'parameters' => array(
'routes' => array(
'blog' => array(
'type' => 'literal',
'options' => array(
'defaults' => array(
'controller' => 'blog-article',
'action' => 'index',
),
),
),
),
),
),
),
),
The key for the routes is the name under which the module is registered for the page. In other words, a $page->getModule()
would return "blog" to load above route configuration. The route is in this case a single route, but child routes can be added too.
Of course it is possible to have aliased controllers or FQCN as a controller, this is completely up to the module.
An early listener is registered for the "route" event to load all pages, parse the routes based on the page route and the module's route(s) and inject those routes into the router. As an example, where $page->getRoute()
would return "/blog", $page->getId()
is 1 and $page->getModule()
is "blog" and a module "blog" is registered with above configuration into the router
, the result is this:
array (
1 =>
array (
'type' => 'literal',
'options' =>
array (
'route' => '/blog',
'defaults' =>
array (
'controller' => 'blog-article',
'action' => 'index',
'page-id' => 1,
),
),
),
),
At this point, all controllers from the modules can be dispatched and follow the general zf2 path.
Of course you might have two pages ("products" and "services") both be for the module "Text". How does this text module know to show for /products the products texts and for /services the services texts? This happens through the same Application\Router\Listener
which has also an early "dispatch" listener. In that callback, the Application\Model\Page
, the model representing a single page, is queried for the current routeMatch
.
As shown above, every route has a parameter page-id, which is the id for the page currently requested. The database is simply queried for that primary key. This page is injected as "page" parameter in the MvcEvent
.
Every controller can attach a listener to it's dispatch event and grab the page from there. If you want to keep the code DRY, you can also use Application\Controller\ActionController
as base class instead of Zend\Mvc\Controller\ActionController
. The application's ActionController has a listener and sets a property $page.
Now you have the page, you want to know whether it's the "products" or "services" page. To accomplish this, Application\Model\Page
has not only a method getModule()
but also a getModuleId()
. It allows a module to place an identifier into the page to, upon request, know which typical page is requested. In the most simple case, the "Text" module stores the primary key for the texts "products" and "services" as module identifier inside the page.
This system is set up with the EventManager to provide hooks for other modules. Currently one additional event is triggered. The listener for the early route event triggers an route.init. For the route.init event, two listeners are attached by default:
- Load pages from database, parse all pages to routes and set the list of routes as param in the event;
- Load routes from event and inject them into the router.
This makes it possible to have events for caching the routes or modifying the routes. An example of the latter is i18n, where the i18n modules takes all routes and transforms them somehow in i18n versions. The second listener will use these new routes to set them into the router.
please come by #jackalope IRC to discuss or send us a mail to the mailing list ... we are very interested in collaboration on the PHPCR / PHPCR ODM level.