==========
Validation
==========
Hopefully the examples of the previous chapters made you shudder or at least
raised some questions. Although it's surely nice to have one-liners for actions
like ``create`` and ``update`` we need some more code to validate the incoming
values before they are eventually persisted and the outgoing content before it's
rendered to the browser.
You won't have to care too much about the latter if you're using Fluid to render
your View because, because it *escapes* your data *by default*.
As a result, even if the post subject contains the string
```` outputting it via ``{post.subject}`` will
result in the unaesthetic but harmless
``<script>alert("danger")</script>``.
But most applications come with additional rules that apply to the domain model.
Maybe you want to make sure that a post subject must consist of at least 3 and
at maximum 50 characters for example.
But do you really want these checks in your action methods? Shouldn't we
rather separate the concerns [#]_ of the action methods (show, create,
update, ...) from others like validation, logging and security?
Fortunately Flow's validation framework doesn't ask you to add any additional
PHP code to your action methods. Validation has been extracted as a separated
concern which does it's job almost transparently to the developer.
Declaring Validation Rules
==========================
When we're talking about validation, we usually refer to validating **models**.
The rules defining how a model should be validated can be classified into
three types:
- **Base Properties** – a set of rules defining the minimum requirements
on the properties of a model which must be met before a model may
be persisted.
- **Base Model** – a set of rules or custom validator enforcing the
minimum requirements on the combination of properties of a model which
must be met before a model may be persisted.
- **Supplemental** – a set of rules defining additional requirements on
a model for a specific situation, for example for a certain
action method.
.. note::
Base model and supplemental rules are not covered by this tutorial.
Detailed information is available in :doc:`Part III - Validation <../PartIII/Validation>`.
Rules for the base properties are defined directly in the model in form
of annotations:
*Classes/Acme/Blog/Domain/Model/Post.php*:
.. code-block:: php
/**
* @Flow\Validate(type="NotEmpty")
* @Flow\Validate(type="StringLength", options={ "minimum"=3, "maximum"=50 })
* @var string
*/
protected $subject;
/**
* @Flow\Validate(type="NotEmpty")
* @var string
*/
protected $author;
/**
* @Flow\Validate(type="NotEmpty")
* @ORM\ManyToOne(inversedBy="posts")
* @var Blog
*/
protected $blog;
The ``Validate`` annotations define one or more validation rules which should apply to a
property. Multiple rules can be defined in dedicated lines by further ``Validate``
annotations.
.. note::
Per convention, every validator allows (passes) empty values, i.e. empty strings or
null values. This is for achieving fields which are not mandatory, but if filled in,
must satisfy a given validation. Consider an email address field for example which
is not mandatory, but has to match an email pattern as soon as filled in.
If you want to make a field mandatory at all, use the ``NotEmpty`` validator in addition,
like in the example above.
The technical background is the ``acceptsEmptyValues`` property of the AbstractValidator,
being ``true`` per default. When writing customized validators, it's basically possible
to set this field to ``false``, however this is not generally recommended due to the convention
that every validator could principally be empty.
.. tip::
Flow provides a range of built-in validators which can be found in the
``Flow\Validation\Validator`` sub package. The names used in the
``type`` attributes are just the unqualified class names of these validators.
It is possible and very simple to program custom validators by implementing
the ``Neos\Flow\Validation\Validator\ValidatorInterface``.
Such validators must, however, be referred to by their fully qualified
class name (i.e. including the namespace).
Make sure the above validation rules are set in your ``Post`` model, click on the
``Create a new post`` link below the list of posts and submit the *empty* form. If all went fine,
you should end up again in the **new post** form, with the tiny difference
that the text boxes for title and author are now framed in red:
.. figure:: Images/CreateNewPostValidationError1.png
:alt: Validation errors shown in form
:class: screenshot-detail
Validation errors shown in form
Displaying Validation Errors
============================
The validation rules seem to be in effect but the output could be a bit more
meaningful. We'd like to display a list of error messages for exactly this case when
the form has been submitted but contained errors.
Fluid comes with a specialized view helper which allows for iterating over
validation errors, the ```` view helper.
We'll need validation results for the *create* and the *update* case, so let's put the
View Helper in a new partial ``FormErrors``::
*Resources/Private/Partials/FormErrors.html*:
.. code-block:: html
-
{propertyName}:
-
{error}
And include that partial to both, the ``New.html`` and the ``Edit.html`` templates just above the
form::
*Resources/Private/Templates/Post/New.html*:
.. code-block:: html
...
and::
*Resources/Private/Templates/Post/Edit.html*:
.. code-block:: html
...
Similar to the ```` view helper ```` defines a loop
iterating over validation errors. The attribute ``as`` is optional and if it's
not specified (like in the above example) ``as="error"`` is assumed.
To clearly understand this addition to the template you need to know that
errors can be nested: There is a global error object containing the errors of
the different domain objects (such as ``newPost``) which contain errors for
each property which in turn can be multiple errors per property.
After saving the modified template and submitting the empty form again you
should see some more verbose error messages:
.. figure:: Images/CreateNewPostValidationError2.png
:alt: More verbose validation errors shown in form
:class: screenshot-detail
More verbose validation errors shown in form
Validating Existing Data
========================
The validation rules are enforced as soon as the GET or POST arguments are mapped to the action's arguments.
But what if you add new validation rules when there are already persisted entities that might violate these?
For example if you had created a post with a subject of "xy" and added the ``StringLength`` annotation
afterwards?
Doing so would prevent you from invoking any of the actions for that particular post.
All you will see is an error message::
Validation failed while trying to call Acme\Blog\Controller\PostController->showAction().
So the problem is that Flow tries to validate the ``$post`` argument for the
action although we don't need a valid post at this point.
What's important is that the post submitted to ``updateAction`` or ``createAction`` is
valid, but we don't really care about the ``showAction`` or ``editAction`` which only displays the post or a form.
There's a very simple remedy to this problem: don't validate the post. With one
additional annotation the whole mechanism works as expected:
*Classes/Acme/Blog/Controller/PostController.php*:
.. code-block:: php
/**
* Displays a single post
*
* @Flow\IgnoreValidation("$post")
*/
public function showAction(Post $post): void
{
$this->view->assignMultiple([
'post' => $post,
'nextPost' => $this->postRepository->findNext($post),
'previousPost' => $this->postRepository->findPrevious($post),
]);
}
Now the ``showAction`` can be called even though ``$post`` is not valid.
You probably want to add the same annotation to the ``editAction`` and even the ``deleteAction`` so that
invalid posts can be fixed or removed.
-----
.. [#] See also: `Separation of Concerns (Wikipedia)
`_