Download the PHP package phphd/exceptional-validation-bundle without Composer
On this page you can find all versions of the php package phphd/exceptional-validation-bundle. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download phphd/exceptional-validation-bundle
More information about phphd/exceptional-validation-bundle
Files in phphd/exceptional-validation-bundle
Package exceptional-validation-bundle
Short Description Capture domain exceptions and map them to the corresponding properties that caused them
License MIT
Informations about the package exceptional-validation-bundle
PhdExceptionalValidationBundle
🧰 Provides exception-to-violation mapper bundled as Symfony Messenger
middleware. It captures thrown exceptions, maps them
into Symfony Validator
violations format, and throws ExceptionalValidationFailedException
.
Installation 📥
-
Install via composer
- Enable the bundle in the
bundles.php
Configuration ⚒️
The recommended way to use this package is via Symfony Messenger.
To leverage features of this bundle, you should add phd_exceptional_validation
middleware to the list:
Usage 🚀
The first thing necessary is to mark your message with #[ExceptionalValidation]
attribute. It is used to include the
message for processing by the middleware.
Then you define #[Capture]
attributes on the properties of the message. These attributes are used to specify mapping
for the thrown exceptions to the corresponding properties of the class with the respective error message translation.
In this example, whenever LoginAlreadyTakenException
or WeakPasswordException
is thrown, it will be captured and
mapped to the login
or password
property.
Eventually when phd_exceptional_validation
middleware has processed the exception, it will
throw ExceptionalValidationFailedException
so that it can be caught and processed as needed:
The $exception
object enfolds constraint violations with respectively mapped error messages. This
violation list can be used for example to render errors into html-form or to serialize them for a json-response.
Advanced usage ⚙️
#[ExceptionalValidation]
and #[Capture]
attributes allow you to implement very flexible mappings.
Here are just few examples of how you can use them.
Capturing exceptions on nested objects
#[ExceptionalValidation]
attribute works side-by-side with Symfony Validator #[Valid]
attribute. Once you have
defined these, the #[Capture]
attribute can be defined on the nested objects.
In this example, whenever InsufficientStockException
is thrown, it will be captured and mapped to the
product.quantity
property with the corresponding message translation.
Capture Closure Conditions
#[Capture]
attribute accepts the callback function to determine whether particular exception instance should
be captured for the given property or not.
In this example, when:
option of the #[Capture]
attribute is used to specify the callback functions that are called
when exception is processed. If isWithdrawalCardBlocked
callback returns true
, then exception is captured for
withdrawalCardId
property; if isDepositCardBlocked
callback returns true
, then exception is captured for
depositCardId
property. If neither of the callbacks return true
, then exception is re-thrown upper in the stack.
Simple Capture Conditions
Since in most cases capture conditions come down to the simple value comparison, it's easier to make your exception
implement ValueException
interface and specify condition: ValueExceptionMatchCondition::class
rather than implementing when:
closure every time. This way, it's possible to avoid boilerplate code, keeping it clean:
The BlockedCardException
should implement ValueException
interface:
In this example BlockedCardException
could be captured both for withdrawalCardId
and depositCardId
properties
depending on the cardId
value from the exception.
Capturing exceptions on nested array items
You are perfectly allowed to map the violations for the nested array items given that you have #[Valid]
attribute
on the iterable property. For example:
In this example, when InsufficientStockException
is captured, it will be mapped to the products[*].quantity
property, where *
stands for the index of the particular ProductDetails
instance from the products
array on which
the exception was captured.
Violation formatters
There are two built-in violation formatters that you can use - DefaultViolationFormatter
and ViolationListExceptionFormatter
. If needed, you can create your own custom violation formatter as described below.
Default
DefaultViolationFormatter
is used by default if other formatter is not specified.
It provides a very basic way to format violations, building ConstraintViolation
with such parameters
as: $message
, $root
, $propertyPath
, $value
.
Constraint Violation List Formatter
ViolationListExceptionFormatter
is used to format violations for the exceptions that
implement ViolationListException
interface. It allows to easily capture the exception that has ConstraintViolationList
obtained from the validator.
The typical exception class implementing ViolationListException
interface would look like this:
Then you can use ViolationListExceptionFormatter
on the #[Capture]
attribute of the property:
In this example, CardNumberValidationFailedException
is captured on the cardNumber
property and all the constraint
violations from this exception are mapped to this property. If there's message specified on the #[Capture]
attribute, it
is ignored in favor of the messages from ConstraintViolationList
.
Custom violation formatters
In some cases, you might need to customize the way violations are formatted such as passing additional
parameters to the message translation. You can achieve this by creating your own violation formatter service that
implements ExceptionViolationFormatter
interface:
Then you should register your custom formatter as a service:
In order for your custom violation formatter to be recognized by this bundle, its service must be tagged with
exceptional_validation.violation_formatter
tag. If you use autoconfiguration, this is done automatically by the service container owing to the fact thatExceptionViolationFormatter
interface is implemented.
Finally, your custom formatter should be specified in the #[Capture]
attribute:
In this example, RegistrationViolationsFormatter
is used to format constraint violations for
both LoginAlreadyTakenException
and WeakPasswordException
(though you are perfectly fine to use separate
formatters), enriching them with additional context.
Limitations
Capturing multiple exceptions at once
Typically, validation process is expected to capture all errors at once and return them as a list of violations. However, the whole concept of exceptional processing in PHP is based on the idea that only one exception could be thrown at a time, since only one logical instruction is executed at a time.
In case of Symfony Messenger, this is somewhat overcome by the fact that HandlerFailedException
can wrap multiple
exceptions collected from the underlying handlers. Though, currently there's no way to collect more than one
exception from the same handler because of the limitations of sequential computing model.
We are currently thinking about the issue and trying to anticipate the solution that will allow capturing multiple exceptions. Most likely the solution will be based on some ideas from the interaction combinators computing model, where code is no longer considered as a mere sequence of instructions, but rather as a graph of interactions that are combined and reduced on each step of evaluation.
All versions of exceptional-validation-bundle with dependencies
symfony/validator Version ^6.0 | ^7.0
webmozart/assert Version ^1.11
phphd/exception-toolkit Version ^1.0