diff --git a/src/ORM/Table.php b/src/ORM/Table.php index ffe06dcdc30..7ada22fee5a 100644 --- a/src/ORM/Table.php +++ b/src/ORM/Table.php @@ -2666,22 +2666,29 @@ public function callFinder(string $type, SelectQuery $query, mixed ...$args): Se */ public function invokeFinder(Closure $callable, SelectQuery $query, array $args): SelectQuery { + // TODO this is where the missing shims need to be. $reflected = new ReflectionFunction($callable); $params = $reflected->getParameters(); $secondParam = $params[1] ?? null; - $secondParamType = null; + + $secondParamType = $secondParam?->getType(); + $secondParamTypeName = $secondParamType instanceof ReflectionNamedType ? $secondParamType->getName() : null; + + $secondParamIsOptions = ( + count($params) === 2 && + $secondParam?->name === 'options' && + !$secondParam->isVariadic() && + ($secondParamType === null || $secondParamTypeName === 'array') + ); if ($args === [] || isset($args[0])) { $secondParamType = $secondParam?->getType(); $secondParamTypeName = $secondParamType instanceof ReflectionNamedType ? $secondParamType->getName() : null; - // Backwards compatibility of 4.x style finders with signature `findFoo(SelectQuery $query, array $options)` + + // Backwards compatibility of 4.x style finders + // with signature `findFoo(SelectQuery $query, array $options)` // called as `find('foo')` or `find('foo', [..])` - if ( - count($params) === 2 && - $secondParam?->name === 'options' && - !$secondParam->isVariadic() && - ($secondParamType === null || $secondParamTypeName === 'array') - ) { + if ($secondParamIsOptions) { if (isset($args[0])) { deprecationWarning( '5.0.0', @@ -2697,8 +2704,9 @@ public function invokeFinder(Closure $callable, SelectQuery $query, array $args) return $callable($query, $query->getOptions()); } - // Backwards compatibility for core finders like `findList()` called in 4.x style - // with an array `find('list', ['valueField' => 'foo'])` instead of `find('list', valueField: 'foo')` + // Backwards compatibility for core finders like `findList()` called in 4.x + // style with an array `find('list', ['valueField' => 'foo'])` instead of + // `find('list', valueField: 'foo')` if (isset($args[0]) && is_array($args[0]) && $secondParamTypeName !== 'array') { deprecationWarning( '5.0.0', @@ -2710,6 +2718,15 @@ public function invokeFinder(Closure $callable, SelectQuery $query, array $args) } } + // Backwards compatibility for 4.x style finders with signatures like + // `findFoo(SelectQuery $query, array $options)` called as + // `find('foo', key: $value)`. + if (!isset($args[0]) && $secondParamIsOptions) { + $query->applyOptions($args); + + return $callable($query, $query->getOptions()); + } + if ($args) { $query->applyOptions($args); // Fetch custom args without the query options. diff --git a/tests/TestCase/ORM/TableTest.php b/tests/TestCase/ORM/TableTest.php index 75bd036887e..d3e577dbf4b 100644 --- a/tests/TestCase/ORM/TableTest.php +++ b/tests/TestCase/ORM/TableTest.php @@ -1286,6 +1286,23 @@ public function testFindTypedParameters(): void $this->assertSame(2, $author->id); } + public function testFindTypedParameterCompatibility(): void + { + $articles = $this->fetchTable('Articles'); + $article = $articles->find('titled')->first(); + $this->assertNotEmpty($article); + + // Options arrays should work + $article = $articles->find('titled', ['title' => 'Second Article'])->first(); + $this->assertNotEmpty($article); + $this->assertEquals('Second Article', $article->title); + + // Named parameters should be compatible with options finders + $article = $articles->find('titled', title: 'Second Article')->first(); + $this->assertNotEmpty($article); + $this->assertEquals('Second Article', $article->title); + } + public function testFindForFinderVariadic(): void { $testTable = $this->fetchTable('Test'); diff --git a/tests/test_app/TestApp/Model/Table/ArticlesTable.php b/tests/test_app/TestApp/Model/Table/ArticlesTable.php index 5d9d045bc2c..542592aa549 100644 --- a/tests/test_app/TestApp/Model/Table/ArticlesTable.php +++ b/tests/test_app/TestApp/Model/Table/ArticlesTable.php @@ -57,6 +57,18 @@ public function findWithAuthors($query, array $options = []): SelectQuery return $query->contain('Authors'); } + /** + * Finder for testing named parameter compatibility + */ + public function findTitled(SelectQuery $query, array $options): SelectQuery + { + if (!empty($options['title'])) { + $query->where(['Articles.title' => $options['title']]); + } + + return $query; + } + /** * Example public method */