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 519f6ed

Browse files
Add precise return types for Model's find, findAll, and first
1 parent 2bb53ee commit 519f6ed

File tree

6 files changed

+183
-0
lines changed

6 files changed

+183
-0
lines changed

‎README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ This extension provides the following features:
1414
* Provides precise return types for `config()` and `model()` functions.
1515
* Provides precise return types for `service()` and `single_service()` functions.
1616
* Provides precise return types for `fake()` helper function.
17+
* Provides precise return types for `CodeIgniter\Model`'s `find()`, `findAll()`, and `first()` methods.
1718

1819
### Rules
1920

‎extension.neon‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ parametersSchema:
2424
])
2525

2626
services:
27+
# helpers
2728
factoriesReturnTypeHelper:
2829
class: CodeIgniter\PHPStan\Type\FactoriesReturnTypeHelper
2930
arguments:
@@ -43,6 +44,7 @@ services:
4344
superglobalRuleHelper:
4445
class: CodeIgniter\PHPStan\Rules\Superglobals\SuperglobalRuleHelper
4546

47+
# DynamicFunctionReturnTypeExtension
4648
-
4749
class: CodeIgniter\PHPStan\Type\FactoriesFunctionReturnTypeExtension
4850
tags:
@@ -58,6 +60,13 @@ services:
5860
tags:
5961
- phpstan.broker.dynamicFunctionReturnTypeExtension
6062

63+
# DynamicMethodReturnTypeExtension
64+
-
65+
class: CodeIgniter\PHPStan\Type\ModelFindReturnTypeExtension
66+
tags:
67+
- phpstan.broker.dynamicMethodReturnTypeExtension
68+
69+
# conditional rules
6170
-
6271
class: CodeIgniter\PHPStan\Rules\Functions\FactoriesFunctionArgumentTypeRule
6372

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
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\Model;
17+
use PhpParser\Node\Expr\MethodCall;
18+
use PHPStan\Analyser\Scope;
19+
use PHPStan\Reflection\ClassReflection;
20+
use PHPStan\Reflection\MethodReflection;
21+
use PHPStan\Type\Accessory\AccessoryArrayListType;
22+
use PHPStan\Type\ArrayType;
23+
use PHPStan\Type\DynamicMethodReturnTypeExtension;
24+
use PHPStan\Type\IntegerType;
25+
use PHPStan\Type\Type;
26+
use PHPStan\Type\TypeCombinator;
27+
28+
final class ModelFindReturnTypeExtension implements DynamicMethodReturnTypeExtension
29+
{
30+
public function __construct(
31+
private readonly ModelFetchedReturnTypeHelper $modelFetchedReturnTypeHelper
32+
) {}
33+
34+
public function getClass(): string
35+
{
36+
return Model::class;
37+
}
38+
39+
public function isMethodSupported(MethodReflection $methodReflection): bool
40+
{
41+
return in_array($methodReflection->getName(), ['find', 'findAll', 'first'], true);
42+
}
43+
44+
public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
45+
{
46+
$methodName = $methodReflection->getName();
47+
48+
if ($methodName === 'find') {
49+
return $this->getTypeFromFind($methodReflection, $methodCall, $scope);
50+
}
51+
52+
if ($methodName === 'findAll') {
53+
return $this->getTypeFromFindAll($methodReflection, $methodCall, $scope);
54+
}
55+
56+
$classReflection = $this->getClassReflection($methodCall, $scope);
57+
58+
return TypeCombinator::addNull($this->modelFetchedReturnTypeHelper->getFetchedReturnType($classReflection, $scope));
59+
}
60+
61+
private function getClassReflection(MethodCall $methodCall, Scope $scope): ClassReflection
62+
{
63+
$classTypes = $scope->getType($methodCall->var)->getObjectClassReflections();
64+
assert(count($classTypes) === 1);
65+
66+
return current($classTypes);
67+
}
68+
69+
private function getTypeFromFind(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
70+
{
71+
$args = $methodCall->getArgs();
72+
73+
if (! isset($args[0])) {
74+
return $this->getTypeFromFindAll($methodReflection, $methodCall, $scope);
75+
}
76+
77+
$idType = $scope->getType($args[0]->value);
78+
79+
if ($idType->isNull()->yes()) {
80+
return $this->getTypeFromFindAll($methodReflection, $methodCall, $scope);
81+
}
82+
83+
if ($idType->isInteger()->yes() || $idType->isString()->yes()) {
84+
$classReflection = $this->getClassReflection($methodCall, $scope);
85+
86+
return TypeCombinator::addNull($this->modelFetchedReturnTypeHelper->getFetchedReturnType($classReflection, $scope));
87+
}
88+
89+
return $this->getTypeFromFindAll($methodReflection, $methodCall, $scope);
90+
}
91+
92+
private function getTypeFromFindAll(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
93+
{
94+
$classReflection = $this->getClassReflection($methodCall, $scope);
95+
96+
return AccessoryArrayListType::intersectWith(
97+
new ArrayType(
98+
new IntegerType(),
99+
$this->modelFetchedReturnTypeHelper->getFetchedReturnType($classReflection, $scope)
100+
)
101+
);
102+
}
103+
}

‎tests/Fixtures/Type/model-find.php‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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+
use CodeIgniter\Shield\Models\GroupModel;
15+
use CodeIgniter\Shield\Models\UserModel;
16+
17+
use function PHPStan\Testing\assertType;
18+
19+
$users = model(UserModel::class);
20+
assertType('CodeIgniter\Shield\Entities\User|null', $users->find(1));
21+
assertType('list<CodeIgniter\Shield\Entities\User>', $users->find());
22+
assertType('list<CodeIgniter\Shield\Entities\User>', $users->find(null));
23+
assertType('list<CodeIgniter\Shield\Entities\User>', $users->find([1, 2, 3]));
24+
25+
$groups = model(GroupModel::class);
26+
assertType('array{user_id: int, group: string, created_at: string}|null', $groups->find(1));
27+
assertType('list<array{user_id: int, group: string, created_at: string}>', $groups->find());
28+
assertType('list<array{user_id: int, group: string, created_at: string}>', $groups->find(null));
29+
assertType('list<array{user_id: int, group: string, created_at: string}>', $groups->find([1, 2, 3]));
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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\Type;
15+
16+
use CodeIgniter\PHPStan\Tests\AdditionalConfigFilesTrait;
17+
use PHPStan\Testing\TypeInferenceTestCase;
18+
use PHPUnit\Framework\Attributes\DataProvider;
19+
use PHPUnit\Framework\Attributes\Group;
20+
21+
/**
22+
* @internal
23+
*/
24+
#[Group('Integration')]
25+
final class DynamicMethodReturnTypeExtensionTest extends TypeInferenceTestCase
26+
{
27+
use AdditionalConfigFilesTrait;
28+
29+
#[DataProvider('provideFileAssertsCases')]
30+
public function testFileAsserts(string $assertType, string $file, mixed ...$args): void
31+
{
32+
$this->assertFileAsserts($assertType, $file, ...$args);
33+
}
34+
35+
public static function provideFileAssertsCases(): iterable
36+
{
37+
yield from self::gatherAssertTypes(__DIR__ . '/../Fixtures/Type/model-find.php');
38+
}
39+
}

‎tests/extension-test.neon‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
parameters:
2+
featureToggles:
3+
listType: true
24
bootstrapFiles:
35
- %rootDir%/../../../tests/bootstrap.php
46
codeigniter:

0 commit comments

Comments
(0)

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