Skip to content

Commit

Permalink
feature #97 Autodiscover resource's model interfaces and deprecate ex…
Browse files Browse the repository at this point in the history
…plicit configuration (pamil)

This PR was merged into the 1.6-dev branch.

Discussion
----------

Fixes #94, solution described in #94 (comment).

Commits
-------

d31f45a Autodiscover resource's model interfaces and deprecate explicit configuration
  • Loading branch information
pamil authored Jun 18, 2019
2 parents d9f9fcb + d31f45a commit 1b3a89f
Show file tree
Hide file tree
Showing 13 changed files with 371 additions and 110 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
namespace Sylius\Bundle\ResourceBundle\DependencyInjection\Compiler;

use Doctrine\Common\EventSubscriber;
use Sylius\Bundle\ResourceBundle\DependencyInjection\Compiler\Helper\TargetEntitiesResolverInterface;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
Expand All @@ -24,9 +25,14 @@
*/
final class DoctrineTargetEntitiesResolverPass implements CompilerPassInterface
{
/**
* {@inheritdoc}
*/
/** @var TargetEntitiesResolverInterface */
private $targetEntitiesResolver;

public function __construct(TargetEntitiesResolverInterface $targetEntitiesResolver)
{
$this->targetEntitiesResolver = $targetEntitiesResolver;
}

public function process(ContainerBuilder $container): void
{
try {
Expand All @@ -36,13 +42,9 @@ public function process(ContainerBuilder $container): void
return;
}

$interfaces = $this->getInterfacesMapping($resources);
$interfaces = $this->targetEntitiesResolver->resolve($resources);
foreach ($interfaces as $interface => $model) {
$resolveTargetEntityListener->addMethodCall('addResolveTargetEntity', [
$this->getInterface($container, $interface),
$this->getClass($container, $model),
[],
]);
$resolveTargetEntityListener->addMethodCall('addResolveTargetEntity', [$interface, $model, []]);
}

$resolveTargetEntityListenerClass = $container->getParameterBag()->resolveValue($resolveTargetEntityListener->getClass());
Expand All @@ -54,60 +56,4 @@ public function process(ContainerBuilder $container): void
$resolveTargetEntityListener->addTag('doctrine.event_listener', ['event' => 'loadClassMetadata']);
}
}

private function getInterfacesMapping(array $resources): array
{
$interfaces = [];
foreach ($resources as $alias => $configuration) {
if (isset($configuration['classes']['interface'])) {
$alias = explode('.', $alias);

if (!isset($alias[0], $alias[1])) {
throw new \RuntimeException(sprintf('Error configuring "%s" resource. The resource alias should follow the "prefix.name" format.', $alias[0]));
}

$interfaces[$configuration['classes']['interface']] = sprintf('%s.model.%s.class', $alias[0], $alias[1]);
}
}

return $interfaces;
}

/**
* @throws \InvalidArgumentException
*/
private function getInterface(ContainerBuilder $container, string $key): string
{
if ($container->hasParameter($key)) {
return $container->getParameter($key);
}

if (interface_exists($key)) {
return $key;
}

throw new \InvalidArgumentException(
sprintf('The interface %s does not exist.', $key)
);
}

/**
* @param string $key
*
* @throws \InvalidArgumentException
*/
private function getClass(ContainerBuilder $container, $key): string
{
if ($container->hasParameter($key)) {
return $container->getParameter($key);
}

if (class_exists($key)) {
return $key;
}

throw new \InvalidArgumentException(
sprintf('The class %s does not exist.', $key)
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php

/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Sylius\Bundle\ResourceBundle\DependencyInjection\Compiler\Helper;

use Sylius\Component\Resource\Model\ResourceInterface;

final class TargetEntitiesResolver implements TargetEntitiesResolverInterface
{
public function resolve(array $resources): array
{
$interfaces = [];

foreach ($resources as $alias => $configuration) {
$model = $this->getModel($alias, $configuration);

foreach (class_implements($model) as $interface) {
if ($interface === ResourceInterface::class) {
continue;
}

$interfaces[$interface][] = $model;
}
}

$interfaces = array_filter($interfaces, function (array $classes): bool {
return count($classes) === 1;
});

$interfaces = array_map(function (array $classes): string {
return (string) current($classes);
}, $interfaces);

foreach ($resources as $alias => $configuration) {
if (isset($configuration['classes']['interface'])) {
$model = $this->getModel($alias, $configuration);
$interface = $configuration['classes']['interface'];

@trigger_error(
sprintf(
'Specifying interface for resources is deprecated since ResourceBundle v1.6 and will be removed in v2.0. ' .
'Please rely on autodiscovering entity interfaces instead. ' .
'Triggered by resource "%s" with model "%s" and interface "%s".',
$alias,
$model,
$interface
),
\E_USER_DEPRECATED
);

$interfaces[$interface] = $model;
}
}

return $interfaces;
}

private function getModel(string $alias, array $configuration): string
{
if (!isset($configuration['classes']['model'])) {
throw new \InvalidArgumentException(sprintf('Could not get model class from resource "%s".', $alias));
}

return $configuration['classes']['model'];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Sylius\Bundle\ResourceBundle\DependencyInjection\Compiler\Helper;

interface TargetEntitiesResolverInterface
{
/**
* @return array Interface to class map.
*/
public function resolve(array $resourcesConfiguration): array;
}
3 changes: 2 additions & 1 deletion src/Bundle/SyliusResourceBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
namespace Sylius\Bundle\ResourceBundle;

use Sylius\Bundle\ResourceBundle\DependencyInjection\Compiler\DoctrineTargetEntitiesResolverPass;
use Sylius\Bundle\ResourceBundle\DependencyInjection\Compiler\Helper\TargetEntitiesResolver;
use Sylius\Bundle\ResourceBundle\DependencyInjection\Compiler\RegisterFormBuilderPass;
use Sylius\Bundle\ResourceBundle\DependencyInjection\Compiler\RegisterResourceRepositoryPass;
use Sylius\Bundle\ResourceBundle\DependencyInjection\Compiler\RegisterResourcesPass;
Expand All @@ -36,7 +37,7 @@ public function build(ContainerBuilder $container): void
parent::build($container);

$container->addCompilerPass(new RegisterResourcesPass());
$container->addCompilerPass(new DoctrineTargetEntitiesResolverPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 1);
$container->addCompilerPass(new DoctrineTargetEntitiesResolverPass(new TargetEntitiesResolver()), PassConfig::TYPE_BEFORE_OPTIMIZATION, 1);
$container->addCompilerPass(new RegisterResourceRepositoryPass());
$container->addCompilerPass(new RegisterFormBuilderPass());
$container->addCompilerPass(new WinzouStateMachinePass());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

use Matthias\SymfonyDependencyInjectionTest\PhpUnit\AbstractCompilerPassTestCase;
use Sylius\Bundle\ResourceBundle\DependencyInjection\Compiler\DoctrineTargetEntitiesResolverPass;
use Sylius\Bundle\ResourceBundle\DependencyInjection\Compiler\Helper\TargetEntitiesResolverInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;

Expand All @@ -23,41 +24,15 @@ final class DoctrineTargetEntitiesResolverPassTest extends AbstractCompilerPassT
/**
* @test
*/
public function it_adds_method_call_to_resolve_doctrine_target_entities_with_interface_given_as_fqcn(): void
public function it_adds_method_call_to_resolve_doctrine_target_entities(): void
{
$this->setDefinition('doctrine.orm.listeners.resolve_target_entity', new Definition());

$this->setParameter(
'sylius.resources',
['app.loremipsum' => ['classes' => ['interface' => \Countable::class]]]
['app.loremipsum' => ['classes' => ['model' => \stdClass::class, 'interface' => \Countable::class]]]
);

$this->setParameter('app.model.loremipsum.class', \stdClass::class);

$this->compile();

$this->assertContainerBuilderHasServiceDefinitionWithMethodCall(
'doctrine.orm.listeners.resolve_target_entity',
'addResolveTargetEntity',
[\Countable::class, \stdClass::class, []]
);
}

/**
* @test
*/
public function it_adds_method_call_to_resolve_doctrine_target_entities_with_interface_given_as_parameter(): void
{
$this->setDefinition('doctrine.orm.listeners.resolve_target_entity', new Definition());

$this->setParameter(
'sylius.resources',
['app.loremipsum' => ['classes' => ['interface' => 'app.interface.loremipsum.class']]]
);

$this->setParameter('app.model.loremipsum.class', \stdClass::class);
$this->setParameter('app.interface.loremipsum.class', \Countable::class);

$this->compile();

$this->assertContainerBuilderHasServiceDefinitionWithMethodCall(
Expand All @@ -67,21 +42,6 @@ public function it_adds_method_call_to_resolve_doctrine_target_entities_with_int
);
}

/**
* @test
*/
public function it_ignores_resources_without_interface(): void
{
$this->setDefinition('doctrine.orm.listeners.resolve_target_entity', new Definition());

$this->setParameter(
'sylius.resources',
['app.loremipsum' => ['classes' => ['model' => \stdClass::class]]]
);

$this->compile();
}

/**
* @test
*/
Expand All @@ -104,6 +64,17 @@ public function it_adds_doctrine_event_listener_tag_to_target_entities_resolver_
*/
protected function registerCompilerPass(ContainerBuilder $container): void
{
$container->addCompilerPass(new DoctrineTargetEntitiesResolverPass());
$targetEntitiesResolver = new class() implements TargetEntitiesResolverInterface {
public function resolve(array $resourcesConfiguration): array
{
if ($resourcesConfiguration === ['app.loremipsum' => ['classes' => ['model' => \stdClass::class, 'interface' => \Countable::class]]]) {
return [\Countable::class => \stdClass::class];
}

return [];
}
};

$container->addCompilerPass(new DoctrineTargetEntitiesResolverPass($targetEntitiesResolver));
}
}
20 changes: 20 additions & 0 deletions src/Bundle/Tests/Fixtures/AnimalInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Sylius\Bundle\ResourceBundle\Tests\Fixtures;

use Sylius\Component\Resource\Model\ResourceInterface;

interface AnimalInterface extends ResourceInterface
{
}
22 changes: 22 additions & 0 deletions src/Bundle/Tests/Fixtures/Bear.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Sylius\Bundle\ResourceBundle\Tests\Fixtures;

final class Bear implements BearInterface
{
public function getId(): int
{
return 42;
}
}
18 changes: 18 additions & 0 deletions src/Bundle/Tests/Fixtures/BearInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Sylius\Bundle\ResourceBundle\Tests\Fixtures;

interface BearInterface extends MammalInterface
{
}
22 changes: 22 additions & 0 deletions src/Bundle/Tests/Fixtures/Fly.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Sylius\Bundle\ResourceBundle\Tests\Fixtures;

final class Fly implements FlyInterface
{
public function getId(): int
{
return 42;
}
}
Loading

0 comments on commit 1b3a89f

Please sign in to comment.