From 8d8463f0899b3087320e4fde927b4205150aebc0 Mon Sep 17 00:00:00 2001 From: mscherer Date: Mon, 13 Oct 2025 12:56:55 +0200 Subject: [PATCH] Fix up Reflection. --- ...tionTableMixinClassReflectionExtension.php | 36 +++++++++++-------- .../TableFindByPropertyMethodReflection.php | 10 +++++- .../Table/VeryCustomize00009ArticlesTable.php | 24 +++++++++++++ 3 files changed, 55 insertions(+), 15 deletions(-) diff --git a/src/Method/AssociationTableMixinClassReflectionExtension.php b/src/Method/AssociationTableMixinClassReflectionExtension.php index 31c0589..adf4de3 100644 --- a/src/Method/AssociationTableMixinClassReflectionExtension.php +++ b/src/Method/AssociationTableMixinClassReflectionExtension.php @@ -48,17 +48,23 @@ protected function getTableReflection(): ClassReflection */ public function hasMethod(ClassReflection $classReflection, string $methodName): bool { - // magic findBy* method - if ($classReflection->is(Table::class) && preg_match('/^find(?:\w+)?By/', $methodName) > 0) { - return true; + // Handle Table classes + if ($classReflection->is(Table::class)) { + if ($classReflection->hasNativeMethod($methodName)) { + return false; // Let the native method be used + } + // magic findBy* and findAllBy* methods - available on ALL table classes + if (preg_match('/^find(All)?By/', $methodName) === 1) { + return true; + } } if (!$classReflection->is(Association::class)) { return false; } - // magic findBy* method on Association - if (preg_match('/^find(?:\w+)?By/', $methodName) > 0) { + // For associations, provide magic find(All)?By methods + if (preg_match('/^find(All)?By/', $methodName) === 1) { return true; } @@ -72,17 +78,19 @@ public function hasMethod(ClassReflection $classReflection, string $methodName): */ public function getMethod(ClassReflection $classReflection, string $methodName): MethodReflection { - // magic findBy* method - if ($classReflection->is(Table::class) && preg_match('/^find(?:\w+)?By/', $methodName) > 0) { - return new TableFindByPropertyMethodReflection($methodName, $classReflection); + // Handle Table classes + if ($classReflection->is(Table::class)) { + if ($classReflection->hasNativeMethod($methodName)) { + return $classReflection->getNativeMethod($methodName); + } + // magic findBy* and findAllBy* methods + if (preg_match('/^find(All)?By/', $methodName) === 1) { + return new TableFindByPropertyMethodReflection($methodName, $classReflection); + } } - // magic findBy* method on Association - $associationReflection = $this->reflectionProvider->getClass(Association::class); - if ( - $classReflection->isSubclassOfClass($associationReflection) - && preg_match('/^find(?:\w+)?By/', $methodName) > 0 - ) { + // For associations, handle magic find(All)?By methods + if (preg_match('/^find(All)?By/', $methodName) === 1) { return new TableFindByPropertyMethodReflection($methodName, $this->getTableReflection()); } diff --git a/src/Method/TableFindByPropertyMethodReflection.php b/src/Method/TableFindByPropertyMethodReflection.php index a9b8b6b..02467d7 100644 --- a/src/Method/TableFindByPropertyMethodReflection.php +++ b/src/Method/TableFindByPropertyMethodReflection.php @@ -210,7 +210,15 @@ public function hasSideEffects(): TrinaryLogic protected function getParams(string $method): array { $method = Inflector::underscore($method); - $fields = substr($method, 8); + // Extract the part after "find" and before "_by" + // Handles: findBy, findAllBy, findOrCreateBy, etc. + if (preg_match('/^find(?:_\w+)?_by_(.+)$/', $method, $matches) === 1) { + $fields = $matches[1]; + } else { + // Fallback for simple cases + $fields = substr($method, 8); + } + if (str_contains($fields, '_and_')) { return explode('_and_', $fields); } diff --git a/tests/test_app/Model/Table/VeryCustomize00009ArticlesTable.php b/tests/test_app/Model/Table/VeryCustomize00009ArticlesTable.php index eb6c90f..0f68433 100644 --- a/tests/test_app/Model/Table/VeryCustomize00009ArticlesTable.php +++ b/tests/test_app/Model/Table/VeryCustomize00009ArticlesTable.php @@ -52,6 +52,11 @@ public function fixArticle($article) $article = $this->findByTitleAndActive('sample', true)->first(); $article->set('title', 'sample two'); + // Test actual (non-magic) findOrCreateBySku method with specific signature (issue #55) + // When called directly on the table (not through association), PHPStan should use the native method + $entityOrCreate = $this->findOrCreateBySku('TEST-SKU', 'Test Value'); + $entityOrCreate->set('title', 'Updated Title'); + return true; } @@ -69,4 +74,23 @@ public function newSample() ], ); } + + /** + * Custom non-magic findOrCreateBySku method with specific signature + * + * @param string $sku + * @param string $foo + * @return \Cake\Datasource\EntityInterface + */ + public function findOrCreateBySku(string $sku, string $foo) + { + /** @var \Cake\ORM\Entity|null $entity */ + $entity = $this->findBySku($sku)->first(); + if ($entity === null) { + $entity = $this->newEntity(['sku' => $sku, 'foo' => $foo]); + $entity = $this->saveOrFail($entity); + } + + return $entity; + } }