Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add value resolver #62

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions DependencyInjection/StfalconApiExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
namespace StfalconStudio\ApiBundle\DependencyInjection;

use Doctrine\ORM\EntityManagerInterface;
use StfalconStudio\ApiBundle\Request\Filter\FilterExtractorInterface;
use StfalconStudio\ApiBundle\Security\JwtBlackListService;
use StfalconStudio\ApiBundle\Service\Exception\ResponseProcessor\CustomAppExceptionResponseProcessorInterface;
use Symfony\Component\Config\FileLocator;
Expand Down Expand Up @@ -53,5 +54,6 @@ public function load(array $configs, ContainerBuilder $container): void
$container->setParameter('stfalcon_api.api_host', $config['api_host']);
$container->setParameter('stfalcon_api.json_schema_dir', $config['json_schema_dir']);
$container->registerForAutoconfiguration(CustomAppExceptionResponseProcessorInterface::class)->addTag('stfalcon_api.exception_response_processor');
$container->registerForAutoconfiguration(FilterExtractorInterface::class)->addTag('stfalcon_api.filter_extractor');
}
}
37 changes: 37 additions & 0 deletions Request/Filter/FilterExtractorInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php
/*
* This file is part of the StfalconApiBundle.
*
* (c) Stfalcon LLC <stfalcon.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace StfalconStudio\ApiBundle\Request\Filter;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;

/**
* FilterExtractorInterface.
*/
interface FilterExtractorInterface
{
/**
* @param Request $request
*
* @return FilterInterface
*/
public function extractFilterFromRequest(Request $request): FilterInterface;

/**
* @param Request $request
* @param ArgumentMetadata $argument
*
* @return bool
*/
public function supports(Request $request, ArgumentMetadata $argument): bool;
}
20 changes: 20 additions & 0 deletions Request/Filter/FilterInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php
/*
* This file is part of the StfalconApiBundle.
*
* (c) Stfalcon LLC <stfalcon.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace StfalconStudio\ApiBundle\Request\Filter;

/**
* FilterInterface.
*/
interface FilterInterface
{
}
64 changes: 64 additions & 0 deletions Request/ValueResolver/FilterValueResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php
/*
* This file is part of the StfalconApiBundle.
*
* (c) Stfalcon LLC <stfalcon.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace StfalconStudio\ApiBundle\Request\ValueResolver;

use StfalconStudio\ApiBundle\Request\Filter\FilterExtractorInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;

/**
* FilterValueResolver.
*/
class FilterValueResolver implements ValueResolverInterface
{
/**
* @param iterable<FilterExtractorInterface> $filterExtractors
*/
public function __construct(private readonly iterable $filterExtractors)
{
}

/**
* @param Request $request
* @param ArgumentMetadata $argument
*
* @return iterable
*/
public function resolve(Request $request, ArgumentMetadata $argument): iterable
{
$extractor = $this->getSupportedExtractor($request, $argument);
if (!$extractor instanceof FilterExtractorInterface) {
return [];
}

yield $extractor->extractFilterFromRequest($request);
}

/**
* @param Request $request
* @param ArgumentMetadata $argument
*
* @return ?FilterExtractorInterface
*/
private function getSupportedExtractor(Request $request, ArgumentMetadata $argument): ?FilterExtractorInterface
{
foreach ($this->filterExtractors as $extractor) {
if ($extractor->supports($request, $argument)) {
return $extractor;
}
}

return null;
}
}
3 changes: 2 additions & 1 deletion Resources/config/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@
->bind('$apiHost', '%stfalcon_api.api_host%')
->bind('$jsonSchemaDir', '%stfalcon_api.json_schema_dir%')
->bind('$environment', '%env(APP_ENV)%')
->bind('iterable $errorResponseProcessors', new TaggedIteratorArgument('stfalcon_api.exception_response_processor'))
->bind('$symfonyConstraintViolationListNormalizer', new Reference('serializer.normalizer.constraint_violation_list'))
->bind('iterable $errorResponseProcessors', new TaggedIteratorArgument('stfalcon_api.exception_response_processor'))
->bind('iterable $filterExtractors', new TaggedIteratorArgument('stfalcon_api.filter_extractor'))
;

$services->load('StfalconStudio\ApiBundle\\', __DIR__.'/../../{Asset,Request,Serializer,Service,Util,Validator}/');
Expand Down
5 changes: 4 additions & 1 deletion Tests/DependencyInjection/StfalconApiExtensionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,10 @@ public function testLoadExtension(): void

$childDefinitions = $this->container->getAutoconfiguredInstanceof();
foreach ($childDefinitions as $childDefinition) {
self::assertTrue($childDefinition->hasTag('stfalcon_api.exception_response_processor'));
self::assertTrue(
$childDefinition->hasTag('stfalcon_api.exception_response_processor')
|| $childDefinition->hasTag('stfalcon_api.filter_extractor')
);
}
}

Expand Down
22 changes: 22 additions & 0 deletions Tests/Request/Filter/DummyFilterExtractor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace StfalconStudio\ApiBundle\Tests\Request\Filter;

use StfalconStudio\ApiBundle\Request\Filter\FilterExtractorInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;

class DummyFilterExtractor implements FilterExtractorInterface
{
public function extractFilterFromRequest(Request $request): DummyFilterModel
{
return new DummyFilterModel('foo');
}

public function supports(Request $request, ArgumentMetadata $argument): bool
{
return DummyFilterModel::class === $argument->getType();
}
}
19 changes: 19 additions & 0 deletions Tests/Request/Filter/DummyFilterModel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace StfalconStudio\ApiBundle\Tests\Request\Filter;

use StfalconStudio\ApiBundle\Request\Filter\FilterInterface;

class DummyFilterModel implements FilterInterface
{
public function __construct(private readonly string $foo)
{
}

public function getFoo()
{
return $this->foo;
}
}
65 changes: 65 additions & 0 deletions Tests/Request/ValueResolver/FilterValueResolverTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

declare(strict_types=1);

namespace StfalconStudio\ApiBundle\Tests\Request\ValueResolver;

use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use StfalconStudio\ApiBundle\Request\ValueResolver\FilterValueResolver;
use StfalconStudio\ApiBundle\Tests\Request\Filter\DummyFilterExtractor;
use StfalconStudio\ApiBundle\Tests\Request\Filter\DummyFilterModel;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;

class FilterValueResolverTest extends TestCase
{
private FilterValueResolver $filterValueResolver;

protected function setUp(): void
{
parent::setUp();

$this->argument = self::createMock(ArgumentMetadata::class);

$filterValueResolver = new FilterValueResolver([new DummyFilterExtractor()]);

$this->filterValueResolver = $filterValueResolver;
}

public function testFilterExtractorFound(): void
{
/** @var Request|MockObject $request */
$request = self::createMock(Request::class);
/** @var ArgumentMetadata|MockObject $request */
$argument = self::createMock(ArgumentMetadata::class);

$argument
->method('getType')
->willReturn(DummyFilterModel::class);

foreach ($this->filterValueResolver->resolve($request, $argument) as $item) {
self::assertInstanceOf(DummyFilterModel::class, $item);
self::assertSame('foo', $item->getFoo());
}
}

public function testFilterExtractorNotFound(): void
{
/** @var Request|MockObject $request */
$request = self::createMock(Request::class);
/** @var ArgumentMetadata|MockObject $request */
$argument = self::createMock(ArgumentMetadata::class);

$argument
->method('getType')
->willReturn('Foo\\Bar');

$numberOfFoundModels = 0;
foreach ($this->filterValueResolver->resolve($request, $argument) as $item) {
++$numberOfFoundModels;
}

self::assertEquals(0, $numberOfFoundModels);
}
}