Front controller

1. Introduction

As decribed in chapter Basics request processing of the Adventure PHP Framework is designed with the boot strapping principle in mind. This means that all requests to your web site or web application are processed by one central file. The APF clearly follows this paradigm to allow central initialization, configuration, and request processing.

Within the boostrap file the Frontcontroller helps you to execute common tasks during the request processing without additional coding. As a central and integrating component, the front controller

2. Timing model

The implementation of the Frontcontroller of the Adventure PHP Framework is based on the pattern definition of Martin Fowler. The APF uses the Page controller to provide a generic implementation of the HMVC pattern for composition and creation of the presentation layer. For this reason, the implementation differs from classic MVC frameworks where the front controller is only used for mapping the request to one controller.

The timing model displayed below describes the process of request management of the framework and the extension points using actions.

Front-Controller timing model

Since the front controller is used as a central instance for request handling there is only one instance of the Frontcontroller class. Within the bootstrap file the instance is created using the Creation of objects implementation and started with the start() method.

Wiki page Unterschied Front-Controller und Page-Controller (German) describes the difference between the front controller and page controller along with common use cases of the front controller.

3. Actions

Front controller actions are intended to execute logic that is designed to run at a certain point in time. This is true for several HMVC elements accessing the same data but the order of execution of those elements cannot be determined at development time - e.g. because editors are able to change the order through a CMS at runtime.

Another use case is encapsulation and re-usability of logic that initializes an application (e.g. filling a (view-)model) or executes view logic (e.g. check user permissions). Besides executing view logic TYPE_PRE_PAGE_CREATE actions can also be used to deliver dynamic resources (e.g. images) or process tracking data collected during the application run within the TYPE_POST_TRANSFORM phase.

3.1. Definition

The timing model in chapter 2 defines four different points in time where actions are executed. These time slots can be used to solve different tasks within your application. The points in time mentioned are defined as constants within the AbstractFrontcontrollerAction:

  • TYPE_PRE_PAGE_CREATE: action is executed after execution of the input filters and prior to creation of the Page controller.
  • TYPE_PRE_TRANSFORM: action is executed before transformation of the page (transform()) by the Page controller.
  • TYPE_POST_TRANSFORM: action is executed after transformation if the page (transform()) and prior to execution of the output filters.

Further notes on the page controller and it's timing model can be found under Page controller or within the Wiki (German).

The above listed constants can be used to define the execution time when implementing actions as follows:

PHP code
class HeadlineImageGenerationAction extends AbstractFrontcontrollerAction { // Define execution time at class declaration protected $type = self::TYPE_PRE_PAGE_CREATE; public function __construct() { // Define execution time at object creation time $this->type = self::TYPE_PRE_PAGE_CREATE } public function run() { ... } }
Please note that execution time can only be defined during implementation of the action class filling the class member $this->type. Changes on runtime are not allowed!

3.2. Configuration

The front controller of the APF offers two different types of actions:

  • Static actions that are registered within the bootstrap file for all requests.
  • Dynamic actions that are addressed via URL and executed on demand.

Static actions are normally used to initialize a view model that is used by several HMVC elements or in case the log-in state of your users must be checked prior executing the main application for security reasons.

Dynamic actions are normally used to deliver dynamic components of your application (e.g. dynamically created images) through one central bootstrap file of to execute view logic only in certain situations (e.g. execution of a search query).

The following chapters describe the configuration and execution of static and dynamic actions.

3.2.1. Static Actions

Static actions can be registered within the bootstrap file before starting the front controller. The order of execution directly depends on the order of registration.

The below code sample shows two actions that are registered as static actions. One initializes a view model and the other one checks the user permissions:

PHP code
include('../apps/core/bootstrap.php'); use APF\core\singleton\Singleton; use APF\core\frontcontroller\Frontcontroller; $fC = Singleton::getInstance(Frontcontroller::class); ... $fC->addAction('APF\site\actions', 'init-model'); $fC->addAction('APF\site\actions', 'check-permissions'); ...

addAction() takes two arguments: the namespace where the action configuration resides and the name of the action. Details on the configuration and addressing of actions can be taken from chapter 5.1.

init-model and check-permissions are now executed for each request and in the provided order.

In case an action should be configured for a certain use case or you wish to pass dynamic or static parameters you may want to use the third argument of method addAction():

PHP code
include('../apps/core/bootstrap.php'); use APF\core\singleton\Singleton; use APF\core\frontcontroller\Frontcontroller; $fC = Singleton::getInstance(Frontcontroller::class); ... $fC->addAction( 'APF\site\actions', 'init-model', [ 'foo' => 'bar', 'baz' => $_REQUEST['baz'] ] ); ...
3.2.2. Dynamic actions

Dynamic actions can be addressed using URL parameters and thus executed on demand. This mechanism is provided by the input filters shipped with the APF. They analyze the current URL, extract the action instructions and apply them to the front controller.

As described in URL rewriting the APF ships two types of input filters: the ChainedStandardInputFilter filters standard URLs and the ChainedUrlRewritingInputFilter resolves rewritten URLs to an internal representation. The URL scheme of action instructions is as follows:

Code
{namespace}-action:{action-name}={param1}:{value1}|{param2}:{value2}|...

{namespace} refers to the namespace of the action configuration file and {action-name} is the name of the action. Parameter couples are separated by "|" (Pipe), name and value by ":". Within one URL you can add any number of action instructions along with standard request parameters. The following URL contains two action instructions as well as further request parameters:

Code
?VENDOR_projects_projectone-action:setModel=pageid:1|lang:de&news-page=3&VENDOR_projects_projectone-action:stat=action:view|referer:32
Please note, that the order of the action instructions within the URL defines the order of execution within a group of equally typed actions! Details on types can be taken from chapter 3.1..

The schema for rewritten URLs has been designed to allow definition of action instructions and normal parameters as well. For this reason the separator "/~/" is used to delimit action instructions from normal parameters and action instructions from other action instructions. Addressing actions for rewritten URLs is as follows:

Code
/~/{namespace}-action/{config-name}/{param1}/{value1}/{param2}/{value2}/...

The above example looks as follows for rewritten URLs:

Code
/~/VENDOR_projects_projectone-action/setModel/pageid/1/lang/de/~/news-page/3/~/VENDOR_projects_projectone-action/stat/action/view/referer/32
The URL scheme is designed with generality in mind. In cas the URL layout does not meet your requirements for SEO or other reasons you can easily change it using input filters. Action calls can be generated by RewriteRules as well as by adapting the input filters that are in charge of decoding the URL. Hints on implementing custom filters can be found in the Wiki (German).
3.2.3. Action URL mapping
Please note, that the feature described in this chapter is available as of release 2.1.

The URL scheme described in chapter 3.2.2 is designed as a general-purpose format. At the same time, it might be inappropriate from an SEO point of view since action URLs may become very long and ugly.

In order to shorten action URLs and make them more attractive you may register URL mappings for both static and dynamic actions. These kind of aliases are used by shipped APF input filters ChainedStandardInputFilter and ChainedUrlRewritingInputFilter to translate action instruction into an internal format. During link generation using the LinkGenerator both LinkScheme implementations DefaultLinkScheme and RewriteLinkScheme read all mappings registered with the Frontcontroller to convert action instructions into an external format.

The number of action mappings is not limited from a technical point of view. You may define aliases for several or all actions within a URL.

In case your search functionality used a front controller action the generated URL may be the following according to the standard configuration:

Code
?VENDOR_components_search-action:executeSearch=type:faq

In case you are using rewritten URLs (details see URL rewriting) the LinkGenerator formats the URL as follows:

Code
/~/VENDOR_components_search-action/executeSearch/type/faq

To shorten the URL and make it more attractive the APF lets you define an ActionUrlMapping. Please add the following lines to your bootstrap file (index.php) or adapt it as necessary:

PHP code
use APF\core\singleton\Singleton; use APF\core\frontcontroller\Frontcontroller; use APF\core\frontcontroller\ActionUrlMapping; $fC = Singleton::getInstance(Frontcontroller::class); ... $fC->addAction('VENDOR\components\search', 'executeSearch'); $fC->registerActionUrlMapping( new ActionUrlMapping('search', 'VENDOR\components\search', 'executeSearch') ); ...

To generate short action URLs, please use the following code that used all aliases registered with the front controller:

PHP code
use APF\tools\link\LinkGenerator; use APF\tools\link\Url; $link = LinkGenerator::generateActionUrl( Url::fromCurrent(), 'VENDOR\components\search', 'executeSearch', array('type' => 'faq') );

Your search functionality is now triggered via

Code
?search=type:faq

or

Code
/search/type/faq

using rewritten URLs respectively.

Please be careful with defining aliases to avoid potential overlapping with normal URL parameters. In case of ambiguous parameters correct analysis or generation of the URL cannot be guaranteed by the framework!

In addition to the classic approach of registering action mappings you may want to use two further options: provide URL parameter on action registration (Frontcontroller::addAction()) or add one or more configuration files.

In case your bootstrap file already defines static actions you may want to use the fourth parameter of the addAction() method. Registration of an action including an URL alias is as follows:

PHP code
$fC->addAction('VENDOR\components\search', 'executeSearch', array(), 'search');
Please note, that the third argument is optional. In case your action should be pre-configured at registration time you can add any number or parameters as desired. You can access them via the Input object within your action.
Please be sure to register actions and mappings before starting the front controller via Frontcontroller::start()!

To separate Action-Mappings and code you are also able to provide a configuration file to the front controller that contains mappings.

For this reason, you may want to use method registerActionUrlMappings(). It takes namespace and name of the configuration. The schema of the configuration is as follows:

APF configuration
[{Url-Token}] ActionNamespace = "" ActionName = ""

The following code box contains the configuration section necessary for our search example:

APF configuration
; search <-> VENDOR_components_search-action:search [search] ActionNamespace = "VENDOR\components\search" ActionName = "executeSearch"
Within one configuration file you may specify any number of action mappings. Further, the front controller takes any number of URL mapping configurations. Thus, you are free to define the structure of your project configuration files just as you like.

Registration of the action mapping described above can be done as follows within your bootstrap file:

PHP code
use APF\core\singleton\Singleton; use APF\core\frontcontroller\Frontcontroller; use APF\core\frontcontroller\ActionUrlMapping; $fC = Singleton::getInstance(Frontcontroller::class); ... $fC->registerActionUrlMappings('VENDOR\components\search', 'url-mappings.ini'); ...
Please be aware, that registration of action mappings must be defined within a separate configuration file. This is because analysis of the URL happens before action execution according to the timing model.
Please be sure to register action mappings before starting the front controller.

3.3. Action creation via DI container

Besides creating actions by the front controller by providing the name of the action class you can also create actions using the DI container. This approach is recommended with complex scenarios where actions make use of other application components. Dependent objects can easily be injected by the DIServiceManager. Details on configuration and usage can be found under Services.

Configuration of actions does not differ too much from normal action configuration (see chapter 3.2.). In this case ActionClass is replaced by namespace and name of the service:

APF configuration
[{Action-Name}] ActionServiceNamespace = "" ActionServiceName = "" [InputClass = ""] [InputParams = ""]
  • ActionServiceNamespace: Defines the namespace of the service configuration (example: APF\project\biz\actions)
  • ActionServiceName: Defines the name of the service that describes the action (example: initialize-model)

For the above example the front controller requires a configuration file config/project/biz/action/{CONTEXT}/{ENVIRONMENT}_serviceobjects.ini that defines service initialize-model. The content of the file may be as follows:

APF configuration
[initialize-model] class = "APF\project\biz\actions\LoadModelAction" servicetype = "NORMAL" init.model.method = "setModel" init.model.namespace = "APF\project\biz\model" init.model.name = "ApplicationModel"

4. Bootstrap file

As noted in the Introduction the APF uses the boot strapping paradigm. Within the bootstrap file the front controller plays a central role for request processing.

The below code box presents a minimal bootstrap file that can be used for any kind of applications as a start:

PHP code
include('./APF/core/bootstrap.php'); use APF\core\singleton\Singleton; use APF\core\frontcontroller\Frontcontroller; $fC = Singleton::getInstance(Frontcontroller::class); echo $fC->start('...', '...');

The first line includes the bootstrap.php file that loads al necessary APF components and initially configures the framework. In case your application requires a different configuration of various components you can change settings prior starting the front controller.

The front controller can be configured with the following methods:

  • setContext(): Defines the context of the application and thus of all objects created by the APF within your application. This parameter is merely used for Configuration.
  • setLanguage(): Defines the language of the application. This parameter can be used in multi-language applications to display language-dependent content.

Further configuration possibilities within the bootstrap file are described under Basics.

5. Implementation of actions

A front controller action within the APF consists of a configuration and a PHP class that inherits from AbstractFrontcontrollerAction. The separation between implementation and configuration provides a clear separation of the implementation from the outside view and enables you to resolve dependencies and configurations transparently. Moreover, the abstraction of action instructions using the URL adds to the security of your application since the internal structure of your application is not exposed to the outside.

Input parameters of an action are stored within an instance of the FrontcontrollerInput class or any class that inherits from it. Through this class you are given the possibility to access all parameters within an action that are either applied via URL or configuration.

5.1. Configuration

Each action is defined within a separate configuration section. Addressing an action, namespace and the name of the configuration section is uses. The name of the configuration file is {ENVIRONMENT}_actionconfig.ini per convention.

The configuration section contains the definition of the action implementation and an optional input implementation as well as static configuration parameters - also optional. The configuration parameters can be used to configure an action for a dedicated used case and thus use the code within multiple applications.

How configuration files are stored and all about the file name schema can be read about under Configuration.

In case an action with namespace APF\modules\captcha\biz and namen showCaptcha is requested to be executed by the front controller the configuration is expected to be stored under the namespace and within a {ENVIRONMENT}_actionconfig.ini file. Assuming that the application is configured with context projectone and other settings - e.g. the environment switch - are left as is, the configuration file path is expected as:

Code
/APF/config/modules/captcha/biz/projectone/DEFAULT_actionconfig.ini

erwartet. The configuration file thereby contains the following content:

APF configuration
[showCaptcha] ActionClass = "APF\modules\captcha\biz\ShowCaptchaImageAction"

The front controller uses this information to create an instance of the ShowCaptchaImageAction class and execute it according to the timing configuration (see chapter 3.1).

The scheme of an action configuration section is as follows:

APF configuration
[{Action name}] ActionClass = "" [InputClass = ""] [InputParams = ""]

Please note the following list with explanations on the above parameters:

  • Action name: External reference name of the action. This name is both used within the URL as well as for static registration of the action using the addAction() method (Example: setModel).
  • ActionClass: Fully-qualified class name of the action implementation (Example: APF\project\biz\actions\LoadModelAction).
  • InputClass: Fully-qualified class name of the input implementation (Example: APF\project\biz\actions\DemositeModel). Default value in case this directive is not specified is APF\core\frontcontroller\FrontcontrollerInput.
  • InputParams: Contains parameters that are injected into the input implementation when the action is created. Keys and values are separated by ":" value couples by "|" (Example: login:true|headview:menu). In case the configuration directive is not present, no initialization happens.
Using a custom input implementation is recommended in case processing input parameters from the URL and the configuration file should be encapsulated.
The input parameters defined within the InputParams directive can be overwritten by URL. This is suitable for use cases where an action should act slightly different in certain use cases. The required logic is already contained in the Frontcontroller::addAction() function.

5.2. Action implementation

A front controller action is defined by a PHP class that extends AbstractFrontcontrollerAction. To run the logic, the run() must be implemented.

The following code shows an action that greets you with Hello World! and terminates the request:

PHP code
namespace ACME\project\actions; use APF\core\frontcontroller\AbstractFrontcontrollerAction; class GreetMeAction extends AbstractFrontcontrollerAction { public function __construct() { $this->type = self::TYPE_PRE_PAGE_CREATE; } public function run() { echo 'Hello World!'; exit(); } }

In case you call the bootstrap file defined in chapter 4 using the URL

Code
?ACME_project-action:greetMe

you will be greeted with Hello World - provided that you have created the necessary configuration.

Please note, that the namespace provided within the URL does not have to match the namespace of the action implementation! To depict this, the example consciously uses a different namespace.

Creating the actions context and language of the application are injected. For this reason, you can put any kind of logic into actions. Context-dependent activities such as loading Configuration can be done as usual. This is also true for accessing e.g. shared resources such as a view model.

Further examples of action implementations can be taken from chapter 8.

5.3. Input implementation

Input classes encapsulate the input parameters of an action and are optional. In case an action definition contains no dedicated input implementation the front controller uses the FrontcontrollerInput by default.

In case the input class is just picking and replaying the action parameters no separate implementation is required. In those cases you can easily rely on the standard implementation FrontcontrollerInput.

In case you want to be greeted by the action from chapter 5.2. with a personal note you can do this by changing the action to that:

PHP code
namespace ACME\project\actions; use APF\core\frontcontroller\AbstractFrontcontrollerAction; class GreetMeAction extends AbstractFrontcontrollerAction { public function __construct() { $this->type = self::TYPE_PRE_PAGE_CREATE; } public function run() { $input = $this->getInput(); echo 'Hello ' . $input->getParameter('name') . '!'; exit(); } }

The above code uses the name parameter within the run() method. Since there is no fallback defined, the action call requires the parameter to be present. In case you now call the bootstrap file defined within chapter 4 using the URL

Code
?ACME_project-action:greetMe=name:Harry

you will be greeted with Hello Harry! - provided that you have created the necessary configuration.

In case no name is applied the greeting is incorrect. With a custom input implementation you are given the possibility to implement an input parameter processing that takes care of a name being present. Please note the following code that checks for the parameter to be present and if not falls back to a default value:

PHP code
namespace ACME\project\actions; use APF\core\frontcontroller\FrontcontrollerInput; class GreetMeInput extends FrontcontrollerInput { public function getName() { return $this->getParameter('name', 'Welt'); } }

Now let's change the action to the below way and you are greeted correctly with and without a parameter applied:

PHP code
namespace ACME\project\actions; use APF\core\frontcontroller\AbstractFrontcontrollerAction; class GreetMeAction extends AbstractFrontcontrollerAction { public function __construct() { $this->type = self::TYPE_PRE_PAGE_CREATE; } public function run() { $input = $this->getInput(); echo 'Hallo ' . $input->getName() . '!'; exit(); } }

6. Action management

6.1. Activation/deactivation

Besides the definition of the execution time (for details, please see chapter 3.1) the front controller provides another possibility to influence action execution. In case you intend to cancel execution of an action under certain circumstances you can do so overwriting the AbstractFrontcontrollerAction::isActive() method.

In case isActive() of your implementation returns true the run() method is executed, with false execution is aborted. This enables you to not execute an action in case a particular action is on the action stack or to only execute it when a dedicated action is present. Of course, you can apply any other logic to this method.

The following example marks the action as active in case another action is not on the action stack:

PHP code
public function isActive() { $captcha = $this->getFrontController()->getActionByName('showCaptcha'); return $captcha === null; }

6.2. Prioritization

Please note, that this feature is available starting with release 2.1.

In certain situations it may become necessary to tweak the order of action execution. This holds true in case you maintain two actions within your application: one to initialize it and one for execution of any application logic (e.g. search). In case the search logic depends on an initialized application you should have the possibility to ensure initialization takes place first.

For such cases the APF offers a feature to define the order of actions on the stack. At delivery status each action takes priority 10. In case an action is assigned a higher priority - 20 for instance - it is executed first. In case an action takes a lower priority - e.g. 1 it is executed afterwards.

The front controller uses AbstractFrontcontrollerAction::getPriority() to determine the priority and to sort them accordingly on the action stack. To change or influence your action's priority, please overwrite the mentioned method. Example:

PHP code
use APF\core\frontcontroller\AbstractFrontcontrollerAction; class SearchAction extends AbstractFrontcontrollerAction { ... public function getPriority() { return 9; } ... }

In case SearchAction is executed together with an action to initialize the application, it comes second.

Please note, that the order of action is primary depending on it's type (prepagecreate, pretransform, and posttransform) determining the execution slot as described in the front controller timing model.

Actions of the same type are sorted by the number returned by getPriority(). In case of equal priority figures the point of time of the registration counts. For staticylly registered actions the order of registration within the bootstrap file (index.php) is essential. For dynamic actions, the position within the URL is key.

Due to the fact that the priority is returned by method getPriority() you are able to dynamically calculate it based on various factors. In case your action should be executed earlier compared to another action in case the related action is on the stack or not you may want to use the following code:

PHP code
public function getPriority() { $relatedAction = $this->getFrontController()->getActionByName('relatedAction'); if ($relatedAction === null) { return 12; } return $relatedAction->getPriority() - 1; }

Using getActions() you can also determine the highest priority to calculate your actions priority:

PHP code
public function getPriority() { $max = 0; foreach ($this->getFrontController()->getActions() as $action) { $max = max($max, $action->getPriority()); } return $max + 1; }

6.3. Permission to execute

By using the method allowExecution() you can affect the permission to execute the action. The behavior is the same like the isActive() method. The action is only executed if the method allowExecution() returns true.

The following example ensures that the action will only be executed when a User is logged in by the Usermanagement-module:

PHP code
use APF\modules\usermanagement\pres\condition\UserDependentContentConditionSet; public function allowExecution() { $condSet = $this->getServiceObject('APF\modules\usermanagement\pres\condition\UserDependentContentConditionSet'); if ($condSet->conditionMatches($this->getContext(), 'logged-in', '')) { return true; } else { return false; } }

The URL scheme of the APF is defined by input and output filters and their respective LinkScheme implementations.

The input filers are - as described in chapter 3.2.2. - responsible for analyzing the URL, extract the front controller instructions contained there and to hand them over to the front controller. For this reaon, the APF ships the ChainedStandardInputFilter for simple URLs and the ChainedUrlRewritingInputFilter for rewritten URLs.

Each LinkScheme implementation offers a formatActionLink() method that is an interface to automatically and in correspondence to the input filter create action URLs. The APF ships the DefaultLinkScheme for simple URLs and the RewriteLinkScheme for rewritten URLs.

Generating URLs with the LinkGenerator::generateActionUrl() method actions on the action stack are not included within the URL automatically. This is because the developer needs to have the power to decide which action should be represented within the URL to be executed automatically during the next request processing.

In case you intentionally want to add an action to the URL generated you can set the $keepInUrl. Example:

PHP code
namespace ACME\project\actions; use APF\core\frontcontroller\AbstractFrontcontrollerAction; class GreetingAction extends AbstractFrontcontrollerAction { public function __construct() { $this->type = self::TYPE_PRE_PAGE_CREATE; $this->setKeepInUrl(true); } ... }

In order to dynamically decide - e.g. by evaluation of one or more parameters of an view model of your application - whether the action should be incluced in the URL or not you can directly overwrite the getKeepInUrl() method. This method is used by the LinkScheme in action to explicitly include the action:

PHP code
namespace ACME\project\actions; use APF\core\frontcontroller\AbstractFrontcontrollerAction; class GreetingAction extends AbstractFrontcontrollerAction { public function __construct() { $this->type = self::TYPE_PRE_PAGE_CREATE; } public function getKeepInUrl() { $model = $this->getViewModel(); return $model->getFoo() === true; } ... }

Details on the generation of URLs can be taken from chapter Links.

8. Further implementation examples

Further implementation examples on dynamic image delivery, checking login state of users or switch the language of the application can be taken from chapter Front controller tutorial.

Another use case on actions is included in article RSS delivery with the APF.

Comments

Do you want to add a comment to the article above, or do you want to post additional hints? So please click here. Comments already posted can be found below.
There are no comments belonging to this article.