dualmedia/symfony-request-dto-bundle

Symfony bundle which provides DTO object functionality and resolving

3.0.0 2024-03-05 11:01 UTC

This package is auto-updated.

Last update: 2024-04-05 11:12:51 UTC


README

Code Coverage Packagist Downloads

This bundle aims to lower the burden of typechecking, casting, loading entities and related annoyances of using requests in your api.

Bundle will automatically hook into Doctrine ORM Bundle and Nelmio API Docs Bundle so no additional configuration should be needed.

Install

Simply composer require dualmedia/symfony-request-dto-bundle, if applicable your Doctrine entity managers will be detected automatically and used as default providers for classes to be loaded with your requests if needed.

Then add the bundle to your config/bundles.php file like so

return [
    Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
    // other bundles ...
    DualMedia\DtoRequestBundle\DtoBundle::class => ['all' => true],
];

Upgrades

See CHANGES.md

Usage

  1. Create a DTO class for your request
use \DualMedia\DtoRequestBundle\Attributes\Dto\Path;
use \DualMedia\DtoRequestBundle\Model\AbstractDto;

class MyDto extends AbstractDto
{
    public int|null $myVar = null;
    
    #[Path("custom_path")]
    public string|null $myString = null;
}
  1. Add your dto as a controller argument
class MyController extends \Symfony\Bundle\FrameworkBundle\Controller\AbstractController
{
    public function myAction(MyDto $dto): \Symfony\Component\HttpFoundation\Response
    {
        // your dto here is already validated!
    }
}

Application wide handling of DTO issues

If you wish to automatically return a 4XX response code when a dto has failed validation you may use something like the following:

# config/services.yaml
App\EventSubscriber\ErrorSubscriber:
  decorates: exception_listener
  arguments:
    - '@App\EventSubscriber\ErrorSubscriber.inner'
class ErrorSubscriber implements \Symfony\Component\EventDispatcher\EventSubscriberInterface
{
    public function __construct(
        private readonly ErrorListener $decorated
    ) {
    }

    public static function getSubscribedEvents(){
        return [
            \Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent::class => 'onControllerArguments',
        ];
    }
    
    public function onControllerArguments(
        ControllerArgumentsEvent $event
    ): void {
        $this->decorated->onControllerArguments($event);

        $violationList = new ConstraintViolationList();

        foreach ($event->getArguments() as $argument) {
            if ($argument instanceof DtoInterface
                && !$argument->isOptional()
                && !$argument->isValid()) {
                $violationList->addAll($argument->getConstraintViolationList());
            }
        }

        if (0 !== $violationList->count()) {
            throw new ValidatorException($violationList); // handle and display, or just do whatever really
        }
    }
}

If you want to map a class-wide assert to a path without having to directly modify the constraint itself you may wrap it in MappedToPath

use \DualMedia\DtoRequestBundle\Constraints\MappedToPath;
use \DualMedia\DtoRequestBundle\Model\AbstractDto;
use Symfony\Component\Validator\Constraints as Assert;

#[MappedToPath(
    'property',
    new Assert\Expression(
        'this.property != null',
        message: 'This property cannot be null'
    )
)]
class MyDto extends AbstractDto
{
    public int|null $property = null;
}

Docs

Currently no documentation is available, but will be added in the future. For the time being see the DTO models for tests