Skip to content

Commit 3ea916b

Browse files
authored
Merge pull request #31 from CakeDC/issue/fix-phpstan-has-method-check
Issue/fix phpstan has method check
2 parents 7f44da9 + fa559f5 commit 3ea916b

File tree

5 files changed

+171
-4
lines changed

5 files changed

+171
-4
lines changed

.semver

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
22
:major: 3
33
:minor: 1
4-
:patch: 1
4+
:patch: 2
55
:special: ''

src/Rule/Model/OrmSelectQueryFindMatchOptionsTypesRule.php

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use Cake\ORM\Query\SelectQuery;
1212
use Cake\ORM\Table;
1313
use CakeDC\PHPStan\Rule\Traits\ParseClassNameFromArgTrait;
14+
use InvalidArgumentException;
1415
use PhpParser\Node;
1516
use PhpParser\Node\Expr\Array_;
1617
use PhpParser\Node\Expr\MethodCall;
@@ -22,6 +23,8 @@
2223
use PHPStan\Rules\RuleError;
2324
use PHPStan\Rules\RuleErrorBuilder;
2425
use PHPStan\Rules\RuleLevelHelper;
26+
use PHPStan\Type\ArrayType;
27+
use PHPStan\Type\MixedType;
2528
use PHPStan\Type\ObjectType;
2629
use PHPStan\Type\Type;
2730
use PHPStan\Type\VerbosityLevel;
@@ -102,7 +105,11 @@ public function processNode(Node $node, Scope $scope): array
102105
if (empty($referenceClasses)) {
103106
return [];
104107
}
105-
$details = $this->getDetails($referenceClasses, $node->name->name, $args);
108+
try {
109+
$details = $this->getDetails($referenceClasses, $node->name->name, $args);
110+
} catch (InvalidArgumentException) {
111+
return [];
112+
}
106113
if ($details === null) {
107114
return [];
108115
}
@@ -273,6 +280,8 @@ protected function getOptionsFromArray(Array_ $source, array $options): array
273280
foreach ($source->items as $item) {
274281
if (isset($item->key) && $item->key instanceof String_) {
275282
$options[$item->key->value] = $item->value;
283+
} else {
284+
throw new InvalidArgumentException('Rule is ignored because one option key is not string');
276285
}
277286
}
278287

@@ -337,18 +346,32 @@ protected function getSpecificFinderOptions(array $details, Scope $scope): array
337346
}
338347
$object = new ObjectType($tableClass);
339348
$finderMethod = 'find' . $finder;
340-
if ($object->hasMethod($finderMethod)->no()) {
349+
if (!$object->hasMethod($finderMethod)->yes()) {
341350
return [];
342351
}
343352
$method = $object->getMethod($finderMethod, $scope);
344353
$parameters = $method->getVariants()[0]->getParameters();
354+
345355
if (!isset($parameters[1])) {
346356
return [];
347357
}
358+
if (count($parameters) === 2) {
359+
//Backward compatibility with CakePHP 4 finder structure, findSomething($query, array $options)
360+
$secondParam = $parameters[1];
361+
$paramType = $secondParam->getType();
362+
if (
363+
$secondParam->getName() === 'options'
364+
&& !$secondParam->isVariadic()
365+
&& ($paramType instanceof MixedType || $paramType instanceof ArrayType)
366+
) {
367+
return [];
368+
}
369+
}
348370
foreach ($parameters as $key => $param) {
349-
if ($key === 0) {
371+
if ($key === 0 || $param->isVariadic()) {
350372
continue;
351373
}
374+
352375
$specificFinderOptions[$param->getName()] = $param;
353376
}
354377

tests/TestCase/Rule/Model/Fake/FailingOrmFindRuleItemsLogic.php

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,5 +138,77 @@ public function process()
138138
$Table->find(
139139
'featured', //custom finder is known but required options are missing
140140
);
141+
$Table->find(
142+
'unkonwn',
143+
);
144+
$Table->find('legacy');//Legacy should ignore params check
145+
$Table->find('legacy', [//Legacy should ignore params check
146+
'sort' => ['Notes.note' => 'ASC'],
147+
]);
148+
$Table->find('legacy', [//Legacy should ignore params check
149+
'sort' => ['Notes.note' => 'ASC'],
150+
'type' => 'featured',
151+
'active' => false,
152+
]);
153+
$Table->find('optionsPacked');
154+
$Table->find('optionsPacked', [//Legacy should ignore params check
155+
'sort' => ['Notes.note' => 'ASC'],
156+
]);
157+
$Table->find('optionsPacked', [//Legacy should ignore params check
158+
'sort' => ['Notes.note' => 'ASC'],
159+
'labelField' => 'id',
160+
]);
161+
$Table->find('optionsPacked', [//Legacy should ignore params check
162+
'sort' => ['Notes.note' => 'ASC'],
163+
'labelField' => 'id',
164+
]);
165+
$Table->find(
166+
'optionsPacked',
167+
sort: ['Notes.note' => 'ASC'],
168+
labelField: 'id'
169+
);
170+
$Table->find(
171+
'optionsPacked',
172+
sort: ['Notes.note' => 'ASC'],
173+
labelField: 'id'
174+
);
175+
$Table->find('argsPacked');
176+
$Table->find(
177+
'argsPacked',
178+
sort: ['Notes.note' => 'ASC'],
179+
groupLabel: 'type'
180+
);
181+
$Table->find('argsPacked', [
182+
'sort' => ['Notes.note' => 'ASC'],
183+
'groupLabel' => 'id',
184+
]);
185+
$Table->find('twoArgsButNotLegacy', [
186+
'sort' => ['Notes.note' => 'ASC'],
187+
'myType' => 'featured',
188+
]);
189+
$Table->find('twoArgsButNotLegacy', [
190+
'sort' => ['Notes.note' => 'ASC'],
191+
]);
192+
$Table->find(
193+
'twoArgsButNotLegacy',
194+
sort: ['Notes.note' => 'ASC'],
195+
myType: 'featured'
196+
);
197+
$Table->find('twoArgsButNotLegacy');
198+
$Table->find(
199+
'twoArgsButNotLegacy',
200+
sort: ['Notes.note' => 'ASC'],
201+
myType: 19
202+
);
203+
$field = $Table->getTypeTestTwoArgsButNotLegacy();
204+
$value = 'featured';
205+
$Table->find('twoArgsButNotLegacy', [$field => $value]);
206+
$Table->find('twoArgsButNotLegacy', [$field => 'test']);
207+
$Table->find('twoArgsButNotLegacy', [$Table->getTypeTestTwoArgsButNotLegacy() => $value]);
208+
$Table->find('twoArgsButNotLegacy', [$Table->getTypeTestTwoArgsButNotLegacy() => 'sample']);
209+
$Table->find('twoArgsButNotLegacy', ...[$field => $value]);
210+
$Table->find('twoArgsButNotLegacy', ...[$field => 'test']);
211+
$Table->find('twoArgsButNotLegacy', ...[$Table->getTypeTestTwoArgsButNotLegacy() => $value]);
212+
$Table->find('twoArgsButNotLegacy', ...[$Table->getTypeTestTwoArgsButNotLegacy() => 'sample']);
141213
}
142214
}

tests/TestCase/Rule/Model/OrmSelectQueryFindMatchOptionsTypesRuleTest.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ public function testRule(): void
7878
['Call to App\Model\Table\NotesTable::find is missing required finder option "fun".', 134],
7979
['Call to App\Model\Table\NotesTable::find is missing required finder option "year".', 138],
8080
['Call to App\Model\Table\NotesTable::find is missing required finder option "fun".', 138],
81+
['Call to App\Model\Table\NotesTable::find is missing required finder option "myType".', 189],
82+
['Call to App\Model\Table\NotesTable::find is missing required finder option "myType".', 197],
83+
['Call to App\Model\Table\NotesTable::find with option "myType" (string) does not accept int.', 198],
8184
]);
8285
}
8386

tests/test_app/Model/Table/NotesTable.php

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,13 @@ public function warning(): array
6060
$this->Users->find('all', order: ['Users.id' => 'DESC'], limit: 12);
6161
$entity = $this->saveOrFail($entity);
6262
}
63+
$this->find('optionsPacked');
64+
$this->find(
65+
'twoArgsButNotLegacy',
66+
sort: ['Notes.note' => 'ASC'],
67+
myType: 'featured'
68+
);
69+
$this->find('argsPacked');
6370

6471
return [
6572
'type' => 'warning',
@@ -88,4 +95,66 @@ public function findFeatured(SelectQuery $query, string|int $year, bool $fun): S
8895
return $query->where($where)
8996
->orderBy(['Notes.created' => 'DESC']);
9097
}
98+
99+
/**
100+
* @param \Cake\ORM\Query\SelectQuery $query
101+
* @param string $myType
102+
* @return \Cake\ORM\Query\SelectQuery
103+
*/
104+
public function findTwoArgsButNotLegacy(SelectQuery $query, string $myType): SelectQuery
105+
{
106+
$where = [
107+
'year <=' => 2010,
108+
'type' => $myType,
109+
];
110+
111+
return $query->where($where)
112+
->orderBy(['Notes.created' => 'DESC']);
113+
}
114+
115+
/**
116+
* @param \Cake\ORM\Query\SelectQuery $query
117+
* @param array<string, mixed> $options
118+
* @return \Cake\ORM\Query\SelectQuery
119+
*/
120+
public function findLegacy(SelectQuery $query, array $options): SelectQuery
121+
{
122+
return $query
123+
->where([
124+
'type' => $options['type'] ?? 'normal',
125+
'active' => $options['active'] ?? false,
126+
]);
127+
}
128+
129+
/**
130+
* @param \Cake\ORM\Query\SelectQuery $query
131+
* @param mixed ...$options
132+
* @return \Cake\ORM\Query\SelectQuery
133+
*/
134+
public function findOptionsPacked(SelectQuery $query, mixed ...$options): SelectQuery
135+
{
136+
return $query->select([
137+
$options['labelField'] ?? 'note',
138+
]);
139+
}
140+
141+
/**
142+
* @param \Cake\ORM\Query\SelectQuery $query
143+
* @param mixed ...$args
144+
* @return \Cake\ORM\Query\SelectQuery
145+
*/
146+
public function findArgsPacked(SelectQuery $query, mixed ...$args): SelectQuery
147+
{
148+
return $query->select([
149+
$args['groupLabel'] ?? 'note',
150+
]);
151+
}
152+
153+
/**
154+
* @return string
155+
*/
156+
public function getTypeTestTwoArgsButNotLegacy(): string
157+
{
158+
return 'myType';
159+
}
91160
}

0 commit comments

Comments
 (0)