 [ ← blog / phpstan-drupal ](/) Oct 13, 2021  ·  \#phpstan-drupal  ·  5 min read 

# phpstan-drupal 0.12.15: Improved detection of deprecated service usage



I have some exciting news to share for [phpstan-drupal](https://github.com/mglaman/phpstan-drupal)! With the [0.12.15](https://github.com/mglaman/phpstan-drupal/releases/tag/0.12.15) release, the ability to detect and report usages of deprecated services should have full coverage! Previously, phpstan-drupal only detected if you used a method on a deprecated service. And that happened to be if the service was fetched from an object implementing `ContainerInterface`. That meant calls to `\Drupal::service` were left uncovered – even though it calls `self::getContainer->get()` (I thought it would "just work," but it did not.)

[Palantir.net](http://palantir.net/) renewed for more sponsored live development streams, this time with a focus on the Upgrade Status module (and in turn the phpstan-drupal project.) This has made a **huge** impact in just a few streams in the capabilities of phpstan-drupal.

## Before 0.12.15

What does all of that mean? Here is a code example taken from the phpstan-drupal test fixtures to explain. I've added inline code comments, but I'll go into detail afterward.

```php
<?php

class TestServicesMappingExtension {
    public function testEntityManager() {
        // This line wouldn't error.
        $entity_manager = \Drupal::getContainer()->get('entity.manager');
        // But it would know that the method didn't exist.
        $doesNotExist = $entity_manager->thisMethodDoesNotExist();
        // Would error about a deprecated method.
        $definitions = $entity_manager->getDefinitions();
    }

    public function testPathAliasManagerServiceRename() {
        // The service is not detected properly and PHPStan skipped analysis.
        $manager = \Drupal::service('path.alias_manager');
        $path = $manager->getPathByAlias('/foo/bar', 'en');
    }
}
```

PHPStan has a concept of [dynamic return type extensions](https://phpstan.org/developing-extensions/dynamic-return-type-extensions). Return type extensions allow you to help PHPStan under what kind of object to return. When it comes to services in the service container, it could be anything. The `ContainerDynamicReturnTypeExtension` in phpstan-drupal utilized a service mapping to return the correct class when fetching a service from the container. Unfortunately, it did not work with `\Drupal::service`. It also didn't report errors that the service was deprecated. It only reported a deprecation if that service had a method called. That means the constructor may have injected deprecated services without warning.

If PHPStan cannot determine the return type like `mixed`, then no analysis can take place to find deprecated usages. Guess what `\Drupal::service` has as its return type? It has `@return mixed` for its return type in its document comment block.

Fixing the return type for `\Drupal::service` would still leave one problem: flagging the service name argument if it is deprecated. This required a new rule to inspect the service name passed to `ContainerInterface::get` and `\Drupal::service` to report when your code calls a deprecated service.

Oh, and figuring out how to *know* if the service was deprecated or not.

That led to the following two issues and their fixes:

 [Dynamic return type extension for \Drupal::service · Issue #190 · mglaman/phpstan-drupal

We have a dynamic return type extension for the service container, but only on the ContainerInterface. It might not be reading that \Drupal::service calls \Drupal::getContainer()-&gt;get. Resolves ...

https://github.com/mglaman/phpstan-drupal/issues/190

 

 ![Dynamic return type extension for &#92;Drupal::service · Issue #190 · mglaman/phpstan-drupal](https://opengraph.githubassets.com/dcf7306b71ce48071815c67c3969f106962e8e8d147a5807557673607d07c774/mglaman/phpstan-drupal/issues/190) 

 

 

 ](https://github.com/mglaman/phpstan-drupal/issues/190)  [Detect when deprecated services are fetched from the container · Issue #205 · mglaman/phpstan-drupal

Right now we detect when a method is called on a deprecated service via chaining: $container-&gt;get('entity.manager')-&gt;getStorage('subject_term_mapping_entity') BUT we do not wh...

https://github.com/mglaman/phpstan-drupal/issues/205

 

 ![Detect when deprecated services are fetched from the container · Issue #205 · mglaman/phpstan-drupal](https://opengraph.githubassets.com/dca784d6e601fb2eb77d082e0b9c2fd921ad6d34ae7311df72a8e0b577b02e83/mglaman/phpstan-drupal/issues/205) 

 

 

 ](https://github.com/mglaman/phpstan-drupal/issues/205) 

## Detect when deprecated services are fetched from the container

One of the first items I worked to fix was catching calls to the container for a deprecated service when constructing an object. This can happen when a class has `ContainerInjectionInterface`, `ContainerFactoryPluginInterface`, or any of the other "I want to get the container injected and construct myself" interfaces in Drupal.

Here is an example of a controller injecting the deprecated `entity.manager` service

```php
class EntityManagerInjectedController implements ContainerInjectionInterface {

    protected $entityManager;

    public function __construct(EntityManagerInterface $entity_manager)
    {
        $this->entityManager = $entity_manager;
    }

    public static function create(ContainerInterface $container)
    {
        return new self(
            $container->get('entity.manager')
        );
    }
}
```

And an example of a block injecting the `entity.manager` service.

```php
class EntityManagerInjectedBlock extends BlockBase implements ContainerFactoryPluginInterface {

    /**
     * @var EntityManagerInterface
     */
    protected $entityManager;

    public function __construct($configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager) {
        parent::__construct($configuration, $plugin_id, $plugin_definition);
        $this->entityManager = $entity_manager;
    }

    /**
     * {@inheritdoc}
     */
    public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
        return new static(
            $configuration,
            $plugin_id,
            $plugin_definition,
            $container->get('entity.manager')
        );
    }
}
```

Previously these were skipped over when PHPStan analyzed Drupal code. That's because a rule didn't exist to inspect the parameters and check if the value provided was a deprecated service.

In phpstan-drupal, I needed to update our service mapping to respect the deprecated flags allowed in Symfony's dependency injection. Once this was added, I was able to write the `GetDeprecatedServiceRule` rule which checked the argument passed to `ContainerInterface::get`, look up the service, and report an error if it was marked as deprecated!

```php
        $service = $this->serviceMap->getService($serviceName->value);
        if (($service instanceof DrupalServiceDefinition) && $service->isDeprecated()) {
            return [$service->getDeprecatedDescription()];
        }
```

Here's the recording from the live stream where I resolved this issue.

  

 

## Dynamic return type extension for \\Drupal::service

Creating a dynamic return type extension for \\Drupal::service helped fix the following issues in the Upgrade Status module and would have probably saved a lot of folks from various curse words during their Drupal 9 upgrades. (Sorry to those folks that I didn't catch or fix this sooner.)

- <https://www.drupal.org/project/upgrade_status/issues/3106988>
- <https://www.drupal.org/project/upgrade_status/issues/3176052>
- <https://www.drupal.org/project/upgrade_status/issues/3224717>

There was an existing ServiceDynamicReturnTypeExtension return type extension, which I renamed to `ContainerDynamicReturnTypeExtension` since it handled `ContainerInterface::get` and `ContainerInterface::has` calls. Then I just needed to make the DrupalServiceDynamicReturnTypeExtension return type extension that targets the `\Drupal::service` method. And voila!

I then made a matching `StaticServiceDeprecatedServiceRule` to check if the service name passed to `\Drupal::service` was deprecated.

Here's that fix's live stream recording

  

 

## Catch my live development streams!

All of my development streams are [live on my Twitch channel](twitch.tv/mglaman) and recordings are [exported to my YouTube channel](https://www.youtube.com/channel/UCFntxnFeNN9DXNNrPi4s4Hg).

- Mondays @ 2 PM: Simplytest.me maintenance and development
- Wednesdays @ 2 PM: Drupal upgrade tools development, sponsored by Palantir.net
- Thursdays @ 2 PM: PHPStan development, sponsored by Esteemed, Inc

Want to sponsor my open source development work?

- You can sponsor my work through [GitHub Sponsors](https://github.com/sponsors/mglaman)
- Interested in sponsoring a live development stream? I can work on something of your choosing (not just Drupal upgrade tooling)! [Contact me](https://mglaman.dev/contact-matt) through my website. Episodes are two hours long for $300 USD.

 

  ![](/themes/custom/mglaman/images/headshot.webp)Thanks for reading

I write about Drupal, PHP, and the quiet infrastructure behind large sites — caching, config, and testing. New posts land every week or two.

 [Subscribe via RSS →](/rss.xml) [About me →](/about-me) 

 



 

  [← Older

PHPStan development sponsored by Esteemed! 

 ](/blog/phpstan-development-sponsored-esteemed) [Newer →

PHPStan 1.0 and phpstan-drupal 1.0!

 ](/blog/phpstan-10-and-phpstan-drupal-10)