Skip to content

Uncaught TypeError when throwing ValidationException in StateProcessor (during serialization) #7406

@quayle

Description

@quayle

API Platform version(s) affected: 4.2.0

Description

I wanted to make additional validation in my custom StateProcessor. When this validation fails I wanted to throw ValidationException to get uniform error response.

I do not need to do additional constraints & extra validators, simple if is enough in my case. That saying I have: throw new ValidationException('error message');.

This results in Fatal error. Error message:

Uncaught TypeError: ApiPlatform\Validator\Exception\ValidationException::getConstraintViolationList(): Return value must be of type Symfony\Component\Validator\ConstraintViolationListInterface, array returned in /app/vendor/api-platform/validator/Exception/ValidationException.php:208

How to reproduce

Throw exception in process method in your custom StateProcessor.

throw new \ApiPlatform\Validator\Exception\ValidationException('custom validation error message');

Possible Solution

$constraintViolationList property is defined as:
private array|ConstraintViolationListInterface $constraintViolationList = [].

private array|ConstraintViolationListInterface $constraintViolationList = [];

getConstraintViolationList method is defined as:
public function getConstraintViolationList(): ConstraintViolationListInterface

public function getConstraintViolationList(): ConstraintViolationListInterface

Fix 1 proposition

Change method declaration to return also array. This will require to change also in ConstraintViolationListAwareExceptionInterface.

Alternative fix 2 proposition

Change current method from:

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

To

    public function getConstraintViolationList(): ConstraintViolationListInterface
    {
        if (is_array($this->constraintViolationList)) {
            return new ConstraintViolationList();
        }
        return $this->constraintViolationList;
    }

Alternative fix 3 proposition

Disallow passing string to $message, because it is misleading in allowed usage?

Additional Context

Stack trace:

#0 /app/vendor/api-platform/symfony/Validator/Serializer/ValidationExceptionNormalizer.php(31): ApiPlatform\Validator\Exception\ValidationException->getConstraintViolationList()
#1 /app/vendor/symfony/serializer/Debug/TraceableNormalizer.php(50): ApiPlatform\Symfony\Validator\Serializer\ValidationExceptionNormalizer->normalize(Object(ApiPlatform\Validator\Exception\ValidationException), 'jsonld', Array)
#2 /app/vendor/symfony/serializer/Serializer.php(152): Symfony\Component\Serializer\Debug\TraceableNormalizer->normalize(Object(ApiPlatform\Validator\Exception\ValidationException), 'jsonld', Array)
#3 /app/vendor/symfony/serializer/Serializer.php(131): Symfony\Component\Serializer\Serializer->normalize(Object(ApiPlatform\Validator\Exception\ValidationException), 'jsonld', Array)
#4 /app/vendor/symfony/serializer/Debug/TraceableSerializer.php(44): Symfony\Component\Serializer\Serializer->serialize(Object(ApiPlatform\Validator\Exception\ValidationException), 'jsonld', Array)
#5 /app/vendor/api-platform/state/Processor/SerializeProcessor.php(85): Symfony\Component\Serializer\Debug\TraceableSerializer->serialize(Object(ApiPlatform\Validator\Exception\ValidationException), 'jsonld', Array)
#6 /app/vendor/api-platform/state/Processor/WriteProcessor.php(54): ApiPlatform\State\Processor\SerializeProcessor->process(Object(ApiPlatform\Validator\Exception\ValidationException), Object(ApiPlatform\Metadata\Error), Array, Array)
#7 /app/vendor/api-platform/symfony/Controller/MainController.php(123): ApiPlatform\State\Processor\WriteProcessor->process(Object(ApiPlatform\Validator\Exception\ValidationException), Object(ApiPlatform\Metadata\Error), Array, Array)
#8 /app/vendor/symfony/http-kernel/HttpKernel.php(183): ApiPlatform\Symfony\Controller\MainController->__invoke(Object(Symfony\Component\HttpFoundation\Request))
#9 /app/vendor/symfony/http-kernel/HttpKernel.php(76): Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object(Symfony\Component\HttpFoundation\Request), 2)
#10 /app/vendor/symfony/http-kernel/EventListener/ErrorListener.php(99): Symfony\Component\HttpKernel\HttpKernel->handle(Object(Symfony\Component\HttpFoundation\Request), 2, false)
#11 /app/vendor/api-platform/symfony/EventListener/ExceptionListener.php(50): Symfony\Component\HttpKernel\EventListener\ErrorListener->onKernelException(Object(Symfony\Component\HttpKernel\Event\ExceptionEvent))
#12 /app/vendor/symfony/event-dispatcher/Debug/WrappedListener.php(115): ApiPlatform\Symfony\EventListener\ExceptionListener->onKernelException(Object(Symfony\Component\HttpKernel\Event\ExceptionEvent), 'kernel.exceptio...', Object(Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher))
#13 /app/vendor/symfony/event-dispatcher/EventDispatcher.php(206): Symfony\Component\EventDispatcher\Debug\WrappedListener->__invoke(Object(Symfony\Component\HttpKernel\Event\ExceptionEvent), 'kernel.exceptio...', Object(Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher))
#14 /app/vendor/symfony/event-dispatcher/EventDispatcher.php(56): Symfony\Component\EventDispatcher\EventDispatcher->callListeners(Array, 'kernel.exceptio...', Object(Symfony\Component\HttpKernel\Event\ExceptionEvent))
#15 /app/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php(126): Symfony\Component\EventDispatcher\EventDispatcher->dispatch(Object(Symfony\Component\HttpKernel\Event\ExceptionEvent), 'kernel.exceptio...')
#16 /app/vendor/symfony/http-kernel/HttpKernel.php(241): Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher->dispatch(Object(Symfony\Component\HttpKernel\Event\ExceptionEvent), 'kernel.exceptio...')
#17 /app/vendor/symfony/http-kernel/HttpKernel.php(91): Symfony\Component\HttpKernel\HttpKernel->handleThrowable(Object(ApiPlatform\Validator\Exception\ValidationException), Object(Symfony\Component\HttpFoundation\Request), 1)
#18 /app/vendor/symfony/http-kernel/Kernel.php(182): Symfony\Component\HttpKernel\HttpKernel->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true)
#19 /app/vendor/runtime/frankenphp-symfony/src/Runner.php(38): Symfony\Component\HttpKernel\Kernel->handle(Object(Symfony\Component\HttpFoundation\Request))
#20 [internal function]: Runtime\FrankenPhpSymfony\Runner->{closure:Runtime\FrankenPhpSymfony\Runner::run():33}()
#21 /app/vendor/runtime/frankenphp-symfony/src/Runner.php(45): frankenphp_handle_request(Object(Closure))
#22 /app/vendor/autoload_runtime.php(29): Runtime\FrankenPhpSymfony\Runner->run()
#23 /app/public/index.php(5): require_once('/app/vendor/aut...')
#24 {main}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions