Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 22 additions & 14 deletions src/Method/AssociationTableMixinClassReflectionExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -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());
}

Expand Down
10 changes: 9 additions & 1 deletion src/Method/TableFindByPropertyMethodReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
24 changes: 24 additions & 0 deletions tests/test_app/Model/Table/VeryCustomize00009ArticlesTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -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;
}
}