Model View Controller
Flow promotes the use of the Model View Controller pattern which clearly separates the information, representation and mediation into separated building blocks. Although the design pattern and its naïve implementation are relatively simple, a capable MVC framework also takes care of more complex tasks such as input sanitizing, validation, form and upload handling and much more.
This chapter puts Flow’s MVC framework into context with the HTTP request / response mechanism, explains how to develop controllers and describes various features of the framework.
HTTP
All action starts with an HTTP request sent from a client. The request contains information about the resource to retrieve or process, the action to take and various various parameters and headers. Flow converts the raw HTTP request into an HTTP Request object and, by invoking the Routing mechanism, determines which controller is responsible for processing the request and creating a matching response. A dispatcher then passes an internal to the controller and gets a response in return which can be sent to back to the client.
If you haven’t done already, we recommend that you read the chapter about Flow’s HTTP Foundation. It contains more detailed information about the application flow and the specific parts of the HTTP API.
Action Request
A typical application contains controllers providing one or more actions. While
HTTP requests and responses are fine for communication between clients and servers,
Flow uses a different kind of request internally to communicate with a controller,
called Action Request
. The default HTTP request handler asks the router to
extract some information from the HTTP request and build an Action Request.
The Action Request contains the all the necessary details for calling the controller action which was requested by the client:
the package key and optionally sub namespace of the package containing the controller supposed to handle the request
the controller name
the action name
any arguments which are passed to the action
the format of the expected response
With this information in place, the request handler can ask the Dispatcher
to
pass control to the specified controller.
Dispatcher
The Dispatcher has the function to invoke a controller specified in the given request and make sure that the request was processed correctly. The Dispatcher class provides one important method:
public function dispatch(RequestInterface $request, ResponseInterface $response) {
On calling this method, the Dispatcher resolves the controller class name of the
controller mentioned in the request object and calls its processRequest()
method. A fresh Response
object is also passed to the controller which is
expected to deliver its response data by calling the respective setter methods on
that object.
Each request carries a dispatched
flag which is set or unset by the controller.
The Action Controller for example sets this flag by default and only unsets it if
an action initiated a forward to another action or controller. If the flag is not
set, the Dispatcher assumes that the request object has been updated with a new
controller, action or arguments and that it should try again to dispatch the request.
If dispatching the request did not succeed after several trials, the Dispatcher
will throw an exception.
Sub Requests
An Http\Request
object always reflects the original HTTP request sent by the
client. It is not possible to create an HTTP sub request because requests which
are passed along within the application must be instances of Mvc\ActionRequest
.
Creating an Action Request as a sub request of the original HTTP Request is simple,
although you rarely need to do that:
$actionRequest = new ActionRequest($httpRequest);
An Action Request always holds a reference to a parent request. In most cases the hierarchy is shallow and the Action Request is just a direct sub request of the HTTP Request. In the context of a controller, it is easy to get a hold of the parent request:
public function fooAction(): void
{
$parentRequest = $this->request->getParentRequest();
$httpRequest = $this->request->getHttpRequest();
// in case of a shallow hierarchy, $parentRequest == $httpRequest
}
In a more complex scenario, an Action Request can be a sub request of another Action Request. This is the case in most implementations of plugins, widgets or other inline elements of a rendered page because each of them is a part of the whole and can be arbitrarily nested. Each element (plugin, widget …) needs its own Action Request instance in order to keep track of invocation details like arguments and other context information.
A sub request can be created manually by passing the parent request to the constructor of the new Action Request:
$subRequest = new ActionRequest($parentRequest);
The top level Action Request (just below the HTTP Request) is referred to as the Main Request:
public function fooAction(): void
{
$parentRequest = $this->request->getParentRequest();
$httpRequest = $this->request->getHttpRequest();
$mainRequest = $this->request->getMainRequest();
if ($this->request === $mainRequest) {
$message = 'This is the main request';
}
// same like above:
if ($this->request->isMainRequest()) {
$message = 'This is the main request';
}
}
Manual creation of sub requests is rarely necessary. In most cases the framework will take care of creating and managing sub requests if plugins or widgets are in the game.
Controllers
A controller is responsible for preparing a model and collecting the necessary data which should be returned as a response. It also controls the application flow and decided if certain operations should be executed and how the application should proceed, for example after the user has submitted a form.
A controller should only sparingly contain logic which goes beyond these tasks. Operations which belong to the domain of the application should be rather be implemented by domain services. This allows for a clear separation of application flow and business logic and enables other parts of the application (for example web services) to execute these operations through a well-defined API.
A controller suitable for being used in Flow needs to implement the
Mvc\Controller\ControllerInterface
. At the bare minimum it must provide a
processRequest()
method which accepts a request and response.
If needed, custom controllers can be implemented in a convenient way by extending
the Mvc\Controller\AbstractController
class. The most common case though is to
use the Action Controller provided by the framework.
Action Controller
Most web applications will interact with the client through execution of specific actions provided by an Action Controller. Flow provides a base class which contains all the logic to map and validate arguments found in the raw request to method arguments of an action. It also provides various convenience methods which are typically needed in Action Controller implementations.
A Simple Action
The most simple way to implement an action is to extend the ActionController class, declare an action method and return a plain string as the response:
namespace Acme\Demo\Controller;
use Neos\Flow\Mvc\Controller\ActionController;
class HelloWorldController extends ActionController
{
/**
* The default action of this controller.
*/
public function indexAction(): string
{
return 'Hello world.';
}
}
Note that the controller must reside in the Controller
sub namespace of your
package in order to be detected by the default routing configuration. In the example
above, Acme\Demo
corresponds with the package key Acme.Demo
.
By convention, indexAction
is the action being called if no specific action was
requested. An action method name must be camelCased and always end with the suffix
“Action”. In the Action Request and other parts of the routing system, it is
referred to simply by its action name, in this case index
.
If an action returns a string or an object which can be cast to a string, a PHP resource stream like an opened file or a PSR7 stream, it will be set as the content of the response automatically:
/**
* Stream a file content to the browser
*/
public function exportAction(): resource
{
$this->response->setContentType('text/csv');
return fopen('/path/fo/file', 'r');
}
Note
This also works for large files, in which case the file content will be streamed to the browser.
Defining Arguments
The unified arguments sent through the HTTP request (that includes query parameters from the URI, possible POST arguments and uploaded files) are pre-processed and mapped to method arguments of an action. That means: all arguments a action needs in order to work should be declared as method parameters of the action method and not be retrieved from one of the superglobals ($_GET, $_POST, …) or the HTTP request.
Declaring arguments in an action controller is very simple:
/**
* Says hello to someone.
*
* @param string $name Name of the someone
* @param bool $formal If the message should be formal (or casual)
*/
public function sayHelloAction(string $name, bool $formal = true): string
{
$message = ($formal ? 'Greetings, Mr. ' : 'Hello, ') . $name;
return $message
}
The first argument $name
is mandatory. The @param
annotation gives Flow
a hint of the expected type, in this case a string.
The second argument $boolean
is optional because a default value has been
defined. The @param
annotation declares this argument to be a boolean, so you
can expect that $formal
will be, in any case, either true
or false
.
A simple way to pass an argument to the action is through the query parameters in a URL:
http://localhost/acme.demo/helloworld/sayhello.html?name=Robert&formal=0
Note
Please note that the documentation block of the action method is mandatory – the annotations (tags) you see in the example are important for Flow to recognize the correct type of each argument.
Additionally to passing the arguments to the action method, all registered arguments
are also available through $this->arguments
.
Argument Mapping
Internally the Action Controller uses the Property Mapper for mapping the raw
arguments of the HTTP request to an Mvc\Controller\Arguments
object. The
Property Mapper can convert and validate properties while mapping them, which allows
for example to transparently map values of a submitted form to a new or existing
model instance. It also makes sure that validation rules are considered and that
only certain parts of a nested object structure can be modified through user input.
In order to understand the mapping process, we recommend that you take a look at the respective chapter about Property Mapping.
Here are some more examples illustrating the mapping process of submitted arguments to the method arguments of an action:
Besides simple types, also special object types, like DateTime
are supported:
# http://localhost/acme.demo/foo/bar.html?date=2012-08-10T14:51:01+02:00
/**
* @param \DateTime $date Some date
*/
public function barAction(\DateTime $date): string
{
# …
}
Properties of domain models (or any other objects) can be set through an array-like syntax. The property mapper creates a new object by default:
# http://localhost/acme.demo/foo/create.html?customer[name]=Robert
/**
* @param Acme\Demo\Domain\Model\Customer $customer A new customer
* @return string
*/
public function createAction(\Acme\Demo\Domain\Model\Customer $customer): string
{
return 'Hello, new customer: ' . $customer->getName();
}
If an identity was specified, the Property Mapper will try to retrieve an object of that type:
# http://localhost/acme.demo/foo/create.html?customer[number]=42&customer[name]=Robert
/**
* @param Acme\Demo\Domain\Model\Customer $customer An existing customer
* @param string $name The name to set
*/
public function updateAction(\Acme\Demo\Domain\Model\Customer $customer, string $name): void
{
$customer->setName($name);
$this->customerRepository->update($customer);
}
Note
number
must be declared as (part of) the identity of a Customer
object
through an @Identity
annotation. You’ll find more information about
identities and also about the creation and update of objects in the
Persistence chapter.
Instead of passing the arguments through the query string, like in the previous examples, they can also be submitted as POST or PUT arguments in the body of a request or even be a mixture of both, query parameters and parameters contained in the HTTP body. Argument values are merged in the following order, while the later sources replace earlier ones
query string (derived from $_GET)
body (typically from POST or PUT requests)
file uploads (derived from $_FILES)
Hint
Sometimes you might need to map a whole request body into a single action argument.
In that case you can use an annotation @Flow\MapRequestBody("$argumentName")
on
your action. Please refer to the Property Mapping chapter for more details.
Internal Arguments
In some situations Flow needs to set special arguments in order to simplify
handling of objects, widgets or other complex operations. In order to avoid
name clashes with arguments declared by a package author, a special prefix
consisting of two underscores __
is used. Two examples of internal arguments
are the automatically generated HMAC and CSRF hashes [1] which are sent along
with the form data:
<form enctype="multipart/form-data" name="newPost" method="post"
action="posts/create">
<input type="hidden" name="__trustedProperties" value="a:3:{s:4:"blog";…
<input type="hidden" name="__csrfToken" value="__csrfToken=cca240aa13af5bdacea3…
<label for="author">Author</label><br />
<input id="author" type="text" name="newPost[author]" value="First Last" /><br />
…
Although internal arguments can be retrieved through a method provided by the
ActionRequest
object, they are, as the name suggests, only for internal use.
You should not use or rely on these arguments in your own applications.
Plugin Arguments
Besides internal arguments, Flow stores arguments being used by recursive controller
invocations, like plugins, in a separate namespace, the so called pluginArguments
.
They are prefixed with two dashes --
and normally, you do not interact with them.
initialize*()
The Action Controller’s processRequest()
method initializes important parts of
the controller, maps and validates arguments and finally calls the requested action
method. In order to execute code before the action method is called, it is possible
to implement one or more initialization methods. The following methods are currently
supported:
initializeAction()
initialize[ActionName]()
initializeView()
The first method executed after the base initialization is initializeAction()
.
The Action Controller only provides an empty method which can be overriden by a
concrete Action Controller. The information about action method arguments and
the corresponding validators has already been collected at this point, but any
arguments sent through the request have not yet been mapped or validated. Therefore,
initializeAction()
can still modify the list of possible arguments or add /
remove certain validators by altering $this->arguments
.
Right after the generic initializeAction()
method has been called, the
Action Controller checks if a more specific initialization method was implemented.
For example, if the action name is “create” and thus the action method name is
createAction()
, the controller would try to call a method
initializeCreateAction()
. This allows for execution of code which is targeted
directly to a specific action.
Finally, after arguments have been mapped and the controller is almost ready to
call the action method, it tries to resolve a suitable view and, if it was
successful, runs the initializeView()
method. In many applications, the view
implementation will be a Fluid Template View. The initializeView()
method can
be used to assign template variables which are needed in any of the existing
actions or conduct other template-specific configuration steps.
Media Type / Format
Any implementation based on AbstractController
can support one or more formats
for its response. Depending on the preferences of the client sending the request
and the route which matched the request the controller needs render the response
in a format the client understands.
The supported and requested formats are specified as an IANA Media Type and is,
by default, text/html
. In order to support a different or more than one media
type, the controller needs override the default simply by declaring a class property
like in the following example:
class FooController extends ActionController
{
/**
* A list of IANA media types which are supported by this controller
*
* @var array
*/
protected $supportedMediaTypes = ['application/json', 'text/html'];
# …
}
The media types listed in $supportedMediaTypes
don’t need to be in any
particular order.
The Abstract Controller determines the preferred format through Content Negotiation.
More specifically, Flow will check if any specific format was defined in the route
which matched the request (see chapter Routing). If no particular format was
defined, the Accept
header of the HTTP Request is consulted for a weighted list
of preferred media types. This list is then matched with the list of supported media
types and hopefully results in one media type which is set as the format
in the
Action Request.
Hint
With “format” we are referring to the typical file extension which corresponds to
a specific media type. For example, the format for text/html
is “html” and
the format corresponding to the media type application/json
would be “json”.
For a complete list of supported media types and their corresponding formats
please refer to the class Neos\Utility\MediaTypes
.
The controller implementation must take care of the actual media type support by supplying a corresponding view or template.
Fluid Template View
An Action Controller can directly return the rendered content by means of a string returned by the action method. However, this approach is not very flexible and ignores the separation of concerns as laid out by the Model View Controller pattern. Instead of rendering an output itself, a controller delegates this task to a view.
Flow uses the Fluid template engine as the default view for action controllers. By following a naming convention for directories and template files, developers of a concrete controller don’t need to configure the view or paths to the respective templates – they are resolved automatically by converting the combination of package key, controller name and action name into a Fluid template path.
Given that the package key is Acme.Demo
, the controller name is HelloWorld
,
the action name is sayHello
and the format is html
, the following path and
filename would be used for the corresponding Fluid template:
./Packages/…/Acme.Demo/Resources/Private/Templates/HelloWorld/SayHello.html
If a template file matching the current request was found, the Action Controller
initializes a Fluid Template View with the correct path name. This pre-initialized
view is available via $this->view
in any Action Controller and can be used for
assigning template variables:
$this->view->assign('products', $this->productRepository->findAll());
If an action does not return a result (that is, the result is null
), an
Action Controller automatically calls the render()
method of the current view.
That means, apart from assigning variables to the template (if any), there is rarely
a need to deal further with a Fluid Template View.
Json View
When used as a web service, controllers may want to return data in a format which can be easily used by other applications. Especially in a web context JSON has become an often used format which is very light-weight and easy to parse. Although it is theoretically possible to render a JSON response through a Fluid Template View, a specialized view does a much better job in a more convenient way.
The JSON View provided by Flow can be used by declaring it as the default view in the concrete Action Controller implementation:
class FooController extends ActionController
{
/**
* @var string
*/
protected $defaultViewObjectName = \Neos\Flow\Mvc\View\JsonView::class;
# …
}
Alternatively, if more than only the JSON format should be supported, the format to view mapping feature can be used:
class FooController extends ActionController
{
/**
* @var string
*/
protected $viewFormatToObjectNameMap = [
'html' => \Neos\FluidAdaptor\View\TemplateView::class,
'json' => \Neos\Flow\Mvc\View\JsonView::class
];
/**
* A list of IANA media types which are supported by this controller
*
* @var array
*/
protected $supportedMediaTypes = ['application/json', 'text/html'];
# …
}
In either case, the JSON View is now invoked if a request is sent which prefers
the media type application/json
. In order to return something useful, the data
which should be rendered as JSON must be set through the assign()
method. By
default JSON View uses the variable named “value”:
public function showAction(Product $product): void
{
$this->view->assign('value', $product);
}
To change the name of the rendered variables, use the setVariablesToRender()
method on the view.
If the controller is configured to use the JSON View, this action may return JSON code like the following:
{"name":"Arabica","weight":1000,"price":23.95}
Furthermore, the JSON view can be configured to determine which variables of the object
should be included in the output. For that, a configuration array needs to be provided
with setConfiguration()
:
public function showAction(Product $product): void
{
$this->view->assign('value', $product);
$this->view->setConfiguration(/* configuration follows here */);
}
The configuration is an array which is structured like in the following example:
[
'value' => [
// only render the "name" property of value
'_only' => ['name']
),
'anothervalue' => [
// render every property except the "password"
// property of anothervalue
'_exclude' => ['password']
// we also want to include the sub-object
// "address" as nested JSON object
'_descend' => [
'address' => [
// here, you can again configure
// _only, _exclude and _descend if needed
]
]
],
'arrayvalue' => [
// descend into all array elements
'_descendAll' => [
// here, you can again configure _only,
// _exclude and _descend for each element
]
],
'valueWithObjectIdentifier' => [
// by default, the object identifier is not
// included in the output, but you can enable it
'_exposeObjectIdentifier' => true,
// the object identifier should not be rendered
// as "__identity", but as "guid"
'_exposedObjectIdentifierKey' => 'guid'
]
]
To sum it up, the JSON view has the following configuration options to control the output structure:
_only
(array): Only include the specified property names in the output_exclude
(array): Include all except the specified property names in the output_descend
(associative array): Descend into the specified sub-objects_descendAll
(array): Descend into all array elements and generate a numeric array_exposeObjectIdentifier
(boolean): if true, the object identifier is displayed inside__identifier
_exposeObjectIdentifierKey
(string): the JSON field name inside which the object identifier should be displayed
Custom View
Similar to the Fluid Template View and the JSON View, packages can provide their
own custom views. The only requirement for such a view is the implementation of
all methods defined in the Neos\Flow\Mvc\View\ViewInterface
.
An Action Controller can be configured to use a custom view through the
$defaultViewObjectName
and $viewFormatToObjectNameMap
properties, as
explained in the section about JSON View.
Configuring Views through Views.yaml
If you want to change Templates, Partials, Layouts or the whole ViewClass for
a foreign package without modifying it directly, and thus breaking updatability,
you can create a Views.yaml
in your configuration folder and override all options
the view supports.
The general syntax of a view configuration looks like this:
-
requestFilter: 'isPackage("Foreign.Package") && isController("Standard")'
viewObjectName: 'Neos\Fusion\View\FusionView'
options:
fusionPathPatterns:
- 'resource://Neos.Fusion/Private/Fusion'
- 'resource://My.Package/Private/Fusion'
fusionPath: 'yourProtoype'
The requestFilter is based on Neos.Eel allowing you to match arbitrary requests so that you can override View configuration for various scenarios. You can combine any of these matchers to filter as specific as you need:
isPackage(“Package.Key”)
isSubPackage(“SubPackage”)
isController(“Standard”)
isAction(“index”)
isFormat(“html”)
There are additional helpers to get the parentRequest or mainRequest of the current request, which you can use to limit some configuration to only take effect inside a specific subRequest. All Eel matchers above can be used with the parentRequest or mainRequest as well:
parentRequest.isPackage(“Neos.Neos”)
parentRequest.isController(“Standard”)
mainRequest.isController(“Standard”)
…
You can combine any of these matchers with boolean operators:
(isPackage(“My.Foo”) || isPackage(‘My.Bar’)) && isFormat(“html”)
The order of the configurations is in most cases unimportant. Each matcher has a specific weight similar to CSS specifity (ID, class, inline, important) to determine which configuration outweighs the other. For each match resulting matcher the weight will be increased by a certain value.
Method |
Weight |
---|---|
isPackage(“Package.Key”) |
1 |
isSubPackage(“SubPackage”) |
10 |
isController(“Standard”) |
100 |
isAction(“index”) |
1000 |
isFormat(“html”) |
10000 |
mainRequest() |
100000 |
parentRequest() |
1000000 |
If the package is “My.Foo” and the Format is “html” the result will be 10001
Note
Previously the configuration of all matching Views.yaml
filters was merged.
From version 4.0 on only the matching filter with the highest weight is respected
in order to reduce ambiguity.
The fusionPathPatterns has to contain the Root-Fusion and the path to Fusion-Folder which contains your Prototype. Your Prototype gets searched recursively by fusionPath.
Controller Context
The Controller Context is an object which encapsulates all the controller-related
objects and makes them accessible to the view. Thus, the $this->request
property
of the controller is available inside the view as
$this->controllerContext->getRequest()
.
Validation
Arguments which were sent along with the HTTP request are usually sanitized and valdidated before they are passed to an action method of a controller. Behind the scenes, the Property Mapper is used for mapping and validating the raw input. During this process, the validators are invoked:
base validation as defined in the model to be validated (if any)
argument validation as defined in the controller or action
The chapter about Validation outlines the general validation mechanism and how declare and configure base validation. While the rules declared in a model describe the minimum requirements for a valid entity, the rules declared in a controller define additional preconditions before arguments may be passed to an action method.
Per-action validation rules are declared through the Validate
annotation. As
an example, an email address maybe optional in a Customer model, but it may be
required when a customer entity is passed to a signUpAction()
method:
/**
* @Flow\Validate(argumentName="emailAddress", type="EmailAddress")
*/
public function signUpAction(Customer $customer): void
{
# …
}
While Validate
defines additional rules, the IgnoreValidation
annotation
does the opposite: any base validation rules declared for the specified argument
will be ignored:
/**
* @Flow\IgnoreValidation("$customer")
*/
public function signUpAction(Customer $customer): void
{
# …
}
By default the validation for an argument annotated with IgnoreValidation
will not be executed. If the result is needed for further processing in the
action method, the evaluate
flag can be enabled:
/**
* @param \Acme\Demo\Domain\Model\Customer $customer
* @Flow\IgnoreValidation("$customer", evaluate=true)
*/
public function signUpAction(Customer $customer) {
if ($this->arguments['customer']->getValidationResults()->hasErrors()) {
# …
}
}
The next section explains how to get a hold of the validation results and react on warnings or errors which occurred during the mapping and validation step.
Error Handling
The argument mapping step based on the validation rules mentioned earlier makes sure that an action method is only called if its arguments are valid. In the reverse it means that the action specified by the request will not be called if a mapping or validation error occurred. In order to deal with these errors and provide a meaningful error message to the user, a special action is called instead of the originally intended action.
The default implementation of the errorAction()
method will redirect the browser
to the URI it came from, for example to redisplay the originally submitted form.
Any errors or warnings which occurred during the argument mapping process are stored in a special object, the mapping results. These mapping results can be conveniently access through a Fluid view helper in order to display warnings and errors along the submitted form or on top of it:
<f:validation.results>
<f:if condition="{validationResults.flattenedErrors}">
<ul class="errors">
<f:for each="{validationResults.flattenedErrors}" as="errors" key="propertyPath">
<li>{propertyPath}
<ul>
<f:for each="{errors}" as="error">
<li>{error.code}: {error}</li>
</f:for>
</ul>
</li>
</f:for>
</ul>
</f:if>
</f:validation.results>
Besides using the view helper to display the validation results, you can also
completely replace the errorAction()
method with your own custom method.
Upload Handling
The handling of file uploads is pretty straight forward. Files are handled
internally as PersistentResource
objects and thus, storing an uploaded file is just a
matter of declaring a property of type PersistentResource
in the respective model.
There is a full example explaining file uploads in the chapter about resource management.
REST Controller
tbd.
Generating Links
Links to other controller and their actions should not be rendered manually because hardcoded or manually rendered links circumvent many of Flow’s features.
For generating links to other controllers, the UriBuilder
which is available
as $this->uriBuilder
can be used. However, in most cases, the user does not
directly interact with this one, but rather uses forward()
, redirect()
in the Controller and <f:link.action />
/ <f:uri.action />
inside Fluid
templates.
forward() and redirect()
Often, controllers need to defer execution to other controllers or actions. For that to happen, Flow supports both, internal and external redirects:
in an internal redirect which is triggered by
forward()
, the URI does not change.in an external redirect, the browser receives a HTTP
Location
header, redirecting him to the new controller. Thus, the URI changes.
As a consequence, forward()
can also call controllers or actions which are
not exposed through the routing mechanism, while redirect()
only works with
publicly callable controllers.
This example demonstrates the usage of redirect()
:
public function createAction(Product $product): void
{
// TODO: store the product somewhere
$this->redirect('show', null, null, array('product' => $product));
// This line is never executed, as redirect() and
// forward() immediately stop execution of this method.
}
It is good practice to have different actions for modifying and showing data.
Often, redirects are used to link between them. As an example, an updateAction()
which updates an object should then redirect()
to the show
action of the
controller, then displays the updated object.
forward()
supports the following arguments:
$actionName
(required): Name of the target action$controllerName
: Name of the target controller. If not specified, the current controller is used.$packageKey
: Name of the package, optionally with sub-package. If not specified, the current package key / subpackage key is specified. The package and sub-package need to be delimited by\
, soFoo.Bar\Test
will set the package toFoo.Bar
and the subpackage toTest
.$arguments
: array of request arguments. Objects are automatically converted to their identity.
redirect()
supports all of the above arguments, additionally with the following ones:
$delay
: Delay in seconds before redirecting$statusCode
: the status code to be used for redirecting. By default, 303 is used.$format
: The target format for the redirect. If not set, the current format is used.
Flash Messages
In many applications users need to be notified about the application flow, telling him for example that an object has been successfully saved or deleted. Such messages, which should be displayed to the user only once, are called Flash Messages.
A Flash Message can be added inside the controller by using the addFlashMessage
method,
which expects the following arguments:
$messageBody
(required): The message which should be shown$messageTitle
: The title of the message$severity
: The severity of the message; by default “OK” is used. Needs to be one of NeosErrorMessagesMessage::SEVERITY_* constants (OK, NOTICE, WARNING, ERROR)$messageArguments
(array): If the message contains any placeholders, these can be filled here. See the PHP functionprintf
for details on the placeholder format.$messageCode
(integer): unique code of this message, can be used f.e. for localization. By convention, if you set this, it should be the UNIX timestamp at time of writing the source code to be roughly unique.
Creating a Flash Messages is a matter of a single line of code:
$this->addFlashMessage('Everything is all right.');
$this->addFlashMessage('Sorry, I messed it all up!', 'My Fault', \Neos\Error\Messages\Message::SEVERITY_ERROR);
The flash messages can be rendered inside the template using the <f:flashMessages />
ViewHelper. Please consult the ViewHelper for a full reference.
Since Flash Messages need to possibly survive over requests until they get displayed, they need to be persisted somehow. Flash Messages can be stored in different ways, the Framework default is to store them in the session. The storage can be configured in Settings.yaml via the following options:
Neos:
Flow:
mvc:
flashMessages:
containers:
'customFlashMessages':
storage: 'Neos\Flow\Mvc\FlashMessage\Storage\FlashMessageCookieStorage'
storageOptions:
cookieName: 'Neos_Flow_FlashMessages_My_Custom'
requestPatterns:
'SomeControllers':
pattern: 'ControllerObjectName'
patternOptions:
'controllerObjectNamePattern': 'Some\Package\Controller\.*'
With this you can specify to store the Flash Messages in an own cookie, and even separate them by
request patterns. New storages can be created by implementing the FlashMessageStorageInterface
and
specifying the storage class in the settings.