Skip to content

Commit

Permalink
Merge pull request #1110 from spiral/feature/tokenizer-scan-parent-at…
Browse files Browse the repository at this point in the history
…tributes

Tokenizer attributes scanner: add option to scan parents
  • Loading branch information
butschster committed May 22, 2024
2 parents 0bca4dd + fb10b25 commit c76e78c
Show file tree
Hide file tree
Showing 8 changed files with 97 additions and 13 deletions.
1 change: 1 addition & 0 deletions src/Boot/src/AbstractKernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ final public static function create(
$container->bind(BootloadManagerInterface::class, $bootloadManager);

if (!$container->has(BootloaderRegistryInterface::class)) {
/** @psalm-suppress InvalidArgument */
$container->bindSingleton(BootloaderRegistryInterface::class, [self::class, 'initBootloaderRegistry']);
}

Expand Down
1 change: 1 addition & 0 deletions src/Framework/Bootloader/CommandBootloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public function init(ConsoleBootloader $console, ContainerInterface $container):
$console->addCommand(Console\Command\ConfigureCommand::class);
$console->addCommand(Console\Command\UpdateCommand::class);

/** @psalm-suppress InvalidArgument */
$console->addConfigureSequence(
[RuntimeDirectory::class, 'ensure'],
'<fg=magenta>[runtime]</fg=magenta> <fg=cyan>verify `runtime` directory access</fg=cyan>'
Expand Down
5 changes: 1 addition & 4 deletions src/Logger/src/LoggerInjector.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,6 @@ public function createInjection(
*/
private function extractChannelAttribute(\ReflectionParameter $parameter): ?string
{
/** @var \ReflectionAttribute<LoggerChannel>[] $attributes */
$attributes = $parameter->getAttributes(LoggerChannel::class);

return $attributes[0]?->newInstance()->name;
return ($parameter->getAttributes(LoggerChannel::class)[0] ?? null)?->newInstance()->name;
}
}
35 changes: 29 additions & 6 deletions src/Tokenizer/src/Attribute/TargetAttribute.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,16 @@ final class TargetAttribute extends AbstractTarget
{
/**
* @param class-string $attribute
* @param non-empty-string|null $scope
* @param non-empty-string|null $scope Class locator scope
* @param bool $namedArguments Whether to use named arguments when reading attributes
* @param bool $scanParents Whether to scan parent classes/interfaces for attributes target of which is class
*/
public function __construct(
private readonly string $attribute,
?string $scope = null,
private readonly bool $useAnnotations = false,
private readonly bool $namedArguments = true,
private readonly bool $scanParents = false,
) {
parent::__construct($scope);
}
Expand All @@ -51,11 +54,31 @@ public function filter(array $classes): \Generator
foreach ($classes as $class) {
// If attribute is defined on class level and class has target attribute
// then we can add it to the list of classes
if (($attribute->flags & \Attribute::TARGET_CLASS)
&& $reader->firstClassMetadata($class, $target->getName())
) {
yield $class->getName();
continue;
if ($attribute->flags & \Attribute::TARGET_CLASS) {
if ($reader->firstClassMetadata($class, $target->getName())) {
yield $class->getName();
continue;
}

if ($this->scanParents) {
// Interfaces
foreach ($class->getInterfaces() as $interface) {
if ($reader->firstClassMetadata($interface, $target->getName())) {
yield $class->getName();
continue 2;
}
}

// Parents
$parent = $class->getParentClass();
while ($parent !== false) {
if ($reader->firstClassMetadata($parent, $target->getName())) {
yield $class->getName();
continue 2;
}
$parent = $parent->getParentClass();
}
}
}

// If attribute is defined on method level and class methods has target attribute
Expand Down
38 changes: 35 additions & 3 deletions src/Tokenizer/tests/Attribute/TargetAttributeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,29 @@

namespace Spiral\Tests\Tokenizer\Attribute;

use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Spiral\Attributes\Exception\SyntaxAttributeException;
use Spiral\Tests\Tokenizer\Classes\Targets\ClassWithAttributeWithArgsOnClass;
use Spiral\Tests\Tokenizer\Fixtures\Attributes\WithTargetClass;
use Spiral\Tests\Tokenizer\Fixtures\Attributes\WithTargetClassWithArgs;
use Spiral\Tests\Tokenizer\Interfaces\Targets\ClassExtendsClassWithAttributeOnClass;
use Spiral\Tests\Tokenizer\Interfaces\Targets\ClassImplementsInterfaceWithAttributeOnClass;
use Spiral\Tests\Tokenizer\Interfaces\Targets\InterfaceWithAttributeOnClass;
use Spiral\Tokenizer\Attribute\TargetAttribute;

final class TargetAttributeTest extends TestCase
{
public function testToString(): void
{
$attribute = new TargetAttribute('foo');
$this->assertSame('d8d66e598d7117a26f6268ea9780774f', (string) $attribute);
$this->assertSame('3dc18b19eed74479a03c069dec2e8724', (string) $attribute);

$attribute = new TargetAttribute('foo', 'bar');
$this->assertSame('5bd549fe54f1e987a4fbfc8513d2dc68', (string) $attribute);
$this->assertSame('52ec767c53f3898bf6de6f6e88125dc8', (string) $attribute);
}

public function testFilter(): void
public function testFilterAttrWithArgs(): void
{
$attribute = new TargetAttribute(attribute: WithTargetClassWithArgs::class);
$this->assertEquals([
Expand All @@ -36,4 +41,31 @@ public function testFilterExceptionAttrWithoutNamedArgument(): void
$this->expectException(SyntaxAttributeException::class);
\iterator_to_array($attribute->filter([new \ReflectionClass(ClassWithAttributeWithArgsOnClass::class)]));
}


#[DataProvider('filterDataProvider')]
public function testFilter(
bool $scanParents,
bool $found,
string $class,
): void {
$attribute = new TargetAttribute(attribute: WithTargetClass::class, scanParents: $scanParents);

$result = \iterator_to_array($attribute->filter([new \ReflectionClass($class)]));

$this->assertEquals(
$found ? [$class] : [],
$result,
);
}

public static function filterDataProvider(): iterable
{
yield [false, true, InterfaceWithAttributeOnClass::class];
yield [false, false, ClassExtendsClassWithAttributeOnClass::class];
yield [false, false, ClassImplementsInterfaceWithAttributeOnClass::class];
yield [true, true, InterfaceWithAttributeOnClass::class];
yield [true, true, ClassExtendsClassWithAttributeOnClass::class];
yield [true, true, ClassImplementsInterfaceWithAttributeOnClass::class];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace Spiral\Tests\Tokenizer\Interfaces\Targets;

use Spiral\Tests\Tokenizer\Fixtures\Attributes\WithTargetClass;

#[WithTargetClass]
abstract class AbstractClassWithAttributeOnClass
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

namespace Spiral\Tests\Tokenizer\Interfaces\Targets;

final class ClassExtendsClassWithAttributeOnClass extends AbstractClassWithAttributeOnClass
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

namespace Spiral\Tests\Tokenizer\Interfaces\Targets;

final class ClassImplementsInterfaceWithAttributeOnClass implements InterfaceWithAttributeOnClass
{
}

0 comments on commit c76e78c

Please sign in to comment.