Last active
May 24, 2023 09:49
-
-
Save DavidBadura/361c183bb76007b6ca822e10ffe5efbd to your computer and use it in GitHub Desktop.
RequestBodyResolver
This file contains 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 | |
declare(strict_types=1); | |
namespace App\Share\Application\ArgumentResolver; | |
use Attribute; | |
#[Attribute(Attribute::TARGET_PARAMETER)] | |
class FromRequestBody | |
{ | |
} |
This file contains 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 | |
declare(strict_types=1); | |
namespace App\Car\Application\Request; | |
use App\Car\Domain\LicensePlate; | |
use OpenApi\Attributes as OA; | |
final class RegisterCar | |
{ | |
public function __construct( | |
#[OA\Property(type: 'string', example: 'BN-AA-1234')] | |
public readonly LicensePlate $licensePlate | |
) { | |
} | |
} |
This file contains 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 | |
declare(strict_types=1); | |
namespace App\Car\Application\Controller; | |
use App\Car\Application\Command\RegisterCar; | |
use App\Car\Application\Request\RegisterCar as RegisterCarDto; | |
use App\Car\Domain\CarId; | |
use App\Share\Application\ArgumentResolver\FromRequestBody; | |
use App\Share\Application\Response\Created; | |
use Nelmio\ApiDocBundle\Annotation\Model; | |
use OpenApi\Attributes as OA; | |
use Symfony\Component\HttpKernel\Attribute\AsController; | |
use Symfony\Component\Messenger\MessageBusInterface; | |
use Symfony\Component\Routing\Annotation\Route; | |
#[AsController] | |
#[OA\Tag(name: 'car')] | |
final class RegisterCarAction | |
{ | |
public function __construct( | |
private readonly MessageBusInterface $commandBus, | |
) { | |
} | |
#[OA\Response( | |
response: 200, | |
description: 'Returns car id', | |
content: new OA\JsonContent( | |
ref: new Model(type: Created::class) | |
) | |
)] | |
#[OA\RequestBody( | |
required: true, | |
content: new OA\JsonContent( | |
ref: new Model(type: Created::class) | |
) | |
)] | |
#[Route('api/car/register', methods: ['POST'])] | |
public function __invoke( | |
#[FromRequestBody] | |
RegisterCarDto $requestDto | |
): Created { | |
$carId = CarId::generate(); | |
$this->commandBus->dispatch( | |
new RegisterCar( | |
$carId, | |
$requestDto->licensePlate | |
) | |
); | |
return new Created($carId->toString()); | |
} | |
} |
This file contains 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 | |
declare(strict_types=1); | |
namespace App\Share\Application\ArgumentResolver; | |
use App\Share\Application\RequestDtoResolver; | |
use RuntimeException; | |
use Symfony\Component\HttpFoundation\Request; | |
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; | |
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; | |
use function class_exists; | |
class RequestBodyResolver implements ArgumentValueResolverInterface | |
{ | |
public function __construct( | |
private readonly RequestDtoResolver $dtoResolver | |
) { | |
} | |
public function supports(Request $request, ArgumentMetadata $argument): bool | |
{ | |
return $argument->getAttributes(FromRequestBody::class) !== []; | |
} | |
/** | |
* @return iterable<object> | |
*/ | |
public function resolve(Request $request, ArgumentMetadata $argument): iterable | |
{ | |
$type = $argument->getType(); | |
if (!$type) { | |
throw new RuntimeException('missing type'); | |
} | |
if (!class_exists($type)) { | |
throw new RuntimeException('class not exists'); | |
} | |
yield $this->dtoResolver->resolve( | |
$request, | |
$type | |
); | |
} | |
} |
This file contains 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 | |
declare(strict_types=1); | |
namespace App\Share\Application; | |
use App\Share\Domain\Id; | |
use CuyZ\Valinor\Mapper\Object\DynamicConstructor; | |
use CuyZ\Valinor\Mapper\Source\JsonSource; | |
use CuyZ\Valinor\Mapper\Tree\Message\MessageBuilder; | |
use CuyZ\Valinor\Mapper\TreeMapper; | |
use CuyZ\Valinor\MapperBuilder; | |
use Symfony\Component\HttpFoundation\Request; | |
use Throwable; | |
use function assert; | |
use function dd; | |
use function is_a; | |
use function is_string; | |
final class RequestDtoResolver | |
{ | |
private TreeMapper $mapper; | |
public function __construct() | |
{ | |
$this->mapper = (new MapperBuilder()) | |
->registerConstructor( | |
#[DynamicConstructor] | |
static function (string $className, string $value): Id { | |
assert(is_a($className, Id::class, true)); | |
return $className::fromString($value); | |
} | |
) | |
->filterExceptions(static fn (Throwable $throwable) => MessageBuilder::from($throwable)) | |
->mapper(); | |
} | |
/** | |
* @param class-string<InstanceType> $className | |
* | |
* @return InstanceType | |
* | |
* @template InstanceType as object | |
*/ | |
public function resolve(Request $request, string $className): object | |
{ | |
$content = $request->getContent(); | |
assert(is_string($content)); | |
$dto = $this->mapper->map( | |
$className, | |
new JsonSource($content) | |
); | |
if (!is_a($dto, $className)) { | |
dd($dto, $className); | |
} | |
assert(is_a($dto, $className)); | |
return $dto; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment