Browse Source

Merge pull request #11172 from cakephp/3.next-association-collection-locator

Make AssociationCollection aware of a table locator.
Mark Story 8 years ago
parent
commit
51259881c0

+ 42 - 0
src/ORM/AssociationCollection.php

@@ -16,6 +16,8 @@ namespace Cake\ORM;
 
 use ArrayIterator;
 use Cake\Datasource\EntityInterface;
+use Cake\ORM\Locator\LocatorAwareTrait;
+use Cake\ORM\Locator\LocatorInterface;
 use InvalidArgumentException;
 use IteratorAggregate;
 
@@ -29,6 +31,7 @@ class AssociationCollection implements IteratorAggregate
 {
 
     use AssociationsNormalizerTrait;
+    use LocatorAwareTrait;
 
     /**
      * Stored associations
@@ -38,6 +41,21 @@ class AssociationCollection implements IteratorAggregate
     protected $_items = [];
 
     /**
+     * Constructor.
+     *
+     * Sets the default table locator for associations.
+     * If no locator is provided, the global one will be used.
+     *
+     * @param \Cake\ORM\Locator\LocatorInterface|null $tableLocator Table locator instance.
+     */
+    public function __construct(LocatorInterface $tableLocator = null)
+    {
+        if ($tableLocator !== null) {
+            $this->_tableLocator = $tableLocator;
+        }
+    }
+
+    /**
      * Add an association to the collection
      *
      * If the alias added contains a `.` the part preceding the `.` will be dropped.
@@ -55,6 +73,30 @@ class AssociationCollection implements IteratorAggregate
     }
 
     /**
+     * Creates and adds the Association object to this collection.
+     *
+     * @param string $className The name of association class.
+     * @param string $associated The alias for the target table.
+     * @param array $options List of options to configure the association definition.
+     * @return \Cake\ORM\Association
+     * @throws InvalidArgumentException
+     */
+    public function load($className, $associated, array $options = [])
+    {
+        $options += [
+            'tableLocator' => $this->getTableLocator()
+        ];
+
+        $association = new $className($associated, $options);
+        if (!$association instanceof Association) {
+            $message = sprintf('The association must extend `%s` class, `%s` given.', Association::class, get_class($association));
+            throw new InvalidArgumentException($message);
+        }
+
+        return $this->add($association->getName(), $association);
+    }
+
+    /**
      * Fetch an attached association by name.
      *
      * @param string $alias The association alias to get.

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

@@ -16,6 +16,7 @@ namespace Cake\ORM\Locator;
 
 use Cake\Core\App;
 use Cake\Datasource\ConnectionManager;
+use Cake\ORM\AssociationCollection;
 use Cake\ORM\Table;
 use Cake\Utility\Inflector;
 use RuntimeException;
@@ -211,6 +212,10 @@ class TableLocator implements LocatorInterface
             }
             $options['connection'] = ConnectionManager::get($connectionName);
         }
+        if (empty($options['associations'])) {
+            $associations = new AssociationCollection($this);
+            $options['associations'] = $associations;
+        }
 
         $options['registryAlias'] = $alias;
         $this->_instances[$alias] = $this->_create($options);

+ 4 - 8
src/ORM/Table.php

@@ -967,9 +967,8 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc
     public function belongsTo($associated, array $options = [])
     {
         $options += ['sourceTable' => $this];
-        $association = new BelongsTo($associated, $options);
 
-        return $this->_associations->add($association->getName(), $association);
+        return $this->_associations->load(BelongsTo::class, $associated, $options);
     }
 
     /**
@@ -1011,9 +1010,8 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc
     public function hasOne($associated, array $options = [])
     {
         $options += ['sourceTable' => $this];
-        $association = new HasOne($associated, $options);
 
-        return $this->_associations->add($association->getName(), $association);
+        return $this->_associations->load(HasOne::class, $associated, $options);
     }
 
     /**
@@ -1061,9 +1059,8 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc
     public function hasMany($associated, array $options = [])
     {
         $options += ['sourceTable' => $this];
-        $association = new HasMany($associated, $options);
 
-        return $this->_associations->add($association->getName(), $association);
+        return $this->_associations->load(HasMany::class, $associated, $options);
     }
 
     /**
@@ -1113,9 +1110,8 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc
     public function belongsToMany($associated, array $options = [])
     {
         $options += ['sourceTable' => $this];
-        $association = new BelongsToMany($associated, $options);
 
-        return $this->_associations->add($association->getName(), $association);
+        return $this->_associations->load(BelongsToMany::class, $associated, $options);
     }
 
     /**

+ 57 - 0
tests/TestCase/ORM/AssociationCollectionTest.php

@@ -18,6 +18,8 @@ use Cake\ORM\AssociationCollection;
 use Cake\ORM\Association\BelongsTo;
 use Cake\ORM\Association\BelongsToMany;
 use Cake\ORM\Entity;
+use Cake\ORM\Locator\LocatorInterface;
+use Cake\ORM\TableRegistry;
 use Cake\TestSuite\TestCase;
 
 /**
@@ -42,6 +44,20 @@ class AssociationCollectionTest extends TestCase
     }
 
     /**
+     * Test the constructor.
+     *
+     * @return void
+     */
+    public function testConstructor()
+    {
+        $this->assertSame(TableRegistry::getTableLocator(), $this->associations->getTableLocator());
+
+        $tableLocator = $this->createMock(LocatorInterface::class);
+        $associations = new AssociationCollection($tableLocator);
+        $this->assertSame($tableLocator, $associations->getTableLocator());
+    }
+
+    /**
      * Test the simple add/has and get methods.
      *
      * @return void
@@ -71,6 +87,47 @@ class AssociationCollectionTest extends TestCase
     }
 
     /**
+     * Test the load method.
+     *
+     * @return void
+     */
+    public function testLoad()
+    {
+        $this->associations->load(BelongsTo::class, 'Users');
+        $this->assertTrue($this->associations->has('Users'));
+        $this->assertInstanceOf(BelongsTo::class, $this->associations->get('Users'));
+        $this->assertSame($this->associations->getTableLocator(), $this->associations->get('Users')->getTableLocator());
+    }
+
+    /**
+     * Test the load method with custom locator.
+     *
+     * @return void
+     */
+    public function testLoadCustomLocator()
+    {
+        $locator = $this->createMock(LocatorInterface::class);
+        $this->associations->load(BelongsTo::class, 'Users', [
+            'tableLocator' => $locator
+        ]);
+        $this->assertTrue($this->associations->has('Users'));
+        $this->assertInstanceOf(BelongsTo::class, $this->associations->get('Users'));
+        $this->assertSame($locator, $this->associations->get('Users')->getTableLocator());
+    }
+
+    /**
+     * Test load invalid class.
+     *
+     * @return void
+     * @expectedException InvalidArgumentException
+     * @expectedExceptionMessage The association must extend `Cake\ORM\Association` class, `stdClass` given.
+     */
+    public function testLoadInvalid()
+    {
+        $this->associations->load('stdClass', 'Users');
+    }
+
+    /**
      * Test removeAll method
      *
      * @return void

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

@@ -164,6 +164,8 @@ class TableLocatorTest extends TestCase
         $result2 = $this->_locator->get('Articles');
         $this->assertSame($result, $result2);
         $this->assertEquals('my_articles', $result->table());
+
+        $this->assertSame($this->_locator, $result->associations()->getTableLocator());
     }
 
     /**