130 lines
4.3 KiB
Plaintext
130 lines
4.3 KiB
Plaintext
|
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>`
|