diff --git a/src/Drupal/DrupalServiceDefinition.php b/src/Drupal/DrupalServiceDefinition.php index 7dce38d2..c0d0046b 100644 --- a/src/Drupal/DrupalServiceDefinition.php +++ b/src/Drupal/DrupalServiceDefinition.php @@ -5,6 +5,7 @@ use PHPStan\Type\ObjectType; use PHPStan\Type\StringType; use PHPStan\Type\Type; +use PHPStan\Type\UnionType; class DrupalServiceDefinition { @@ -44,6 +45,11 @@ class DrupalServiceDefinition */ private $alias; + /** + * @var array + */ + private $decorators = []; + public function __construct(string $id, ?string $class, bool $public = true, ?string $alias = null) { $this->id = $id; @@ -109,6 +115,28 @@ public function getType(): Type return new StringType(); } + $decorating_services = $this->getDecorators(); + if (count($decorating_services) !== 0) { + $combined_services = []; + $combined_services[] = new ObjectType($this->getClass() ?? $this->id); + foreach ($decorating_services as $service_id => $service_definition) { + $combined_services[] = $service_definition->getType(); + } + return new UnionType($combined_services); + } return new ObjectType($this->getClass() ?? $this->id); } + + public function addDecorator(DrupalServiceDefinition $definition): void + { + $this->decorators[$definition->getId()] = $definition; + } + + /** + * @return array + */ + public function getDecorators(): array + { + return $this->decorators; + } } diff --git a/src/Drupal/ServiceMap.php b/src/Drupal/ServiceMap.php index 8c2c4433..6809e424 100644 --- a/src/Drupal/ServiceMap.php +++ b/src/Drupal/ServiceMap.php @@ -23,6 +23,7 @@ public function getServices(): array public function setDrupalServices(array $drupalServices): void { self::$services = []; + $decorators = []; foreach ($drupalServices as $serviceId => $serviceDefinition) { if (isset($serviceDefinition['alias'], $drupalServices[$serviceDefinition['alias']])) { @@ -32,6 +33,10 @@ public function setDrupalServices(array $drupalServices): void $serviceDefinition = $this->resolveParentDefinition($serviceDefinition['parent'], $serviceDefinition, $drupalServices); } + if (isset($serviceDefinition['decorates'])) { + $decorators[$serviceDefinition['decorates']][] = $serviceId; + } + // @todo support factories if (!isset($serviceDefinition['class'])) { if (class_exists($serviceId)) { @@ -51,6 +56,12 @@ public function setDrupalServices(array $drupalServices): void self::$services[$serviceId]->setDeprecated(true, $deprecated); } } + + foreach ($decorators as $decorated_service_id => $services) { + foreach ($services as $dcorating_service_id) { + self::$services[$decorated_service_id]->addDecorator(self::$services[$dcorating_service_id]); + } + } } private function resolveParentDefinition(string $parentId, array $serviceDefinition, array $drupalServices): array diff --git a/tests/src/ServiceMapFactoryTest.php b/tests/src/ServiceMapFactoryTest.php index 5a6fa640..835a1163 100644 --- a/tests/src/ServiceMapFactoryTest.php +++ b/tests/src/ServiceMapFactoryTest.php @@ -5,6 +5,8 @@ use Drupal\Core\Logger\LoggerChannel; use mglaman\PHPStanDrupal\Drupal\DrupalServiceDefinition; use mglaman\PHPStanDrupal\Drupal\ServiceMap; +use PHPStan\Type\ObjectType; +use PHPStan\Type\UnionType; use PHPUnit\Framework\TestCase; final class ServiceMapFactoryTest extends TestCase @@ -93,6 +95,14 @@ public function testFactory(string $id, callable $validator): void ], 'Psr\Log\LoggerInterface $loggerWorkspaces' => [ 'alias' => 'logger.channel.workspaces' + ], + 'service_map.base_to_be_decorated' => [ + 'class' => 'Drupal\service_map\Base', + 'abstract' => true, + ], + 'service_map.deocrating_base' => [ + 'decorates' => 'service_map.base_to_be_decorated', + 'class' => 'Drupal\service_map\SecondBase', ] ]); $validator($service->getService($id)); @@ -212,6 +222,17 @@ function (DrupalServiceDefinition $service): void { self::assertEquals(LoggerChannel::class, $service->getClass()); } ]; + yield [ + 'service_map.base_to_be_decorated', + function (DrupalServiceDefinition $service): void { + $combined_class = [ + new ObjectType('Drupal\service_map\Base'), + new ObjectType('Drupal\service_map\SecondBase') + ]; + $expected_class = new UnionType($combined_class); + self::assertEquals($expected_class, $service->getType()); + } + ]; } }