Navigation Menu

Skip to content

Commit

Permalink
Basic implementation of magic finders.
Browse files Browse the repository at this point in the history
* Support generating conditions based on method.
* Allow first and all to be used as find types.
* Allow 'and' and 'or' operators.
  • Loading branch information
markstory committed Dec 1, 2013
1 parent ad11d7f commit 1bc729a
Show file tree
Hide file tree
Showing 2 changed files with 178 additions and 13 deletions.
74 changes: 72 additions & 2 deletions Cake/ORM/Table.php
Expand Up @@ -27,6 +27,7 @@
use Cake\ORM\Association\HasOne;
use Cake\ORM\BehaviorRegistry;
use Cake\ORM\Entity;
use Cake\ORM\Error\MissingEntityException;
use Cake\Validation\Validator;
use Cake\Utility\Inflector;

Expand Down Expand Up @@ -400,7 +401,7 @@ public function entityClass($name = null) {
}

if (!$this->_entityClass) {
throw new Error\MissingEntityException([$name]);
throw new MissingEntityException([$name]);
}

return $this->_entityClass;
Expand Down Expand Up @@ -1225,7 +1226,73 @@ public function callFinder($type, Query $query, $options = []) {
}

/**
* ## Behavior delegation
* Provides the magic findBy and findByAll methods.
*
* @param string $method The method name that was fired.
* @param array $args List of arguments passed to the function.
* @return mixed.
*/
public function _magicFinder($method, $args) {
$method = Inflector::underscore($method);
$findType = 'first';
preg_match('/^find_([\w]+)_by_/', $method, $matches);
if (empty($matches)) {
// find_by_ is 8 characters.
$fields = substr($method, 8);
} else {
$fields = substr($method, strlen($matches[0]));
$findType = Inflector::variable($matches[1]);
}
$limit = null;
$conditions = [];
$hasOr = strpos($fields, '_or_');
$hasAnd = strpos($fields, '_and_');

$makeConditions = function($fields, $args) {
$order = null;
$conditions = [];
if (count($args) < count($fields)) {
throw new \Cake\Error\Exception(__d(
'cake_dev',
'Not enough arguments to magic finder. Got %s required %s',
count($args),
count($fields)
));
}
foreach ($fields as $field) {
$conditions[$field] = array_shift($args);
}
$order = array_shift($args);
return [$conditions, $order];
};

if ($hasOr === false && $hasAnd === false) {
list($conditions, $order) = $makeConditions([$fields], $args);
} elseif ($hasOr !== false) {
$fields = explode('_or_', $fields);
list($conditions, $order) = $makeConditions($fields, $args);
$conditions = [
'OR' => $conditions
];
} elseif ($hasAnd !== false) {
$fields = explode('_and_', $fields);
list($conditions, $order) = $makeConditions($fields, $args);
}

if ($findType === 'first') {
$findType = 'all';
$limit = 1;
}

return $this->find($findType, [
'conditions' => $conditions,
'order' => $order,
'limit' => $limit,
]);
}

/**
* Handles behavior delegation + magic finders.
*
* If your Table uses any behaviors you can call them as if
* they were on the table object.
Expand All @@ -1239,6 +1306,9 @@ public function __call($method, $args) {
if ($this->_behaviors && $this->_behaviors->hasMethod($method)) {
return $this->_behaviors->call($method, $args);
}
if (strpos($method, 'findBy') === 0 || strpos($method, 'findAllBy') === 0) {
return $this->_magicFinder($method, $args);
}

throw new \BadMethodCallException(
__d('cake_dev', 'Unknown method "%s"', $method)
Expand Down
117 changes: 106 additions & 11 deletions Cake/Test/TestCase/ORM/TableTest.php
Expand Up @@ -1951,15 +1951,79 @@ public function testMagicFindFirst() {

$result = $table->findByUsername('garrett');
$this->assertInstanceOf('Cake\ORM\Query', $result);
$this->assertEquals(1, $result->limit());
$this->assertEquals(['username' => 'garrett'], $result->clause('where'));
$this->assertEquals(1, $result->clause('limit'));
$expected = new QueryExpression(['username' => 'garrett'], ['username' => 'string']);
$this->assertEquals($expected, $result->clause('where'));
}

/**
* Test magic findByXX method.
*
* @expectedException Cake\Error\Exception
* @expectedExceptionMessage Not enough arguments to magic finder. Got 0 required 1
* @return void
*/
public function testMagicFindError() {
$table = TableRegistry::get('Users');

$table->findByUsername();
}

/**
* Test magic findByXX method.
*
* @expectedException Cake\Error\Exception
* @expectedExceptionMessage Not enough arguments to magic finder. Got 1 required 2
* @return void
*/
public function testMagicFindErrorMissingField() {
$table = TableRegistry::get('Users');

$table->findByUsernameAndId('garrett');
}

/**
* Test magic findByXX method.
*
* @return void
*/
public function testMagicFindFirstAnd() {
$table = TableRegistry::get('Users');

$result = $table->findByUsernameAndId('garrett', 4);
$this->assertInstanceOf('Cake\ORM\Query', $result);
$this->assertEquals(1, $result->limit());
$this->assertEquals(['username' => 'garrett'], $result->clause('where'));
$this->assertEquals(1, $result->clause('limit'));
$expected = new QueryExpression(
['username' => 'garrett', 'id' => 4],
['username' => 'string', 'id' => 'integer'],
'AND'
);
$this->assertEquals($expected, $result->clause('where'));
}

/**
* Test magic findByXX method.
*
* @return void
*/
public function testMagicFindFirstOr() {
$table = TableRegistry::get('Users');

$result = $table->findByUsernameOrId('garrett', 4);
$this->assertInstanceOf('Cake\ORM\Query', $result);
$this->assertEquals(1, $result->clause('limit'));
$expected = new QueryExpression();
$expected->add([
'OR' => [
'username' => 'garrett',
'id' => 4
]],
['username' => 'string', 'id' => 'integer']
);
$this->assertEquals($expected, $result->clause('where'));
}


/**
* Test magic findAllByXX method.
*
Expand All @@ -1970,16 +2034,47 @@ public function testMagicFindAll() {

$result = $table->findAllByAuthorId(1);
$this->assertInstanceOf('Cake\ORM\Query', $result);
$this->assertEquals(null, $result->limit());
$this->assertEquals(['author_id' => '1'], $result->clause('where'));
$this->assertEquals(null, $result->clause('limit'));
$expected = new QueryExpression(
['author_id' => 1],
['author_id' => 'integer'],
'AND'
);
$this->assertEquals($expected, $result->clause('where'));
}

/**
* Test magic findAllByXX method.
*
* @return void
*/
public function testMagicFindAllAnd() {
$table = TableRegistry::get('Users');

$result = $table->findAllByAuthorIdAndPublished(1, 'Y');
$this->assertInstanceOf('Cake\ORM\Query', $result);
$this->assertEquals(null, $result->clause('limit'));
$expected = new QueryExpression(
['author_id' => 1, 'published' => 'Y']
);
$this->assertEquals($expected, $result->clause('where'));
}

/**
* Test magic findAllByXX method.
*
* @return void
*/
public function testMagicFindAllOr() {
$table = TableRegistry::get('Users');

$result = $table->findAllByAuthorIdOrPublished(1, 'Y');
$this->assertInstanceOf('Cake\ORM\Query', $result);
$this->assertEquals(null, $result->limit());
$expected = [
'author_id' => 1,
'published' => 'Y',
];
$this->assertEquals(null, $result->clause('limit'));
$expected = new QueryExpression();
$expected->add(
['or' => ['author_id' => 1, 'published' => 'Y']]
);
$this->assertEquals($expected, $result->clause('where'));
}

Expand Down

0 comments on commit 1bc729a

Please sign in to comment.