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.
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],
];
See CHANGES.md
- 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;
}
- 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!
}
}
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;
}
Currently no documentation is available, but will be added in the future. For the time being see the DTO models for tests