Skip to content

Commit

Permalink
Support container interfaces for get() return type
Browse files Browse the repository at this point in the history
  • Loading branch information
jhatlak committed Nov 25, 2022
1 parent 2aa4998 commit f76b6a5
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 95 deletions.
8 changes: 8 additions & 0 deletions extension.neon
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ services:
class: LaminasPhpStan\ServiceManagerLoader
arguments:
serviceManagerLoader: %laminasframework.serviceManagerLoader%
-
class: LaminasPhpStan\Type\Laminas\InteropContainerGetDynamicReturnTypeExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
-
class: LaminasPhpStan\Type\Laminas\PsrContainerGetDynamicReturnTypeExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
-
class: LaminasPhpStan\Type\Laminas\ServiceManagerGetDynamicReturnTypeExtension
tags:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<?php

declare(strict_types=1);

namespace LaminasPhpStan\Type\Laminas;

use Laminas\ServiceManager\AbstractPluginManager;
use LaminasPhpStan\ServiceManagerLoader;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\DynamicMethodReturnTypeExtension;
use PHPStan\Type\MixedType;
use PHPStan\Type\NeverType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use ReflectionClass;

abstract class AbstractServiceLocatorGetDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
{
public function __construct(
private ReflectionProvider $reflectionProvider,
private ServiceManagerLoader $serviceManagerLoader
) {
}

public function isMethodSupported(MethodReflection $methodReflection): bool
{
return 'get' === $methodReflection->getName();
}

public function getTypeFromMethodCall(
MethodReflection $methodReflection,
MethodCall $methodCall,
Scope $scope
): Type {
$calledOnType = $scope->getType($methodCall->var);
if (! $calledOnType instanceof ObjectType) {
return new MixedType();
}

$args = $methodCall->getArgs();
if (1 !== \count($args)) {
return new MixedType();
}

$serviceManager = $this->serviceManagerLoader->getServiceLocator($calledOnType->getClassName());

$firstArg = $args[0];
if (! $firstArg instanceof Arg) {
throw new \PHPStan\ShouldNotHappenException(\sprintf(
'Argument passed to %s::%s should be a string, %s given',
$methodReflection->getDeclaringClass()->getName(),
$methodReflection->getName(),
$firstArg->getType()
));
}
$argType = $scope->getType($firstArg->value);
if (! $argType instanceof ConstantStringType) {
if ($serviceManager instanceof AbstractPluginManager) {
$refClass = new ReflectionClass($serviceManager);
$refProperty = $refClass->getProperty('instanceOf');
$refProperty->setAccessible(true);
$returnedInstance = $refProperty->getValue($serviceManager);
\assert(null === $returnedInstance || \is_string($returnedInstance));
if (null !== $returnedInstance && $this->reflectionProvider->hasClass($returnedInstance)) {
return new ObjectType($returnedInstance);
}
}

return new MixedType();
}

$serviceName = $argType->getValue();
if (! $serviceManager->has($serviceName)) {
return new NeverType();
}

if (\class_exists($serviceName) || \interface_exists($serviceName)) {
return new ObjectServiceManagerType($serviceName, $serviceName);
}

$service = $serviceManager->get($serviceName);
if (\is_object($service)) {
$className = $service::class;
$refClass = new ReflectionClass($service);
if ($refClass->isAnonymous()) {
if (false !== ($parentClass = $refClass->getParentClass())) {
$className = $parentClass->getName();
} elseif ([] !== ($interfaces = $refClass->getInterfaces())) {
$className = \current($interfaces)->getName();
}
}

return new ObjectServiceManagerType($className, $serviceName);
}

return $scope->getTypeFromValue($service);
}
}
15 changes: 15 additions & 0 deletions src/Type/Laminas/InteropContainerGetDynamicReturnTypeExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace LaminasPhpStan\Type\Laminas;

use Interop\Container\Containerinterface;

final class InteropContainerGetDynamicReturnTypeExtension extends AbstractServiceLocatorGetDynamicReturnTypeExtension
{
public function getClass(): string
{
return ContainerInterface::class;
}
}
15 changes: 15 additions & 0 deletions src/Type/Laminas/PsrContainerGetDynamicReturnTypeExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace LaminasPhpStan\Type\Laminas;

use Psr\Container\ContainerInterface;

final class PsrContainerGetDynamicReturnTypeExtension extends AbstractServiceLocatorGetDynamicReturnTypeExtension
{
public function getClass(): string
{
return ContainerInterface::class;
}
}
96 changes: 1 addition & 95 deletions src/Type/Laminas/ServiceManagerGetDynamicReturnTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,106 +4,12 @@

namespace LaminasPhpStan\Type\Laminas;

use Laminas\ServiceManager\AbstractPluginManager;
use Laminas\ServiceManager\ServiceLocatorInterface;
use LaminasPhpStan\ServiceManagerLoader;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\DynamicMethodReturnTypeExtension;
use PHPStan\Type\MixedType;
use PHPStan\Type\NeverType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use ReflectionClass;

final class ServiceManagerGetDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
final class ServiceManagerGetDynamicReturnTypeExtension extends AbstractServiceLocatorGetDynamicReturnTypeExtension
{
public function __construct(
private ReflectionProvider $reflectionProvider,
private ServiceManagerLoader $serviceManagerLoader
) {
}

public function getClass(): string
{
return ServiceLocatorInterface::class;
}

public function isMethodSupported(MethodReflection $methodReflection): bool
{
return 'get' === $methodReflection->getName();
}

public function getTypeFromMethodCall(
MethodReflection $methodReflection,
MethodCall $methodCall,
Scope $scope
): Type {
$calledOnType = $scope->getType($methodCall->var);
if (! $calledOnType instanceof ObjectType) {
return new MixedType();
}

$args = $methodCall->getArgs();
if (1 !== \count($args)) {
return new MixedType();
}

$serviceManager = $this->serviceManagerLoader->getServiceLocator($calledOnType->getClassName());

$firstArg = $args[0];
if (! $firstArg instanceof Arg) {
throw new \PHPStan\ShouldNotHappenException(\sprintf(
'Argument passed to %s::%s should be a string, %s given',
$methodReflection->getDeclaringClass()->getName(),
$methodReflection->getName(),
$firstArg->getType()
));
}
$argType = $scope->getType($firstArg->value);
if (! $argType instanceof ConstantStringType) {
if ($serviceManager instanceof AbstractPluginManager) {
$refClass = new ReflectionClass($serviceManager);
$refProperty = $refClass->getProperty('instanceOf');
$refProperty->setAccessible(true);
$returnedInstance = $refProperty->getValue($serviceManager);
\assert(null === $returnedInstance || \is_string($returnedInstance));
if (null !== $returnedInstance && $this->reflectionProvider->hasClass($returnedInstance)) {
return new ObjectType($returnedInstance);
}
}

return new MixedType();
}

$serviceName = $argType->getValue();
if (! $serviceManager->has($serviceName)) {
return new NeverType();
}

if (\class_exists($serviceName) || \interface_exists($serviceName)) {
return new ObjectServiceManagerType($serviceName, $serviceName);
}

$service = $serviceManager->get($serviceName);
if (\is_object($service)) {
$className = $service::class;
$refClass = new ReflectionClass($service);
if ($refClass->isAnonymous()) {
if (false !== ($parentClass = $refClass->getParentClass())) {
$className = $parentClass->getName();
} elseif ([] !== ($interfaces = $refClass->getInterfaces())) {
$className = \current($interfaces)->getName();
}
}

return new ObjectServiceManagerType($className, $serviceName);
}

return $scope->getTypeFromValue($service);
}
}

0 comments on commit f76b6a5

Please sign in to comment.