Browse Source

Merge pull request #6244 from robertpustulka/3.1-static

Sample facade/singleton solution for "static classes" based on TableRegistry
José Lorenzo Rodríguez 11 years ago
parent
commit
639e2b712b

+ 75 - 0
src/ORM/Locator/LocatorInterface.php

@@ -0,0 +1,75 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link          http://cakephp.org CakePHP(tm) Project
+ * @since         3.1.0
+ * @license       http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+namespace Cake\ORM\Locator;
+
+use Cake\ORM\Table;
+
+/**
+ * Registries for Table objects should implement this interface.
+ */
+interface LocatorInterface
+{
+
+    /**
+     * Stores a list of options to be used when instantiating an object
+     * with a matching alias.
+     *
+     * @param string|null $alias Name of the alias
+     * @param array|null $options list of options for the alias
+     * @return array The config data.
+     */
+    public function config($alias = null, $options = null);
+
+    /**
+     * Get a table instance from the registry.
+     *
+     * @param string $alias The alias name you want to get.
+     * @param array $options The options you want to build the table with.
+     * @return \Cake\ORM\Table
+     */
+    public function get($alias, array $options = []);
+
+    /**
+     * Check to see if an instance exists in the registry.
+     *
+     * @param string $alias The alias to check for.
+     * @return bool
+     */
+    public function exists($alias);
+
+    /**
+     * Set an instance.
+     *
+     * @param string $alias The alias to set.
+     * @param \Cake\ORM\Table $object The table to set.
+     * @return \Cake\ORM\Table
+     */
+    public function set($alias, Table $object);
+
+    /**
+     * Clears the registry of configuration and instances.
+     *
+     * @return void
+     */
+    public function clear();
+
+    /**
+     * Removes an instance from the registry.
+     *
+     * @param string $alias The alias to remove.
+     * @return void
+     */
+    public function remove($alias);
+}

+ 238 - 0
src/ORM/Locator/TableLocator.php

@@ -0,0 +1,238 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link          http://cakephp.org CakePHP(tm) Project
+ * @since         3.1.0
+ * @license       http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+namespace Cake\ORM\Locator;
+
+use Cake\Core\App;
+use Cake\Datasource\ConnectionManager;
+use Cake\ORM\Locator\LocatorInterface;
+use Cake\ORM\Table;
+use Cake\Utility\Inflector;
+use RuntimeException;
+
+/**
+ * Provides a default registry/factory for Table objects.
+ */
+class TableLocator implements LocatorInterface
+{
+
+    /**
+     * Configuration for aliases.
+     *
+     * @var array
+     */
+    protected $_config = [];
+
+    /**
+     * Instances that belong to the registry.
+     *
+     * @var array
+     */
+    protected $_instances = [];
+
+    /**
+     * Contains a list of Table objects that were created out of the
+     * built-in Table class. The list is indexed by table alias
+     *
+     * @var array
+     */
+    protected $_fallbacked = [];
+
+    /**
+     * Contains a list of options that were passed to get() method.
+     *
+     * @var array
+     */
+    protected $_options = [];
+
+    /**
+     * Stores a list of options to be used when instantiating an object
+     * with a matching alias.
+     *
+     * The options that can be stored are those that are recognized by `get()`
+     * If second argument is omitted, it will return the current settings
+     * for $alias.
+     *
+     * If no arguments are passed it will return the full configuration array for
+     * all aliases
+     *
+     * @param string|null $alias Name of the alias
+     * @param array|null $options list of options for the alias
+     * @return array The config data.
+     * @throws RuntimeException When you attempt to configure an existing table instance.
+     */
+    public function config($alias = null, $options = null)
+    {
+        if ($alias === null) {
+            return $this->_config;
+        }
+        if (!is_string($alias)) {
+            return $this->_config = $alias;
+        }
+        if ($options === null) {
+            return isset($this->_config[$alias]) ? $this->_config[$alias] : [];
+        }
+        if (isset($this->_instances[$alias])) {
+            throw new RuntimeException(sprintf(
+                'You cannot configure "%s", it has already been constructed.',
+                $alias
+            ));
+        }
+        return $this->_config[$alias] = $options;
+    }
+
+    /**
+     * Get a table instance from the registry.
+     *
+     * Tables are only created once until the registry is flushed.
+     * This means that aliases must be unique across your application.
+     * This is important because table associations are resolved at runtime
+     * and cyclic references need to be handled correctly.
+     *
+     * The options that can be passed are the same as in `Table::__construct()`, but the
+     * key `className` is also recognized.
+     *
+     * If $options does not contain `className` CakePHP will attempt to construct the
+     * class name based on the alias. For example 'Users' would result in
+     * `App\Model\Table\UsersTable` being attempted. If this class does not exist,
+     * then the default `Cake\ORM\Table` class will be used. By setting the `className`
+     * option you can define the specific class to use. This className can
+     * use a plugin short class reference.
+     *
+     * If you use a `$name` that uses plugin syntax only the name part will be used as
+     * key in the registry. This means that if two plugins, or a plugin and app provide
+     * the same alias, the registry will only store the first instance.
+     *
+     * If no `table` option is passed, the table name will be the underscored version
+     * of the provided $alias.
+     *
+     * If no `connection` option is passed the table's defaultConnectionName() method
+     * will be called to get the default connection name to use.
+     *
+     * @param string $alias The alias name you want to get.
+     * @param array $options The options you want to build the table with.
+     *   If a table has already been loaded the options will be ignored.
+     * @return \Cake\ORM\Table
+     * @throws \RuntimeException When you try to configure an alias that already exists.
+     */
+    public function get($alias, array $options = [])
+    {
+        if (isset($this->_instances[$alias])) {
+            if (!empty($options) && $this->_options[$alias] !== $options) {
+                throw new RuntimeException(sprintf(
+                    'You cannot configure "%s", it already exists in the registry.',
+                    $alias
+                ));
+            }
+            return $this->_instances[$alias];
+        }
+
+        $this->_options[$alias] = $options;
+        list(, $classAlias) = pluginSplit($alias);
+        $options = ['alias' => $classAlias] + $options;
+
+        if (empty($options['className'])) {
+            $options['className'] = Inflector::camelize($alias);
+        }
+        $className = App::className($options['className'], 'Model/Table', 'Table');
+        if ($className) {
+            $options['className'] = $className;
+        } else {
+            if (!isset($options['table']) && strpos($options['className'], '\\') === false) {
+                list(, $table) = pluginSplit($options['className']);
+                $options['table'] = Inflector::underscore($table);
+            }
+            $options['className'] = 'Cake\ORM\Table';
+        }
+
+        if (isset($this->_config[$alias])) {
+            $options += $this->_config[$alias];
+        }
+        if (empty($options['connection'])) {
+            $connectionName = $options['className']::defaultConnectionName();
+            $options['connection'] = ConnectionManager::get($connectionName);
+        }
+
+        $options['registryAlias'] = $alias;
+        $this->_instances[$alias] = $this->_create($options);
+
+        if ($options['className'] === 'Cake\ORM\Table') {
+            $this->_fallbacked[$alias] = $this->_instances[$alias];
+        }
+
+        return $this->_instances[$alias];
+    }
+
+    /**
+     * Wrapper for creating table instances
+     *
+     * @param array $options The alias to check for.
+     * @return \Cake\ORM\Table
+     */
+    protected function _create(array $options)
+    {
+        return new $options['className']($options);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function exists($alias)
+    {
+        return isset($this->_instances[$alias]);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function set($alias, Table $object)
+    {
+        return $this->_instances[$alias] = $object;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function clear()
+    {
+        $this->_instances = [];
+        $this->_config = [];
+        $this->_fallbacked = [];
+    }
+
+    /**
+     * Returns the list of tables that were created by this registry that could
+     * not be instantiated from a specific subclass. This method is useful for
+     * debugging common mistakes when setting up associations or created new table
+     * classes.
+     *
+     * @return array
+     */
+    public function genericInstances()
+    {
+        return $this->_fallbacked;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function remove($alias)
+    {
+        unset(
+            $this->_instances[$alias],
+            $this->_config[$alias],
+            $this->_fallbacked[$alias]
+        );
+    }
+}

+ 39 - 138
src/ORM/TableRegistry.php

@@ -14,10 +14,7 @@
  */
 namespace Cake\ORM;
 
-use Cake\Core\App;
-use Cake\Datasource\ConnectionManager;
-use Cake\Utility\Inflector;
-use RuntimeException;
+use Cake\ORM\Locator\LocatorInterface;
 
 /**
  * Provides a registry/factory for Table objects.
@@ -55,150 +52,61 @@ class TableRegistry
 {
 
     /**
-     * Configuration for aliases.
+     * LocatorInterface implementation instance.
      *
-     * @var array
+     * @var \Cake\ORM\Locator\LocatorInterface
      */
-    protected static $_config = [];
+    protected static $_locator;
 
     /**
-     * Instances that belong to the registry.
+     * Default LocatorInterface implementation class.
      *
-     * @var array
+     * @var string
      */
-    protected static $_instances = [];
+    protected static $_defaultLocatorClass = 'Cake\ORM\Locator\TableLocator';
 
     /**
-     * Contains a list of Table objects that were created out of the
-     * built-in Table class. The list is indexed by table alias
+     * Sets and returns a singleton instance of LocatorInterface implementation.
      *
-     * @var array
+     * @param \Cake\ORM\Locator\LocatorInterface $locator Instance of a locator to use.
+     * @return \Cake\ORM\Locator\LocatorInterface
      */
-    protected static $_fallbacked = [];
+    public static function locator(LocatorInterface $locator = null)
+    {
+        if ($locator) {
+            static::$_locator = $locator;
+        }
 
-    /**
-     * Contains a list of options that were passed to get() method.
-     *
-     * @var array
-     */
-    protected static $_options = [];
+        if (!static::$_locator) {
+            static::$_locator = new static::$_defaultLocatorClass;
+        }
+
+        return static::$_locator;
+    }
 
     /**
      * Stores a list of options to be used when instantiating an object
      * with a matching alias.
      *
-     * The options that can be stored are those that are recognized by `get()`
-     * If second argument is omitted, it will return the current settings
-     * for $alias.
-     *
-     * If no arguments are passed it will return the full configuration array for
-     * all aliases
-     *
      * @param string|null $alias Name of the alias
      * @param array|null $options list of options for the alias
      * @return array The config data.
-     * @throws \RuntimeException When you attempt to configure an existing table instance.
      */
     public static function config($alias = null, $options = null)
     {
-        if ($alias === null) {
-            return static::$_config;
-        }
-        if (!is_string($alias)) {
-            return static::$_config = $alias;
-        }
-        if ($options === null) {
-            return isset(static::$_config[$alias]) ? static::$_config[$alias] : [];
-        }
-        if (isset(static::$_instances[$alias])) {
-            throw new RuntimeException(sprintf(
-                'You cannot configure "%s", it has already been constructed.',
-                $alias
-            ));
-        }
-        return static::$_config[$alias] = $options;
+        return static::locator()->config($alias, $options);
     }
 
     /**
      * Get a table instance from the registry.
      *
-     * Tables are only created once until the registry is flushed.
-     * This means that aliases must be unique across your application.
-     * This is important because table associations are resolved at runtime
-     * and cyclic references need to be handled correctly.
-     *
-     * The options that can be passed are the same as in `Table::__construct()`, but the
-     * key `className` is also recognized.
-     *
-     * If $options does not contain `className` CakePHP will attempt to construct the
-     * class name based on the alias. For example 'Users' would result in
-     * `App\Model\Table\UsersTable` being attempted. If this class does not exist,
-     * then the default `Cake\ORM\Table` class will be used. By setting the `className`
-     * option you can define the specific class to use. This className can
-     * use a plugin short class reference.
-     *
-     * If you use a `$name` that uses plugin syntax only the name part will be used as
-     * key in the registry. This means that if two plugins, or a plugin and app provide
-     * the same alias, the registry will only store the first instance.
-     *
-     * If no `table` option is passed, the table name will be the underscored version
-     * of the provided $alias.
-     *
-     * If no `connection` option is passed the table's defaultConnectionName() method
-     * will be called to get the default connection name to use.
-     *
      * @param string $alias The alias name you want to get.
      * @param array $options The options you want to build the table with.
-     *   If a table has already been loaded the options will be ignored.
      * @return \Cake\ORM\Table
-     * @throws \RuntimeException When you try to configure an alias that already exists.
      */
     public static function get($alias, array $options = [])
     {
-        if (isset(static::$_instances[$alias])) {
-            if (!empty($options) && static::$_options[$alias] !== $options) {
-                throw new RuntimeException(sprintf(
-                    'You cannot configure "%s", it already exists in the registry.',
-                    $alias
-                ));
-            }
-            return static::$_instances[$alias];
-        }
-
-        static::$_options[$alias] = $options;
-        list(, $classAlias) = pluginSplit($alias);
-        $options = ['alias' => $classAlias] + $options;
-
-        if (empty($options['className'])) {
-            $options['className'] = Inflector::camelize($alias);
-        }
-        $className = App::className($options['className'], 'Model/Table', 'Table');
-        if ($className) {
-            $options['className'] = $className;
-        } else {
-            if (!isset($options['table']) && strpos($options['className'], '\\') === false) {
-                list(, $table) = pluginSplit($options['className']);
-                $options['table'] = Inflector::underscore($table);
-            }
-            $options['className'] = 'Cake\ORM\Table';
-        }
-
-        if (isset(static::$_config[$alias])) {
-            $options += static::$_config[$alias];
-        }
-        if (empty($options['connection'])) {
-            $connectionName = $options['className']::defaultConnectionName();
-            $options['connection'] = ConnectionManager::get($connectionName);
-        }
-
-        $options['registryAlias'] = $alias;
-        static::$_instances[$alias] = new $options['className']($options);
-
-        if ($options['className'] === 'Cake\ORM\Table') {
-            static::$_fallbacked[$alias] = static::$_instances[$alias];
-        }
-
-        return static::$_instances[$alias];
+        return static::locator()->get($alias, $options);
     }
 
     /**
@@ -209,7 +117,7 @@ class TableRegistry
      */
     public static function exists($alias)
     {
-        return isset(static::$_instances[$alias]);
+        return static::locator()->exists($alias);
     }
 
     /**
@@ -221,46 +129,39 @@ class TableRegistry
      */
     public static function set($alias, Table $object)
     {
-        return static::$_instances[$alias] = $object;
+        return static::locator()->set($alias, $object);
     }
 
     /**
-     * Clears the registry of configuration and instances.
+     * Removes an instance from the registry.
      *
+     * @param string $alias The alias to remove.
      * @return void
      */
-    public static function clear()
+    public static function remove($alias)
     {
-        static::$_instances = [];
-        static::$_config = [];
-        static::$_fallbacked = [];
+        static::locator()->remove($alias);
     }
 
     /**
-     * Returns the list of tables that were created by this registry that could
-     * not be instantiated from a specific subclass. This method is useful for
-     * debugging common mistakes when setting up associations or created new table
-     * classes.
+     * Clears the registry of configuration and instances.
      *
-     * @return array
+     * @return void
      */
-    public static function genericInstances()
+    public static function clear()
     {
-        return static::$_fallbacked;
+        static::locator()->clear();
     }
 
     /**
-     * Removes an instance from the registry.
+     * Proxy for static calls on a locator.
      *
-     * @param string $alias The alias to remove.
-     * @return void
+     * @param string $name Method name.
+     * @param array $arguments Method arguments.
+     * @return mixed
      */
-    public static function remove($alias)
+    public static function __callStatic($name, $arguments)
     {
-        unset(
-            static::$_instances[$alias],
-            static::$_config[$alias],
-            static::$_fallbacked[$alias]
-        );
+        return call_user_func_array([static::locator(), $name], $arguments);
     }
 }

+ 550 - 0
tests/TestCase/ORM/Locator/TableLocatorTest.php

@@ -0,0 +1,550 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link          http://cakephp.org CakePHP(tm) Project
+ * @since         3.1.0
+ * @license       http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+namespace Cake\Test\TestCase\ORM\Locator;
+
+use Cake\Core\Configure;
+use Cake\Core\Plugin;
+use Cake\Datasource\ConnectionManager;
+use Cake\ORM\Locator\TableLocator;
+use Cake\ORM\Table;
+use Cake\TestSuite\TestCase;
+use Cake\Validation\Validator;
+
+/**
+ * Used to test correct class is instantiated when using $this->_locator->get();
+ */
+class MyUsersTable extends Table
+{
+
+    /**
+     * Overrides default table name
+     *
+     * @var string
+     */
+    protected $_table = 'users';
+}
+
+
+/**
+ * Test case for TableLocator
+ */
+class TableLocatorTest extends TestCase
+{
+
+    /**
+     * TableLocator instance.
+     *
+     * @var \Cake\ORM\Locator\TableLocator
+     */
+    protected $_locator;
+
+
+    /**
+     * setup
+     *
+     * @return void
+     */
+    public function setUp()
+    {
+        parent::setUp();
+        Configure::write('App.namespace', 'TestApp');
+        
+        $this->_locator = new TableLocator;
+    }
+
+    /**
+     * Test config() method.
+     *
+     * @return void
+     */
+    public function testConfig()
+    {
+        $this->assertEquals([], $this->_locator->config('Tests'));
+
+        $data = [
+            'connection' => 'testing',
+            'entityClass' => 'TestApp\Model\Entity\Article',
+        ];
+        $result = $this->_locator->config('Tests', $data);
+        $this->assertEquals($data, $result, 'Returns config data.');
+
+        $result = $this->_locator->config();
+        $expected = ['Tests' => $data];
+        $this->assertEquals($expected, $result);
+    }
+
+    /**
+     * Test config() method with plugin syntax aliases
+     *
+     * @return void
+     */
+    public function testConfigPlugin()
+    {
+        Plugin::load('TestPlugin');
+
+        $data = [
+            'connection' => 'testing',
+            'entityClass' => 'TestPlugin\Model\Entity\Comment',
+        ];
+
+        $result = $this->_locator->config('TestPlugin.TestPluginComments', $data);
+        $this->assertEquals($data, $result, 'Returns config data.');
+    }
+
+    /**
+     * Test calling config() on existing instances throws an error.
+     *
+     * @expectedException \RuntimeException
+     * @expectedExceptionMessage You cannot configure "Users", it has already been constructed.
+     * @return void
+     */
+    public function testConfigOnDefinedInstance()
+    {
+        $users = $this->_locator->get('Users');
+        $this->_locator->config('Users', ['table' => 'my_users']);
+    }
+
+    /**
+     * Test the exists() method.
+     *
+     * @return void
+     */
+    public function testExists()
+    {
+        $this->assertFalse($this->_locator->exists('Articles'));
+
+        $this->_locator->config('Articles', ['table' => 'articles']);
+        $this->assertFalse($this->_locator->exists('Articles'));
+
+        $this->_locator->get('Articles', ['table' => 'articles']);
+        $this->assertTrue($this->_locator->exists('Articles'));
+    }
+
+    /**
+     * Test the exists() method with plugin-prefixed models.
+     *
+     * @return void
+     */
+    public function testExistsPlugin()
+    {
+        $this->assertFalse($this->_locator->exists('Comments'));
+        $this->assertFalse($this->_locator->exists('TestPlugin.Comments'));
+
+        $this->_locator->config('TestPlugin.Comments', ['table' => 'comments']);
+        $this->assertFalse($this->_locator->exists('Comments'), 'The Comments key should not be populated');
+        $this->assertFalse($this->_locator->exists('TestPlugin.Comments'), 'The plugin.alias key should not be populated');
+
+        $this->_locator->get('TestPlugin.Comments', ['table' => 'comments']);
+        $this->assertFalse($this->_locator->exists('Comments'), 'The Comments key should not be populated');
+        $this->assertTrue($this->_locator->exists('TestPlugin.Comments'), 'The plugin.alias key should now be populated');
+    }
+
+    /**
+     * Test getting instances from the registry.
+     *
+     * @return void
+     */
+    public function testGet()
+    {
+        $result = $this->_locator->get('Articles', [
+            'table' => 'my_articles',
+        ]);
+        $this->assertInstanceOf('Cake\ORM\Table', $result);
+        $this->assertEquals('my_articles', $result->table());
+
+        $result2 = $this->_locator->get('Articles');
+        $this->assertSame($result, $result2);
+        $this->assertEquals('my_articles', $result->table());
+    }
+
+    /**
+     * Are auto-models instanciated correctly? How about when they have an alias?
+     *
+     * @return void
+     */
+    public function testGetFallbacks()
+    {
+        $result = $this->_locator->get('Droids');
+        $this->assertInstanceOf('Cake\ORM\Table', $result);
+        $this->assertEquals('droids', $result->table());
+        $this->assertEquals('Droids', $result->alias());
+
+        $result = $this->_locator->get('R2D2', ['className' => 'Droids']);
+        $this->assertInstanceOf('Cake\ORM\Table', $result);
+        $this->assertEquals('droids', $result->table(), 'The table should be derived from the className');
+        $this->assertEquals('R2D2', $result->alias());
+
+        $result = $this->_locator->get('C3P0', ['className' => 'Droids', 'table' => 'rebels']);
+        $this->assertInstanceOf('Cake\ORM\Table', $result);
+        $this->assertEquals('rebels', $result->table(), 'The table should be taken from options');
+        $this->assertEquals('C3P0', $result->alias());
+
+        $result = $this->_locator->get('Funky.Chipmunks');
+        $this->assertInstanceOf('Cake\ORM\Table', $result);
+        $this->assertEquals('chipmunks', $result->table(), 'The table should be derived from the alias');
+        $this->assertEquals('Chipmunks', $result->alias());
+
+        $result = $this->_locator->get('Awesome', ['className' => 'Funky.Monkies']);
+        $this->assertInstanceOf('Cake\ORM\Table', $result);
+        $this->assertEquals('monkies', $result->table(), 'The table should be derived from the classname');
+        $this->assertEquals('Awesome', $result->alias());
+
+        $result = $this->_locator->get('Stuff', ['className' => 'Cake\ORM\Table']);
+        $this->assertInstanceOf('Cake\ORM\Table', $result);
+        $this->assertEquals('stuff', $result->table(), 'The table should be derived from the alias');
+        $this->assertEquals('Stuff', $result->alias());
+    }
+
+    /**
+     * Test that get() uses config data set with config()
+     *
+     * @return void
+     */
+    public function testGetWithConfig()
+    {
+        $this->_locator->config('Articles', [
+            'table' => 'my_articles',
+        ]);
+        $result = $this->_locator->get('Articles');
+        $this->assertEquals('my_articles', $result->table(), 'Should use config() data.');
+    }
+
+    /**
+     * Test get with config throws an exception if the alias exists already.
+     *
+     * @expectedException \RuntimeException
+     * @expectedExceptionMessage You cannot configure "Users", it already exists in the registry.
+     * @return void
+     */
+    public function testGetExistingWithConfigData()
+    {
+        $users = $this->_locator->get('Users');
+        $this->_locator->get('Users', ['table' => 'my_users']);
+    }
+
+    /**
+     * Test get() can be called several times with the same option without
+     * throwing an exception.
+     *
+     * @return void
+     */
+    public function testGetWithSameOption()
+    {
+        $result = $this->_locator->get('Users', ['className' => 'Cake\Test\TestCase\ORM\Locator\MyUsersTable']);
+        $result2 = $this->_locator->get('Users', ['className' => 'Cake\Test\TestCase\ORM\Locator\MyUsersTable']);
+        $this->assertEquals($result, $result2);
+    }
+
+    /**
+     * Tests that tables can be instantiated based on conventions
+     * and using plugin notation
+     *
+     * @return void
+     */
+    public function testGetWithConventions()
+    {
+        $table = $this->_locator->get('articles');
+        $this->assertInstanceOf('TestApp\Model\Table\ArticlesTable', $table);
+        $table = $this->_locator->get('Articles');
+        $this->assertInstanceOf('TestApp\Model\Table\ArticlesTable', $table);
+
+        $table = $this->_locator->get('authors');
+        $this->assertInstanceOf('TestApp\Model\Table\AuthorsTable', $table);
+        $table = $this->_locator->get('Authors');
+        $this->assertInstanceOf('TestApp\Model\Table\AuthorsTable', $table);
+    }
+
+    /**
+     * Test get() with plugin syntax aliases
+     *
+     * @return void
+     */
+    public function testGetPlugin()
+    {
+        Plugin::load('TestPlugin');
+        $table = $this->_locator->get('TestPlugin.TestPluginComments');
+
+        $this->assertInstanceOf('TestPlugin\Model\Table\TestPluginCommentsTable', $table);
+        $this->assertFalse(
+            $this->_locator->exists('TestPluginComments'),
+            'Short form should NOT exist'
+        );
+        $this->assertTrue(
+            $this->_locator->exists('TestPlugin.TestPluginComments'),
+            'Long form should exist'
+        );
+
+        $second = $this->_locator->get('TestPlugin.TestPluginComments');
+        $this->assertSame($table, $second, 'Can fetch long form');
+    }
+
+    /**
+     * Test get() with same-alias models in different plugins
+     *
+     * There should be no internal cache-confusion
+     *
+     * @return void
+     */
+    public function testGetMultiplePlugins()
+    {
+        Plugin::load('TestPlugin');
+        Plugin::load('TestPluginTwo');
+
+        $app = $this->_locator->get('Comments');
+        $plugin1 = $this->_locator->get('TestPlugin.Comments');
+        $plugin2 = $this->_locator->get('TestPluginTwo.Comments');
+
+        $this->assertInstanceOf('Cake\ORM\Table', $app, 'Should be an app table instance');
+        $this->assertInstanceOf('TestPlugin\Model\Table\CommentsTable', $plugin1, 'Should be a plugin 1 table instance');
+        $this->assertInstanceOf('TestPluginTwo\Model\Table\CommentsTable', $plugin2, 'Should be a plugin 2 table instance');
+
+        $plugin2 = $this->_locator->get('TestPluginTwo.Comments');
+        $plugin1 = $this->_locator->get('TestPlugin.Comments');
+        $app = $this->_locator->get('Comments');
+
+        $this->assertInstanceOf('Cake\ORM\Table', $app, 'Should still be an app table instance');
+        $this->assertInstanceOf('TestPlugin\Model\Table\CommentsTable', $plugin1, 'Should still be a plugin 1 table instance');
+        $this->assertInstanceOf('TestPluginTwo\Model\Table\CommentsTable', $plugin2, 'Should still be a plugin 2 table instance');
+    }
+
+    /**
+     * Test get() with plugin aliases + className option.
+     *
+     * @return void
+     */
+    public function testGetPluginWithClassNameOption()
+    {
+        Plugin::load('TestPlugin');
+        $table = $this->_locator->get('Comments', [
+            'className' => 'TestPlugin.TestPluginComments',
+        ]);
+        $class = 'TestPlugin\Model\Table\TestPluginCommentsTable';
+        $this->assertInstanceOf($class, $table);
+        $this->assertFalse($this->_locator->exists('TestPluginComments'), 'Class name should not exist');
+        $this->assertFalse($this->_locator->exists('TestPlugin.TestPluginComments'), 'Full class alias should not exist');
+        $this->assertTrue($this->_locator->exists('Comments'), 'Class name should exist');
+
+        $second = $this->_locator->get('Comments');
+        $this->assertSame($table, $second);
+    }
+
+    /**
+     * Test get() with full namespaced classname
+     *
+     * @return void
+     */
+    public function testGetPluginWithFullNamespaceName()
+    {
+        Plugin::load('TestPlugin');
+        $class = 'TestPlugin\Model\Table\TestPluginCommentsTable';
+        $table = $this->_locator->get('Comments', [
+            'className' => $class,
+        ]);
+        $this->assertInstanceOf($class, $table);
+        $this->assertFalse($this->_locator->exists('TestPluginComments'), 'Class name should not exist');
+        $this->assertFalse($this->_locator->exists('TestPlugin.TestPluginComments'), 'Full class alias should not exist');
+        $this->assertTrue($this->_locator->exists('Comments'), 'Class name should exist');
+    }
+
+    /**
+     * Tests that table options can be pre-configured for the factory method
+     *
+     * @return void
+     */
+    public function testConfigAndBuild()
+    {
+        $this->_locator->clear();
+        $map = $this->_locator->config();
+        $this->assertEquals([], $map);
+
+        $connection = ConnectionManager::get('test', false);
+        $options = ['connection' => $connection];
+        $this->_locator->config('users', $options);
+        $map = $this->_locator->config();
+        $this->assertEquals(['users' => $options], $map);
+        $this->assertEquals($options, $this->_locator->config('users'));
+
+        $schema = ['id' => ['type' => 'rubbish']];
+        $options += ['schema' => $schema];
+        $this->_locator->config('users', $options);
+
+        $table = $this->_locator->get('users', ['table' => 'users']);
+        $this->assertInstanceOf('Cake\ORM\Table', $table);
+        $this->assertEquals('users', $table->table());
+        $this->assertEquals('users', $table->alias());
+        $this->assertSame($connection, $table->connection());
+        $this->assertEquals(array_keys($schema), $table->schema()->columns());
+        $this->assertEquals($schema['id']['type'], $table->schema()->column('id')['type']);
+
+        $this->_locator->clear();
+        $this->assertEmpty($this->_locator->config());
+
+        $this->_locator->config('users', $options);
+        $table = $this->_locator->get('users', ['className' => __NAMESPACE__ . '\MyUsersTable']);
+        $this->assertInstanceOf(__NAMESPACE__ . '\MyUsersTable', $table);
+        $this->assertEquals('users', $table->table());
+        $this->assertEquals('users', $table->alias());
+        $this->assertSame($connection, $table->connection());
+        $this->assertEquals(array_keys($schema), $table->schema()->columns());
+        $this->assertEquals($schema['id']['type'], $table->schema()->column('id')['type']);
+    }
+
+    /**
+     * Tests that table options can be pre-configured with a single validator
+     *
+     * @return void
+     */
+    public function testConfigWithSingleValidator()
+    {
+        $validator = new Validator();
+
+        $this->_locator->config('users', ['validator' => $validator]);
+        $table = $this->_locator->get('users');
+
+        $this->assertSame($table->validator('default'), $validator);
+    }
+
+    /**
+     * Tests that table options can be pre-configured with multiple validators
+     *
+     * @return void
+     */
+    public function testConfigWithMultipleValidators()
+    {
+        $validator1 = new Validator();
+        $validator2 = new Validator();
+        $validator3 = new Validator();
+
+        $this->_locator->config('users', [
+            'validator' => [
+                'default' => $validator1,
+                'secondary' => $validator2,
+                'tertiary' => $validator3,
+            ]
+        ]);
+        $table = $this->_locator->get('users');
+
+        $this->assertSame($table->validator('default'), $validator1);
+        $this->assertSame($table->validator('secondary'), $validator2);
+        $this->assertSame($table->validator('tertiary'), $validator3);
+    }
+
+    /**
+     * Test setting an instance.
+     *
+     * @return void
+     */
+    public function testSet()
+    {
+        $mock = $this->getMock('Cake\ORM\Table');
+        $this->assertSame($mock, $this->_locator->set('Articles', $mock));
+        $this->assertSame($mock, $this->_locator->get('Articles'));
+    }
+
+    /**
+     * Test setting an instance with plugin syntax aliases
+     *
+     * @return void
+     */
+    public function testSetPlugin()
+    {
+        Plugin::load('TestPlugin');
+
+        $mock = $this->getMock('TestPlugin\Model\Table\CommentsTable');
+
+        $this->assertSame($mock, $this->_locator->set('TestPlugin.Comments', $mock));
+        $this->assertSame($mock, $this->_locator->get('TestPlugin.Comments'));
+    }
+
+    /**
+     * Tests genericInstances
+     *
+     * @return void
+     */
+    public function testGenericInstances()
+    {
+        $foos = $this->_locator->get('Foos');
+        $bars = $this->_locator->get('Bars');
+        $this->_locator->get('Articles');
+        $expected = ['Foos' => $foos, 'Bars' => $bars];
+        $this->assertEquals($expected, $this->_locator->genericInstances());
+    }
+
+    /**
+     * Tests remove an instance
+     *
+     * @return void
+     */
+    public function testRemove()
+    {
+        $first = $this->_locator->get('Comments');
+
+        $this->assertTrue($this->_locator->exists('Comments'));
+
+        $this->_locator->remove('Comments');
+        $this->assertFalse($this->_locator->exists('Comments'));
+
+        $second = $this->_locator->get('Comments');
+
+        $this->assertNotSame($first, $second, 'Should be different objects, as the reference to the first was destroyed');
+        $this->assertTrue($this->_locator->exists('Comments'));
+    }
+
+    /**
+     * testRemovePlugin
+     *
+     * Removing a plugin-prefixed model should not affect any other
+     * plugin-prefixed model, or app model.
+     * Removing an app model should not affect any other
+     * plugin-prefixed model.
+     *
+     * @return void
+     */
+    public function testRemovePlugin()
+    {
+        Plugin::load('TestPlugin');
+        Plugin::load('TestPluginTwo');
+
+        $app = $this->_locator->get('Comments');
+        $this->_locator->get('TestPlugin.Comments');
+        $plugin = $this->_locator->get('TestPluginTwo.Comments');
+
+        $this->assertTrue($this->_locator->exists('Comments'));
+        $this->assertTrue($this->_locator->exists('TestPlugin.Comments'));
+        $this->assertTrue($this->_locator->exists('TestPluginTwo.Comments'));
+
+        $this->_locator->remove('TestPlugin.Comments');
+
+        $this->assertTrue($this->_locator->exists('Comments'));
+        $this->assertFalse($this->_locator->exists('TestPlugin.Comments'));
+        $this->assertTrue($this->_locator->exists('TestPluginTwo.Comments'));
+
+        $app2 = $this->_locator->get('Comments');
+        $plugin2 = $this->_locator->get('TestPluginTwo.Comments');
+
+        $this->assertSame($app, $app2, 'Should be the same Comments object');
+        $this->assertSame($plugin, $plugin2, 'Should be the same TestPluginTwo.Comments object');
+
+        $this->_locator->remove('Comments');
+
+        $this->assertFalse($this->_locator->exists('Comments'));
+        $this->assertFalse($this->_locator->exists('TestPlugin.Comments'));
+        $this->assertTrue($this->_locator->exists('TestPluginTwo.Comments'));
+
+        $plugin3 = $this->_locator->get('TestPluginTwo.Comments');
+
+        $this->assertSame($plugin, $plugin3, 'Should be the same TestPluginTwo.Comments object');
+    }
+}

+ 48 - 438
tests/TestCase/ORM/TableRegistryTest.php

@@ -14,44 +14,33 @@
  */
 namespace Cake\Test\TestCase\ORM;
 
-use Cake\Core\Configure;
-use Cake\Core\Plugin;
-use Cake\Datasource\ConnectionManager;
-use Cake\ORM\Table;
+use Cake\ORM\Locator\LocatorInterface;
 use Cake\ORM\TableRegistry;
 use Cake\TestSuite\TestCase;
-use Cake\Validation\Validator;
 
 /**
- * Used to test correct class is instantiated when using TableRegistry::get();
+ * Test case for TableRegistry
  */
-class MyUsersTable extends Table
+class TableRegistryTest extends TestCase
 {
 
     /**
-     * Overrides default table name
+     * Original TableLocator.
      *
-     * @var string
+     * @var Cake\ORM\Locator\LocatorInterface
      */
-    protected $_table = 'users';
-}
-
-
-/**
- * Test case for TableRegistry
- */
-class TableRegistryTest extends TestCase
-{
+    protected $_originalLocator;
 
     /**
-     * setup
+     * Remember original instance to set it back on tearDown() just to make sure
+     * other tests are not broken.
      *
      * @return void
      */
     public function setUp()
     {
         parent::setUp();
-        Configure::write('App.namespace', 'TestApp');
+        $this->_originalLocator = TableRegistry::locator();
     }
 
     /**
@@ -62,490 +51,111 @@ class TableRegistryTest extends TestCase
     public function tearDown()
     {
         parent::tearDown();
-        TableRegistry::clear();
+        TableRegistry::locator($this->_originalLocator);
     }
 
     /**
-     * Test config() method.
+     * Sets and returns mock LocatorInterface instance.
      *
-     * @return void
+     * @return Cake\ORM\Locator\LocatorInterface
      */
-    public function testConfig()
+    protected function _setMockLocator()
     {
-        $this->assertEquals([], TableRegistry::config('Tests'));
+        $locator = $this->getMock('Cake\ORM\Locator\LocatorInterface');
+        TableRegistry::locator($locator);
 
-        $data = [
-            'connection' => 'testing',
-            'entityClass' => 'TestApp\Model\Entity\Article',
-        ];
-        $result = TableRegistry::config('Tests', $data);
-        $this->assertEquals($data, $result, 'Returns config data.');
-
-        $result = TableRegistry::config();
-        $expected = ['Tests' => $data];
-        $this->assertEquals($expected, $result);
+        return $locator;
     }
 
     /**
-     * Test config() method with plugin syntax aliases
+     * Test locator() method.
      *
      * @return void
      */
-    public function testConfigPlugin()
+    public function testLocator()
     {
-        Plugin::load('TestPlugin');
+        $this->assertInstanceOf('Cake\ORM\Locator\LocatorInterface', TableRegistry::locator());
 
-        $data = [
-            'connection' => 'testing',
-            'entityClass' => 'TestPlugin\Model\Entity\Comment',
-        ];
+        $locator = $this->_setMockLocator();
 
-        $result = TableRegistry::config('TestPlugin.TestPluginComments', $data);
-        $this->assertEquals($data, $result, 'Returns config data.');
+        $this->assertSame($locator, TableRegistry::locator());
     }
 
     /**
-     * Test calling config() on existing instances throws an error.
+     * Test that locator() method is returing TableLocator by default.
      *
-     * @expectedException \RuntimeException
-     * @expectedExceptionMessage You cannot configure "Users", it has already been constructed.
      * @return void
      */
-    public function testConfigOnDefinedInstance()
+    public function testLocatorDefault()
     {
-        $users = TableRegistry::get('Users');
-        TableRegistry::config('Users', ['table' => 'my_users']);
+        $locator = TableRegistry::locator();
+        $this->assertInstanceOf('Cake\ORM\Locator\TableLocator', $locator);
     }
 
     /**
-     * Test the exists() method.
-     *
-     * @return void
-     */
-    public function testExists()
-    {
-        $this->assertFalse(TableRegistry::exists('Articles'));
-
-        TableRegistry::config('Articles', ['table' => 'articles']);
-        $this->assertFalse(TableRegistry::exists('Articles'));
-
-        TableRegistry::get('Articles', ['table' => 'articles']);
-        $this->assertTrue(TableRegistry::exists('Articles'));
-    }
-
-    /**
-     * Test the exists() method with plugin-prefixed models.
+     * Test config() method.
      *
      * @return void
      */
-    public function testExistsPlugin()
+    public function testConfig()
     {
-        $this->assertFalse(TableRegistry::exists('Comments'));
-        $this->assertFalse(TableRegistry::exists('TestPlugin.Comments'));
+        $locator = $this->_setMockLocator();
+        $locator->expects($this->once())->method('config')->with('Test', []);
 
-        TableRegistry::config('TestPlugin.Comments', ['table' => 'comments']);
-        $this->assertFalse(TableRegistry::exists('Comments'), 'The Comments key should not be populated');
-        $this->assertFalse(TableRegistry::exists('TestPlugin.Comments'), 'The plugin.alias key should not be populated');
-
-        TableRegistry::get('TestPlugin.Comments', ['table' => 'comments']);
-        $this->assertFalse(TableRegistry::exists('Comments'), 'The Comments key should not be populated');
-        $this->assertTrue(TableRegistry::exists('TestPlugin.Comments'), 'The plugin.alias key should now be populated');
+        TableRegistry::config('Test', []);
     }
 
     /**
-     * Test getting instances from the registry.
+     * Test the get() method.
      *
      * @return void
      */
     public function testGet()
     {
-        $result = TableRegistry::get('Articles', [
-            'table' => 'my_articles',
-        ]);
-        $this->assertInstanceOf('Cake\ORM\Table', $result);
-        $this->assertEquals('my_articles', $result->table());
-
-        $result2 = TableRegistry::get('Articles');
-        $this->assertSame($result, $result2);
-        $this->assertEquals('my_articles', $result->table());
-    }
-
-    /**
-     * Are auto-models instanciated correctly? How about when they have an alias?
-     *
-     * @return void
-     */
-    public function testGetFallbacks()
-    {
-        $result = TableRegistry::get('Droids');
-        $this->assertInstanceOf('Cake\ORM\Table', $result);
-        $this->assertEquals('droids', $result->table());
-        $this->assertEquals('Droids', $result->alias());
-
-        $result = TableRegistry::get('R2D2', ['className' => 'Droids']);
-        $this->assertInstanceOf('Cake\ORM\Table', $result);
-        $this->assertEquals('droids', $result->table(), 'The table should be derived from the className');
-        $this->assertEquals('R2D2', $result->alias());
-
-        $result = TableRegistry::get('C3P0', ['className' => 'Droids', 'table' => 'rebels']);
-        $this->assertInstanceOf('Cake\ORM\Table', $result);
-        $this->assertEquals('rebels', $result->table(), 'The table should be taken from options');
-        $this->assertEquals('C3P0', $result->alias());
-
-        $result = TableRegistry::get('Funky.Chipmunks');
-        $this->assertInstanceOf('Cake\ORM\Table', $result);
-        $this->assertEquals('chipmunks', $result->table(), 'The table should be derived from the alias');
-        $this->assertEquals('Chipmunks', $result->alias());
-
-        $result = TableRegistry::get('Awesome', ['className' => 'Funky.Monkies']);
-        $this->assertInstanceOf('Cake\ORM\Table', $result);
-        $this->assertEquals('monkies', $result->table(), 'The table should be derived from the classname');
-        $this->assertEquals('Awesome', $result->alias());
-
-        $result = TableRegistry::get('Stuff', ['className' => 'Cake\ORM\Table']);
-        $this->assertInstanceOf('Cake\ORM\Table', $result);
-        $this->assertEquals('stuff', $result->table(), 'The table should be derived from the alias');
-        $this->assertEquals('Stuff', $result->alias());
-    }
-
-    /**
-     * Test that get() uses config data set with config()
-     *
-     * @return void
-     */
-    public function testGetWithConfig()
-    {
-        TableRegistry::config('Articles', [
-            'table' => 'my_articles',
-        ]);
-        $result = TableRegistry::get('Articles');
-        $this->assertEquals('my_articles', $result->table(), 'Should use config() data.');
-    }
-
-    /**
-     * Test get with config throws an exception if the alias exists already.
-     *
-     * @expectedException \RuntimeException
-     * @expectedExceptionMessage You cannot configure "Users", it already exists in the registry.
-     * @return void
-     */
-    public function testGetExistingWithConfigData()
-    {
-        $users = TableRegistry::get('Users');
-        TableRegistry::get('Users', ['table' => 'my_users']);
-    }
-
-    /**
-     * Test get() can be called several times with the same option without
-     * throwing an exception.
-     *
-     * @return void
-     */
-    public function testGetWithSameOption()
-    {
-        $result = TableRegistry::get('Users', ['className' => 'Cake\Test\TestCase\ORM\MyUsersTable']);
-        $result2 = TableRegistry::get('Users', ['className' => 'Cake\Test\TestCase\ORM\MyUsersTable']);
-        $this->assertEquals($result, $result2);
-    }
-
-    /**
-     * Tests that tables can be instantiated based on conventions
-     * and using plugin notation
-     *
-     * @return void
-     */
-    public function testGetWithConventions()
-    {
-        $table = TableRegistry::get('articles');
-        $this->assertInstanceOf('TestApp\Model\Table\ArticlesTable', $table);
-        $table = TableRegistry::get('Articles');
-        $this->assertInstanceOf('TestApp\Model\Table\ArticlesTable', $table);
-
-        $table = TableRegistry::get('authors');
-        $this->assertInstanceOf('TestApp\Model\Table\AuthorsTable', $table);
-        $table = TableRegistry::get('Authors');
-        $this->assertInstanceOf('TestApp\Model\Table\AuthorsTable', $table);
-    }
-
-    /**
-     * Test get() with plugin syntax aliases
-     *
-     * @return void
-     */
-    public function testGetPlugin()
-    {
-        Plugin::load('TestPlugin');
-        $table = TableRegistry::get('TestPlugin.TestPluginComments');
-
-        $this->assertInstanceOf('TestPlugin\Model\Table\TestPluginCommentsTable', $table);
-        $this->assertFalse(
-            TableRegistry::exists('TestPluginComments'),
-            'Short form should NOT exist'
-        );
-        $this->assertTrue(
-            TableRegistry::exists('TestPlugin.TestPluginComments'),
-            'Long form should exist'
-        );
-
-        $second = TableRegistry::get('TestPlugin.TestPluginComments');
-        $this->assertSame($table, $second, 'Can fetch long form');
-    }
-
-    /**
-     * Test get() with same-alias models in different plugins
-     *
-     * There should be no internal cache-confusion
-     *
-     * @return void
-     */
-    public function testGetMultiplePlugins()
-    {
-        Plugin::load('TestPlugin');
-        Plugin::load('TestPluginTwo');
-
-        $app = TableRegistry::get('Comments');
-        $plugin1 = TableRegistry::get('TestPlugin.Comments');
-        $plugin2 = TableRegistry::get('TestPluginTwo.Comments');
-
-        $this->assertInstanceOf('Cake\ORM\Table', $app, 'Should be an app table instance');
-        $this->assertInstanceOf('TestPlugin\Model\Table\CommentsTable', $plugin1, 'Should be a plugin 1 table instance');
-        $this->assertInstanceOf('TestPluginTwo\Model\Table\CommentsTable', $plugin2, 'Should be a plugin 2 table instance');
-
-        $plugin2 = TableRegistry::get('TestPluginTwo.Comments');
-        $plugin1 = TableRegistry::get('TestPlugin.Comments');
-        $app = TableRegistry::get('Comments');
-
-        $this->assertInstanceOf('Cake\ORM\Table', $app, 'Should still be an app table instance');
-        $this->assertInstanceOf('TestPlugin\Model\Table\CommentsTable', $plugin1, 'Should still be a plugin 1 table instance');
-        $this->assertInstanceOf('TestPluginTwo\Model\Table\CommentsTable', $plugin2, 'Should still be a plugin 2 table instance');
-    }
-
-    /**
-     * Test get() with plugin aliases + className option.
-     *
-     * @return void
-     */
-    public function testGetPluginWithClassNameOption()
-    {
-        Plugin::load('TestPlugin');
-        $table = TableRegistry::get('Comments', [
-            'className' => 'TestPlugin.TestPluginComments',
-        ]);
-        $class = 'TestPlugin\Model\Table\TestPluginCommentsTable';
-        $this->assertInstanceOf($class, $table);
-        $this->assertFalse(TableRegistry::exists('TestPluginComments'), 'Class name should not exist');
-        $this->assertFalse(TableRegistry::exists('TestPlugin.TestPluginComments'), 'Full class alias should not exist');
-        $this->assertTrue(TableRegistry::exists('Comments'), 'Class name should exist');
-
-        $second = TableRegistry::get('Comments');
-        $this->assertSame($table, $second);
-    }
-
-    /**
-     * Test get() with full namespaced classname
-     *
-     * @return void
-     */
-    public function testGetPluginWithFullNamespaceName()
-    {
-        Plugin::load('TestPlugin');
-        $class = 'TestPlugin\Model\Table\TestPluginCommentsTable';
-        $table = TableRegistry::get('Comments', [
-            'className' => $class,
-        ]);
-        $this->assertInstanceOf($class, $table);
-        $this->assertFalse(TableRegistry::exists('TestPluginComments'), 'Class name should not exist');
-        $this->assertFalse(TableRegistry::exists('TestPlugin.TestPluginComments'), 'Full class alias should not exist');
-        $this->assertTrue(TableRegistry::exists('Comments'), 'Class name should exist');
-    }
-
-    /**
-     * Tests that table options can be pre-configured for the factory method
-     *
-     * @return void
-     */
-    public function testConfigAndBuild()
-    {
-        TableRegistry::clear();
-        $map = TableRegistry::config();
-        $this->assertEquals([], $map);
-
-        $connection = ConnectionManager::get('test', false);
-        $options = ['connection' => $connection];
-        TableRegistry::config('users', $options);
-        $map = TableRegistry::config();
-        $this->assertEquals(['users' => $options], $map);
-        $this->assertEquals($options, TableRegistry::config('users'));
-
-        $schema = ['id' => ['type' => 'rubbish']];
-        $options += ['schema' => $schema];
-        TableRegistry::config('users', $options);
-
-        $table = TableRegistry::get('users', ['table' => 'users']);
-        $this->assertInstanceOf('Cake\ORM\Table', $table);
-        $this->assertEquals('users', $table->table());
-        $this->assertEquals('users', $table->alias());
-        $this->assertSame($connection, $table->connection());
-        $this->assertEquals(array_keys($schema), $table->schema()->columns());
-        $this->assertEquals($schema['id']['type'], $table->schema()->column('id')['type']);
-
-        TableRegistry::clear();
-        $this->assertEmpty(TableRegistry::config());
-
-        TableRegistry::config('users', $options);
-        $table = TableRegistry::get('users', ['className' => __NAMESPACE__ . '\MyUsersTable']);
-        $this->assertInstanceOf(__NAMESPACE__ . '\MyUsersTable', $table);
-        $this->assertEquals('users', $table->table());
-        $this->assertEquals('users', $table->alias());
-        $this->assertSame($connection, $table->connection());
-        $this->assertEquals(array_keys($schema), $table->schema()->columns());
-        $this->assertEquals($schema['id']['type'], $table->schema()->column('id')['type']);
-    }
-
-    /**
-     * Tests that table options can be pre-configured with a single validator
-     *
-     * @return void
-     */
-    public function testConfigWithSingleValidator()
-    {
-        $validator = new Validator();
+        $locator = $this->_setMockLocator();
+        $locator->expects($this->once())->method('get')->with('Test', []);
 
-        TableRegistry::config('users', ['validator' => $validator]);
-        $table = TableRegistry::get('users');
-
-        $this->assertSame($table->validator('default'), $validator);
+        TableRegistry::get('Test', []);
     }
 
     /**
-     * Tests that table options can be pre-configured with multiple validators
-     *
-     * @return void
-     */
-    public function testConfigWithMultipleValidators()
-    {
-        $validator1 = new Validator();
-        $validator2 = new Validator();
-        $validator3 = new Validator();
-
-        TableRegistry::config('users', [
-            'validator' => [
-                'default' => $validator1,
-                'secondary' => $validator2,
-                'tertiary' => $validator3,
-            ]
-        ]);
-        $table = TableRegistry::get('users');
-
-        $this->assertSame($table->validator('default'), $validator1);
-        $this->assertSame($table->validator('secondary'), $validator2);
-        $this->assertSame($table->validator('tertiary'), $validator3);
-    }
-
-    /**
-     * Test setting an instance.
+     * Test the get() method.
      *
      * @return void
      */
     public function testSet()
     {
-        $mock = $this->getMock('Cake\ORM\Table');
-        $this->assertSame($mock, TableRegistry::set('Articles', $mock));
-        $this->assertSame($mock, TableRegistry::get('Articles'));
-    }
-
-    /**
-     * Test setting an instance with plugin syntax aliases
-     *
-     * @return void
-     */
-    public function testSetPlugin()
-    {
-        Plugin::load('TestPlugin');
+        $table = $this->getMock('Cake\ORM\Table');
 
-        $mock = $this->getMock('TestPlugin\Model\Table\CommentsTable');
+        $locator = $this->_setMockLocator();
+        $locator->expects($this->once())->method('set')->with('Test', $table);
 
-        $this->assertSame($mock, TableRegistry::set('TestPlugin.Comments', $mock));
-        $this->assertSame($mock, TableRegistry::get('TestPlugin.Comments'));
+        TableRegistry::set('Test', $table);
     }
 
     /**
-     * Tests genericInstances
-     *
-     * @return void
-     */
-    public function testGenericInstances()
-    {
-        $foos = TableRegistry::get('Foos');
-        $bars = TableRegistry::get('Bars');
-        TableRegistry::get('Articles');
-        $expected = ['Foos' => $foos, 'Bars' => $bars];
-        $this->assertEquals($expected, TableRegistry::genericInstances());
-    }
-
-    /**
-     * Tests remove an instance
+     * Test the remove() method.
      *
      * @return void
      */
     public function testRemove()
     {
-        $first = TableRegistry::get('Comments');
-
-        $this->assertTrue(TableRegistry::exists('Comments'));
-
-        TableRegistry::remove('Comments');
-        $this->assertFalse(TableRegistry::exists('Comments'));
+        $locator = $this->_setMockLocator();
+        $locator->expects($this->once())->method('remove')->with('Test');
 
-        $second = TableRegistry::get('Comments');
-
-        $this->assertNotSame($first, $second, 'Should be different objects, as the reference to the first was destroyed');
-        $this->assertTrue(TableRegistry::exists('Comments'));
+        TableRegistry::remove('Test');
     }
 
     /**
-     * testRemovePlugin
-     *
-     * Removing a plugin-prefixed model should not affect any other
-     * plugin-prefixed model, or app model.
-     * Removing an app model should not affect any other
-     * plugin-prefixed model.
+     * Test the clear() method.
      *
      * @return void
      */
-    public function testRemovePlugin()
+    public function testClear()
     {
-        Plugin::load('TestPlugin');
-        Plugin::load('TestPluginTwo');
+        $locator = $this->_setMockLocator();
+        $locator->expects($this->once())->method('clear');
 
-        $app = TableRegistry::get('Comments');
-        TableRegistry::get('TestPlugin.Comments');
-        $plugin = TableRegistry::get('TestPluginTwo.Comments');
-
-        $this->assertTrue(TableRegistry::exists('Comments'));
-        $this->assertTrue(TableRegistry::exists('TestPlugin.Comments'));
-        $this->assertTrue(TableRegistry::exists('TestPluginTwo.Comments'));
-
-        TableRegistry::remove('TestPlugin.Comments');
-
-        $this->assertTrue(TableRegistry::exists('Comments'));
-        $this->assertFalse(TableRegistry::exists('TestPlugin.Comments'));
-        $this->assertTrue(TableRegistry::exists('TestPluginTwo.Comments'));
-
-        $app2 = TableRegistry::get('Comments');
-        $plugin2 = TableRegistry::get('TestPluginTwo.Comments');
-
-        $this->assertSame($app, $app2, 'Should be the same Comments object');
-        $this->assertSame($plugin, $plugin2, 'Should be the same TestPluginTwo.Comments object');
-
-        TableRegistry::remove('Comments');
-
-        $this->assertFalse(TableRegistry::exists('Comments'));
-        $this->assertFalse(TableRegistry::exists('TestPlugin.Comments'));
-        $this->assertTrue(TableRegistry::exists('TestPluginTwo.Comments'));
-
-        $plugin3 = TableRegistry::get('TestPluginTwo.Comments');
-
-        $this->assertSame($plugin, $plugin3, 'Should be the same TestPluginTwo.Comments object');
+        TableRegistry::clear();
     }
 }