diff --git a/extension.neon b/extension.neon index 80c0ee7f..81fcbacb 100644 --- a/extension.neon +++ b/extension.neon @@ -272,6 +272,9 @@ services: - class: mglaman\PHPStanDrupal\Type\EntityStorage\EntityStorageDynamicReturnTypeExtension tags: [phpstan.broker.dynamicMethodReturnTypeExtension] + - + class: mglaman\PHPStanDrupal\Type\EntityRepositoryReturnTypeExtension + tags: [phpstan.broker.dynamicMethodReturnTypeExtension] - class: mglaman\PHPStanDrupal\Type\EntityStorage\GetQueryReturnTypeExtension tags: [phpstan.broker.dynamicMethodReturnTypeExtension] diff --git a/src/Type/EntityRepositoryReturnTypeExtension.php b/src/Type/EntityRepositoryReturnTypeExtension.php new file mode 100644 index 00000000..215bcfc7 --- /dev/null +++ b/src/Type/EntityRepositoryReturnTypeExtension.php @@ -0,0 +1,96 @@ +entityDataRepository = $entityDataRepository; + } + + public function getClass(): string + { + return EntityRepositoryInterface::class; + } + + public function isMethodSupported(MethodReflection $methodReflection): bool + { + return in_array( + $methodReflection->getName(), + [ + 'getTranslationFromContext', + 'loadEntityByUuid', + 'loadEntityByConfigTarget', + 'getActive', + 'getActiveMultiple', + 'getCanonical', + 'getCanonicalMultiple', + ], + true + ); + } + + public function getTypeFromMethodCall( + MethodReflection $methodReflection, + MethodCall $methodCall, + Scope $scope + ): ?Type { + $methodName = $methodReflection->getName(); + $methodArgs = $methodCall->getArgs(); + $returnType = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + + if (count($methodArgs) === 0) { + return $returnType; + } + + if ($methodName === 'getTranslationFromContext') { + return $scope->getType($methodArgs[0]->value); + } + + $entityObjectTypes = []; + $entityIdArg = $scope->getType($methodArgs[0]->value); + foreach ($entityIdArg->getConstantStrings() as $constantStringType) { + $entityObjectTypes[] = $this->entityDataRepository->get($constantStringType->getValue())->getClassType() ?? $returnType; + } + $entityTypes = TypeCombinator::union(...$entityObjectTypes); + + if ($returnType->isArray()->no()) { + if ($returnType->isNull()->maybe()) { + $entityTypes = TypeCombinator::addNull($entityTypes); + } + return $entityTypes; + } + + if ((new ObjectType(ConfigEntityInterface::class))->isSuperTypeOf($entityTypes)->yes()) { + $keyType = new StringType(); + } else { + $keyType = new IntegerType(); + } + + return new ArrayType($keyType, $entityTypes); + } +} diff --git a/tests/src/Type/EntityRepositoryTypeTest.php b/tests/src/Type/EntityRepositoryTypeTest.php new file mode 100644 index 00000000..ccaf29b4 --- /dev/null +++ b/tests/src/Type/EntityRepositoryTypeTest.php @@ -0,0 +1,33 @@ +assertFileAsserts($assertType, $file, ...$args); + } +} diff --git a/tests/src/Type/data/entity-repository.php b/tests/src/Type/data/entity-repository.php new file mode 100644 index 00000000..a975ebfc --- /dev/null +++ b/tests/src/Type/data/entity-repository.php @@ -0,0 +1,47 @@ +loadEntityByUuid('node', '3f205175-04f7-4f57-b48b-9799299252c3') +); + +assertType( + 'Drupal\Core\Entity\Entity\EntityViewMode|null', + $entityRepository->loadEntityByConfigTarget('entity_view_mode', 'media.default') +); + +assertType( + 'Drupal\node\Entity\Node', + $entityRepository->getTranslationFromContext(Node::create()) +); + +assertType( + 'Drupal\node\Entity\Node|null', + $entityRepository->getActive('node', 5) +); + +assertType( + 'array', + $entityRepository->getActiveMultiple('node', [5]) +); +assertType( + 'array', + $entityRepository->getActiveMultiple('block', ['foo']) +); + +assertType( + 'Drupal\node\Entity\Node|null', + $entityRepository->getCanonical('node', 5) +); + +assertType( + 'array', + $entityRepository->getCanonicalMultiple('node', [5]) +);