130 lines
4.3 KiB
ReStructuredText
130 lines
4.3 KiB
ReStructuredText
Validation of Documents
|
|
=======================
|
|
|
|
.. sectionauthor:: Benjamin Eberlei <kontakt@beberlei.de>
|
|
|
|
Doctrine does not ship with any internal validators, the reason
|
|
being that we think all the frameworks out there already ship with
|
|
quite decent ones that can be integrated into your Domain easily.
|
|
What we offer are hooks to execute any kind of validation.
|
|
|
|
.. note::
|
|
|
|
You don't need to validate your documents in the lifecycle
|
|
events. Its only one of many options. Of course you can also
|
|
perform validations in value setters or any other method of your
|
|
documents that are used in your code.
|
|
|
|
Documents can register lifecycle event methods with Doctrine that
|
|
are called on different occasions. For validation we would need to
|
|
hook into the events called before persisting and updating. Even
|
|
though we don't support validation out of the box, the
|
|
implementation is even simpler than in Doctrine 1 and you will get
|
|
the additional benefit of being able to re-use your validation in
|
|
any other part of your domain.
|
|
|
|
Say we have an ``Order`` with several ``OrderLine`` instances. We
|
|
never want to allow any customer to order for a larger sum than he
|
|
is allowed to:
|
|
|
|
.. code-block:: php
|
|
|
|
<?php
|
|
|
|
class Order
|
|
{
|
|
public function assertCustomerAllowedBuying()
|
|
{
|
|
$orderLimit = $this->customer->getOrderLimit();
|
|
|
|
$amount = 0;
|
|
foreach ($this->orderLines AS $line) {
|
|
$amount += $line->getAmount();
|
|
}
|
|
|
|
if ($amount > $orderLimit) {
|
|
throw new CustomerOrderLimitExceededException();
|
|
}
|
|
}
|
|
}
|
|
|
|
Now this is some pretty important piece of business logic in your
|
|
code, enforcing it at any time is important so that customers with
|
|
a unknown reputation don't owe your business too much money.
|
|
|
|
We can enforce this constraint in any of the metadata drivers.
|
|
First Annotations:
|
|
|
|
.. configuration-block::
|
|
|
|
.. code-block:: php
|
|
|
|
<?php
|
|
|
|
/** @Document @HasLifecycleCallbacks */
|
|
class Order
|
|
{
|
|
/** @PrePersist @PreUpdate */
|
|
public function assertCustomerAllowedBuying() {}
|
|
}
|
|
|
|
.. code-block:: xml
|
|
|
|
<doctrine-mapping>
|
|
<document name="Order">
|
|
<lifecycle-callbacks>
|
|
<lifecycle-callback type="prePersist" method="assertCustomerallowedBuying" />
|
|
<lifecycle-callback type="preUpdate" method="assertCustomerallowedBuying" />
|
|
</lifecycle-callbacks>
|
|
</document>
|
|
</doctrine-mapping>
|
|
|
|
Now validation is performed whenever you call
|
|
``DocumentManager#persist($order)`` or when you call
|
|
``DocumentManager#flush()`` and an order is about to be updated. Any
|
|
Exception that happens in the lifecycle callbacks will be cached by
|
|
the DocumentManager and the current transaction is rolled back.
|
|
|
|
Of course you can do any type of primitive checks, not null,
|
|
email-validation, string size, integer and date ranges in your
|
|
validation callbacks.
|
|
|
|
.. code-block:: php
|
|
|
|
<?php
|
|
|
|
/** @Document @HasLifecycleCallbacks */
|
|
class Order
|
|
{
|
|
/** @PrePersist @PreUpdate */
|
|
public function validate()
|
|
{
|
|
if (!($this->plannedShipDate instanceof DateTime)) {
|
|
throw new ValidateException();
|
|
}
|
|
|
|
if ($this->plannedShipDate->format('U') < time()) {
|
|
throw new ValidateException();
|
|
}
|
|
|
|
if ($this->customer == null) {
|
|
throw new OrderRequiresCustomerException();
|
|
}
|
|
}
|
|
}
|
|
|
|
What is nice about lifecycle events is, you can also re-use the
|
|
methods at other places in your domain, for example in combination
|
|
with your form library. Additionally there is no limitation in the
|
|
number of methods you register on one particular event, i.e. you
|
|
can register multiple methods for validation in "PrePersist" or
|
|
"PreUpdate" or mix and share them in any combinations between those
|
|
two events.
|
|
|
|
There is no limit to what you can and can't validate in
|
|
"PrePersist" and "PreUpdate" as long as you don't create new document
|
|
instances. This was already discussed in the previous blog post on
|
|
the Versionable extension, which requires another type of event
|
|
called "onFlush".
|
|
|
|
Further readings: :doc:`Lifecycle Events <../reference/events>` |