lsbproject / request-bundle
Bundle to structure common request data
Installs: 270
Dependents: 1
Suggesters: 0
Security: 0
Stars: 1
Watchers: 2
Forks: 0
Open Issues: 0
Type:symfony-bundle
Requires
- php: >=5.6
- ext-json: *
- sensio/framework-extra-bundle: ^3 || ^4 || ^5 || ^6
- symfony/framework-bundle: ^3 || ^4 || ^5 || ^6
- symfony/property-info: ^3 || ^4 || ^5 || ^6
Requires (Dev)
- roave/security-advisories: dev-latest
- dev-master
- v8.0.0.x-dev
- v7.2.4
- v7.2.3
- v7.2.2
- v7.2.1
- v7.1.0
- v7.0.0
- v6.1.3
- v6.1.2
- v6.1.1
- v6.1.0
- v6.0.1
- v6.0.0
- v5.0.1
- v5.0.0
- v4.0.0
- v3.1.0
- v3.0.2
- v3.0.1
- v3.0.0
- v2.3.2
- v2.2.2
- v2.2.1
- v2.2.0
- v2.1.0
- v2.0.0
- v1.3.1
- v1.3.0
- v1.2.1
- v1.2.0
- v1.1.0
- v1.0.0
- v0.1.3
- v0.1.2
- v0.1.1
- v0.1.0
- dev-refactoring-and-cleanup
- dev-collection
- dev-refactoring-request-factory
- dev-entity-collection
- dev-alternate-isdto
This package is auto-updated.
Last update: 2023-07-09 00:31:55 UTC
README
Caution: bundle will not have updates in favor of new symfony attribute argument resolvers, consider use it instead of this package
Request bundle created to represent request data as a strict objects.
Installation
Open a command console, enter your project directory and execute the following command to download the latest stable version of this bundle:
$ composer require lsbproject/request-bundle
Usage
Create a request class and extend AbstractRequest:
use LSBProject\RequestBundle\Request\RequestInterface; class TestRequest implements RequestInterface { public $fooBar; }
That's all. This will require foo_bar
parameter to be present in request, query, cookie, head or attribute (All types you can find in RequestStorage
).
Then use it in controller:
/** * @Route("/test") */ public function test(TestRequest $testRequest): Response { return new Response($testRequest->fooBar); }
Using objects
Also this bundle supports loading objects like you usually do with @ParamConverter
. All ParamConverts will be performed to the property.
use LSBProject\RequestBundle\Request\RequestInterface; use App\Model\SomeAwesomeClass; class TestRequest implements RequestInterface { public SomeAwesomeClass $fooBar; }
If you are not using PHP 7.4, you can point the class with annotations @var
or @PropConverter
(attributes also available right now with the same annotation class)
use LSBProject\RequestBundle\Configuration\PropConverter; use LSBProject\RequestBundle\Request\RequestInterface; class TestRequest implements RequestInterface { /** * @var App\Model\SomeAwesomeClass */ public $fooBar; // or /** * @PropConverter(App\Model\SomeAwesomeClass::class) */ public $fooBarBaz; // or #[PropConverter(App\Model\SomeAwesomeClass::class)] public $fooBarBaz; }
(Be aware that you need to specify full classname with a namespace)
Configuring property
As you could notice there is a useful annotation @PropConverter
which is in fact is an adapter to @ParamConverter
of sensio-framework-bundle.
Be free to modify any of parameters, as they are working in the same way as in the original one.
use LSBProject\RequestBundle\Request\RequestInterface; use LSBProject\RequestBundle\Configuration as LSB; class TestRequest implements RequestInterface { /** * @LSB\PropConverter(App\Model\SomeAwesomeClass::class, converter="awesome_converter", options={"mapping": {"user_id": "id"}}) */ public $fooBarBaz; }
Request storage
By default all parameters from body, request, headers, cookies or path will be used.
To restrict parameter to be located in exact place you can use @RequestStorage
annotation
use LSBProject\RequestBundle\Request\RequestInterface; use LSBProject\RequestBundle\Configuration as LSB; /** * @LSB\RequestStorage({LSB\RequestStorage::BODY, LSB\RequestStorage::PATH}) */ class TestRequest implements RequestInterface { public $fooBaz; /** * @LSB\RequestStorage({LSB\RequestStorage::BODY}) */ public $fooBar; }
From example above you will get foo_baz
parameter from request body or path, and foo_bar
parameter exactly from request body.
There are 5 types of storage: query
, body
, path
, cookie
and header
.
Validation
You can use symfony/validation
to validate parameters in request.
Install component and use it as usual
use LSBProject\RequestBundle\Request\RequestInterface; class TestRequest implements RequestInterface { /** * @Assert\NotBlank() */ public int $userId; }
Using mutators
To specify property you also can use setters instead of public
properties to add some additional logic.
use LSBProject\RequestBundle\Request\RequestInterface; class TestRequest implements RequestInterface { private string $comment; public function setComment(string $comment): void { $this->comment = trim($comment); } }
Working with entities
There is an annotation @Entity
which is almost equal to the sensio annotation.
use LSBProject\RequestBundle\Configuration as LSB; use LSBProject\RequestBundle\Request\RequestInterface; use App\Entity\User; class TestRequest implements RequestInterface { /** * @LSB\Entity(App\Entity\User::class, expr="repository.find(id)", mapping={"id": "user_id"}) */ public $userA; // or /** * @LSB\Entity(options={"id"="user_id"}) */ public User $userB; // or /** * @LSB\Entity(options={"mapping": {"user_id": "id"}}) */ public User $userC; }
Use mapping
property to point aliases from the request to the original parameters names.
Custom naming conversion
By default all properties will be converter to snake_case style. You can change this behaviour
by creating a class which implements LSBProject\RequestBundle\Util\NamingConversion\NamingConversionInterface
<?php namespace LSBProject\RequestBundle\Util\NamingConversion; final class CamelCaseToSnakeConversion implements NamingConversionInterface { /** * {@inheritDoc} */ public function normalize($value) { return strtolower(preg_replace('/[A-Z]/', '_\\0', lcfirst($value)) ?: ''); } /** * {@inheritDoc} */ public function denormalize($value) { $camelCasedName = preg_replace_callback('/(^|_|\.)+(.)/', function ($match) { return ('.' === $match[1] ? '_' : '') . strtoupper($match[2]); }, $value); return lcfirst($camelCasedName); } }
then you should register it as a service and point it out in the bundle configuration.
# ./config/packages/lsb_project_request.yaml lsb_project_request: naming_conversion: my_custom_conversion
You can also apply your conversion to the separate object or its part. Just register your conversion
as a service and point its name in RequestStorage::converter
Using DTOs as property
There is also a possibility to specify deeper nested level in the request. To do it, specify special option of PropConverter::isDto
for class property. This will prevent standart ParamConverter
to be applied and will recursively perform AbstractRequest
converter to the object.
use LSBProject\RequestBundle\Request\RequestInterface; use LSBProject\RequestBundle\Configuration as LSB; use Symfony\Component\HttpFoundation\Request; use App\Request\DTO\Data; /** * @LSB\RequestStorage({@LSB\RequestStorage::BODY}) */ class JsonRpcRequest extends Request implements RequestInterface { public string $jsonrpc; /** * 'method' property is already present in a base Request class, so alias should be used * * @LSB\PropConverter(name="method") */ public string $methodName; public int $id; /** @LSB\PropConverter(isDto=true) */ public Data $params; }
Using Collections
To specify an array of objects which should be converted, use isCollection
property or use typehinting. You can use it in combination
with isDto
to specify array of objects to be set.
Be aware that it's not recommended to use collection for entities as it's very inefficient way of getting data as it
will perform request to DB.
use LSBProject\RequestBundle\Request\RequestInterface; use LSBProject\RequestBundle\Configuration as LSB; use App\Request\DTO\Data; /** * @LSB\RequestStorage({@LSB\RequestStorage::BODY}) */ class JsonRpcRequest implements RequestInterface { public string $jsonrpc; /** * 'method' property is already present in a base Request class, so alias should be used * * @LSB\PropConverter(name="method") */ public string $methodName; public int $id; /** * @LSB\PropConverter(isDto=true) * @var App\Request\DTO\Data[] */ public array $params; }
Using discriminator field
Sometimes we do not know which object will be returned. To resolve this discriminator mechanism appears in game.
use LSBProject\RequestBundle\Configuration as LSB; use LSBProject\RequestBundle\Request\RequestInterface; final class TestDiscriminatedRequest implements RequestInterface { #[LSB\PropConverter(isDto: true)] #[LSB\Discriminator( field: 'type', mapping: [ 'foo' => new LSB\PropConverter(class: DiscriminatorParamsFoo::class, isDto: false), 'bar' => DiscriminatorParamsBar::class ] )] public DiscriminatorParamsFoo|DiscriminatorParamsBar $discriminated; } abstract class AbstractDiscriminatorParams { public string $type; } final class DiscriminatorParamsBar extends AbstractDiscriminatorParams { public string $bar; } final class DiscriminatorParamsFoo extends AbstractDiscriminatorParams { public string $foo; }
Correspondingly to field
field in Discriminator
attribute object will be configured accordingly to mapping.
Use on a custom objects
There is also possibility to apply LSB converter to the object
not inheriting AbstractRequest. You can use @LSB\Request
annotation
to point out parameter in controller.
use LSBProject\RequestBundle\Configuration as LSB; //... /** * @Route("/foo") * @LSB\Request("params", storage=@LSB\RequestStorage({@LSB\RequestStorage::HEAD})) */ public function testHeadRequest(TestParamsA $params): Response { return new Response($params->foo); }
Examples
More examples you can find here
Writing documentation
OpenApi: https://github.com/22116/request-doc-bundle
Known issues
- Multiple types such as a mix of classes and arrays are not supported, but you can still use a mix of scalar types