Skip to content
Merged
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ What it currently enables:
- `cacheableDependencyRule` — flags `addCacheableDependency()` calls with non-cacheable arguments
- `loggerFromFactoryPropertyAssignmentRule` — flags logger channels assigned to properties in classes using `DependencySerializationTrait`
- `entityStorageDirectInjectionRule` — flags direct injection of entity storage into a constructor; inject `EntityTypeManagerInterface` and call `getStorage()` instead
- `containerHasAlwaysTrue: false` — `ContainerInterface::has()` returns `bool` instead of always-`true` for known services, preventing false positives that may cause developers to remove legitimate conditional service guards

> [!NOTE]
> `checkDeprecatedHooksInApiFiles` is deprecated. Use `checkCoreDeprecatedHooksInApiFiles` and `checkContribDeprecatedHooksInApiFiles` instead.
Expand Down
1 change: 1 addition & 0 deletions bleedingEdge.neon
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ parameters:
checkDeprecatedHooksInApiFiles: false
checkCoreDeprecatedHooksInApiFiles: true
checkContribDeprecatedHooksInApiFiles: true
Comment thread
mglaman marked this conversation as resolved.
containerHasAlwaysTrue: false
rules:
testClassSuffixNameRule: true
dependencySerializationTraitPropertyRule: true
Expand Down
4 changes: 4 additions & 0 deletions extension.neon
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ parameters:
checkDeprecatedHooksInApiFiles: false
checkCoreDeprecatedHooksInApiFiles: false
checkContribDeprecatedHooksInApiFiles: false
containerHasAlwaysTrue: true
extensions:
entityFieldsViaMagicReflection: true
entityFieldMethodsViaMagicReflection: true
Expand Down Expand Up @@ -259,6 +260,7 @@ parametersSchema:
checkDeprecatedHooksInApiFiles: boolean()
checkCoreDeprecatedHooksInApiFiles: boolean()
checkContribDeprecatedHooksInApiFiles: boolean()
containerHasAlwaysTrue: boolean()
])
extensions: structure([
entityFieldsViaMagicReflection: boolean()
Expand Down Expand Up @@ -325,6 +327,8 @@ services:
-
class: mglaman\PHPStanDrupal\Type\ContainerDynamicReturnTypeExtension
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
arguments:
containerHasAlwaysTrue: %drupal.bleedingEdge.containerHasAlwaysTrue%
-
class: mglaman\PHPStanDrupal\Type\DrupalClassResolverDynamicReturnTypeExtension
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
Expand Down
18 changes: 10 additions & 8 deletions src/Type/ContainerDynamicReturnTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Type\BooleanType;
use PHPStan\Type\Constant\ConstantBooleanType;
use PHPStan\Type\DynamicMethodReturnTypeExtension;
use PHPStan\Type\NullType;
Expand All @@ -18,14 +19,11 @@

class ContainerDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
{
/**
* @var ServiceMap
*/
private ServiceMap $serviceMap;

public function __construct(ServiceMap $serviceMap)
{
$this->serviceMap = $serviceMap;
public function __construct(
private ServiceMap $serviceMap,
private bool $containerHasAlwaysTrue,
) {
}

public function getClass(): string
Expand Down Expand Up @@ -62,7 +60,11 @@ public function getTypeFromMethodCall(
foreach ($argType->getConstantStrings() as $constantStringType) {
$serviceId = $constantStringType->getValue();
$service = $this->serviceMap->getService($serviceId);
$types[] = new ConstantBooleanType($service !== null);
if (!$this->containerHasAlwaysTrue && $service !== null) {
$types[] = new BooleanType();
} else {
$types[] = new ConstantBooleanType($service !== null);
}
}

return TypeCombinator::union(...$types);
Expand Down
18 changes: 18 additions & 0 deletions tests/fixtures/config/phpunit-drupal-phpstan-no-bleedingedge.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
parameters:
drupal:
entityMapping:
content_entity_using_custom_storage:
class: Drupal\phpstan_fixtures\Entity\ContentEntityUsingCustomStorage
storage: Drupal\phpstan_fixtures\CustomContentEntityStorage
config_entity_using_default_storage:
class: Drupal\phpstan_fixtures\Entity\ConfigEntityUsingDefaultStorage
storage: Drupal\Core\Config\Entity\ConfigEntityStorage
config_entity_using_custom_storage:
class: Drupal\phpstan_fixtures\Entity\ConfigEntityUsingCustomStorage
storage: Drupal\phpstan_fixtures\CustomConfigEntityStorage
content_entity_using_default_storage:
class: Drupal\phpstan_fixtures\Entity\ContentEntityUsingDefaultStorage
includes:
- ../../../extension.neon
- ../../../rules.neon
- ../../../vendor/phpstan/phpstan-deprecation-rules/rules.neon
37 changes: 37 additions & 0 deletions tests/src/Type/DrupalContainerDynamicReturnTypeLegacyTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

declare(strict_types=1);

namespace mglaman\PHPStanDrupal\Tests\Type;

use PHPStan\Testing\TypeInferenceTestCase;

final class DrupalContainerDynamicReturnTypeLegacyTest extends TypeInferenceTestCase
{

public static function getAdditionalConfigFiles(): array
{
return array_merge(parent::getAdditionalConfigFiles(), [
__DIR__ . '/../../fixtures/config/phpunit-drupal-phpstan-no-bleedingedge.neon',
]);
}

public static function dataFileAsserts(): iterable
{
yield from self::gatherAssertTypes(__DIR__ . '/data/container-legacy-has.php');
}

/**
* @dataProvider dataFileAsserts
* @param string $assertType
* @param string $file
* @param mixed ...$args
*/
public function testFileAsserts(
string $assertType,
string $file,
...$args
): void {
$this->assertFileAsserts($assertType, $file, ...$args);
}
}
13 changes: 13 additions & 0 deletions tests/src/Type/data/container-legacy-has.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace DrupalContainerStaticLegacy;

use Drupal\service_map\MyService;
use function PHPStan\Testing\assertType;

function test(): void {
$container = \Drupal::getContainer();

assertType('true', $container->has('service_map.my_service'));
assertType('false', $container->has('unknown_service'));
}
2 changes: 1 addition & 1 deletion tests/src/Type/data/container.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ function test(): void {
$container = \Drupal::getContainer();

assertType(MyService::class, $container->get('service_map.my_service'));
assertType('true', $container->has('service_map.my_service'));
assertType('bool', $container->has('service_map.my_service'));
assertType('false', $container->has('unknown_service'));
assertType(MyService::class, $container->get('service_map.concrete_service'));
assertType(MyService::class, $container->get('service_map.concrete_service_with_a_parent_which_has_a_parent'));
Expand Down
Loading