Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions extension.neon
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,9 @@ parametersSchema:
services:
-
class: mglaman\PHPStanDrupal\Drupal\ServiceMap
-
class: mglaman\PHPStanDrupal\Drupal\RectorServiceMapInitializer
tags: [phpstan.broker.dynamicStaticMethodReturnTypeExtension]
-
class: mglaman\PHPStanDrupal\Drupal\ExtensionMap
-
Expand Down
54 changes: 54 additions & 0 deletions src/Drupal/RectorServiceMapInitializer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php declare(strict_types=1);

namespace mglaman\PHPStanDrupal\Drupal;

use PhpParser\Node\Expr\StaticCall;
use PHPStan\Analyser\Scope;
use PHPStan\DependencyInjection\Container;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Type\DynamicStaticMethodReturnTypeExtension;
use PHPStan\Type\Type;

/**
* Populates the Drupal service map when the bootstrap file is not executed.
*
* The service map is built by {@see DrupalAutoloader::register()}, which runs as
* a `bootstrapFiles` entry (drupal-autoloader.php). PHPStan executes those through
* CommandHelper::executeBootstrapFile(). Tools that build the PHPStan container
* directly never call CommandHelper, so the bootstrap is skipped and the map stays
* empty — most notably Rector, whose PHPStanServicesFactory calls
* ContainerFactory::create() and stops there. The result is that \Drupal::service()
* resolves to `object` under Rector instead of the concrete service class.
*
* This service is registered as a no-op dynamic return type extension purely so
* PHPStan instantiates it eagerly when the broker is built. If the map is still
* empty at that point, it runs the autoloader against the live container. The
* empty-map guard keeps a normal PHPStan run a no-op, since the bootstrap already
* populated the map there.
*
* @see https://github.com/mglaman/phpstan-drupal/issues/995
*/
final class RectorServiceMapInitializer implements DynamicStaticMethodReturnTypeExtension
{
public function __construct(Container $container, ServiceMap $serviceMap)
{
if ($serviceMap->getServices() === []) {
(new DrupalAutoloader())->register($container);
}
}

public function getClass(): string
{
return 'Drupal';
}

public function isStaticMethodSupported(MethodReflection $methodReflection): bool
{
return false;
}

public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): ?Type
{
return null;
}
}
Loading