Skip to content

Commit

Permalink
Added the ability to customize request parameter deserialization (#323)
Browse files Browse the repository at this point in the history
* Started adding concept of a request parameter deserializer (previously known as scalar parameters, which was not quite accurate), still a WIP

* Updated CHANGELOG

* Added unit tests, added more powerful built-in boolean conversion
  • Loading branch information
davidbyoung authored Jan 4, 2025
1 parent b2ba9b5 commit 816fadb
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 14 deletions.
32 changes: 32 additions & 0 deletions src/Api/Binders/ControllerBinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,23 @@
namespace Aphiria\Framework\Api\Binders;

use Aphiria\Api\Controllers\ControllerParameterResolver;
use Aphiria\Api\Controllers\IRequestParameterDeserializer;
use Aphiria\Api\Controllers\IRouteActionInvoker;
use Aphiria\Api\Controllers\RequestParameterDeserializer;
use Aphiria\Api\Controllers\RouteActionInvoker;
use Aphiria\Api\Validation\RequestBodyValidator;
use Aphiria\Application\Configuration\GlobalConfiguration;
use Aphiria\Application\Configuration\MissingConfigurationValueException;
use Aphiria\ContentNegotiation\IBodyDeserializer;
use Aphiria\ContentNegotiation\IContentNegotiator;
use Aphiria\DependencyInjection\Binders\Binder;
use Aphiria\DependencyInjection\IContainer;
use Aphiria\Net\Http\IResponseFactory;
use Aphiria\Validation\ErrorMessages\IErrorMessageInterpolator;
use Aphiria\Validation\IValidator;
use DateTime;
use DateTimeImmutable;
use DateTimeInterface;

/**
* Defines the binder for controllers
Expand All @@ -31,6 +38,7 @@ final class ControllerBinder extends Binder
{
/**
* @inheritdoc
* @throws MissingConfigurationValueException Thrown if the date format config value does not exist
*/
public function bind(IContainer $container): void
{
Expand All @@ -46,5 +54,29 @@ public function bind(IContainer $container): void
$controllerParameterResolver
);
$container->bindInstance(IRouteActionInvoker::class, $routeActionInvoker);
$container->bindInstance(IRequestParameterDeserializer::class, $this->getRequestParameterDeserializer($container));
}

/**
* Gets the request parameter deserializer
*
* @param IContainer $container The DI container
* @return IRequestParameterDeserializer The request parameter deserializer
* @throws MissingConfigurationValueException Thrown if the date format config value does not exist
*/
protected function getRequestParameterDeserializer(IContainer $container): IRequestParameterDeserializer
{
$deserializer = new RequestParameterDeserializer();
$dateFormat = GlobalConfiguration::getString('aphiria.serialization.dateFormat');
$deserializer->registerDeserializer(
DateTime::class,
fn (mixed $value): DateTime => DateTime::createFromFormat($dateFormat, (string)$value),
);
$deserializer->registerDeserializer(
DateTimeImmutable::class,
fn (mixed $value): DateTimeImmutable => DateTimeImmutable::createFromFormat($dateFormat, (string)$value),
);

return $deserializer;
}
}
2 changes: 1 addition & 1 deletion src/Serialization/Binders/SymfonySerializerBinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public function bind(IContainer $container): void
switch ($normalizerName) {
case DateTimeNormalizer::class:
$normalizers[] = new DateTimeNormalizer([
DateTimeNormalizer::FORMAT_KEY => GlobalConfiguration::getString('aphiria.serialization.dateFormat')
DateTimeNormalizer::FORMAT_KEY => GlobalConfiguration::getString('aphiria.serialization.dateTimeFormat')
]);
break;
case ObjectNormalizer::class:
Expand Down
47 changes: 35 additions & 12 deletions tests/Api/Binders/ControllerBinderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,42 +12,65 @@

namespace Aphiria\Framework\Tests\Api\Binders;

use Aphiria\Api\Controllers\IRequestParameterDeserializer;
use Aphiria\Api\Controllers\IRouteActionInvoker;
use Aphiria\Api\Controllers\RouteActionInvoker;
use Aphiria\Application\Configuration\GlobalConfiguration;
use Aphiria\Application\Configuration\HashTableConfiguration;
use Aphiria\ContentNegotiation\IBodyDeserializer;
use Aphiria\ContentNegotiation\IContentNegotiator;
use Aphiria\DependencyInjection\IContainer;
use Aphiria\Framework\Api\Binders\ControllerBinder;
use Aphiria\Net\Http\IResponseFactory;
use Aphiria\Validation\ErrorMessages\IErrorMessageInterpolator;
use Aphiria\Validation\IValidator;
use PHPUnit\Framework\MockObject\MockObject;
use Mockery;
use Mockery\MockInterface;
use PHPUnit\Framework\TestCase;

class ControllerBinderTest extends TestCase
{
private ControllerBinder $binder;
private IContainer&MockObject $container;
private IContainer&MockInterface $container;

protected function setUp(): void
{
$this->container = $this->createMock(IContainer::class);
$this->container = Mockery::spy(IContainer::class);
$this->binder = new ControllerBinder();

// Set up some universal mocks
$this->container->method('resolve')
->willReturnMap([
[IValidator::class, $this->createMock(IValidator::class)],
[IErrorMessageInterpolator::class, $this->createMock(IErrorMessageInterpolator::class)],
[IContentNegotiator::class, $this->createMock(IContentNegotiator::class)],
[IBodyDeserializer::class, $this->createMock(IBodyDeserializer::class)],
[IResponseFactory::class, $this->createMock(IResponseFactory::class)]
]);
$this->container->shouldReceive('resolve')
->with(IValidator::class)
->andReturn($this->createMock(IValidator::class));
$this->container->shouldReceive('resolve')
->with(IErrorMessageInterpolator::class)
->andReturn($this->createMock(IErrorMessageInterpolator::class));
$this->container->shouldReceive('resolve')
->with(IContentNegotiator::class)
->andReturn($this->createMock(IContentNegotiator::class));
$this->container->shouldReceive('resolve')
->with(IBodyDeserializer::class)
->andReturn($this->createMock(IBodyDeserializer::class));
$this->container->shouldReceive('resolve')
->with(IResponseFactory::class)
->andReturn($this->createMock(IResponseFactory::class));

// Set up the date format config
GlobalConfiguration::addConfigurationSource(new HashTableConfiguration(['aphiria' => ['serialization' => ['dateFormat' => 'Y-m-d']]]));
}

public function testRequestParameterDeserializerIsBound(): void
{
$this->container->shouldReceive('bindInstance')
->with(IRequestParameterDeserializer::class, $this->isInstanceOf(IRequestParameterDeserializer::class));
$this->binder->bind($this->container);
// Dummy assertion
$this->assertTrue(true);
}

public function testRouteActionInvokerIsBound(): void
{
$this->container->method('bindInstance')
$this->container->shouldReceive('bindInstance')
->with(IRouteActionInvoker::class, $this->isInstanceOf(RouteActionInvoker::class));
$this->binder->bind($this->container);
// Dummy assertion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ private static function getBaseConfig(): array
return [
'aphiria' => [
'serialization' => [
'dateFormat' => 'Ymd',
'dateTimeFormat' => 'Ymd',
'encoders' => [],
'nameConverter' => null,
'normalizers' => [],
Expand Down

0 comments on commit 816fadb

Please sign in to comment.