Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@adrenalinkin
Last active November 12, 2019 08:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save adrenalinkin/f5cddf1afea865a3b91ac078a1cb8337 to your computer and use it in GitHub Desktop.
Save adrenalinkin/f5cddf1afea865a3b91ac078a1cb8337 to your computer and use it in GitHub Desktop.
Usage example for Swagger Resolver Bundle

Environment

Usage example for the adrenalinkin/swagger-resolver-bundle.

Example assumes you will use Symfony 4 with NelmioApiDocBundle as Swagger wrap.

Note: Feel free to connect with me by email adrenalinkin@gmail.com or in Telegramm @adrenaL1nkin.

Prepare:

  1. Install dto-resolver for creating valid DTO from array.
  2. Register argument resolvers EntryDtoArgumentResolver.php and CollectionEntryDtoArgumentResolver.php (look services.yaml). After that you will be able to pass already resolved DTO as controller argument!

Create your business code. Example with UseCase paradigm:

Note: Handlers with business logic has been omitted.

  1. Add CreateProfileEntryDto.php to validate request body.
  2. Add GetBaseProfileResultDto.php to validate response body. (IF NEEDED)
  3. Add controller ProfileController.php.
<?php
declare(strict_types=1);
namespace App\ArgumentResolver;
use Exception;
use Generator;
use Linkin\Bundle\SwaggerResolverBundle\Factory\SwaggerResolverFactory;
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\OptionsResolver\Exception\InvalidOptionsException;
use Wakeapp\Component\DtoResolver\Dto\DtoResolverInterface;
use function array_merge;
use function is_subclass_of;
use function json_decode;
class EntryDtoArgumentResolver implements ArgumentValueResolverInterface
{
/**
* @var SwaggerResolverFactory
*/
private $factory;
public function __construct(SwaggerResolverFactory $factory)
{
$this->factory = $factory;
}
/**
* {@inheritDoc}
*/
public function supports(Request $request, ArgumentMetadata $argument): bool
{
return is_subclass_of($argument->getType(), DtoResolverInterface::class);
}
/**
* {@inheritDoc}
*/
public function resolve(Request $request, ArgumentMetadata $argument): Generator
{
$dtoClassName = $argument->getType();
$resolver = $this->factory->createForRequest($request);
$data = $this->getRequestDataByMethod($request);
try {
yield new $dtoClassName($data, $resolver);
} catch (InvalidOptionsException $e) {
throw new BadRequestHttpException($e->getMessage(), $e);
}
}
private function getRequestDataByMethod(Request $request): array
{
$requestMethod = $request->getMethod();
$data = $request->attributes->all();
if ($requestMethod === Request::METHOD_GET || $requestMethod === Request::METHOD_DELETE) {
return array_merge($data, $request->query->all());
}
try {
$body = json_decode($request->getContent(), true);
} catch (Exception $e) {
throw new BadRequestHttpException('Unexpected content - should be JSON', $e);
}
return array_merge($data, $body);
}
}
<?php
declare(strict_types=1);
namespace App\ArgumentResolver;
use Exception;
use Generator;
use Linkin\Bundle\SwaggerResolverBundle\Factory\SwaggerResolverFactory;
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\OptionsResolver\Exception\InvalidOptionsException;
use Wakeapp\Component\DtoResolver\Dto\CollectionDtoResolverInterface;
use function is_subclass_of;
use function json_decode;
class CollectionEntryDtoArgumentResolver implements ArgumentValueResolverInterface
{
/**
* @var SwaggerResolverFactory
*/
private $factory;
public function __construct(SwaggerResolverFactory $factory)
{
$this->factory = $factory;
}
/**
* {@inheritDoc}
*/
public function supports(Request $request, ArgumentMetadata $argument): bool
{
return is_subclass_of($argument->getType(), CollectionDtoResolverInterface::class);
}
/**
* {@inheritDoc}
*/
public function resolve(Request $request, ArgumentMetadata $argument): Generator
{
/** @var CollectionDtoResolverInterface $dtoCollectionClassName */
$dtoCollectionClassName = $argument->getType();
$resolver = $this->factory->createForDefinition($dtoCollectionClassName::getItemDtoClassName());
/** @var CollectionDtoResolverInterface $dtoCollection */
$dtoCollection = new $dtoCollectionClassName($resolver);
try {
$body = json_decode($request->getContent(), true);
} catch (Exception $e) {
throw new BadRequestHttpException('Unexpected content - should be JSON', $e);
}
foreach ($body as $item) {
try {
$dtoCollection->add($item);
} catch (InvalidOptionsException $e) {
throw new BadRequestHttpException($e->getMessage(), $e);
}
}
yield $dtoCollection;
}
}
services:
# other configuration ...
App\ArgumentResolver\EntryDtoArgumentResolver:
arguments:
- '@Linkin\Bundle\SwaggerResolverBundle\Factory\SwaggerResolverFactory'
tags:
- { name: controller.argument_value_resolver, priority: 250 }
App\ArgumentResolver\CollectionEntryDtoArgumentResolver:
arguments:
- '@Linkin\Bundle\SwaggerResolverBundle\Factory\SwaggerResolverFactory'
tags:
- { name: controller.argument_value_resolver, priority: 250 }
<?php
declare(strict_types=1);
namespace App\UseCase\CreateProfile;
use Swagger\Annotations as SWG;
use Wakeapp\Component\DtoResolver\Dto\DtoResolverTrait;
use Wakeapp\Component\DtoResolver\Dto\DtoResolverInterface;
/**
* @SWG\Definition(
* type="object",
* description="Profile info",
* required={"email", "firstName", "lastName", "age", "version"},
* )
*/
class CreateProfileEntryDto implements DtoResolverInterface
{
use DtoResolverTrait;
/**
* @var string
*
* @SWG\Property(description="Profile email", example="test@gmail.com")
*/
private $email;
/**
* @var string
*
* @SWG\Property(description="User's first name", example="John")
*/
private $firstName;
/**
* @var string
*
* @SWG\Property(description="User's last name", example="Doe")
*/
private $lastName;
/**
* @var int
*
* @SWG\Property(description="User's age", example="28")
*/
private $age;
/**
* @var string
*
* @SWG\Property(
* description="Actual version of the user application",
* example="1.2.3",
* pattern="^[\d]+\.[\d]+\.[\d]+$"
* )
*/
protected $version;
public function getEmail(): string
{
return $this->email;
}
public function getFirstName(): string
{
return $this->firstName;
}
public function getLastName(): string
{
return $this->lastName;
}
public function getAge(): int
{
return $this->age;
}
public function getVersion(): string
{
return $this->version;
}
}
<?php
declare(strict_types=1);
namespace App\UseCase\GetBaseProfile;
use Swagger\Annotations as SWG;
use Wakeapp\Component\DtoResolver\Dto\DtoResolverTrait;
use Wakeapp\Component\DtoResolver\Dto\DtoResolverInterface;
/**
* @SWG\Definition(
* type="object",
* description="Profile info",
* required={"email", "firstName", "lastName"},
* )
*/
class GetBaseProfileResultDto implements DtoResolverInterface
{
use DtoResolverTrait;
/**
* @var string
*
* @SWG\Property(description="Profile email", example="test@gmail.com")
*/
private $email;
/**
* @var string
*
* @SWG\Property(description="User's first name", example="John")
*/
private $firstName;
/**
* @var string
*
* @SWG\Property(description="User's last name", example="Doe")
*/
private $lastName;
public function getEmail(): string
{
return $this->email;
}
public function getFirstName(): string
{
return $this->firstName;
}
public function getLastName(): string
{
return $this->lastName;
}
}
<?php
declare(strict_types=1);
namespace App\Controller;
use App\UseCase\CreateProfile\CreateProfileEntryDto;
use App\UseCase\GetBaseProfile\GetBaseProfileResultDto;
use Linkin\Bundle\SwaggerResolverBundle\Factory\SwaggerResolverFactory;
use Nelmio\ApiDocBundle\Annotation\Model;
use Swagger\Annotations as SWG;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Annotation\Route;
/**
* Assume this file registered with tag 'controller.service_arguments'
*
* @Route("/api/profile")
*/
class ProfileController
{
/**
* Create profile
*
* @Route("/create", methods={"POST"})
*
* @SWG\Parameter(name="body", in="body", @Model(type=CreateProfileEntryDto::class), required=true)
*
* @param CreateProfileEntryDto $dto
*
* @return JsonResponse
*/
public function createAction(CreateProfileEntryDto $dto): JsonResponse
{
return new JsonResponse($dto); // return as is for show result
}
/**
* Returns user profile
*
* @Route("/info", methods={"GET"})
*
* @SWG\Response(response=ApiResponse::HTTP_OK, @Model(type=GetBaseProfileResultDto::class))
*
* @param SwaggerResolverFactory $factory
*
* @return JsonResponse
*/
public function getProfile(SwaggerResolverFactory $factory): JsonResponse
{
// business logic
$data = [
'email' => 'test-user@mail.ru',
'firstName' => 'Test',
'lastName' => 'User',
];
// if you want to validate response
// you can move this code into fabric
$resolver = $factory->createForDefinition(GetBaseProfileResultDto::class);
$dto = new GetBaseProfileResultDto($data, $resolver);
return new JsonResponse($dto);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment