From 5a7b539c540edb6e039fe2e9e2b39a591670a827 Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Fri, 28 Jul 2023 02:44:07 -0700 Subject: [PATCH] AutoDefinition - Inherit service-tags from interfaces, traits, and parent classes --- Civi/Core/Service/AutoDefinition.php | 58 +++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 10 deletions(-) diff --git a/Civi/Core/Service/AutoDefinition.php b/Civi/Core/Service/AutoDefinition.php index 457d0516cf..36bffa778e 100644 --- a/Civi/Core/Service/AutoDefinition.php +++ b/Civi/Core/Service/AutoDefinition.php @@ -2,15 +2,22 @@ namespace Civi\Core\Service; -use Civi\Api4\Service\Spec\Provider\Generic\SpecProviderInterface; use Civi\Api4\Utils\ReflectionUtils; -use Civi\Core\HookInterface; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; -use Symfony\Component\EventDispatcher\EventSubscriberInterface; class AutoDefinition { + /** + * Oddballs - AutoDefinition can apply a tag to a third-party class/interface. But it's better + * for classes/interfaces to declare `serviceTags` for themselves. + * + * @var string[] + */ + protected static $forceServiceTags = [ + 'Symfony\Component\EventDispatcher\EventSubscriberInterface' => 'event_subscriber', + ]; + /** * Identify any/all service-definitions for the given class. * @@ -117,18 +124,49 @@ class AutoDefinition { if (!empty($docBlock['internal'])) { $def->addTag('internal'); } - if ($class->implementsInterface(HookInterface::class) || $class->implementsInterface(EventSubscriberInterface::class)) { - $def->addTag('event_subscriber'); + + $tags = static::findTags($class, $docBlock, FALSE); + foreach ($tags as $tag) { + $def->addTag($tag); } - if ($class->implementsInterface(SpecProviderInterface::class)) { - $def->addTag('spec_provider'); + } + + /** + * Find all `serviceTags` annotations that apply to a class -- either + * directly or indirectly (via interface, trait, or parent-class). + * + * @param \ReflectionClass $class + * @param array|null $docBlock + * @param bool $isTransitiveLookup + * @return array|mixed + */ + public static function findTags(\ReflectionClass $class, ?array $docBlock, bool $isTransitiveLookup) { + $className = $class->getName(); + $cache = &\Civi::$statics[__CLASS__]['tagidx']; + if (isset($cache[$className])) { + return $cache[$className]; } - if (!empty($classDoc['serviceTags'])) { - foreach (static::splitSymbols($classDoc['serviceTags']) as $extraTag) { - $def->addTag($extraTag); + $docBlock = $docBlock ?: ReflectionUtils::parseDocBlock($class->getDocComment()); + $result = isset($docBlock['serviceTags']) ? static::splitSymbols($docBlock['serviceTags']) : []; + if (isset(static::$forceServiceTags[$className])) { + $result[] = static::$forceServiceTags[$className]; + } + $parents = array_merge($class->getInterfaces(), $class->getTraits(), [$class->getParentClass()]); + foreach ($parents as $parent) { + if ($parent) { + $result = array_merge($result, static::findTags($parent, NULL, TRUE)); + // Aside: The recursion might theoretically visit an interface multiple times, but ancestral + // lookups are cached... so not really... } } + $result = array_unique($result); + + // We cache info about common/re-usable classes (interfaces, traits, parents). + if ($isTransitiveLookup) { + $cache[$className] = $result; + } + return $result; } /** -- 2.25.1