Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 8da11ff

Browse files
Add CacheFactoryGetHandlerReturnTypeExtension (#37)
1 parent 6957bed commit 8da11ff

File tree

6 files changed

+255
-5
lines changed

6 files changed

+255
-5
lines changed

‎bootstrap.php‎

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,16 @@
1313

1414
require_once __DIR__ . '/vendor/codeigniter4/framework/system/Test/bootstrap.php';
1515

16-
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(__DIR__ . '/vendor/codeigniter4/framework/system/Helpers'));
16+
foreach ([
17+
'vendor/codeigniter4/framework/app/Config',
18+
'vendor/codeigniter4/framework/system/Helpers'
19+
] as $directory) {
20+
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($directory));
1721

18-
/** @var SplFileInfo $helper */
19-
foreach ($iterator as $helper) {
20-
if ($helper->isFile()) {
21-
require_once $helper->getRealPath();
22+
/** @var SplFileInfo $file */
23+
foreach ($iterator as $file) {
24+
if ($file->isFile() && $file->getExtension() === 'php') {
25+
require_once $file->getRealPath();
26+
}
2227
}
2328
}

‎docs/type-inference.md‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,42 @@ This also allows dynamic return type transformation of `CodeIgniter\Model` when
122122
123123
## Dynamic Static Method Return Type Extensions
124124
125+
### CacheFactoryHandlerReturnTypeExtension
126+
127+
This extension provides precise return type to `CacheFactory::getHandler()` static method.
128+
129+
**Before:**
130+
```php
131+
\PHPStan\dumpType(CacheFactory::getHandler(new Cache())); // CodeIgniter\Cache\CacheInterface
132+
\PHPStan\dumpType(CacheFactory::getHandler(new Cache(), 'redis')); // CodeIgniter\Cache\CacheInterface
133+
```
134+
135+
**After:**
136+
```php
137+
\PHPStan\dumpType(CacheFactory::getHandler(new Cache())); // CodeIgniter\Cache\Handlers\FileHandler
138+
\PHPStan\dumpType(CacheFactory::getHandler(new Cache(), 'redis')); // CodeIgniter\Cache\Handlers\RedisHandler
139+
```
140+
141+
> [!NOTE]
142+
> **Configuration:**
143+
>
144+
> By default, this extension only considers the primary handler as the return type. If that fails (e.g. the handler
145+
> is not defined in the Cache config's `$validHandlers` array), then this will return the backup handler as
146+
> return type. If you want to return both primary and backup handlers as return type, you can set this:
147+
>
148+
> ```yml
149+
> parameters:
150+
> codeigniter:
151+
> addBackupHandlerAsReturnType: true
152+
> ```
153+
>
154+
> This setting will give the return type as a benevolent union of the primary and backup handler types.
155+
>
156+
> ```php
157+
> \PHPStan\dumpType(CacheFactory::getHandler(new Cache())); // (FileHandler|DummyHandler)
158+
> \PHPStan\dumpType(CacheFactory::getHandler(new Cache(), 'redis', 'file')); // (FileHandler|RedisHandler)
159+
> ```
160+
125161
### ReflectionHelperGetPrivateMethodInvokerReturnTypeExtension
126162
127163
This extension provides precise return type to `ReflectionHelper`'s static `getPrivateMethodInvoker()` method.

‎extension.neon‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ parameters:
1010
- CodeIgniter\Config\
1111
additionalModelNamespaces: []
1212
additionalServices: []
13+
addBackupHandlerAsReturnType: false
1314
notStringFormattedFields: []
1415
checkArgumentTypeOfFactories: true
1516
checkArgumentTypeOfConfig: true
@@ -21,6 +22,7 @@ parametersSchema:
2122
additionalConfigNamespaces: listOf(string())
2223
additionalModelNamespaces: listOf(string())
2324
additionalServices: listOf(string())
25+
addBackupHandlerAsReturnType: bool()
2426
notStringFormattedFields: arrayOf(string())
2527
checkArgumentTypeOfFactories: bool()
2628
checkArgumentTypeOfConfig: bool()
@@ -78,6 +80,13 @@ services:
7880
- phpstan.broker.dynamicMethodReturnTypeExtension
7981

8082
# DynamicStaticMethodReturnTypeExtension
83+
-
84+
class: CodeIgniter\PHPStan\Type\CacheFactoryGetHandlerReturnTypeExtension
85+
tags:
86+
- phpstan.broker.dynamicStaticMethodReturnTypeExtension
87+
arguments:
88+
addBackupHandlerAsReturnType: %codeigniter.addBackupHandlerAsReturnType%
89+
8190
-
8291
class: CodeIgniter\PHPStan\Type\ReflectionHelperGetPrivateMethodInvokerReturnTypeExtension
8392
tags:
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of CodeIgniter 4 framework.
7+
*
8+
* (c) 2023 CodeIgniter Foundation <admin@codeigniter.com>
9+
*
10+
* For the full copyright and license information, please view
11+
* the LICENSE file that was distributed with this source code.
12+
*/
13+
14+
namespace CodeIgniter\PHPStan\Type;
15+
16+
use CodeIgniter\Cache\CacheFactory;
17+
use CodeIgniter\Cache\CacheInterface;
18+
use CodeIgniter\Cache\Handlers\BaseHandler;
19+
use Config\Cache;
20+
use PhpParser\Node\Expr;
21+
use PhpParser\Node\Expr\ConstFetch;
22+
use PhpParser\Node\Expr\StaticCall;
23+
use PhpParser\Node\Name;
24+
use PHPStan\Analyser\Scope;
25+
use PHPStan\Reflection\MethodReflection;
26+
use PHPStan\Type\DynamicStaticMethodReturnTypeExtension;
27+
use PHPStan\Type\IntersectionType;
28+
use PHPStan\Type\NeverType;
29+
use PHPStan\Type\ObjectType;
30+
use PHPStan\Type\Type;
31+
use PHPStan\Type\TypeCombinator;
32+
use PHPStan\Type\TypeTraverser;
33+
use PHPStan\Type\TypeUtils;
34+
use PHPStan\Type\UnionType;
35+
36+
final class CacheFactoryGetHandlerReturnTypeExtension implements DynamicStaticMethodReturnTypeExtension
37+
{
38+
public function __construct(
39+
private readonly bool $addBackupHandlerAsReturnType,
40+
) {}
41+
42+
public function getClass(): string
43+
{
44+
return CacheFactory::class;
45+
}
46+
47+
public function isStaticMethodSupported(MethodReflection $methodReflection): bool
48+
{
49+
return $methodReflection->getName() === 'getHandler';
50+
}
51+
52+
public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): ?Type
53+
{
54+
$args = $methodCall->getArgs();
55+
56+
if ($args === []) {
57+
return null;
58+
}
59+
60+
$cache = $this->getCache($args[0]->value, $scope);
61+
62+
if ($cache === null || $cache->validHandlers === []) {
63+
return new NeverType(true);
64+
}
65+
66+
$handlerType = $this->getHandlerType(
67+
$args[1]->value ?? new ConstFetch(new Name('null')),
68+
$scope,
69+
$cache->validHandlers,
70+
$cache->handler,
71+
);
72+
$backupHandlerType = $this->getHandlerType(
73+
$args[2]->value ?? new ConstFetch(new Name('null')),
74+
$scope,
75+
$cache->validHandlers,
76+
$cache->backupHandler,
77+
);
78+
79+
if (! $handlerType->isObject()->yes()) {
80+
return $backupHandlerType;
81+
}
82+
83+
if (! $this->addBackupHandlerAsReturnType) {
84+
return $handlerType;
85+
}
86+
87+
return TypeUtils::toBenevolentUnion(TypeCombinator::union($handlerType, $backupHandlerType));
88+
}
89+
90+
private function getCache(Expr $expr, Scope $scope): ?Cache
91+
{
92+
foreach ($scope->getType($expr)->getObjectClassReflections() as $classReflection) {
93+
if ($classReflection->getName() === Cache::class) {
94+
$cache = $classReflection->getNativeReflection()->newInstance();
95+
96+
if ($cache instanceof Cache) {
97+
return $cache;
98+
}
99+
}
100+
}
101+
102+
return null;
103+
}
104+
105+
/**
106+
* @param array<string, class-string<CacheInterface>> $validHandlers
107+
*/
108+
private function getHandlerType(Expr $expr, Scope $scope, array $validHandlers, string $default): Type
109+
{
110+
return TypeTraverser::map(
111+
$scope->getType($expr),
112+
static function (Type $type, callable $traverse) use ($validHandlers, $default): Type {
113+
if ($type instanceof UnionType || $type instanceof IntersectionType) {
114+
return $traverse($type);
115+
}
116+
117+
if ($type->isNull()->yes()) {
118+
if (! isset($validHandlers[$default])) {
119+
return new NeverType(true);
120+
}
121+
122+
return new ObjectType($validHandlers[$default]);
123+
}
124+
125+
$types = [];
126+
127+
foreach ($type->getConstantStrings() as $constantString) {
128+
$name = $constantString->getValue();
129+
130+
if (isset($validHandlers[$name])) {
131+
$types[] = new ObjectType($validHandlers[$name]);
132+
} else {
133+
$types[] = new NeverType(true);
134+
}
135+
}
136+
137+
if ($types === []) {
138+
return new ObjectType(BaseHandler::class);
139+
}
140+
141+
return TypeCombinator::union(...$types);
142+
},
143+
);
144+
}
145+
}

‎tests/Type/DynamicStaticMethodReturnTypeExtensionTest.php‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ public function testFileAsserts(string $assertType, string $file, mixed ...$args
3737
*/
3838
public static function provideFileAssertsCases(): iterable
3939
{
40+
yield from self::gatherAssertTypes(__DIR__ . '/data/cache-factory.php');
41+
4042
yield from self::gatherAssertTypes(__DIR__ . '/data/reflection-helper.php');
4143

4244
yield from self::gatherAssertTypes(__DIR__ . '/data/services-get-shared-instance.php');

‎tests/Type/data/cache-factory.php‎

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of CodeIgniter 4 framework.
7+
*
8+
* (c) 2023 CodeIgniter Foundation <admin@codeigniter.com>
9+
*
10+
* For the full copyright and license information, please view
11+
* the LICENSE file that was distributed with this source code.
12+
*/
13+
14+
namespace CodeIgniter\PHPStan\Tests\Fixtures\Type;
15+
16+
use CodeIgniter\Cache\CacheFactory;
17+
use CodeIgniter\Cache\Handlers\BaseHandler;
18+
use CodeIgniter\Cache\Handlers\DummyHandler;
19+
use CodeIgniter\Cache\Handlers\FileHandler;
20+
use CodeIgniter\Cache\Handlers\MemcachedHandler;
21+
use CodeIgniter\Cache\Handlers\PredisHandler;
22+
use CodeIgniter\Cache\Handlers\RedisHandler;
23+
use CodeIgniter\Cache\Handlers\WincacheHandler;
24+
use Config\Cache;
25+
26+
use function PHPStan\Testing\assertType;
27+
28+
$cache = new Cache();
29+
assertType(FileHandler::class, CacheFactory::getHandler($cache));
30+
assertType(FileHandler::class, CacheFactory::getHandler($cache, null));
31+
32+
assertType(DummyHandler::class, CacheFactory::getHandler($cache, 'dummy'));
33+
assertType(FileHandler::class, CacheFactory::getHandler($cache, 'file'));
34+
assertType(MemcachedHandler::class, CacheFactory::getHandler($cache, 'memcached'));
35+
assertType(PredisHandler::class, CacheFactory::getHandler($cache, 'predis'));
36+
assertType(RedisHandler::class, CacheFactory::getHandler($cache, 'redis'));
37+
assertType(WincacheHandler::class, CacheFactory::getHandler($cache, 'wincache'));
38+
39+
assertType(DummyHandler::class, CacheFactory::getHandler($cache, 'invalid'));
40+
assertType('*NEVER*', CacheFactory::getHandler($cache, 'unknown', 'invalid'));
41+
42+
assertType(
43+
FileHandler::class . '|' . RedisHandler::class,
44+
CacheFactory::getHandler($cache, (static fn (): string => mt_rand(0, 1) ? 'file' : 'redis')()),
45+
);
46+
47+
/**
48+
* @param non-empty-string $name
49+
*/
50+
function getCache(string $name): void
51+
{
52+
assertType(BaseHandler::class, CacheFactory::getHandler(new Cache(), $name));
53+
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /