Chapter 8. MVC Framework

8.1. Introduction

Model-View-Controller

In the design of FLOW3's architecture we have taken great care to separate concerns and assign each part of the framework with well-defined tasks. The separation of concerns is an important principle of good software design and its most prominent representative probably is the Model-View-Controller pattern. MVC separates the business logic from the presentation by splitting up user interaction into three roles:

  • The model is an object which contains data and business logic of a certain domain. It doesn't contain any information about the presentation of that data, but rather defines the behaviour. In the FLOW3 project we prefer a special kind of model, the Domain Model.

  • The view represents the display of the model on the web or another output channel. Views only display data, they don't build or modify it.

  • The controller reacts on user input, selects and manipulates the model as accordingly, selects a view and passes it the prepared model for rendering.

This diagram outlines the collaboration between model, view and controller:

Figure 8.1. Model-View-Controller Pattern

Model-View-Controller Pattern

Other Patterns Used

Design Patterns (and MVC is one of them) are not only great for solving reoccuring design problems in a structured manner - they also help you communicating software designs. The following patterns play an important role in FLOW3's MVC mechanism and might give you a better idea of the overall design:

Hello World!

Let's start with an example before we go into greater detail of request handling and the internals of the MVC framework. The minimal approach is to create an Action Controller which just returns Hello World!. To begin with, we need to create some directories which contain the code of our FLOW3 package and eventually the controller class:

Packages/
  Demo/
    Classes/
      Controller/
        StandardController.php

The StandardController class looks as simple as this (leaving out the very recommended comments):

Example 8.1. Hello World! controller

namespace F3\Demo\Controller;

class StandardController extends \F3\FLOW3\MVC\Controller\ActionController {
	public function indexAction() {
		return 'Hello World!';
	}
}

Provided that the document root of your local server points to FLOW3's Web/ directory, you will get the following output when calling the URI http://localhost/demo/:

Hello World!

Great, that was easy - but didn't we say that it's the view's responsibility to take care of the presentation? Let's create a simple PHP-based view for that purpose:

Packages/
  Demo/
    Classes/
      Controller/
        StandardController.php
      View/
        Standard/
          Index.php

The view's code is equally trivial:

Example 8.2. Hello World! view

namespace F3\Demo\View\Standard;

class Index extends \F3\FLOW3\MVC\View\AbstractView {
	public function render() {
		return 'Hello World from your view!';
	}
}

Finally our action controller needs a little tweak to return the rendered view instead of shouting Hello World! itself:

Example 8.3. Improved Hello World! controller

namespace F3\Demo\Controller;

class StandardController extends \F3\FLOW3\MVC\Controller\ActionController {
	public function indexAction() {
		return $this->view->render();
	}
}

Some notes about the view: Although a view class written in PHP is the most basic way to implement a view, it is not the most common way you'll take. In practice you'll want to use Fluid, FLOW3's powerful templating engine which allows you to write views in plain HTML and still have all the power like loops and conditions.

Recommended File Structure

As you have seen in the hello world example, conventions for the directory layout simplify your development a lot. There's no need to register controllers, actions or views if you follow our recommended file structure. These are the rules:

  • Controllers are located in their own directory Controller just below the Classes directory of your package. They can have arbitrary names while the StandardController has a special meaning: If the package was specified in the request but no controller, the StandardController will be used.

  • View classes are situated below a View directory. The classname of the view is a combination of the name of the controller and the name of the action.

This sample directory layout demonstrates the above rules:

Example 8.4. Sample file structure

Packages/
  Demo/
    Classes/
      Controller/
        StandardController.php
        CustomerController.php
        OrderController.php
      View/
        Standard/
          Index.php
        Customer/
          Index.php
          List.php
          Details.php
        Order/
          List.php


Adhering to these conventions has the advantage that the classname of the view for example is resolved automatically. However it is possible (and not really difficult) to deviate from this layout and have a completely different structure.

From the URI to the view

Caution

For the example URIs we assume that the web root directory of your local server points to FLOW3's public/ directory. If that's not the case you have to extend the URI accordingly.

FLOW3 provides a standard way of resolving the URI to your MVC-Objects.

Say, you want to see the list of customers (based on the file-structure-example above). The URI to get the list would be: http://localhost/demo/customer/list.html or just http://localhost/demo/customer/list.

This URI will be resolved into the package-name (Demo), controller-name (Customer), action-name(list) and format-name (html - which is the default format).

Depending on that, the controller \F3\Demo\Controller\CustomerController (pattern: 'F3\@package\Controller\@controllerController') and its method listAction() will be used. The corresponding view is \F3\Demo\View\CustomerList (Pattern: 'F3\@package\View\@controller@action@format').

By looking at the view pattern you easily see that it's fairly easily to address a view which renders a different format, for example XML. All you need to do is creating a class called \F3\Demo\View\Customer\ListXML. To see the output of this new view, just use the URI http://localhost/demo/customer/list.xml.

8.2. Request and Response

No matter if a FLOW3 application runs in a web context or is launched from the command line, the basic workflow is always the same: The user request is analyzed and forwarded to an appropriate controller which decides on which actions to take and finally returns a response which is handed over to the user. This section highlights the flow and the collaborators in the request-response machinery.

Request Processing Overview

A sequence diagram is worth a thousand words said my grandma, so let's take a look at the standard request-response workflow in FLOW3:

Figure 8.2. Example of a Web Request-Response Workflow

Example of a Web Request-Response Workflow

As you see, there are a lot of parts of the framework involved for answering a request - and the diagram doesn't even consider caching or forwarding of requests. But we didn't create this structure just for the fun of it - each object plays an important role as you'll see in the next sections.

Request Handler

The request handler takes the important task to handle and respond to a request. There exists exactly one request handler for each request type. By default web and command line requests are supported, but more specialized request handlers can be developed, too.

Before one of the request handlers comes to play, the framework needs to determine which of them is the most suitable for the current request. The request handler resolver asks all of the registered request handlers to rate on a scale how well they can handle the current raw request. The resolver then chooses the request handler with the most points and passes over the control.

Custom request handlers for special purposes just need to implement the \F3\FLOW3\MVC\RequestHandlerInterface. All classes implementing that interface are automatically registered and will be considered while resolving a suitable request handler.

Request Builder

When a request handler receives a raw request, it needs to build a request object which can be passed to the dispatcher and later to the controller. The request building delegated to a request builder which can build the required request type (ie. web, CLI etc.).

The building process mainly consists of

  1. create a new request object

  2. set some request-type specific parameters (like the request URI for a web request)

  3. determine and set the responsible controller, action and action arguments

Especially the last step is important and requires some more or less complex routing in case of web requests.

Request Dispatcher

The final task of the MVC framework consists in dispatching the request to the controller specified in the request object. Dispatching means that the request and response object is passed to the controller specified in the request object and after the controller did its job, control is returned to the request handler which eventually sends the response.

The dispatch method itself is a loop which tries to invoke a controller until a flag in the request object indicates that the request has been dispatched. In most cases this loop has only one cycle: the action method specified in the request is called and the action controller automatically sets the dispatched flag which leads to exiting the dispatch loop. However, when the controller wants to forward or redirect the request to another controller or action, the respective information is written to the request object and the dispatched flag remains unset. Therefore the dispatcher calls the next controller which hopefully can finally process the request and exits the dispatch loop.

The dispatcher comes with a safeguard which assures that the dispatching process does not end up in an endless loop.

Request Types

FLOW3 supports the most important request types out of the box. Additional request types can easily be implemented by implementing the \F3\FLOW3\MVC\RequestInterface or extending the \F3\FLOW3\MVC\Request class and registering a request handling which can handle the new request type (and takes care of building the request object). Here are the request types which come with the default FLOW3 distribution:

Web Request / Response

Web requests are the most common request types. Additional to the common request functionality, this request type delivers information about the request method, request URI and the base URI.

CLI Request / Response

Requests from the command line are recognized by the used SAPI (Server Application Programming Interface).

8.3. Controller

The main responsibility of a controller is to process a request and deliver a meaningful response. The \F3\FLOW3\MVC\Controller\ControllerInterface therefore only contains two methods:

/**
 * Checks if the current request type is supported by the controller.
 *
 * @param \F3\FLOW3\MVC\RequestInterface $request The current request
 * @return boolean TRUE if this request type is supported, otherwise FALSE
 */
public function canProcessRequest(\F3\FLOW3\MVC\RequestInterface $request);

/**
 * Processes a general request. The result can be returned by altering the given response.
 *
 * @param \F3\FLOW3\MVC\RequestInterface $request The request object
 * @param \F3\FLOW3\MVC\ResponseInterface $response The response, modified by the controller
 * @return void
 * @throws \F3\FLOW3\MVC\Exception\UnsupportedRequestTypeException if the controller doesn't support the current request type
 */
public function processRequest(\F3\FLOW3\MVC\RequestInterface $request, \F3\FLOW3\MVC\ResponseInterface $response);

However, only few applications will implement the whole request processing logic themselves. Most of the time you'll be extending the Action Controller.

Action Controller

The \F3\FLOW3\MVC\Controller\ActionController processes a request by calling action methods. Actions are the foundation of your application's workflow and logic. Which action is called is usually determined by the URI as you have already seen in the short hello world example.

Initialization Methods

Before an action method is called, the view, validation and arguments are initialized. The following methods can be extended or overloaded to hook into this initialization process:

  • initializeView() is called in order to resolve, set and initialize a view which matches the current action.

  • initializeAction() is called after the view has been initialized and the automatic registration of arguments is done. At that point, the argument's values are still empty and have not been validated.

  • initializeFooAction() where Foo is the name of the action, is called only before an action of that name is called.

Action arguments are usually registered automatically (see below). If you wish to register additional arguments manually, you may do that in one of the initialize*Action() methods.

Configuration

The settings of the package containing your controller are automatically injected into the action controller and can be accessed through the $this->settings variable. Please note that this variable contains all settings of the package as an array, not only settings specific to your controller.

You should not modify the settings array because any information you'd add would be lost anyway and is invisible to other parts of your application.

Supported Request Types

The action controller generally supports any kind of request, which means that theoretically you need only one controller for web and CLI requests. In practice you might want to structure your application so that a controller is responsable for only one request type. If that is the case, you can define the supported request types by setting the supportedRequestTypes property of your class:

Example 8.5. Defining the supported request types

/**
 * My web-specific controller
 */
class StandardController extends \F3\FLOW3\MVC\Controller\ActionController {

   /**
    * @var array
    */
   protected $supportedRequestTypes = array('F3\FLOW3\MVC\Web\Request');


Arguments

An action usually needs some more information about what it's supposed to do. This information comes in form of GET or POST arguments in case of a web request or via command line options if we're dealing with a CLI request. Because there are even more ways to pass information to an action and because this information needs special care to assure a secure application, these arguments are abstracted by FLOW3 in form of controller arguments.

The basic rule in FLOW3 is: an action method only gets those arguments it asked for which have always values which are allowed for the declared data type. Therefore arguments need to be registered and it is not possible to access PHP's superglobals $_GET and $_POST directly.

Fortunately argument registration is very easy in FLOW3. It's just a matter declaring the arguments in your action method signature:

Example 8.6. Declaring arguments in an action method

/**
 * An example action method
 *
 * @param string $emailAddress Some email address
 * @param string $streetName Some street name
 * @param F3\Foo\Model\Customer $customer A customer object
 * @return void
 */
public function exampleAction($emailAddress, $streetName, \F3\Foo\Model\Customer $customer) {
   
}

FLOW3 will automatically register the arguments $emailAddress, $streetName and $customer. Their expected data types are Text, Text and \F3\Foo\customer respectively.

In essence this means that if you send a GET parameter emailAddress with your request, it will end up in $emailAddress if it is a valid Text (which means: no HTML, no JavaScript, no danger). Even the $customer argument will contain a real Customer object if enough information has been sent with the request.

Argument Validation

All arguments passed to the action methods will automatically validated through their base validation rules defined in the respective models. Additional validation rules can be defined by using the @validate annotation. The syntax is similar to a regular @validate declaration with the addition that the argument name must be specified:

Example 8.7. Additional validation rules in an action method

/**
 * An example action method
 *
 * @param string $emailAddress Some email address
 * @param string $streetName Some street name
 * @param F3\Foo\Model\Customer $customer A customer object
 * @return void
 * @validate $emailAddress EmailAddress
 * @validate $streeName Alphanumeric, StringLength(minimum = 5, maximum = 100)
 * @validate $customer F3\Foo\Validator\PlatinumCustomerValidator
 */
public function exampleAction($emailAddress, $streetName, \F3\Foo\Model\Customer $customer) {
   
}

In a few situations it is necessary to disable validation for a single argument, for example if an object needs to passed between actions while it is beeing edited and therefore is knowingly in an incomplete state. In these cases validation can be swtiched off through the @dontvalidate annotation:

/**
 * An example edit action method
 *
 * @param F3\Foo\Model\Customer $customer A customer object
 * @return void
 * @dontvalidate $customer
 */
public function editAction(\F3\Foo\Model\Customer $customer) {
   
}

Action Methods

$this->indexActionMethodName

Other Controllers

Abstract Controller

Request Handling Controller

Standard Controller

Not Found Controller

The F3\FLOW3\MVC\Controller\NotFoundController is used whenever no other controller could be resolved which would match the current request. It displays a generic "404 Page Not Found" message.

It is possible to define your own custom controller which is used in these cases. Just specify the object name in the FLOW3 settings.

8.4. View

Template View

Special Views

Standard View

Empty View

8.5. Helpers

8.6. Model

8.7. Routing

As explained in the beginning of this chapter, in FLOW3 the dispatcher passes the request to a controller which then calls the respective action. But how to tell, what controller of what package is the right one for the current request? This is were the routing framework comes into play.

Router

The request builder asks the router for the correct package, controller and action. For this it passes the current request path to the routers match method. The router then iterates through all configured routes and invokes their matches method. The first route that matches, determines which action will be called with what parameters.

The same works for the opposite direction: If a link is generated the router calls the resolve method of all routes until one route can return the correct URI for the specified arguments.

Note

If no matching route can be found, the indexAction of the StandardController of the FLOW3 package is called.

Route

A route describes the way from your browser to the controller - and back.

With the URI pattern you can define how a route is represented in the browsers address bar. By setting defaults you can specify package, controller and action that should apply when a request matches the route. Besides you can set arbitrary default values that will be available in your controller. They are called defaults because you can overwrite them by so called dynamic route parts.

But let's start with an easy example:

Example 8.8. Simple route - Routes.yaml

--
  name'Homepage'
  uriPattern''
  defaults:
    '@package': Demo


Note

name is optional, but it's recommended to set a name for all routes to make debugging easier.

If you insert these lines at the beginning of the file Configurations/Routes.yaml, the indexAction of the StandardController in your Demo package will be called when you open up the homepage of your FLOW3 installation (http://localhost/).

Note

You don't have to specify action and controller in this example as the indexAction of the StandardController is always called by default.

URI pattern

The URI pattern defines the appearance of the URI. In a simple setup the pattern only consists of static route parts and is equal to the actual URI (without protocol and host).

In order to reduce the amount of routes that have to be created, you are allowed to insert markers, so called dynamic route parts, that will be repaced by the routing framework. You can even mark route parts optional.

But first things first.

Static route parts

A static route part is really simple - it will be mapped one-to-one to the resulting URI without transformation.

Let's create a route that calls the listAction of the CustomerController when browsing to http://localhost/my/demo:

Example 8.9. Simple route with static route parts - Configuration/Routes.yaml

--
  name'Static demo route'
  uriPattern'my/demo'
  defaults:
    '@package':    Demo
    '@controller': Customer
    '@action':     list


Dynamic route parts

Dynamic route parts are enclosed in curly brackets and define parts of the URI that are not fixed.

Let's add some dynamics to the previous example:

Example 8.10. Simple route with static and dynamic route parts - Configuration/Routes.yaml

--
  name'Dynamic demo route'
  uriPattern'my/demo/{@action}'
  defaults:
    '@package':    Demo
    '@controller': Customer


Now http://localhost/my/demo/list calls the listAction just like in the previous example.

With http://localhost/my/demo/index you'd invoke the indexAction and so on.

Note

It's not allowed to have successive dynamic route parts in the URI pattern because it wouldn't be possible to determine the end of the first dynamic route part then.

The @-prefix should reveal that action has a special meaning here. Other predefined keys are @package, @subpackage, @controller and @format. But you can use dynamic route parts to set any kind of arguments:

Example 8.11. dynamic parameters - Configuration/Routes.yaml

--
  name'Dynamic demo route'
  uriPattern'clients/{sortOrder}.{@format}'
  defaults:
    '@package':    Demo
    '@controller': Customer
    '@action':     list


Browsing to http://localhost/clients/descending.xml would call the listAction in your Customer controller and the request argument "sortOrder" had the value of "descending".

By default, dynamic route parts match anything apart from empty strings. If you have more specialized requirements you can create your custom route part handlers.

Route part handler

Route part handlers are classes that implement F3\FLOW3\MVC\Web\Routing\DynamicRoutePartInterface. But for most cases it will be sufficient to extend F3\FLOW3\MVC\Web\Routing\DynamicRoutePart and overwrite the methods matchValue and resolveValue.

Let's have a look at the (very simple) route part handler of the blog example:

Example 8.12. BlogRoutePartHandler.php

class BlogRoutePartHandler extends \F3\FLOW3\MVC\Web\Routing\DynamicRoutePart {

	/**
	 * While matching, converts the blog title into an identifer array
	 *
	 * @param string $value value to match, the blog title
	 * @return boolean TRUE if value could be matched successfully, otherwise FALSE.
	 */
	protected function matchValue($value) {
		if ($value === NULL || $value === '') return FALSE;
		$this->value = array('__identity' => array('name' => $value));
		return TRUE;
	}

	/**
	 * Resolves the name of the blog
	 *
	 * @param \F3\Blog\Domain\Model\Blog $value The Blog object
	 * @return boolean TRUE if the name of the blog could be resolved and stored in $this->value, otherwise FALSE.
	 */
	protected function resolveValue($value) {
		if (!$value instanceof \F3\Blog\Domain\Model\Blog) return FALSE;
		$this->value = $value->getName();
		return TRUE;
	}
}


The corresponding route might look like this:

Example 8.13. Route with route part handlers - Configuration/Routes.yaml

--
  name'Blog route'
  uriPattern'blogs/{blog}/{@action}'
  defaults:
    '@package':    Blog
    '@controller': Blog
  routeParts:
    blog:
      handler: F3\Blog\RoutePartHandlers\BlogRoutePartHandler


Have a look at the blog example for a working setup.

Optional route parts

By putting one or more route parts in round brackets you mark them optional. The following route matches http://localhost/my/demo/ and http://localhost/my/demo/list.html.

Example 8.14. Route with optional route parts - Configuration/Routes.yaml

--
  name'Dynamic demo route'
  uriPattern'my/demo(/{@action}.html)'
  defaults:
    '@package':    'Demo'
    '@controller''Customer'
    '@action':     'list'

Note

http://localhost/my/demo/list won't match here, because either all optional parts have to match - or none.

Note

You have to define default values for all optional dynamic route parts.

Case sensitivity

By Default the case is not changed when creating URIs. The following example with a username of "Kasper" will result in http://localhost/Users/Kasper

Example 8.15. Route with default case handling

--
  uriPattern'Users/{username}'
  defaults:
    @package:    'Demo'
    @controller'Customer'
    @action:     'show'


You can change this behavior for routes and/or dynamic route parts:

Example 8.16. Route with customised case handling

--
  uriPattern'Users/{username}'
  defaults:
    @package:    'Demo'
    @controller'Customer'
    @action:     'show'
  toLowerCase: true
  routeParts:
    username:
      toLowerCase: false


This will change the default behavior for this route and reset it for the username route part. Given the same username of "Kasper" the resulting URI will now be http://localhost/users/Kasper (note the lower case "u" in "users").

Note

The predefined route parts @package, @subpackage, @controller, @action and @format are an exception, they're always lower cased!

Matching of incoming URIs is always done case insensitive. So both "Users/Kasper" and "users/Kasper" will match, and the value of the dynamic part will never be changed. If you want to handle data coming in through dynamic route parts case-insensitive, you need to handle that in your own code.

Subroutes

For security reasons and to avoid confusion, only routes configured in your global configuration folder are active. But FLOW3 supports what we call subroutes enabling you to provide custom routes with your package and reference them in the global routing setup.

Imagine following routes in the Routes.yaml file inside your demo package:

Example 8.17. Demo Subroutes - Demo/Configuration/Routes.yaml

--
  name'Customer routes'
  uriPattern'/clients/{@action}'
  defaults:
    '@controller': Customer

--
  name: 'Standard routes'
  uriPattern'/{@action}'
  defaults:
    '@controller': Standard

--
  name: 'Fallback'
  uriPattern: ''
  defaults:
    '@controller': Standard
    '@action':     index


And in your global Routes.yaml:

Example 8.18. Referencing subroutes - Configuration/Routes.yaml

--
  name'Demo subroutes'
  uriPattern'demo<DemoSubroutes>(.{@format})'
  defaults:
    '@package': Demo
    '@format':  html
  subRoutes:
    DemoSubroutes:
      package: Demo


As you can see, you can reference subroutes by putting parts of the URI pattern in angle brackets (like <subRoutes>). With the subRoutes setting you specify where to load the subroutes from.

Internally the ConfigurationManager merges toghether the main route with its subroutes:

Example 8.19. Composite routes

--
  name'Demo subroutes :: Customer routes'
  uriPattern'demo/clients/{@action}(.{@format})'
  defaults:
    '@package': Demo
    '@format':  html
    '@controller': Customer

--
  name: 'Demo subroutes :: Standard routes'
  uriPattern'demo/{@action}(.{@format})'
  defaults:
    '@package': Demo
    '@format':  html
    '@controller': Standard

--
  name: 'Demo subroutes :: Fallback'
  uriPattern: 'demo(.{@format})'
  defaults:
    '@package': Demo
    '@format':  html
    '@controller': Standard
    '@action':     index

You can even reference multiple subroutes from one route - that will create one route for all possible combinations.


8.8. CLI request handling

FLOW3's CLI request handling offers a comfortable and flexible way of calling code from the command line:

php index.php [command] [options] [--] [arguments]

command, options and arguments are optional, with varying results. The command structure follows what is commonly accpeted on unixoid systems for CLI programs:

command

If not given, the default controller of the FLOW3 package is used and it's index action is called. While this is an allowed call, it hardly makes sense (other than checking if FLOW basically works). If command is given then it is defined as package [[sub1..N] controller action]

First part is always the package. If only the package is given, it's StandardController's index action is called.

If at least three command parts are given, the last two sepcify controller and action. Anything in between specifys a sub package structure.

Example 8.20. Some FLOW3 CLI command specifications

testing cli run would call the "run" action of the "cli" controller in the "Testing" package

typo3cr admin setup foo would call the "setup" controller's "foo" action in the subpackage "admin" of the package "TYPO3CR"


options

Options are either short- or long-style. The first option detected ends collecting command parts. Here are some examples:

Example 8.21. Giving options to FLOW3 CLI requests

-o -f=value --a-long-option --with-spaces="is possible" --input file1 -o=file2 --event-this = works


arguments

Arguments can follow and will be available to the called controller in the request object. To distinguish between command and arguments in cases where no options are given the seperator -- must be used.

Example 8.22. Some FLOW3 CLI commands

Calling the TYPO3CR setup:

php index.php typo3cr admin setup setup --dsn=sqlite:/tmp/typo3cr.db --indexlocation=/tmp/lucene/

Running FLOW3 unit tests:

php index.php testing cli run --package-key=FLOW3 --output-directory=./

Rendering the FLOW3 documentation to HTML:

php index.php doctools manual render -f html -o flow3-manual/ FLOW3