ConstraintViolationListInterface to Exception in Symfony
Asked Answered
T

5

8

I need to convert an object of type ConstraintViolationListInterface to a single exception for further logging, where the message is a concatenation of the messages from each constraint violation on the list, when the validation fails.

Obviously I can't repeat a foreach loop in every bundle using validation to achieve this, therefore I was thinking about creating one more bundle providing a simple service accepting ConstraintViolationListInterface and returning a single exception. Is there a standard solution for this in Symfony? Seems weird that I need to write this service, the problem seems to be common to me.

Tobytobye answered 18/12, 2017 at 20:32 Comment(0)
U
13

I also was surprised that symfony has nothing helpful for this, that's why I've created my custom exception:

class ValidationException extends \Exception
{
    private $violations;

    public function __construct(array $violations)
    {
        $this->violations = $violations;
        parent::__construct('Validation failed.');
    }

    public function getMessages()
    {
        $messages = [];
        foreach ($this->violations as $paramName => $violationList) {
            foreach ($violationList as $violation) {
                $messages[$paramName][] = $violation->getMessage();
            }
        }
        return $messages;
    }

    public function getJoinedMessages()
    {
        $messages = [];
        foreach ($this->violations as $paramName => $violationList) {
            foreach ($violationList as $violation) {
                $messages[$paramName][] = $violation->getMessage();
            }
            $messages[$paramName] = implode(' ', $messages[$paramName]);
        }
        return $messages;
    }
}

All code available here.

And I use this exception in a next way:

try {
    $errors = $validator->validate(...);
    if (0 !== count($errors)) {
        throw new ValidationException($errors);
    }
} catch (ValidationException $e) {
    // Here you can obtain your validation errors. 
    var_dump($e->getMessages());
}
Unemployed answered 19/12, 2017 at 10:35 Comment(1)
The question was just related on create a custom exception or related on how to handle correctly an exception thrown after detecting violations? @SergeyFrambesia
N
4

This solution works fine for me:

protected function violationsToArray(ConstraintViolationListInterface $violations)
{
    $messages = [];

    foreach ($violations as $constraint) {
        $prop = $constraint->getPropertyPath();
        $messages[$prop][] = $constraint->getMessage();
    }

    return $messages;
}

Note that using $violations array keys as property names will not work:

    $messages = [];

    foreach ($violations as $prop => $constraint) {
        // $prop will not contain any value and this will not work as expected
        $messages[$prop][] = $constraint->getMessage();
    }
Nolen answered 26/7, 2019 at 16:30 Comment(0)
H
3

There is a ValidationFailedException which might come in handy here.

You can find it here: https://github.com/symfony/symfony/blob/6.3/src/Symfony/Component/Validator/Exception/ValidationFailedException.php

Hoem answered 13/4, 2023 at 10:17 Comment(0)
T
2

I don't know if it was the case before, but, with Symfony 5.4 (and probably for awhile) you can use a string cast on the ConstraintViolationList object returned:

/** @var ConstraintViolationList $violations */
$violations = $this->validator->validate($object);
if ($violations->count()) {
    throw new \Exception('Validation failed: '.$violations);
} 

In this case, the cast is automatically done when concatening $violations with the previous string. Of course, you have no control over the output.

Trillbee answered 22/4, 2022 at 12:23 Comment(1)
the phpdoc comment also fixes phpstan reporting Cannot cast Symfony\Component\Validator\ConstraintViolationListInterface to stringCleodell
F
0

Maybe you can create a ConstraintViolationsEvent like this :

namespace AppBundle\Event;

use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\Validator\ConstraintViolationListInterface;

/**  
 * The order.placed event is dispatched each time an order is created
 * in the system.
 */
class ConstraintViolationsEvent extends Event
{
    const VIOLATIONS_DETECTED = 'constraint_violations.detected';

    protected $constraintViolationList;

    public function __construct(ConstraintViolationListInterface $constraintViolationList)
    {
        $this->constraintViolationList = $constraintViolationList;
    }

    public function getConstraintViolationList()
    {
        return $this->constraintViolationList;
    }
}

Then you can create a listener for this event, and inside this listener, you create your Exception based on all the violations found. Each time that you will find violations you just dispatch your event inside your controller like this :

class MyController extends Controller
{
    public function myFormAction(Request $request)
    {
        /** handle the request, get the form data, validate the form...etc. **/
        $event = new ConstraintViolationsEvent($constraintViolationList);
        $dispatcher->dispatch(ConstraintViolationsEvent::VIOLATIONS_DETECTED, $event);
    }
}

In fact, you can manage the creation of your Exception inside a service and call the service in the listener. It is up to you.

Frambesia answered 19/12, 2017 at 10:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.