Skip to content

Commit

Permalink
Separate storage of finder and non finder methods.
Browse files Browse the repository at this point in the history
This makes checking for method presence later easier and allows finder
methods to be not be confused with normal mixin methods. Use the 'find'
prefix as a way to identify finder methods.
  • Loading branch information
markstory committed Oct 25, 2013
1 parent 6fa2d9d commit 02ebe1e
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 14 deletions.
50 changes: 40 additions & 10 deletions Cake/ORM/BehaviorRegistry.php
Expand Up @@ -51,6 +51,13 @@ class BehaviorRegistry extends ObjectRegistry {
*/
protected $_methodMap = [];

/**
* Finder method mappings.
*
* @var array
*/
protected $_finderMap = [];

/**
* Constructor
*
Expand Down Expand Up @@ -130,21 +137,26 @@ protected function _mapMethods(Behavior $instance, $class, $alias) {
}

foreach ($methods as $method) {
if (isset($this->_methodMap[$method])) {
$isFinder = substr($method, 0, 4) === 'find';
if (($isFinder && isset($this->_finderMap[$method])) || isset($this->_methodMap[$method])) {
$message = '%s contains duplicate method "%s" which is already provided by %s';
$error = __d('cake_dev', $message, $class, $method, $this->_methodMap[$method]);
throw new Error\Exception($error);
}
$this->_methodMap[$method] = $alias;
if ($isFinder) {
$this->_finderMap[$method] = $alias;
} else {
$this->_methodMap[$method] = $alias;
}
}
return $instance;
}

/**
* Check if any of the loaded behaviors implement a method.
* Check if any loaded behavior implements a method.
*
* Will return true if any behavior provides a public method with
* the chosen name.
* Will return true if any behavior provides a public non-finder method
* with the chosen name.
*
* @param string $method The method to check for.
* @return boolean
Expand All @@ -154,18 +166,36 @@ public function hasMethod($method) {
}

/**
* Invoke a method on a behavior.
* Check if any loaded behavior implements the named finder.
*
* Will return true if any behavior provides a public method with
* the chosen name.
*
* @param string $method The method to check for.
* @return boolean
*/
public function hasFinder($method) {
return isset($this->_finderMap[$method]);
}

/**
* Invoke a method or finder on a behavior.
*
* @param string $method The method to invoke.
* @param array $args The arguments you want to invoke the method with.
* @return mixed The return value depends on the underlying behavior method.
* @throw Cake\Error\Exception When the method is unknown.
*/
public function call($method, array $args = []) {
if (!$this->hasMethod($method)) {
return false;
if ($this->hasMethod($method)) {
$alias = $this->_methodMap[$method];
return call_user_func_array([$this->_loaded[$alias], $method], $args);
}
if ($this->hasFinder($method)) {
$alias = $this->_finderMap[$method];
return call_user_func_array([$this->_loaded[$alias], $method], $args);
}
$alias = $this->_methodMap[$method];
return call_user_func_array([$this->_loaded[$alias], $method], $args);
throw new Error\Exception(__d('cake_dev', 'Cannot call "%s" it does not belong to any attached behaviors.', $method));
}

}
3 changes: 2 additions & 1 deletion Cake/Test/TestApp/Model/Behavior/SluggableBehavior.php
Expand Up @@ -14,6 +14,7 @@
*/
namespace TestApp\Model\Behavior;

use Cake\Event\Event;
use Cake\ORM\Behavior;
use Cake\ORM\Query;
use Cake\ORM\Table;
Expand All @@ -25,7 +26,7 @@ public function __construct(Table $table, $options = []) {
$this->settings = $options;
}

public function beforeFind(Query $query, $options = []) {
public function beforeFind(Event $event, Query $query, $options = []) {
$query->where(['slug' => 'test']);
return $query;
}
Expand Down
35 changes: 32 additions & 3 deletions Cake/Test/TestCase/ORM/BehaviorRegistryTest.php
Expand Up @@ -131,6 +131,23 @@ public function testHasMethod() {
$this->Behaviors->load('Sluggable');
$this->assertTrue($this->Behaviors->hasMethod('slugify'));
$this->assertFalse($this->Behaviors->hasMethod('nope'));
$this->assertFalse($this->Behaviors->hasMethod('beforeFind'));
$this->assertFalse($this->Behaviors->hasMethod('findNoSlug'));
}

/**
* Test hasFinder() method.
*
* @return void
*/
public function testHasFinder() {
$this->Behaviors->load('Sluggable');

$this->assertTrue($this->Behaviors->hasFinder('findNoSlug'));
$this->assertFalse($this->Behaviors->hasFinder('findnoslug'));
$this->assertFalse($this->Behaviors->hasFinder('findnoslug'));
$this->assertFalse($this->Behaviors->hasFinder('slugify'));
$this->assertFalse($this->Behaviors->hasFinder('nope'));
}

/**
Expand All @@ -140,11 +157,23 @@ public function testHasMethod() {
*/
public function testCall() {
$this->Behaviors->load('Sluggable');
$result = $this->Behaviors->call('nope');
$this->assertFalse($result);

$result = $this->Behaviors->call('slugify', ['some value']);
$this->assertEquals('some_value', $result);

$query = $this->getMock('Cake\ORM\Query', [], [null, null]);
$result = $this->Behaviors->call('findNoSlug', [$query]);
$this->assertEquals($query, $result);
}

/**
* Test errors on unknown methods.
*
* @expectedException Cake\Error\Exception
* @expectedExceptionMessage Cannot call "nope"
*/
public function testCallError() {
$this->Behaviors->load('Sluggable');
$this->Behaviors->call('nope');
}

}

0 comments on commit 02ebe1e

Please sign in to comment.