Last active
October 13, 2021 13:07
-
-
Save faizanakram99/33c97a5f5b834dceeb90003574c7b98d to your computer and use it in GitHub Desktop.
RequestDTOArgumentValueResolver
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Qbil\Tests\CommonBundle\Services; | |
use Qbil\CommonBundle\Services\DTOArgumentValueResolver; | |
use Qbil\Tests\Fixtures\Controller\DTOController; | |
use Qbil\Tests\Fixtures\DTO\Person; | |
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; | |
use Symfony\Component\HttpFoundation\Request; | |
use Symfony\Component\HttpKernel\Controller\ArgumentResolver; | |
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactory; | |
class ArgumentResolverTest extends KernelTestCase | |
{ | |
/** @var ArgumentResolver */ | |
private static $resolver; | |
public static function setUpBeforeClass(): void | |
{ | |
self::bootKernel(); | |
$factory = new ArgumentMetadataFactory(); | |
$denormalizer = self::$container->get('serializer'); | |
$validator = self::$container->get('validator'); | |
self::$resolver = new ArgumentResolver($factory, [new DTOArgumentValueResolver($denormalizer, $validator)]); | |
} | |
public function testDTOIsPassedToController() | |
{ | |
$request = Request::create( | |
'/', | |
Request::METHOD_POST, | |
['id' => 1, 'name' => 'foo'] | |
); | |
$controller = [new DTOController(), 'foo']; | |
$expectedPerson = new Person(); | |
$expectedPerson->id = 1; | |
$expectedPerson->name = 'foo'; | |
self::assertEquals([$expectedPerson], self::$resolver->getArguments($request, $controller)); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Qbil\CommonBundle\Services; | |
use Qbil\CommonBundle\RequestDTOInterface; | |
use Symfony\Component\HttpFoundation\Request; | |
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; | |
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; | |
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; | |
use Symfony\Component\PropertyInfo\Type; | |
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; | |
use Symfony\Component\Validator\ConstraintViolationInterface; | |
use Symfony\Component\Validator\Validator\ValidatorInterface; | |
final class DTOArgumentValueResolver implements ArgumentValueResolverInterface | |
{ | |
private $denormalizer; | |
private $validator; | |
public function __construct( | |
DenormalizerInterface $denormalizer, | |
ValidatorInterface $validator | |
) { | |
$this->denormalizer = $denormalizer; | |
$this->validator = $validator; | |
} | |
public function supports(Request $request, ArgumentMetadata $argument): bool | |
{ | |
return null !== ($type = $argument->getType()) && | |
!in_array($type, Type::$builtinTypes, true) && | |
in_array(RequestDTOInterface::class, class_implements($type), true); | |
} | |
public function resolve(Request $request, ArgumentMetadata $argument) | |
{ | |
if (Request::METHOD_POST === $request->getMethod()) { | |
$data = $request->request->all(); | |
} elseif (Request::METHOD_GET === $request->getMethod()) { | |
$data = $request->query->all(); | |
} | |
if ( | |
'json' === $request->getContentType() | |
// not sure if the second check is required, so commented out | |
// && in_array($request->getMethod(), [Request::METHOD_PATCH, Request::METHOD_PUT, Request::METHOD_POST], true) | |
) { | |
$data = \json_decode($request->getContent(), true, 512, JSON_THROW_ON_ERROR); | |
} | |
$dto = $this->denormalizer->denormalize($data, $argument->getType()); | |
$this->assertDTOIsValid($dto); | |
yield $dto; | |
} | |
private function assertDTOIsValid(RequestDTOInterface $dto) | |
{ | |
$errors = []; | |
foreach ($this->validator->validate($dto) as $constraintViolation) { | |
/* @var ConstraintViolationInterface $constraintViolation */ | |
$errors[] = $constraintViolation->getMessage(); | |
} | |
if (0 !== count($errors)) { | |
throw new BadRequestHttpException(implode("\n", $errors)); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Qbil\Tests\CommonBundle\Services; | |
use Qbil\CommonBundle\Services\DTOArgumentValueResolver; | |
use Qbil\Tests\Fixtures\DTO\Person; | |
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; | |
use Symfony\Component\HttpFoundation\Request; | |
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; | |
class DTOArgumentValueResolverTest extends KernelTestCase | |
{ | |
public function testResolve() | |
{ | |
self::bootKernel(); | |
$argumentMetaData = $this->createMock(ArgumentMetadata::class); | |
$denormalizer = self::$container->get('serializer'); | |
$validator = self::$container->get('validator'); | |
$argumentValueResolver = new DTOArgumentValueResolver($denormalizer, $validator); | |
$request = new Request([], ['id' => 1, 'name' => 'foo']); | |
$request->setMethod(Request::METHOD_POST); | |
$expectedPerson = new Person(); | |
$expectedPerson->id = 1; | |
$expectedPerson->name = 'foo'; | |
$argumentMetaData | |
->method('getType') | |
->willReturn(Person::class); | |
$actualPerson = $argumentValueResolver->resolve($request, $argumentMetaData)->current(); | |
self::assertEquals($expectedPerson, $actualPerson); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Qbil\Tests\Fixtures\Controller; | |
use Qbil\Tests\Fixtures\DTO\Person; | |
class DTOController | |
{ | |
public function foo(Person $person) | |
{ | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Qbil\Tests\Fixtures\DTO; | |
use Qbil\CommonBundle\RequestDTOInterface; | |
use Symfony\Component\Validator\Constraints as Assert; | |
class Person implements RequestDTOInterface | |
{ | |
/** | |
* @Assert\Positive(message="id should be a natural number") | |
*/ | |
public $id; | |
public $name; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Qbil\CommonBundle; | |
interface RequestDTOInterface | |
{ | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
services: | |
Qbil\CommonBundle\Services\DTOArgumentValueResolver: | |
tags: ['controller.argument_value_resolver'] |
In
DTOArgumentValueResolver::resolve()
, I would suggest to not merge GET and POST data or content, this could lead to bad injections, and instead to use a more straightforward implementation likeif ($request->isMethod('GET')) { $data = $request->query->all(); } elseif (...) {...}
Done
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
In
DTOArgumentValueResolver::resolve()
, I would suggest to not merge GET and POST data or content, this could lead to bad injections, and instead to use a more straightforward implementation likeif ($request->isMethod('GET')) { $data = $request->query->all(); } elseif (...) {...}