Router Architecture

From PKP Wiki
Jump to: navigation, search

Router and Component Architecture

Recent versions of PKP applications use a component architecture. Examples for components are:

  • pages
  • grids
  • list builders

Every component consists of a model (=DAO), a view (=template) and a controller (=handler). Components can be combined to form composite components. In other words: a component can be built from sub-components.

On the web, MVC component operations (=events) can be triggered over HTTP calls (including AJAX or XML-Http calls). This means that we have to map (=route) URLs to component handler operations.

PKP applications currently employ two different URL schemes - one for pages and one for sub-components. While page URLs are short, easy to read and search engine friendly, component URLs are more flexible and above all allow us to organize components in nested packages and sub-packages (=sub-directories) which is not possible with the page URL scheme. Page URLs appear in the browser's address bar, sub-component URLs usually don't. Most of the time sub-component controller operations are triggered over XML-HTTP calls.

For backwards compatibility, page controllers and other component controllers cannot currently be placed in the same directory structure. This doesn't mean that there is a principal difference between page handlers and other component handlers. They are all just component handlers and they all inherit from the same base classes.

This also means that the page routing scheme is just a special case of the more general component routing scheme. While page handler operations could in principle be addressed with the more general component routing scheme this is not usually possible the other way round.

The following UML sequence diagram gives an overview of the control flow for both routing schemes:

Router Architecture.jpg

How to use the router?

Router and dispatcher do two things:

  1. They map incoming requests to handler methods
  2. They construct URLs to be used in the view

Incoming calls

The router is used by the PKP framework to map incoming HTTP calls to component handler operations. It makes sure that component operation methods are called with two parameters:

  1. an array of arguments to the call ($args)
  2. a request object that represents the HTTP client request

A typical handler method will therefore use the following method signature:

/**
 * A sample handler method
 *
 * @param $args array of arguments to the call
 * @param $request PKPRequest a reference to a PKPRequest instance
 */
function handlerOp($args, &$request) {
  ...
}

Please note that:

  • The handler operation method signature is the same for page and other component handlers.
  • Statical calls to the Request class have been deprecated. Use calls to the $request instance instead.
  • All methods that are specific to the routing scheme (context resolving, handler resolving, operation resolving, url construction, caching) have been moved from the Application/Request to the Router/Dispatcher classes. The Request class still implements all methods for backwards compatibility but they are deprecated there. Make sure you don't use them any more.

Please re-factor deprecated method calls wherever you come across them.

Outgoing calls (URL construction)

Both router implementations (page router and component router) have a url() method that is used to construct URLs.

You can retrieve the current request scheme's router instance from the request:

...
$router =& $request->getRouter();
$url = $router->url(...);
...

If you need to construct a URL from within a non-handler class (e.g. a form class) you'll have to pass the $request instance along by reference via method parameters.

A router can only construct URLs according to it's own routing scheme. If you wish to construct a component router URL from within a request routed by a page router or vice versa then you have to use the dispatcher's more general url() method. All handlers get a reference to the dispatcher singleton instance before control is passed to the handler operation.

...
$dispatcher =& $this->getDispatcher();
$url = $dispatcher->url('page', ...); // for a page scheme url within a component scheme request
...

or

...
$dispatcher =& $this->getDispatcher();
$url = $dispatcher->url('component', ...); // for a component scheme url within a page scheme request
...

If you need to construct a URL via dispatcher from within a non-handler class (e.g. a form class) you'll have to retrieve the dispatcher from the global registry:

...
$dispatcher =& Registry::get('dispatcher');
$url = $dispatcher->url(...);
...

NB: State changing calls to components should be made over POST requests. Stateless calls should be made over GET.

Where to find more information?

The information on this wiki page is of a very general nature. If you need more specific information then please look at the following places:

Look at the classdoc (doxygen API documentation) of the router classes which you find in the core package. There you'll find a more complete description of the respective URL schemes.

If you need more information about the specific usage of a given dispatcher or router method then please look at the respective unit tests. You'll find the DispatcherTest, PKPComponentRouterTest, PKPPageRouterTest and PKPRouterTest classes in the directory tests/classes/core. There are a large number of examples that cover all use cases that have been considered while designing these classes.