Skip to content

Commit

Permalink
Merge pull request #16 from pfilsx/feature/DPC-11
Browse files Browse the repository at this point in the history
DPC-11 argument resolver
  • Loading branch information
pfilsx authored May 30, 2022
2 parents b7b5274 + 425040e commit a9d8222
Show file tree
Hide file tree
Showing 28 changed files with 976 additions and 193 deletions.
23 changes: 11 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ Requirement
-----------
* PHP 7.4+
* Symfony 4.4+|5.3+|6.0+
* SensioFrameworkExtraBundle

Installation
------------
Expand All @@ -38,7 +37,7 @@ You need to add the following lines in your deps :
```json
{
"require": {
"pfilsx/dto-param-converter-bundle": "^1.0"
"pfilsx/dto-param-converter-bundle": "^2.0"
}
}
```
Expand Down Expand Up @@ -166,16 +165,16 @@ Or You can configure converter for each action
```php
/**
* @ParamConverter("someDto", options={
* DtoParamConverter::OPTION_SERIALIZER_CONTEXT: {},
* DtoParamConverter::OPTION_VALIDATOR_GROUPS: {},
* DtoParamConverter::OPTION_PRELOAD_ENTITY: true,
* DtoParamConverter::OPTION_STRICT_PRELOAD_ENTITY: true,
* DtoParamConverter::OPTION_ENTITY_ID_ATTRIBUTE: null,
* DtoParamConverter::OPTION_ENTITY_MANAGER: null,
* DtoParamConverter::OPTION_ENTITY_MAPPING: {}
* DtoParamConverter::OPTION_ENTITY_EXPR: null,
* DtoParamConverter::OPTION_VALIDATE: false
* @DtoResolver("someDto", options={
* DtoArgumentResolver::OPTION_SERIALIZER_CONTEXT: {},
* DtoArgumentResolver::OPTION_VALIDATOR_GROUPS: {},
* DtoArgumentResolver::OPTION_PRELOAD_ENTITY: true,
* DtoArgumentResolver::OPTION_STRICT_PRELOAD_ENTITY: true,
* DtoArgumentResolver::OPTION_ENTITY_ID_ATTRIBUTE: null,
* DtoArgumentResolver::OPTION_ENTITY_MANAGER: null,
* DtoArgumentResolver::OPTION_ENTITY_MAPPING: {}
* DtoArgumentResolver::OPTION_ENTITY_EXPR: null,
* DtoArgumentResolver::OPTION_VALIDATE: false
* })
*/
public function someAction(SomeDto $someDto): Response
Expand Down
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
"php": "^7.4 || ^8.0",
"symfony/framework-bundle": "^4.4|^5.3|^6.0",
"doctrine/annotations": "^1.0",
"sensio/framework-extra-bundle": "^5.6|^6.1",
"symfony/serializer": "^4.4|^5.3|^6.0",
"symfony/property-access": "^4.4|^5.3|^6.0",
"symfony/property-info": "^4.4|^5.3|^6.0"
Expand All @@ -36,7 +35,8 @@
"symfony/phpunit-bridge": "^6.0",
"phpunit/phpunit": "^9.5",
"symfony/yaml": "^5.3|^6.0",
"symfony/browser-kit": "^5.3|^6.0"
"symfony/browser-kit": "^5.3|^6.0",
"dg/bypass-finals": "^1.3"
},
"autoload": {
"psr-4": {
Expand Down
2 changes: 1 addition & 1 deletion phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" bootstrap="./vendor/autoload.php" colors="true" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" bootstrap="./tests/bootstrap.php" colors="true" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
<php>
<server name="KERNEL_CLASS" value="Pfilsx\DtoParamConverter\Tests\Fixtures\TestKernel" />
<server name="KERNEL_DIR" value="tests/Fixtures/" />
Expand Down
15 changes: 14 additions & 1 deletion src/Annotation/Dto.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,26 @@

namespace Pfilsx\DtoParamConverter\Annotation;

use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
use Doctrine\Common\Annotations\Annotation\Target;

/**
* @Annotation
* @NamedArgumentConstructor
* @Target({"CLASS"})
*/
#[\Attribute(\Attribute::TARGET_CLASS)]
final class Dto
{
public ?string $linkedEntity = null;
private ?string $linkedEntity;

public function __construct(?string $linkedEntity = null)
{
$this->linkedEntity = $linkedEntity;
}

public function getLinkedEntity(): ?string
{
return $this->linkedEntity;
}
}
27 changes: 27 additions & 0 deletions src/Annotation/DtoResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

namespace Pfilsx\DtoParamConverter\Annotation;

use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;

/**
* @Annotation
* @NamedArgumentConstructor
*/
#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_METHOD)]
final class DtoResolver
{
private array $options;

public function __construct(array $options = [])
{
$this->options = $options;
}

public function getOptions(): array
{
return $this->options;
}
}
12 changes: 10 additions & 2 deletions src/Contract/DtoMapperInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,15 @@ interface DtoMapperInterface
{
public static function getDtoClassName(): string;

public function mapToDto(object $entity, object $dto): void;
/**
* @param mixed $entity
* @param mixed $dto
*/
public function mapToDto($entity, $dto): void;

public function mapToEntity(object $dto, object $entity): void;
/**
* @param mixed $dto
* @param mixed $entity
*/
public function mapToEntity($dto, $entity): void;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

namespace Pfilsx\DtoParamConverter\DependencyInjection\Compiler;

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;

final class AddExpressionLanguageProvidersPass implements CompilerPassInterface
{
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
if ($container->hasDefinition('pfilsx.dto_converter.expression_language.default')) {
$definition = $container->findDefinition('pfilsx.dto_converter.expression_language.default');
foreach ($container->findTaggedServiceIds('security.expression_language_provider') as $id => $attributes) {
$definition->addMethodCall('registerProvider', [new Reference($id)]);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;

final class DtoParamConverterPass implements CompilerPassInterface
final class AddMapperFactoriesPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
Expand Down
15 changes: 15 additions & 0 deletions src/DependencyInjection/DtoParamConverterExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@

use Pfilsx\DtoParamConverter\Contract\DtoMapperInterface;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Config\Resource\ClassExistenceResource;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;

final class DtoParamConverterExtension extends Extension
Expand All @@ -17,11 +19,24 @@ public function load(array $configs, ContainerBuilder $container)
{
$this->loadConfiguration($configs, $container);

$definitionsToRemove = [];

$container->addResource(new ClassExistenceResource(ExpressionLanguage::class));
if (class_exists(ExpressionLanguage::class)) {
$container->setAlias('pfilsx.dto_converter.expression_language', new Alias('pfilsx.dto_converter.expression_language.default', false));
} else {
$definitionsToRemove[] = 'pfilsx.dto_converter.expression_language.default';
}

$container->registerForAutoconfiguration(DtoMapperInterface::class)
->addTag('pfilsx.dto_converter.dto_mapper');

$loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
$loader->load('services.xml');

foreach ($definitionsToRemove as $definition) {
$container->removeDefinition($definition);
}
}

private function loadConfiguration(array $configs, ContainerBuilder $container)
Expand Down
6 changes: 4 additions & 2 deletions src/DtoParamConverterBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

namespace Pfilsx\DtoParamConverter;

use Pfilsx\DtoParamConverter\DependencyInjection\Compiler\DtoParamConverterPass;
use Pfilsx\DtoParamConverter\DependencyInjection\Compiler\AddExpressionLanguageProvidersPass;
use Pfilsx\DtoParamConverter\DependencyInjection\Compiler\AddMapperFactoriesPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;

Expand All @@ -13,6 +14,7 @@ final class DtoParamConverterBundle extends Bundle
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new DtoParamConverterPass());
$container->addCompilerPass(new AddMapperFactoriesPass());
$container->addCompilerPass(new AddExpressionLanguageProvidersPass());
}
}
48 changes: 48 additions & 0 deletions src/EventSubscriber/ControllerEventSubscriber.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

declare(strict_types=1);

namespace Pfilsx\DtoParamConverter\EventSubscriber;

use Pfilsx\DtoParamConverter\Provider\RouteMetadataProvider;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\KernelEvent;
use Symfony\Component\HttpKernel\KernelEvents;

final class ControllerEventSubscriber implements EventSubscriberInterface
{
private RouteMetadataProvider $routeMetadataProvider;

public function __construct(RouteMetadataProvider $routeMetadataProvider)
{
$this->routeMetadataProvider = $routeMetadataProvider;
}

public static function getSubscribedEvents(): array
{
return [
KernelEvents::CONTROLLER => 'onKernelController',
];
}

public function onKernelController(KernelEvent $event): void
{
$controller = $event->getController();

if (!\is_array($controller) && method_exists($controller, '__invoke')) {
$controller = [$controller, '__invoke'];
}

if (!\is_array($controller)) {
return;
}

$route = $event->getRequest()->attributes->get('_route');

if (empty($route)) {
return;
}

$this->routeMetadataProvider->createMetadata($route, $controller);
}
}
64 changes: 64 additions & 0 deletions src/Provider/DtoMetadataProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

declare(strict_types=1);

namespace Pfilsx\DtoParamConverter\Provider;

use Doctrine\Common\Annotations\Reader;
use Pfilsx\DtoParamConverter\Annotation\Dto;
use ReflectionClass;

final class DtoMetadataProvider
{
private Reader $reader;

/**
* @var array<string, null|Dto>
*/
private array $localCollection = [];

public function __construct(Reader $reader)
{
$this->reader = $reader;
}

public function getDtoMetadata(string $className): ?Dto
{
if (array_key_exists($className, $this->localCollection)) {
return $this->localCollection[$className];
}

try {
$refClass = new ReflectionClass($className);
} catch (\ReflectionException $e) {
$this->localCollection[$className] = null;

return null;
}

$metadata = $this->reader->getClassAnnotation($refClass, Dto::class);

if ($metadata !== null) {
$this->localCollection[$className] = $metadata;

return $metadata;
}

if (\PHP_VERSION_ID >= 80000) {
$metadata = array_map(
function (\ReflectionAttribute $attribute) {
return $attribute->newInstance();
},
$refClass->getAttributes(Dto::class, \ReflectionAttribute::IS_INSTANCEOF)
)[0] ?? null;

if ($metadata !== null) {
$this->localCollection[$className] = $metadata;

return $metadata;
}
}

return null;
}
}
Loading

0 comments on commit a9d8222

Please sign in to comment.