Presentation

Presentation

Presentation refers to the part of the application that is visible to the user - the user interface - and the handling of user interaction.

Services

The presentation layer usually depends on underlying services like persistency, event handling, caching - just to name a few. These services typically exist as one instance that is created on application startup and is used throughout the whole application lifetime.

The preferred way to get access to a service instance in client code is to set the dependency explicitly or let it be injected, if the instance is not created explicitly (see Dependency injection). But there are also situations where this is not possible (e.g. a global function that is used by third party code). Since most of these services are registered with ObjectFactory, it's getInstance method is used as a service locator.

The following example shows how to get the persistence service at any code location:

$persistenceFacade = ObjectFactory::getInstance('persistenceFacade');

Application

Web applications typically implement a request-response pattern, where a client sends a (HTTP-)request to the application, which returns a response after processing it. wCMF encapsulates the described procedure inside the Application class. The following code demonstrates the usage of this class in the main entry script of the default application.

$application = new Application();
try {
// initialize the application
$request = $application->initialize('', '', 'cms');
// run the application
$response = $application->run($request);
}
catch (Exception $ex) {
try {
$application->handleException($ex);
}
catch (Exception $unhandledEx) {
echo("An unhandled exception occured. Please see log file for details.");
}
}

The example shows the three important methods of the Application class:

  • The initialize method is used to setup the Application. It returns a Request instance, that could be modified before execution.
  • The run method is called to execute the given request. The method returns a Response instance, that is not used in this example.
  • The handleException method is called, if an exception occurs. The method rolls back the database transaction and calls the failure action.

The details of request execution are the topic of the next section.

Request processing

The Request instance created on initialization of the application provides all information about the incoming HTTP request, that is necessary for execution. Upon execution, the following actions are performed:

  1. The Request instance is passed to ActionMapper for further processing.
  2. The Action Key is determined from the request parameters.
  3. PermissionManager is asked to authorize the action key for the current user (see Checking permissions).
  4. If authorization is successful, the request data is transformed into the internal application format (see Formats).
  5. The Controller instance matching the request is determined (see Routing) and executed.
  6. The Response instance is obtained after execution.
  7. The response data is transformed into the requested response format (see Formats).
  8. Execution returns to step 3, if a valid action key is contained in the response data. It terminates, if either no action key is found or the next action key would be the same as the previous (to prevent recursion).

Formats

wCMF is designed to be able to consume various request formats and produce several response formats. While some clients communicate using JSON format, others might prefer to encode data in XML. Formatter is used to determine the required format and delegate the actual formatting to the correct Format implementation. wCMF currently provides the following implementations:

  • HtmlFormat expects all data to be sent as key-value-pairs. Object data are transferred in parameters named value-name-oid (e.g. value-title-Book:3). Responses in this format are rendered as HTML views (see Views).
  • JsonFormat handles JSON encoded request data and encodes response data into the same format.
  • SoapFormat is used together with the NuSOAP library to implement a SOAP interface.
  • GenericFormat is used to output arbitrary responses.
  • DownloadFormat is used to create a downloadable reponse document. It is automatically chosen, if the Response::setDocument method is called with a ResponseDocument.
  • NullFormat is used internally, if no formatting is required, e.g. if one controller calls another controller.

If not explicitely set, the request and response format is automatically determined from the HTTP headers sent with the request:

  • The Content-Type header defines the request format
  • The Accept header defines the response format

To find the correct format, the Media Type value set in those headers is matched against the mime type of all registered formats (see Format::getMimeType).

Formats are defined in the Formats configuration section as shown in the following example:

[Formats]
html = $htmlFormat
null = $nullFormat
[HtmlFormat]
__class = wcmf\lib\presentation\format\impl\HtmlFormat
[NullFormat]
__class = wcmf\lib\presentation\format\impl\NullFormat

Routing

Routing is the process of selecting the correct Controller for a given request. wCMF distinguishes between internal and external routing.

Internal routing

Internal routing takes place after the Request instance is created and initialized. wCMF inspects the action key formed from it's sender, context and action parameters (see Action Key) to determine the controller to be executed for the request. The mapping of action keys to controllers is defined in the ActionMapping configuration section.

If the executed controller together with the context and action parameters of the response match another action key, the corresponding controller will be executed afterwards. This allows to chain actions together. If no matching action key is found, the response is returned to the client.

The following code is taken from the default application configuration and shows the configuration of the indexing process (see Long running requests):

[ActionMapping]
??indexAll = wcmf\application\controller\SearchIndexController
wcmf\application\controller\SearchIndexController??continue =
wcmf\application\controller\SearchIndexController

Controller methods

The previous example maps the action keys to a controller class without specifying a method to be called. In these cases, the framework calls the default method doExecute, which must then be defined in the controller class (see Controllers).

Alternatively a specific controller method to be called could be defined in the action mapping, like illustrated in the following example:

[ActionMapping]
??indexAll = wcmf\application\controller\SearchIndexController::doBegin
wcmf\application\controller\SearchIndexController??continue =
wcmf\application\controller\SearchIndexController::doContinue

In this case SearchIndexController would have to define the methods doBegin and doContinue which are called for the appropriate action keys.

External routing

The mapping of the current request uri to an action key is called external routing. The default mapping logic is implemented in the DefaultRequest::initialize method. The method matches the path part of the request uri against the entries of the Routes configuration section to find an appropriate action key.

Variables declared in path segments will be automatically passed as request parameters, but would be overwritten by explicit request parameters if provided. The character * is used as wildcard. Regular expression patterns can be added to variables to narrow the set of possibilities.

The following example configuration taken from the default application illustrates the concept:

[Routes]
POST/session = action=login
DELETE/session = action=logout
/rest/{language}/{className} = action=restAction&collection=1
/rest/{language}/{className}/{id|[0-9]+} = action=restAction&collection=0
GET/* = action=cms
  • The first two entries define the login and logout routes using the appropriate HTTP methods.
  • The third entry defines the language and className variables (surrounded by curly braces) and would be matched by the request uris /rest/de/Author or /rest/en/Book. The executed action would be restAction.
  • The id variable in the next entry must be an integer because of the regular expression constraint [0-9]+.
  • The last entry is matched by any GET request to a path that was not matched before. It is mapped to the cms action - corresponding to the action key ??cms.
Note
Any requests on routes that are not defined in the Routes configuration section will cause a 404 error. This is why this section is also considered the public interface of the application.

Precedence rules

Due to the use of variables in route definitions several routes might match a given request. This leads to the question which route to take, since the application only processes one per request. To find the best matching route DefaultRequest orders all matching routes and takes the first one. The sorting is done under the assumption that a more specific route is a better match.

The following rules are implemented to find the more specific route:

  • the route with less variables is more specific and if two routes have the same number of variables
  • the route with more patterns in the variable definitions is more specific.

In cases where these simple rules are not sufficient, you could configure a custom Request implementation (see Dependency injection) which overrides the DefaultRequest::isMatch method.

HTTP methods

To restrict a path to one or more HTTP methods, they are added in front of the route definition. In the following example the cms action is only available for the GET method, while the other actions accept GET, POST, PUT and DELETE requests:

[Routes]
GET/ = action=cms
GET,POST,PUT,DELETE/rest/{language}/{className} = action=restAction&collection=1
GET,POST,PUT,DELETE/rest/{language}/{className}/{id|[0-9]+} = action=restAction&collection=0

If no method is added to a route, all methods are accepted.

Controllers

Controllers take the user input from the request and modify the model according to it. As a result a response is created which is presented to the user in a view or any other format. Which controller is executed on a specific request is determined in the routing process (see Routing).

wCMF provides Controller as abstract base class for controller implementations. There are three important methods defined in this class, which are called by ActionMapper in the following order:

  1. Controller::initialize is called directly after instantiation of the controller. The current Request and Response instances are passed as parameters and subclasses can override this method to implement task specific initializations.
  2. Controller::validate is called afterwards to check the validity of the request parameters. The default implementation returns true and subclasses are assumed to override this method if necessary to do task specific validations.
  3. Controller::execute is called finally. This method accepts a parameter, that defines the actual method to be called on the subclass. If the parameter is omitted it defaults to doExecute which must then be defined in the subclass (see \pres_controller_methods).

Controllers can redirect to other controllers by using the Controller::redirect method.

If variables need to be shared between several requests to the same controller they can be stored in local session variables to avoid side effects (see Controller::setLocalSessionValue).

Error handling

Errors are typically divided into fatal errors and non-fatal errors.

By definition the application is not able to recover from a fatal error, meaning that it's not functioning correctly. An example would be a programming error or a missing vital resource. These errors normally need to be fixed by the application maintainer. In case of non-fatal errors a notice to the user is sufficient in most cases. A typical example would be invalid user input, that can be fixed by the user itself.

In wCMF the following two strategies are recommended for handling these kind of situations:

  • In case of a fatal error, an exception should be thrown. If it is not caught inside the application code, it will bubble up to the main script (usually index.php). In case the application is set up like in the Application section, the method Application::handleException will be called. This method rolls back the current transaction and calls the failure action, which is executed by FailureController by default.
  • If a non-fatal error occurs, an instance of ApplicationError should be created and added to the response using the Response::addError method. The error class provides the ApplicationError::get method to retrieve predefined errors. The following example shows how to signal an invalid type parameter while request validation:
$response->addError(ApplicationError::get('PARAMETER_INVALID',
['invalidParameters' => ['type']]));

Action composition

The Internal routing section describes how chaining controller actions works. This is usefull, if there is a sequence of actions to be executed. If a complex action can be composed from simpler (already existing) actions, it is also possible to execute another controller inside a controller action and return to that action afterwards. This is done using the Controller::executeSubAction method. An example is RESTController which serves as a facade to the CRUD controllers.

Automatic transactions

To run a controller method inside a persistence transaction the convenience method Controller::requireTransaction can be called at the beginning of the method. This call will start a transaction or join the already opened one. The transaction will be automatically committed after method execution or rolled back in case of an exception. Calls to Controller::executeSubAction will use the same transaction.

Long running requests

There are situations where you want to split a long running process into parts, because it's exceeding memory or time limits or simply to give the user feedback about the progress. By subclassing BatchController the implementation of this behavior is as simple as defining the steps of the process in the BatchController::getWorkPackage method.

SearchIndexController is an example for a controller implementing a long running process. It is used to create a Lucene search index over all searchable entity objects. The process is split into collecting all object ids and then indexing them. In the final step the index is optimized.

Views

Views are used to present application information to the user. In a web application they are typically HTML pages displayed in the browser.

In wCMF the response will be turned into an HTML page, if the Accept HTTP header is set to text/html (see Formats). The appropriate Format implementation is HtmlFormat. It renders the response into a template file using the configured View implementation. The format of the template files depends on that implementation. Since different actions could require different views to be displayed, a mapping of action keys to view templates is defined in the Views configuration section.

The following example shows the configuration of the SmartyView class and the mapping of action keys to views in the default application:

[View]
__class = wcmf\lib\presentation\view\impl\SmartyView
__shared = false
compileCheck = true
caching = false
cacheLifetime = 3600
cacheDir = app/cache/smarty/
[Views]
app\src\controller\RootController?? = app/src/views/cms.tpl

Since the default application only uses an HTML page to bootstrap the actual Dojo application, there is only one view mapping for RootController.

Device dependent views

HtmlFormat allows to provide different versions of a view template. This is especially useful, if you want to deliver device dependent content for the same action key.

To select a specific template version, the property html_tpl_format has to be set on the response instance (see ControllerMessage::setProperty). E.g. if the template file would be home.tpl, setting the value to mobile would select the template file home-mobile.tpl. If the requested version does not exist, it is ignored and the default template is used (home.tpl in this example).

Webservice APIs

Besides the user interface driven default application wCMF provides APIs for using the application as a web service. These APIs provide create/read/update/delete (CRUD) operations on all entity types.

RESTful API

The RESTful interface is implemented in RESTController, which basically acts as a facade in front of the application. That means the controller checks the request data only and delegates the actual processing to the action specific controller.

The following example shows the configuration of the RESTful interface in the default application:

[Routes]
/rest/{language}/{className} = action=restAction&collection=1
/rest/{language}/{className}/{id|[0-9]+} = action=restAction&collection=0
/rest/{language}/{className}/{sourceId|[0-9]+}/{relation} = action=restAction&collection=1
/rest/{language}/{className}/{sourceId|[0-9]+}/{relation}/{targetId|[0-9]+} = action=restAction&collection=0
[ActionMapping]
??restAction = wcmf\application\controller\RESTController

The Routes configuration section defines the urls for the interface. They all call the action restAction internally, which is handled by RESTController as defined in the ActionMapping section. For example the english version of the Author instance with id 1 is retrieved by making a GET request to the url /rest/en/Author/1.

SOAP API

wCMF uses the NuSOAP library for implementing the SOAP interface. It consists of the SoapServer and SOAPController classes. The controller class handles all requests and delegates the processing to the server class. The service description in the WSDL format is generated by the code generator into a file called soap-interface.php (see PHP Code).

The following example shows the configuration of the SOAP interface in the default application:

[Routes]
/soap = action=soapAction
[ActionMapping]
??soapAction = wcmf\application\controller\SOAPController

The Routes configuration section defines that the url /soap redirects to the action soapAction internally. The ActionMapping section defines that this action is handled by SOAPController. The interface description is available at the url /soap?wsdl.

Caching

Caching is an effective method to improve application performance. Caches in wCMF are supposed to be divided into sections, which hold key-value pairs (see Cache interface). Cache instances are defined in the application configuration and retrieved by using ObjectFactory, which makes it easy to exchange the underlying caching implementation.

The following example shows the configuration of a FileCache instance in the default application:

[Cache]
__class = wcmf\lib\io\impl\FileCache
cacheDir = app/cache/

The usage of this cache is illustrated in the code example:

$cache = ObjectFactory::getInstance('cache');
$cacheSection = 'calculations';
$cacheKey = 'resultA';
if (!$cache->exists($cacheSection, $cacheKey)) {
// calculate the result and store it in the cache
$result = complexCalculationA();
$cache->put($cacheSection, $cacheKey, $value);
}
else {
// retrieve the result from the cache
$resultA = $cache->get($cacheSection, $cacheKey);
}

Supposed that complexCalculationA in this example takes long to finish, it should only run once at the first time the result is needed. So we check if resultA is already cached and calculate it, if not. Since it is put it into the cache after calculation, it can be retrieved resultA directly from there the next time it is needed.

Response caching

Besides setting caching headers by using the Response::setHeader method, wCMF provides a convenient way to set the Etag and Last Modified headers on the response.

All one needs to do is derive a cache identifier from the request data, that allows to distinguish different requests and set it on the response using the Response::setCacheId method. If the used Format is capable of caching (e.g. HtmlFormat) it will provide the last modified date from the cached data. DefaultFormatter will then use this information to create the Etag and Last Modified headers:

...
Cache-Control:public
ETag:"93256174cf75e43ba8172a6ce01ceded"
Last-Modified:Fri, 07 Oct 2016 19:19:13 GMT
...

Another advantage of using a cache identifier is the possibility to avoid expensive calculations in Controller implementations by using the Response::isCached method. The following example shows it's usage:

// define a unique id for the request data and set it on the response
$cacheId = $pageId.$language;
$response->setCacheId($cacheId);
if (!$response->isCached()) {
// this will be executed only, if the response is not cached yet
...
}

Events

Events are a way to decouple parts of a software by introducing an indirect communication. They allow clients to react to certain application events without the event source knowing them. wCMF uses the publish-subscribe messaging pattern, in which EventManager acts as the message broker. Subscribers use this class to register for certain Event types and publishers to send the events.

The following events are defined in wCMF:

The event system can be extended by custom events by inheriting from the Event class.

The class below is a basic example for a listener that subscribes to persistence events:

class PersistenceEventListener {
private $_eventListener = null;
public function __construct(EventListener $eventListener) {
$this->_eventListener = $eventListener;
$this->_eventListener->addListener(PersistenceEvent::NAME,
[$this, 'persisted']);
}
public function __destruct() {
$this->_eventListener->removeListener(PersistenceEvent::NAME,
[this, 'persisted']);
}
/**
Listen to PersistenceEvent
@param $event PersistenceEvent instance
*/
public function persisted(PersistenceEvent $event) {
// do something on any create/update/delete
}
}
Note
To prevent memory leaks the EventManager::removeListener method must be called, if the event listener is destroyed.

To send a persistence event, the following code is used:

$eventListener = ObjectFactory::getInstance('eventManager');
$eventListener->dispatch(PersistenceEvent::NAME,
new PersistenceEvent($object, PersistenceAction::UPDATE));

Implicit listener installation

In some cases you might want to install event listeners without explicitely instantiating them, because there is no appropriate place for that. For these cases the Application class reads the listeners value of the Application configuration section and initializes all instances listed there.

The default application defines two listeners as shown in the following example:

[Application]
listeners = {Search, EventListener}

Each entry in the listeners array is supposed to refer to an instance configuration (see Dependency injection).

Logging

wCMF integrates the logging frameworks log4php and Monolog. To abstract from these libraries wCMF defines the Logger interface and implementations for each framework. The decision, which framework to use is made by instantiating the appropriate Logger instance and passing it to the LogManager::configure method as shown for Monolog in the following example:

$logger = new MonologFileLogger('main', WCMF_BASE.'app/config/log.ini');
LogManager::configure($logger);

Afterwards Logger instances can be retrieved using the following code:

$logger = LogManager::getLogger(__CLASS__);

The parameter used in the LogManager::getLogger method is the logger name. It's a good practice to use the __CLASS__ constant as logger name, since this allows to enable/disable loggers by class names in the configuration (see Logging configuration).

The following example shows how to log an error message with a stack trace appended (see ErrorHandler::getStackTrace):

$logger->error("An error occured.\n".ErrorHandler::getStackTrace());