diff --git a/extension.neon b/extension.neon index 57c95ff..7d8faa2 100644 --- a/extension.neon +++ b/extension.neon @@ -63,6 +63,10 @@ services: tags: - phpstan.phpDoc.typeNodeResolverExtension - - class: CakeDC\PHPStan\Type\GetMailerExpressionTypeResolverExtension + factory: CakeDC\PHPStan\Type\BaseTraitExpressionTypeResolverExtension(Cake\Mailer\MailerAwareTrait, getMailer, %s\Mailer\%sMailer) + tags: + - phpstan.broker.expressionTypeResolverExtension + - + factory: CakeDC\PHPStan\Type\BaseTraitExpressionTypeResolverExtension(Cake\ORM\Locator\LocatorAwareTrait, fetchTable, %s\Model\Table\%sTable, defaultTable) tags: - phpstan.broker.expressionTypeResolverExtension diff --git a/src/Type/GetMailerExpressionTypeResolverExtension.php b/src/Type/BaseTraitExpressionTypeResolverExtension.php similarity index 53% rename from src/Type/GetMailerExpressionTypeResolverExtension.php rename to src/Type/BaseTraitExpressionTypeResolverExtension.php index 507c3ba..88cfcb3 100644 --- a/src/Type/GetMailerExpressionTypeResolverExtension.php +++ b/src/Type/BaseTraitExpressionTypeResolverExtension.php @@ -13,7 +13,6 @@ namespace CakeDC\PHPStan\Type; -use Cake\Mailer\MailerAwareTrait; use CakeDC\PHPStan\Utility\CakeNameRegistry; use PhpParser\Node\Expr; use PhpParser\Node\Identifier; @@ -24,24 +23,24 @@ use PHPStan\Type\ObjectType; use PHPStan\Type\ThisType; use PHPStan\Type\Type; +use ReflectionException; -class GetMailerExpressionTypeResolverExtension implements ExpressionTypeResolverExtension +class BaseTraitExpressionTypeResolverExtension implements ExpressionTypeResolverExtension { /** - * @var string + * TableLocatorDynamicReturnTypeExtension constructor. + * + * @param string $targetTrait The target trait. + * @param string $methodName The dynamic method to handle. + * @param string $namespaceFormat The resolve namespace format. + * @param string|null $propertyDefaultValue A property name for default classname, used when no args in method call. */ - protected string $methodName; - protected string $namespaceFormat; - protected string $targetTrait; - - /** - * Contructor - */ - public function __construct() - { - $this->targetTrait = MailerAwareTrait::class; - $this->methodName = 'getMailer'; - $this->namespaceFormat = '%s\\Mailer\\%sMailer'; + public function __construct( + protected string $targetTrait, + protected string $methodName, + protected string $namespaceFormat, + protected ?string $propertyDefaultValue = null + ) { } /** @@ -61,18 +60,20 @@ public function getType(Expr $expr, Scope $scope): ?Type $callerType = $scope->getType($expr->var); - if ( - !$callerType instanceof ThisType - || !$this->isFromTargetTrait($callerType->getClassReflection()) - ) { + if (!$callerType instanceof ThisType) { + return null; + } + $reflection = $callerType->getClassReflection(); + if (!$this->isFromTargetTrait($reflection)) { return null; } $value = $expr->getArgs()[0]->value ?? null; - if (!$value instanceof String_) { + $baseName = $this->getBaseName($value, $reflection); + if ($baseName === null) { return null; } - $className = CakeNameRegistry::getClassName($value->value, $this->namespaceFormat); + $className = CakeNameRegistry::getClassName($baseName, $this->namespaceFormat); if ($className !== null) { return new ObjectType($className); } @@ -99,4 +100,30 @@ protected function isFromTargetTrait(ClassReflection $reflection): bool return false; } + + /** + * @param \PhpParser\Node\Expr|null $value + * @param \PHPStan\Reflection\ClassReflection $reflection + * @return string|null + */ + protected function getBaseName(?Expr $value, ClassReflection $reflection): ?string + { + if ($value instanceof String_) { + return $value->value; + } + + try { + if ($value === null && $this->propertyDefaultValue) { + $value = $reflection->getNativeReflection() + ->getProperty($this->propertyDefaultValue) + ->getDefaultValue(); + + return is_string($value) ? $value : null; + } + } catch (ReflectionException) { + return null; + } + + return null; + } } diff --git a/tests/test_app/Model/Logic/Action/BlockUsers.php b/tests/test_app/Model/Logic/Action/BlockUsers.php new file mode 100644 index 0000000..36860f5 --- /dev/null +++ b/tests/test_app/Model/Logic/Action/BlockUsers.php @@ -0,0 +1,40 @@ +fetchTable()->blockOld(); + } +} diff --git a/tests/test_app/Model/Logic/Action/WarnUsers.php b/tests/test_app/Model/Logic/Action/WarnUsers.php new file mode 100644 index 0000000..e223892 --- /dev/null +++ b/tests/test_app/Model/Logic/Action/WarnUsers.php @@ -0,0 +1,36 @@ +fetchTable('Notes') + ->warning(); + } +} diff --git a/tests/test_app/Model/Table/UsersTable.php b/tests/test_app/Model/Table/UsersTable.php index 8a1b04c..2a7809d 100644 --- a/tests/test_app/Model/Table/UsersTable.php +++ b/tests/test_app/Model/Table/UsersTable.php @@ -39,4 +39,8 @@ public function logLastLogin(User $user): User return $this->saveOrFail($user); } + + public function blockOld() + { + } }