142 lines
4.3 KiB
ReStructuredText
142 lines
4.3 KiB
ReStructuredText
Validation of Entities
|
|
======================
|
|
|
|
Doctrine 2 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 entities 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
|
|
entities that are used in your code.
|
|
|
|
|
|
Entities 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:
|
|
|
|
::
|
|
|
|
<?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:
|
|
|
|
::
|
|
|
|
<?php
|
|
/**
|
|
* @Entity
|
|
* @HasLifecycleCallbacks
|
|
*/
|
|
class Order
|
|
{
|
|
/**
|
|
* @PrePersist @PreUpdate
|
|
*/
|
|
public function assertCustomerAllowedBuying() {}
|
|
}
|
|
|
|
In XML Mappings:
|
|
|
|
::
|
|
|
|
[xml]
|
|
<doctrine-mapping>
|
|
<entity name="Order">
|
|
<lifecycle-callbacks>
|
|
<lifecycle-callback type="prePersist" method="assertCustomerallowedBuying" />
|
|
<lifecycle-callback type="preUpdate" method="assertCustomerallowedBuying" />
|
|
</lifecycle-callbacks>
|
|
</entity>
|
|
</doctirne-mapping>
|
|
|
|
YAML needs some little change yet, to allow multiple lifecycle
|
|
events for one method, this will happen before Beta 1 though.
|
|
|
|
Now validation is performed whenever you call
|
|
``EntityManager#persist($order)`` or when you call
|
|
``EntityManager#flush()`` and an order is about to be updated. Any
|
|
Exception that happens in the lifecycle callbacks will be cached by
|
|
the EntityManager 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.
|
|
|
|
::
|
|
|
|
<?php
|
|
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 entity
|
|
instances. This was already discussed in the previous blog post on
|
|
the Versionable extension, which requires another type of event
|
|
called "onFlush".
|
|
|
|
Further readings:
|
|
|
|
|
|
- `Doctrine 2 Manual: Events <http://www.doctrine-project.org/documentation/manual/2_0/en/events#lifecycle-events>`_
|
|
|
|
|