Presentation refers to the part of the application that is visible to the user - the user interface - and the handling of user interaction.
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
getInstance method may be used as a service locator.
The following example shows how to get the persistence service at any code location:
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.
The example shows the three important methods of the
initializemethod is used to setup the
Application. It returns a
Requestinstance, that may be modified before execution.
runmethod is called to execute the given request. The method returns a
Responseinstance, that is not used in this example.
handleExceptionmethod 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 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:
Requestinstance is passed to
ActionMapperfor further processing.
- The Action Key is determined from the request parameters.
PermissionManageris asked to authorize the action key for the current user (see Checking permissions).
- If authorization is successful, the request data is transformed into the internal application format (see Formats).
Controllerinstance matching the request is determined (see Routing) and executed.
Responseinstance is obtained after execution.
- The response data is transformed into the requested response format (see Formats).
- Execution returns to step 3, if a valid action key is contained in the response data and terminates, if no action key is found or the response is finalized by calling the method
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:
HtmlFormatexpects all data to be sent as key-value-pairs. Object data are transferred in parameters named
value-title-Book:3). Responses in this format are rendered as HTML views (see Views).
JsonFormathandles JSON encoded request data and encodes response data into the same format.
SoapFormatis used together with the NuSOAP library to implement a SOAP interface.
NullFormatis 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:
Content-Typeheader defines the request format
Acceptheader defines the response format
Formats are defined in the
Formats configuration section as shown in the following example:
Routing is the process of selecting the correct
Controller for a given request. wCMF distinguishes between internal and external 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 several actions together. If no matching action key is found, the response is returned to the client.
- The first line states that the action indexAll called from any controller and in any context will invoke
SearchIndexController, which will set a state dependent action name on the response (see
- The second line tells
SearchIndexController, if it was the last controller and the action is continue. So this action key only matches, if the last controller was
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 may be defined in the action mapping, like illustrated in the following example:
In this case
SearchIndexController would have to define the methods
doContinue which are called for the appropriate action keys.
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 may be overwritten by explicit request parameters. The character * is used as wildcard.
The following example configuration taken from the default application illustrates the concept:
- 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
- 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.
- Any requests on routes that are not defined in the
Routesconfiguration section will cause a
404error. This is why this section is also considered the public interface of the application.
To restrict a path to one or more HTTP methods, they may be 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:
If no method is added to a route, all methods are accepted.
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).
Controller::initializeis called directly after instantiation of the controller. The current
Responseinstances are passed as parameters and subclasses may override this method to implement task specific initializations.
Controller::validateis 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.
Controller::executeis 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
doExecutewhich must then be defined in the subclass.
Controllers can redirect to other controllers by using the
If variables need be shared between several requests to one controller they can be stored in local session variables to avoid side effects (see
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::handleExceptionwill be called. This method rolls back the current transaction and calls the failure action, which is executed by
- If a non-fatal error occurs, an instance of
ApplicationErrorshould be created and added to the response using the
Response::addErrormethod. The error class provides the
ApplicationError::getmethod to retrieve predefined errors. The following example shows how to signal an invalid type parameter while request validation:
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
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 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 the chosen that implementation. Since different actions may require different views to be displayed, a mapping of action keys to view templates is defined in the
Views configuration section.
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 value
html_tpl_format has to be set on the response instance. 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).
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.
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 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 may be retrieved by making a GET request to the url /rest/en/Author/1.
wCMF uses the NuSOAP library for implementing the SOAP interface. It consists of the
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 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 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 may be defined in the application configuration and retrieved by using
ObjectFactory, which makes it easy to exchange the underlying caching implementation.
The usage of this cache is illustrated in the code example:
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.
Besides setting caching headers by using the
Response::setHeader method, wCMF provides a convenient way to set the
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
Last Modified headers:
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:
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:
PersistenceEventis sent whenever a
PersistentObjectinstance is created, updated or deleted.
StateChangeEventsignal changes in
TransactionEventis fired at different phases of a transaction.
ApplicationEventallows to listen to the different steps of the request handling process.
The event system may be extended by custom events by inheriting from the
The class below is a basic example for a listener that subscribes to persistence events:
- To prevent memory leaks the
EventManager::removeListenermethod must be called, if the event listener is destroyed.
To send a persistence event, the following code is used:
In some cases you may 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:
Each entry in the
listeners array is supposed to refer to an instance configuration (see Dependency injection).
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 instances can be retrieved using the following code:
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