Menu

Dynamic Return Type Extensions

If the return type of a method is not always the same, but depends on an argument passed to the method, you can specify the return type by writing and registering an extension.

The implementation is all about applying the core concepts so check out that guide first and then continue here.

Because you have to write the code with the type-resolving logic, it can be as complex as you want.

After writing the sample extension, the variable $mergedArticle will have the correct type:

$mergedArticle = $this->entityManager->merge($article);
// $mergedArticle will have the same type as $article

This is the interface for dynamic return type extension:

namespace PHPStan\Type;

use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;

interface DynamicMethodReturnTypeExtension
{
	public function getClass(): string;

	public function isMethodSupported(MethodReflection $methodReflection): bool;

	public function getTypeFromMethodCall(
		MethodReflection $methodReflection,
		MethodCall $methodCall,
		Scope $scope
	): ?Type;
}

And this is how you’d write the extension that correctly resolves the EntityManager::merge() return type:

namespace App\PHPStan;

use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Type\DynamicMethodReturnTypeExtension;
use PHPStan\Type\Type;

class EntityManagerDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
{
	public function getClass(): string
	{
		return \Doctrine\ORM\EntityManager::class;
	}

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

	public function getTypeFromMethodCall(
		MethodReflection $methodReflection,
		MethodCall $methodCall,
		Scope $scope
	): ?Type
	{
		if (count($methodCall->getArgs()) === 0) {
			return null;
		}
		$arg = $methodCall->getArgs()[0]->value;

		return $scope->getType($arg);
	}
}

ParametersAcceptorSelector::selectFromArgs(...) is the default way to resolve the return type of a method call. Starting from PHPStan 1.5.0 the return type of getTypeFromMethodCall() is optional, so you can return null from it if you don’t want to resolve to a specific Type.

Finally, register the extension in the configuration file:

services:
	-
		class: App\PHPStan\EntityManagerDynamicReturnTypeExtension
		tags:
			- phpstan.broker.dynamicMethodReturnTypeExtension

There’s also analogous functionality for:

Tracking dependencies for the result cache #

Available in PHPStan 2.3.0

If the extension reads something PHPStan doesn’t know about to resolve the return type, like a configuration file, typehint the $scope parameter as Scope&DependencyTracker in the PHPDoc and track it with $scope->trackFileDependency() or one of the other DependencyTracker methods. When it changes, the result cache analyses the file with the call again. Learn more »

Theme
A
© 2026 PHPStan s.r.o.