Skip to content

Commit 835984d

Browse files
committed
Fix inference of type of Model::find() to base on framework behavior
1 parent 6d05e39 commit 835984d

File tree

3 files changed

+35
-9
lines changed

3 files changed

+35
-9
lines changed

src/Type/ModelFindReturnTypeExtension.php

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use PHPStan\Reflection\MethodReflection;
2121
use PHPStan\Type\Accessory\AccessoryArrayListType;
2222
use PHPStan\Type\ArrayType;
23+
use PHPStan\Type\Constant\ConstantArrayType;
2324
use PHPStan\Type\DynamicMethodReturnTypeExtension;
2425
use PHPStan\Type\IntegerType;
2526
use PHPStan\Type\IntersectionType;
@@ -49,11 +50,11 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method
4950
$methodName = $methodReflection->getName();
5051

5152
if ($methodName === 'find') {
52-
return $this->getTypeFromFind($methodReflection, $methodCall, $scope);
53+
return $this->getTypeFromFind($methodCall, $scope);
5354
}
5455

5556
if ($methodName === 'findAll') {
56-
return $this->getTypeFromFindAll($methodReflection, $methodCall, $scope);
57+
return $this->getTypeFromFindAll($methodCall, $scope);
5758
}
5859

5960
$classReflection = $this->getClassReflection($methodCall, $scope);
@@ -69,23 +70,23 @@ private function getClassReflection(MethodCall $methodCall, Scope $scope): Class
6970
return current($classTypes);
7071
}
7172

72-
private function getTypeFromFind(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
73+
private function getTypeFromFind(MethodCall $methodCall, Scope $scope): Type
7374
{
7475
$args = $methodCall->getArgs();
7576

7677
if (! isset($args[0])) {
77-
return $this->getTypeFromFindAll($methodReflection, $methodCall, $scope);
78+
return $this->getTypeFromFindAll($methodCall, $scope);
7879
}
7980

8081
return TypeTraverser::map(
8182
$scope->getType($args[0]->value),
82-
function (Type $idType, callable $traverse) use ($methodReflection, $methodCall, $scope): Type {
83+
function (Type $idType, callable $traverse) use ($methodCall, $scope): Type {
8384
if ($idType instanceof UnionType || $idType instanceof IntersectionType) {
8485
return $traverse($idType);
8586
}
8687

87-
if ($idType->isNull()->yes()) {
88-
return $this->getTypeFromFindAll($methodReflection, $methodCall, $scope);
88+
if ($idType->isArray()->yes() && ! $idType->isIterableAtLeastOnce()->yes()) {
89+
return new ConstantArrayType([], []);
8990
}
9091

9192
if ($idType->isInteger()->yes() || $idType->isString()->yes()) {
@@ -94,12 +95,12 @@ function (Type $idType, callable $traverse) use ($methodReflection, $methodCall,
9495
return TypeCombinator::addNull($this->modelFetchedReturnTypeHelper->getFetchedReturnType($classReflection, $methodCall, $scope));
9596
}
9697

97-
return $this->getTypeFromFindAll($methodReflection, $methodCall, $scope);
98+
return $this->getTypeFromFindAll($methodCall, $scope);
9899
}
99100
);
100101
}
101102

102-
private function getTypeFromFindAll(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
103+
private function getTypeFromFindAll(MethodCall $methodCall, Scope $scope): Type
103104
{
104105
$classReflection = $this->getClassReflection($methodCall, $scope);
105106

tests/Type/DynamicMethodReturnTypeExtensionTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,5 +38,7 @@ public function testFileAsserts(string $assertType, string $file, mixed ...$args
3838
public static function provideFileAssertsCases(): iterable
3939
{
4040
yield from self::gatherAssertTypes(__DIR__ . '/data/model-find.php');
41+
42+
yield from self::gatherAssertTypes(__DIR__ . '/data/bug-7.php');
4143
}
4244
}

tests/Type/data/bug-7.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
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 <[email protected]>
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\UserModel;
15+
16+
use function PHPStan\Testing\assertType;
17+
18+
$users = model(UserModel::class);
19+
assertType('array{}', $users->find([]));
20+
assertType('list<CodeIgniter\Shield\Entities\User>', $users->find([1]));
21+
22+
// Model::find() does not fail if not `array|int|string|null` but defaults to get all
23+
assertType('list<CodeIgniter\Shield\Entities\User>', $users->find(new \stdClass()));

0 commit comments

Comments
 (0)