Skip to content

Commit

Permalink
Extend autowiring to container params & plain values
Browse files Browse the repository at this point in the history
  • Loading branch information
claudiu-cristea committed Jul 12, 2024
1 parent 937127e commit 6e4f736
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 9 deletions.
5 changes: 5 additions & 0 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,8 @@ parameters:
message: "#^Unreachable statement \\- code above always terminates\\.$#"
count: 1
path: src/Symfony/BootstrapCompilerPass.php

-
message: "#^Method Drush\\\\Commands\\\\core\\\\DrupalDependenciesCommands\\:\\:create\\(\\) should return static\\(Drush\\\\Commands\\\\core\\\\DrupalDependenciesCommands\\) but returns Drush\\\\Commands\\\\core\\\\DrupalDependenciesCommands\\.$#"
count: 1
path: src/Commands/AutowireTrait.php
59 changes: 50 additions & 9 deletions src/Commands/AutowireTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

namespace Drush\Commands;

use Psr\Container\ContainerInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Exception\AutowiringFailedException;

/**
Expand All @@ -16,7 +16,18 @@
*/
trait AutowireTrait
{
/**
/**
* Limit to service and param or plain value.
*
* @see \Symfony\Component\DependencyInjection\Attribute\Autowire::__construct
*/
private const ACCEPTED_AUTOWIRE_ARGUMENTS = [
0 => 'value',
1 => 'service',
4 => 'param',
];

/**
* Instantiates a new instance of the implementing class using autowiring.
*
* @param ContainerInterface $container
Expand All @@ -31,16 +42,46 @@ public static function create(ContainerInterface $container)
if (method_exists(static::class, '__construct')) {
$constructor = new \ReflectionMethod(static::class, '__construct');
foreach ($constructor->getParameters() as $parameter) {
$service = ltrim((string) $parameter->getType(), '?');
foreach ($parameter->getAttributes(Autowire::class) as $attribute) {
$service = (string) $attribute->newInstance()->value;
if (!$attributes = $parameter->getAttributes(Autowire::class)) {
// No #[Autowire()] attribute.
$service = ltrim((string) $parameter->getType(), '?');
if (!$container->has($service)) {
throw new AutowiringFailedException($service, sprintf('Cannot autowire service "%s": argument "$%s" of method "%s::_construct()", you should configure its value explicitly.', $service, $parameter->getName(), static::class));
}
$args[] = $container->get($service);
continue;
}

if (!$container->has($service)) {
throw new AutowiringFailedException($service, sprintf('Cannot autowire service "%s": argument "$%s" of method "%s::_construct()", you should configure its value explicitly.', $service, $parameter->getName(), static::class));
}
// This parameter has an #[Autowire()] attribute.
[$attribute] = $attributes;
$value = null;
foreach ($attribute->getArguments() as $key => $argument) {
// Resolve the name when arguments are passed as list.
if (is_int($key)) {
if ($argument === null || !isset(self::ACCEPTED_AUTOWIRE_ARGUMENTS[$key])) {
continue;
}
$key = self::ACCEPTED_AUTOWIRE_ARGUMENTS[$key];
}

$args[] = $container->get($service);
if (!in_array($key, self::ACCEPTED_AUTOWIRE_ARGUMENTS, true)) {
continue;
}

$value = $attribute->newInstance()->value;
$valueAsString = (string) $value;
$value = match ($key) {
'service' => $container->has($valueAsString) ? $container->get($valueAsString) : throw new AutowiringFailedException($valueAsString, sprintf('Cannot autowire service "%s": argument "$%s" of method "%s::_construct()", you should configure its value explicitly.', $valueAsString, $parameter->getName(), static::class)),
// Container param comes as %foo.bar.param%.
'param' => $container->getParameter(trim($valueAsString, '%')),
default => $value,
};
// Done as Autowire::__construct() only needs one argument.
break;
}
if ($value !== null) {
$args[] = $value;
}
}
}

Expand Down
35 changes: 35 additions & 0 deletions tests/fixtures/lib/AutowireTestClasses/AutowireTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace Custom\Library\AutowireTestClasses;

use Drush\Commands\AutowireTrait;
use Symfony\Component\DependencyInjection\Attribute\Autowire;

class AutowireTest
{
use AutowireTrait;

public function __construct(
#[Autowire('a string as it is')]
protected string $argListPlainValue,
#[Autowire(null, 'autowire_test')]
protected AutowireTestService $argListContainerService,
#[Autowire(null, null, null, null, 'foo')]
protected string $argListContainerParam,
#[Autowire(value: 'a string as it is')]
protected string $namedArgPlainValue,
#[Autowire(service: 'autowire_test')]
protected AutowireTestService $namedArgContainerService,
#[Autowire(param: 'foo')]
protected string $namedArgContainerParam,
protected AutowireTestServiceInterface $noAutowireAttributeContainerService,
) {
assert($argListPlainValue === 'a string as it is');
assert($argListContainerService->greeting() === 'Hello World!');
assert($argListContainerParam === 'bar');
assert($namedArgPlainValue === 'a string as it is');
assert($namedArgContainerService->greeting() === 'Hello World!');
assert($namedArgContainerParam === 'bar');
assert($noAutowireAttributeContainerService->greeting() === 'Hello World!');
}
}
11 changes: 11 additions & 0 deletions tests/fixtures/lib/AutowireTestClasses/AutowireTestService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace Custom\Library\AutowireTestClasses;

class AutowireTestService implements AutowireTestServiceInterface
{
public function greeting(): string
{
return 'Hello World!';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace Custom\Library\AutowireTestClasses;

interface AutowireTestServiceInterface
{
}
32 changes: 32 additions & 0 deletions tests/unit/AutowireArgumentsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace Unish;

use Custom\Library\AutowireTestClasses\AutowireTest;
use Custom\Library\AutowireTestClasses\AutowireTestService;
use Custom\Library\AutowireTestClasses\AutowireTestServiceInterface;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drush\Commands\AutowireTrait;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\Reference;

/**
* @group base
*/
class AutowireArgumentsTest extends TestCase
{
/**
* @covers \Drush\Commands\AutowireTrait::create
*/
public function test()
{
$container = new ContainerBuilder();
$container->register('autowire_test', AutowireTestService::class);
$container->setAlias(AutowireTestServiceInterface::class, new Reference('autowire_test'));
$container->setParameter('foo', 'bar');
$container->compile();

$instance = AutowireTest::create($container);
$this->assertInstanceOf(AutowireTest::class, $instance);
}
}

0 comments on commit 6e4f736

Please sign in to comment.