From 0dea91ba319f23581677df7bfede153f5688575b Mon Sep 17 00:00:00 2001 From: Dave Barnwell Date: Fri, 30 Jan 2026 22:56:34 +0000 Subject: [PATCH] Add camelCase dynamic finders and counters --- README.md | 10 ++++ src/Model/Model.php | 100 ++++++++++++++++++++++++++++++----- test-src/Model/Category.php | 5 ++ tests/Model/CategoryTest.php | 38 +++++++++++++ 4 files changed, 139 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index b5e89f9..0daf7a2 100644 --- a/README.md +++ b/README.md @@ -155,6 +155,12 @@ Return an object for the first matching the name Category::findOne_by_name('changed name'); ``` +CamelCase alternatives are also supported: + +```php +Category::findOneByName('changed name'); +``` + Return an object for the first match from the names ```php @@ -167,6 +173,10 @@ Return an array of objects that match the name Category::find_by_name('changed name'); ``` +```php +Category::findByName('changed name'); +``` + Return an array of objects that match the names ```php diff --git a/src/Model/Model.php b/src/Model/Model.php index 24f9ff7..ed3a8bc 100644 --- a/src/Model/Model.php +++ b/src/Model/Model.php @@ -510,39 +510,111 @@ public static function find($id) public static function __callStatic($name, $arguments) { // Note: value of $name is case sensitive. + $match = $arguments[0] ?? null; if (preg_match('/^find_by_/', $name) == 1) { // it's a find_by_{fieldname} dynamic method $fieldname = substr($name, 8); // remove find by - $match = $arguments[0]; - return static::fetchAllWhereMatchingSingleField($fieldname, $match); + return static::fetchAllWhereMatchingSingleField(static::resolveFieldName($fieldname), $match); } elseif (preg_match('/^findOne_by_/', $name) == 1) { // it's a findOne_by_{fieldname} dynamic method $fieldname = substr($name, 11); // remove findOne_by_ - $match = $arguments[0]; - return static::fetchOneWhereMatchingSingleField($fieldname, $match, 'ASC'); + return static::fetchOneWhereMatchingSingleField(static::resolveFieldName($fieldname), $match, 'ASC'); } elseif (preg_match('/^first_by_/', $name) == 1) { // it's a first_by_{fieldname} dynamic method $fieldname = substr($name, 9); // remove first_by_ - $match = $arguments[0]; - return static::fetchOneWhereMatchingSingleField($fieldname, $match, 'ASC'); + return static::fetchOneWhereMatchingSingleField(static::resolveFieldName($fieldname), $match, 'ASC'); } elseif (preg_match('/^last_by_/', $name) == 1) { // it's a last_by_{fieldname} dynamic method $fieldname = substr($name, 8); // remove last_by_ - $match = $arguments[0]; - return static::fetchOneWhereMatchingSingleField($fieldname, $match, 'DESC'); + return static::fetchOneWhereMatchingSingleField(static::resolveFieldName($fieldname), $match, 'DESC'); } elseif (preg_match('/^count_by_/', $name) == 1) { // it's a count_by_{fieldname} dynamic method $fieldname = substr($name, 9); // remove find by - $match = $arguments[0]; - if (is_array($match)) { - return static::countAllWhere(static::_quote_identifier($fieldname) . ' IN (' . static::createInClausePlaceholders($match) . ')', $match); - } else { - return static::countAllWhere(static::_quote_identifier($fieldname) . ' = ?', array($match)); - } + return static::countByField(static::resolveFieldName($fieldname), $match); + } elseif (preg_match('/^findBy/', $name) == 1) { + // it's a findBy{Fieldname} dynamic method + $fieldname = substr($name, 6); // remove findBy + return static::fetchAllWhereMatchingSingleField(static::resolveFieldName($fieldname), $match); + } elseif (preg_match('/^findOneBy/', $name) == 1) { + // it's a findOneBy{Fieldname} dynamic method + $fieldname = substr($name, 9); // remove findOneBy + return static::fetchOneWhereMatchingSingleField(static::resolveFieldName($fieldname), $match, 'ASC'); + } elseif (preg_match('/^firstBy/', $name) == 1) { + // it's a firstBy{Fieldname} dynamic method + $fieldname = substr($name, 7); // remove firstBy + return static::fetchOneWhereMatchingSingleField(static::resolveFieldName($fieldname), $match, 'ASC'); + } elseif (preg_match('/^lastBy/', $name) == 1) { + // it's a lastBy{Fieldname} dynamic method + $fieldname = substr($name, 6); // remove lastBy + return static::fetchOneWhereMatchingSingleField(static::resolveFieldName($fieldname), $match, 'DESC'); + } elseif (preg_match('/^countBy/', $name) == 1) { + // it's a countBy{Fieldname} dynamic method + $fieldname = substr($name, 7); // remove countBy + return static::countByField(static::resolveFieldName($fieldname), $match); } throw new \Exception(__CLASS__ . ' not such static method[' . $name . ']'); } + /** + * Resolve a dynamic field name from snake_case or CamelCase to an actual column name. + * + * @param string $fieldname + * + * @return string + * @throws \Exception + */ + protected static function resolveFieldName($fieldname) + { + $fieldnames = static::getFieldnames(); + if (in_array($fieldname, $fieldnames, true)) { + return $fieldname; + } + foreach ($fieldnames as $field) { + if (strcasecmp($field, $fieldname) === 0) { + return $field; + } + } + $snake = static::camelToSnake($fieldname); + if (in_array($snake, $fieldnames, true)) { + return $snake; + } + foreach ($fieldnames as $field) { + if (strcasecmp($field, $snake) === 0) { + return $field; + } + } + return $fieldname; + } + + /** + * Convert CamelCase or mixedCase to snake_case. + * + * @param string $fieldname + * + * @return string + */ + protected static function camelToSnake($fieldname) + { + $snake = preg_replace('/(?assertContainsOnlyInstancesOf('App\Model\Category', $categories); $this->assertCount(count($_names), $categories); } + + /** + * @covers ::__callStatic + * @covers ::fetchAllWhereMatchingSingleField + * @covers ::fetchOneWhereMatchingSingleField + * @covers ::countAllWhere + */ + public function testDynamicFindersCamelCase(): void + { + $_names = [ + 'Camel_' . uniqid('a_', true), + 'Camel_' . uniqid('b_', true), + ]; + foreach ($_names as $_name) { + $category = new App\Model\Category(array( + 'name' => $_name + )); + $category->save(); + } + + $categories = App\Model\Category::findByName($_names); + $this->assertCount(count($_names), $categories); + + $one = App\Model\Category::findOneByName($_names[0]); + $this->assertNotNull($one); + $this->assertEquals($_names[0], $one->name); + + $first = App\Model\Category::firstByName($_names); + $this->assertNotNull($first); + $this->assertContains($first->name, $_names); + + $last = App\Model\Category::lastByName($_names); + $this->assertNotNull($last); + $this->assertContains($last->name, $_names); + + $count = App\Model\Category::countByName($_names); + $this->assertEquals(count($_names), $count); + } }