Skip to content

Commit

Permalink
Extension to detect return type of LocatorAwareTrait::fetchTable
Browse files Browse the repository at this point in the history
  • Loading branch information
rochamarcelo committed Oct 11, 2024
1 parent ab8fb5a commit 09e8f53
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 22 deletions.
6 changes: 5 additions & 1 deletion extension.neon
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
) {
}

/**
Expand All @@ -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);
}
Expand All @@ -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;
}
}
40 changes: 40 additions & 0 deletions tests/test_app/Model/Logic/Action/BlockUsers.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);

/**
* Copyright 2020, Cake Development Corporation (https://www.cakedc.com)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright 2020, Cake Development Corporation (https://www.cakedc.com)
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/

namespace App\Model\Logic\Action;

/**
* Class DeleteUsers
*
* @package App\Model\Logic\Action
*/
class BlockUsers extends WarnUsers
{
/**
* This object's default table alias.
*
* @var string|null
*/
protected ?string $defaultTable = 'Users';

/**
* @throws \Exception
* @return void
*/
public function process()
{
parent::process();

$this->fetchTable()->blockOld();
}
}
36 changes: 36 additions & 0 deletions tests/test_app/Model/Logic/Action/WarnUsers.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);

/**
* Copyright 2020, Cake Development Corporation (https://www.cakedc.com)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright 2020, Cake Development Corporation (https://www.cakedc.com)
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/

namespace App\Model\Logic\Action;

use Cake\ORM\Locator\LocatorAwareTrait;

/**
* Class DeleteUsers
*
* @package App\Model\Logic\Action
*/
class WarnUsers
{
use LocatorAwareTrait;

/**
* @throws \Exception
* @return void
*/
public function process()
{
$this->fetchTable('Notes')
->warning();
}
}
4 changes: 4 additions & 0 deletions tests/test_app/Model/Table/UsersTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,8 @@ public function logLastLogin(User $user): User

return $this->saveOrFail($user);
}

public function blockOld()
{
}
}

0 comments on commit 09e8f53

Please sign in to comment.