Table of Contents
Table of Contents
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:
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:
Incoming requests are handled by a Request Handler which takes the role of a Front Controller.
Template View is the most commonly used pattern for views, but Transform Views and Two-Step Views are equally supported.
The preferred type of model is the Domain Model.
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.
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.
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.
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.
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:
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.
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.
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
create a new request object
set some request-type specific parameters (like the request URI for a web request)
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.
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.
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 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.
Requests from the command line are recognized by the used SAPI (Server Application Programming Interface).
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.
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.
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.
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.
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');
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.
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) { }
$this->indexActionMethodName
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.
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.
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.
If no matching route can be found, the
indexAction of the
StandardController of the
FLOW3 package is called.
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
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/).
You don't have to specify action and controller in this
example as the indexAction of the
StandardController is always called by
default.
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.
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 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.
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 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.
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'
http://localhost/my/demo/list won't match here,
because either all optional parts have to match - or none.
You have to define default values for all optional dynamic route parts.
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").
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.
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.
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:
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 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 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