Skip to content

Commit

Permalink
Add value resolver (#62)
Browse files Browse the repository at this point in the history
  • Loading branch information
gabplch authored May 7, 2024
1 parent aa6f188 commit 26cabfa
Show file tree
Hide file tree
Showing 9 changed files with 235 additions and 2 deletions.
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);
}
}

0 comments on commit 26cabfa

Please sign in to comment.