jrm / request-bundle
Makes it easy to get your own request as a controller argument
Installs: 2 179
Dependents: 0
Suggesters: 0
Security: 0
Stars: 2
Watchers: 1
Forks: 0
Open Issues: 0
Type:symfony-bundle
Requires
- php: ^8.2 || ^8.3
- ext-filter: *
- symfony/config: ^6.4 || ^7.0
- symfony/dependency-injection: ^6.4 || ^7.0
- symfony/http-foundation: ^6.4 || ^7.0
- symfony/http-kernel: ^6.4 || ^7.0
- symfony/property-access: ^6.4 || ^7.0
- symfony/property-info: ^6.4|^7.0
- symfony/serializer: ^6.4 || ^7.0
- symfony/translation-contracts: ^3.4
- symfony/type-info: ^6.4 || ^7.0
- symfony/validator: ^6.4 || ^7.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.16
- maglnet/composer-require-checker: ^4.6
- phpstan/phpstan: ^1.10
- phpstan/phpstan-phpunit: ^1.3
- phpunit/phpunit: ^10.1
- rector/rector: ^0.15
- roave/security-advisories: dev-latest
- symfony/framework-bundle: ^6.4 || ^7.0
- symfony/translation: ^6.4 || ^7.0
This package is not auto-updated.
Last update: 2024-11-10 17:36:28 UTC
README
This is an implementation of hydrating data from symfony requests in narrow request for specific controller/action.
Installation
- Require this bundle in your application:
composer require jrm/request-bundle
- Enable the bundle in your application:
return [ # ... Jrm\RequestBundle\JrmRequestBundle::class => ['all' => true], # ... ];
Usage
Create request using some sources of data:
Body
(take data from request body or form)Collection
(Needed for hydrate of collection some sub objects)Cookie
(take data from cookies)EmbeddableRequest
(Needed for hydrate of some sub object)File
(take data from files)Header
(take data from headers)PathAttribute
(take data from path attributes)Query
(take data from query string)
Request
use Jrm\RequestBundle\Model\Source; use Jrm\RequestBundle\Attribute\Collection; use Jrm\RequestBundle\Attribute\Header; use Jrm\RequestBundle\Attribute\PathAttribute; final class MyRequest { public function __construct( #[PathAttribute()] public readonly int $id, #[Body('pos_id')] private readonly string $posId, #[Header('Content-Type')] public readonly string $contentType, #[Assert\Valid] #[Collection( type: ProductItem::class, source: Source::BODY, path: 'products', )] public readonly array $products, ) { } }
Controller
This example with invokable controller, but you can use it with regular controller.
use Jrm\RequestBundle\MapRequest; #[Route( '/do-something/{id}', name: 'app.do.something', methods: [Request::METHOD_POST], )] final class MyAction { public function __invoke(#[MapRequest] MyRequest $request): JsonResponse { //do something return new JsonResponse(null); } }
Nested fields
Your data, for example request body, may have some nesting.
{ "request": { "some_field": "some_value", "next_field": "next_value" } }
You can pass path to this filed.
use Jrm\RequestBundle\Attribute\Body; final class MyRequest { public function __construct( #[Body('request.some_field')] public readonly string $someField, #[Body('request.next_field')] public readonly string $nextField, ) { } }
Validation
You can validate your request by symfony constraints, if validation will be failed, Jrm\RequestBundle\Listener\RequestValidationFailedExceptionListener will send response with all failed fields and error messages for them
use Jrm\RequestBundle\Attribute\Query; use Symfony\Component\Validator\Constraints as Assert; final class MyRequest { public function __construct( #[Assert\NotBlank] #[Query('some_field')] public readonly string $field, ) { } }
{ "message": "Validation failed.", "errors": [ { "code": "48b70abd-a021-4ce7-9662-616cd58eeaee", "message": "This value should not be blank.", "parameters": [], "property_path": "some_field" } ] }
Collection
In some cases you may need to hydrate collection of data, you can use Collection attribute and "describe" this collection items as a separate object.
use Jrm\RequestBundle\Attribute\Internal\Item; use Symfony\Component\Validator\Constraints as Assert; final class MyCollectionItem { public function __construct( #[Assert\Uuid] #[Item()] public readonly string $id, ) { } }
use Jrm\RequestBundle\Model\Source; use Jrm\RequestBundle\Attribute\Collection; use Symfony\Component\Validator\Constraints as Assert; final class MyRequest { public function __construct( #[Assert\Valid] #[Collection( type: MyCollectionItem::class, source: Source::BODY, path: 'sub_ids', )] public readonly array $items, ) { } }
NOTE: You should use #[Assert\Valid] for your collection as in the example above for validating your collection, because without this constraint, symfony validator ignore it
Custom Resolver
You can create your custom resolver to define new way to get of data for request
For this you need to create:
Parameter
use Jrm\RequestBundle\Attribute\RequestAttribute; #[Attribute(Attribute::TARGET_PARAMETER, Attribute::TARGET_PROPERTY)] final class UserId implements RequestAttribute { /** * @return class-string<UserIdResolver> */ public function resolvedBy(): string { return UserIdResolver::class; } }
ParameterResolver
use App\Domain\User\Exception\UserNotAuthorizedException; use App\Domain\User\Model\User; use Jrm\RequestBundle\Exception\UnexpectedAttributeException; use Jrm\RequestBundle\Model\Metadata; use Jrm\RequestBundle\Attribute\RequestAttribute; use Jrm\RequestBundle\Attribute\ValueResolver; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; final class UserIdResolver implements ValueResolver { public function __construct( private readonly TokenStorageInterface $tokenStorage, ) { } public function resolve( Request $request, Metadata $metadata, RequestAttribute $attribute, ): int { if (!$attribute instanceof UserId) { throw new UnexpectedAttributeException(UserId::class, $attribute::class); } try { $user = $this->tokenStorage->getToken()?->getUser(); if ($user === null) { throw new UserNotAuthorizedException(); } return $user->id(); } catch (Throwable $throwable) { if ($parameter->isOptional()) { return $parameter->defaultValue(); } throw $throwable; } } }
Plans:
- Make automation conversion to Open Api Doc
- Make the
Item
attribute optional - Add validation tests that all requests are valid classes with supported attributes and types
- Fix issue with validation, when your request haven't any required params
- Add bundle to symfony flex
- Add more unit and integration tests