Browse Source

Merge pull request #11375 from sdustinh/association-dot-syntax

Access table associations using the dot syntax
Mark Story 8 years ago
parent
commit
b19018c25d

+ 22 - 22
src/ORM/Association/BelongsToMany.php

@@ -346,14 +346,14 @@ class BelongsToMany extends Association
         $junctionAlias = $junction->getAlias();
         $sAlias = $source->getAlias();
 
-        if (!$target->association($junctionAlias)) {
+        if (!$target->getAssociation($junctionAlias)) {
             $target->hasMany($junctionAlias, [
                 'targetTable' => $junction,
                 'foreignKey' => $this->getTargetForeignKey(),
                 'strategy' => $this->_strategy,
             ]);
         }
-        if (!$target->association($sAlias)) {
+        if (!$target->getAssociation($sAlias)) {
             $target->belongsToMany($sAlias, [
                 'sourceTable' => $target,
                 'targetTable' => $source,
@@ -383,7 +383,7 @@ class BelongsToMany extends Association
     protected function _generateSourceAssociations($junction, $source)
     {
         $junctionAlias = $junction->getAlias();
-        if (!$source->association($junctionAlias)) {
+        if (!$source->getAssociation($junctionAlias)) {
             $source->hasMany($junctionAlias, [
                 'targetTable' => $junction,
                 'foreignKey' => $this->getForeignKey(),
@@ -413,13 +413,13 @@ class BelongsToMany extends Association
         $tAlias = $target->getAlias();
         $sAlias = $source->getAlias();
 
-        if (!$junction->association($tAlias)) {
+        if (!$junction->getAssociation($tAlias)) {
             $junction->belongsTo($tAlias, [
                 'foreignKey' => $this->getTargetForeignKey(),
                 'targetTable' => $target
             ]);
         }
-        if (!$junction->association($sAlias)) {
+        if (!$junction->getAssociation($sAlias)) {
             $junction->belongsTo($sAlias, [
                 'foreignKey' => $this->getForeignKey(),
                 'targetTable' => $source
@@ -453,7 +453,7 @@ class BelongsToMany extends Association
         }
 
         $junction = $this->junction();
-        $belongsTo = $junction->association($this->getSource()->getAlias());
+        $belongsTo = $junction->getAssociation($this->getSource()->getAlias());
         $cond = $belongsTo->_joinCondition(['foreignKey' => $belongsTo->getForeignKey()]);
         $cond += $this->junctionConditions();
 
@@ -463,7 +463,7 @@ class BelongsToMany extends Association
         }
 
         // Attach the junction table as well we need it to populate _joinData.
-        $assoc = $this->_targetTable->association($junction->getAlias());
+        $assoc = $this->_targetTable->getAssociation($junction->getAlias());
         $newOptions = array_intersect_key($options, ['joinType' => 1, 'fields' => 1]);
         $newOptions += [
             'conditions' => $cond,
@@ -492,7 +492,7 @@ class BelongsToMany extends Association
             $options['conditions'] = [];
         }
         $junction = $this->junction();
-        $belongsTo = $junction->association($this->getSource()->getAlias());
+        $belongsTo = $junction->getAssociation($this->getSource()->getAlias());
         $conds = $belongsTo->_joinCondition(['foreignKey' => $belongsTo->getForeignKey()]);
 
         $subquery = $this->find()
@@ -504,7 +504,7 @@ class BelongsToMany extends Association
             $subquery = $options['queryBuilder']($subquery);
         }
 
-        $assoc = $junction->association($this->getTarget()->getAlias());
+        $assoc = $junction->getAssociation($this->getTarget()->getAlias());
         $conditions = $assoc->_joinCondition([
             'foreignKey' => $this->getTargetForeignKey()
         ]);
@@ -567,7 +567,7 @@ class BelongsToMany extends Association
             'sort' => $this->getSort(),
             'junctionAssociationName' => $name,
             'junctionProperty' => $this->_junctionProperty,
-            'junctionAssoc' => $this->getTarget()->association($name),
+            'junctionAssoc' => $this->getTarget()->getAssociation($name),
             'junctionConditions' => $this->junctionConditions(),
             'finder' => function () {
                 return $this->_appendJunctionJoin($this->find(), []);
@@ -598,7 +598,7 @@ class BelongsToMany extends Association
         }
 
         $table = $this->junction();
-        $hasMany = $this->getSource()->association($table->getAlias());
+        $hasMany = $this->getSource()->getAssociation($table->getAlias());
         if ($this->_cascadeCallbacks) {
             foreach ($hasMany->find('all')->where($conditions)->all()->toList() as $related) {
                 $table->delete($related, $options);
@@ -806,7 +806,7 @@ class BelongsToMany extends Association
         $target = $this->getTarget();
         $junction = $this->junction();
         $entityClass = $junction->getEntityClass();
-        $belongsTo = $junction->association($target->getAlias());
+        $belongsTo = $junction->getAssociation($target->getAlias());
         $foreignKey = (array)$this->getForeignKey();
         $assocForeignKey = (array)$belongsTo->getForeignKey();
         $targetPrimaryKey = (array)$target->getPrimaryKey();
@@ -863,7 +863,7 @@ class BelongsToMany extends Association
      *
      * ```
      * $newTags = $tags->find('relevant')->toArray();
-     * $articles->association('tags')->link($article, $newTags);
+     * $articles->getAssociation('tags')->link($article, $newTags);
      * ```
      *
      * `$article->get('tags')` will contain all tags in `$newTags` after liking
@@ -913,7 +913,7 @@ class BelongsToMany extends Association
      * ```
      * $article->tags = [$tag1, $tag2, $tag3, $tag4];
      * $tags = [$tag1, $tag2, $tag3];
-     * $articles->association('tags')->unlink($article, $tags);
+     * $articles->getAssociation('tags')->unlink($article, $tags);
      * ```
      *
      * `$article->get('tags')` will contain only `[$tag4]` after deleting in the database
@@ -1097,7 +1097,7 @@ class BelongsToMany extends Association
             return $query;
         }
 
-        $belongsTo = $this->junction()->association($this->getTarget()->getAlias());
+        $belongsTo = $this->junction()->getAssociation($this->getTarget()->getAlias());
         $conditions = $belongsTo->_joinCondition([
             'foreignKey' => $this->getTargetForeignKey()
         ]);
@@ -1125,7 +1125,7 @@ class BelongsToMany extends Association
             ]
         ];
 
-        $assoc = $this->getTarget()->association($name);
+        $assoc = $this->getTarget()->getAssociation($name);
         $query
             ->addDefaultTypes($assoc->getTarget())
             ->join($matching + $joins, [], true);
@@ -1168,7 +1168,7 @@ class BelongsToMany extends Association
      * $article->tags = [$tag1, $tag2, $tag3, $tag4];
      * $articles->save($article);
      * $tags = [$tag1, $tag3];
-     * $articles->association('tags')->replaceLinks($article, $tags);
+     * $articles->getAssociation('tags')->replaceLinks($article, $tags);
      * ```
      *
      * `$article->get('tags')` will contain only `[$tag1, $tag3]` at the end
@@ -1195,7 +1195,7 @@ class BelongsToMany extends Association
         return $this->junction()->getConnection()->transactional(
             function () use ($sourceEntity, $targetEntities, $primaryValue, $options) {
                 $foreignKey = array_map([$this->_junctionTable, 'aliasField'], (array)$this->getForeignKey());
-                $hasMany = $this->getSource()->association($this->_junctionTable->getAlias());
+                $hasMany = $this->getSource()->getAssociation($this->_junctionTable->getAlias());
                 $existing = $hasMany->find('all')
                     ->where(array_combine($foreignKey, $primaryValue));
 
@@ -1247,7 +1247,7 @@ class BelongsToMany extends Association
     {
         $junction = $this->junction();
         $target = $this->getTarget();
-        $belongsTo = $junction->association($target->getAlias());
+        $belongsTo = $junction->getAssociation($target->getAlias());
         $foreignKey = (array)$this->getForeignKey();
         $assocForeignKey = (array)$belongsTo->getForeignKey();
 
@@ -1367,8 +1367,8 @@ class BelongsToMany extends Association
             return $result;
         }
 
-        $belongsTo = $junction->association($target->getAlias());
-        $hasMany = $source->association($junction->getAlias());
+        $belongsTo = $junction->getAssociation($target->getAlias());
+        $hasMany = $source->getAssociation($junction->getAlias());
         $foreignKey = (array)$this->getForeignKey();
         $assocForeignKey = (array)$belongsTo->getForeignKey();
         $sourceKey = $sourceEntity->extract((array)$source->getPrimaryKey());
@@ -1398,7 +1398,7 @@ class BelongsToMany extends Association
     {
         if (!$this->_junctionAssociationName) {
             $this->_junctionAssociationName = $this->getTarget()
-                ->association($this->junction()->getAlias())
+                ->getAssociation($this->junction()->getAlias())
                 ->getName();
         }
 

+ 1 - 1
src/ORM/Association/HasMany.php

@@ -424,7 +424,7 @@ class HasMany extends Association
      * $author->articles = [$article1, $article2, $article3, $article4];
      * $authors->save($author);
      * $articles = [$article1, $article3];
-     * $authors->association('articles')->replace($author, $articles);
+     * $authors->getAssociation('articles')->replace($author, $articles);
      * ```
      *
      * `$author->get('articles')` will contain only `[$article1, $article3]` at the end

+ 2 - 2
src/ORM/Behavior/CounterCacheBehavior.php

@@ -122,7 +122,7 @@ class CounterCacheBehavior extends Behavior
         }
 
         foreach ($this->_config as $assoc => $settings) {
-            $assoc = $this->_table->association($assoc);
+            $assoc = $this->_table->getAssociation($assoc);
             foreach ($settings as $field => $config) {
                 if (is_int($field)) {
                     continue;
@@ -191,7 +191,7 @@ class CounterCacheBehavior extends Behavior
     protected function _processAssociations(Event $event, EntityInterface $entity)
     {
         foreach ($this->_config as $assoc => $settings) {
-            $assoc = $this->_table->association($assoc);
+            $assoc = $this->_table->getAssociation($assoc);
             $this->_processAssociation($event, $entity, $assoc, $settings);
         }
     }

+ 1 - 1
src/ORM/Behavior/TranslateBehavior.php

@@ -696,7 +696,7 @@ class TranslateBehavior extends Behavior implements PropertyMarshalInterface
      */
     protected function _findExistingTranslations($ruleSet)
     {
-        $association = $this->_table->association($this->_translationTable->getAlias());
+        $association = $this->_table->getAssociation($this->_translationTable->getAlias());
 
         $query = $association->find()
             ->select(['id', 'num' => 0])

+ 1 - 1
src/ORM/EagerLoader.php

@@ -508,7 +508,7 @@ class EagerLoader
     protected function _normalizeContain(Table $parent, $alias, $options, $paths)
     {
         $defaults = $this->_containOptions;
-        $instance = $parent->association($alias);
+        $instance = $parent->getAssociation($alias);
         if (!$instance) {
             throw new InvalidArgumentException(
                 sprintf('%s is not associated with %s', $parent->getAlias(), $alias)

+ 1 - 1
src/ORM/Marshaller.php

@@ -88,7 +88,7 @@ class Marshaller
                 $key = $nested;
                 $nested = [];
             }
-            $assoc = $this->_table->association($key);
+            $assoc = $this->_table->getAssociation($key);
             // If the key is not a special field like _ids or _joinData
             // it is a missing association that we should error on.
             if (!$assoc) {

+ 1 - 1
src/ORM/Query.php

@@ -433,7 +433,7 @@ class Query extends DatabaseQuery implements JsonSerializable, QueryInterface
     protected function _addAssociationsToTypeMap($table, $typeMap, $associations)
     {
         foreach ($associations as $name => $nested) {
-            $association = $table->association($name);
+            $association = $table->getAssociation($name);
             if (!$association) {
                 continue;
             }

+ 1 - 1
src/ORM/Rule/ExistsIn.php

@@ -80,7 +80,7 @@ class ExistsIn
     public function __invoke(EntityInterface $entity, array $options)
     {
         if (is_string($this->_repository)) {
-            $repository = $options['repository']->association($this->_repository);
+            $repository = $options['repository']->getAssociation($this->_repository);
             if (!$repository) {
                 throw new RuntimeException(sprintf(
                     "ExistsIn rule for '%s' is invalid. '%s' is not associated with '%s'.",

+ 1 - 1
src/ORM/SaveOptionsBuilder.php

@@ -107,7 +107,7 @@ class SaveOptionsBuilder extends ArrayObject
             }
             $this->_checkAssociation($table, $key);
             if (isset($associated['associated'])) {
-                $this->_associated($table->association($key)->getTarget(), $associated['associated']);
+                $this->_associated($table->getAssociation($key)->getTarget(), $associated['associated']);
                 continue;
             }
         }

+ 32 - 1
src/ORM/Table.php

@@ -874,12 +874,43 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc
     /**
      * Returns an association object configured for the specified alias if any
      *
+     * @deprecated 3.6.0 Use getAssociation() instead.
      * @param string $name the alias used for the association.
      * @return \Cake\ORM\Association|null Either the association or null.
      */
     public function association($name)
     {
-        return $this->_associations->get($name);
+        deprecationWarning('Use Table::getAssociation() instead.');
+
+        return $this->getAssociation($name);
+    }
+
+    /**
+     * Returns an association object configured for the specified alias if any.
+     *
+     * The name argument also supports dot syntax to access deeper associations.
+     *
+     * ```
+     * $users = $this->getAssociation('Articles.Comments.Users');
+     * ```
+     *
+     * @param string $name the alias used for the association.
+     * @return \Cake\ORM\Association|null Either the association or null.
+     */
+    public function getAssociation($name)
+    {
+        if (strpos($name, '.') === false) {
+            return $this->_associations->get($name);
+        }
+
+        list($name, $next) = array_pad(explode('.', $name, 2), 2, null);
+        $result = $this->_associations->get($name);
+
+        if ($result !== null && $next !== null) {
+            $result = $result->getTarget()->getAssociation($next);
+        }
+
+        return $result;
     }
 
     /**

+ 27 - 27
tests/TestCase/ORM/Association/BelongsToManyTest.php

@@ -150,20 +150,20 @@ class BelongsToManyTest extends TestCase
         $this->assertInstanceOf('Cake\ORM\Table', $junction);
         $this->assertEquals('ArticlesTags', $junction->alias());
         $this->assertEquals('articles_tags', $junction->table());
-        $this->assertSame($this->article, $junction->association('Articles')->target());
-        $this->assertSame($this->tag, $junction->association('Tags')->target());
+        $this->assertSame($this->article, $junction->getAssociation('Articles')->target());
+        $this->assertSame($this->tag, $junction->getAssociation('Tags')->target());
 
         $belongsTo = '\Cake\ORM\Association\BelongsTo';
-        $this->assertInstanceOf($belongsTo, $junction->association('Articles'));
-        $this->assertInstanceOf($belongsTo, $junction->association('Tags'));
+        $this->assertInstanceOf($belongsTo, $junction->getAssociation('Articles'));
+        $this->assertInstanceOf($belongsTo, $junction->getAssociation('Tags'));
 
-        $this->assertSame($junction, $this->tag->association('ArticlesTags')->target());
-        $this->assertSame($this->article, $this->tag->association('Articles')->target());
+        $this->assertSame($junction, $this->tag->getAssociation('ArticlesTags')->target());
+        $this->assertSame($this->article, $this->tag->getAssociation('Articles')->target());
 
         $hasMany = '\Cake\ORM\Association\HasMany';
         $belongsToMany = '\Cake\ORM\Association\BelongsToMany';
-        $this->assertInstanceOf($belongsToMany, $this->tag->association('Articles'));
-        $this->assertInstanceOf($hasMany, $this->tag->association('ArticlesTags'));
+        $this->assertInstanceOf($belongsToMany, $this->tag->getAssociation('Articles'));
+        $this->assertInstanceOf($hasMany, $this->tag->getAssociation('ArticlesTags'));
 
         $this->assertSame($junction, $assoc->junction());
         $junction2 = TableRegistry::get('Foos');
@@ -173,9 +173,9 @@ class BelongsToManyTest extends TestCase
         $assoc->junction('ArticlesTags');
         $this->assertSame($junction, $assoc->junction());
 
-        $this->assertSame($assoc->strategy(), $this->tag->association('Articles')->strategy());
-        $this->assertSame($assoc->strategy(), $this->tag->association('ArticlesTags')->strategy());
-        $this->assertSame($assoc->strategy(), $this->article->association('ArticlesTags')->strategy());
+        $this->assertSame($assoc->strategy(), $this->tag->getAssociation('Articles')->strategy());
+        $this->assertSame($assoc->strategy(), $this->tag->getAssociation('ArticlesTags')->strategy());
+        $this->assertSame($assoc->strategy(), $this->article->getAssociation('ArticlesTags')->strategy());
     }
 
     /**
@@ -218,13 +218,13 @@ class BelongsToManyTest extends TestCase
             'foreignKey' => 'tag',
             'targetForeignKey' => 'article'
         ]);
-        $junction = $this->article->association('Tags')->junction();
-        $this->assertEquals('article', $junction->association('Articles')->foreignKey());
-        $this->assertEquals('article', $this->article->association('ArticlesTags')->foreignKey());
+        $junction = $this->article->getAssociation('Tags')->junction();
+        $this->assertEquals('article', $junction->getAssociation('Articles')->foreignKey());
+        $this->assertEquals('article', $this->article->getAssociation('ArticlesTags')->foreignKey());
 
-        $junction = $this->tag->association('Articles')->junction();
-        $this->assertEquals('tag', $junction->association('Tags')->foreignKey());
-        $this->assertEquals('tag', $this->tag->association('ArticlesTags')->foreignKey());
+        $junction = $this->tag->getAssociation('Articles')->junction();
+        $this->assertEquals('tag', $junction->getAssociation('Tags')->foreignKey());
+        $this->assertEquals('tag', $this->tag->getAssociation('ArticlesTags')->foreignKey());
     }
 
     /**
@@ -300,7 +300,7 @@ class BelongsToManyTest extends TestCase
         $association = new BelongsToMany('Tags', $config);
         $association->junction($articleTag);
         $this->article
-            ->association($articleTag->alias())
+            ->getAssociation($articleTag->alias())
             ->conditions(['click_count' => 3]);
 
         $articleTag->expects($this->once())
@@ -333,7 +333,7 @@ class BelongsToManyTest extends TestCase
         $association = new BelongsToMany('Tags', $config);
         $association->junction($articleTag);
         $this->article
-            ->association($articleTag->alias())
+            ->getAssociation($articleTag->alias())
             ->conditions(['click_count' => 3]);
 
         $articleTag->expects($this->never())
@@ -360,7 +360,7 @@ class BelongsToManyTest extends TestCase
         ];
         $association = new BelongsToMany('Tag', $config);
         $association->junction($articleTag);
-        $this->article->association($articleTag->alias());
+        $this->article->getAssociation($articleTag->alias());
 
         $counter = $this->getMockBuilder('StdClass')
             ->setMethods(['__invoke'])
@@ -1037,10 +1037,10 @@ class BelongsToManyTest extends TestCase
             'targetForeignKey' => 'Tag'
         ]);
         $junction = $assoc->junction();
-        $this->assertEquals('Art', $junction->association('Articles')->foreignKey());
-        $this->assertEquals('Tag', $junction->association('Tags')->foreignKey());
+        $this->assertEquals('Art', $junction->getAssociation('Articles')->foreignKey());
+        $this->assertEquals('Tag', $junction->getAssociation('Tags')->foreignKey());
 
-        $inverseRelation = $this->tag->association('Articles');
+        $inverseRelation = $this->tag->getAssociation('Articles');
         $this->assertEquals('Tag', $inverseRelation->foreignKey());
         $this->assertEquals('Art', $inverseRelation->targetForeignKey());
     }
@@ -1096,25 +1096,25 @@ class BelongsToManyTest extends TestCase
         // Generate associations
         $assoc->junction();
 
-        $tagAssoc = $articles->association('Tags');
+        $tagAssoc = $articles->getAssociation('Tags');
         $this->assertNotEmpty($tagAssoc, 'btm should exist');
         $this->assertEquals($conditions, $tagAssoc->conditions());
         $this->assertEquals('target_foreign_key', $tagAssoc->targetForeignKey());
         $this->assertEquals('foreign_key', $tagAssoc->foreignKey());
 
-        $jointAssoc = $articles->association('SpecialTags');
+        $jointAssoc = $articles->getAssociation('SpecialTags');
         $this->assertNotEmpty($jointAssoc, 'has many to junction should exist');
         $this->assertInstanceOf('Cake\ORM\Association\HasMany', $jointAssoc);
         $this->assertEquals('foreign_key', $jointAssoc->foreignKey());
 
-        $articleAssoc = $tags->association('Articles');
+        $articleAssoc = $tags->getAssociation('Articles');
         $this->assertNotEmpty($articleAssoc, 'reverse btm should exist');
         $this->assertInstanceOf('Cake\ORM\Association\BelongsToMany', $articleAssoc);
         $this->assertEquals($conditions, $articleAssoc->conditions());
         $this->assertEquals('foreign_key', $articleAssoc->targetForeignKey(), 'keys should swap');
         $this->assertEquals('target_foreign_key', $articleAssoc->foreignKey(), 'keys should swap');
 
-        $jointAssoc = $tags->association('SpecialTags');
+        $jointAssoc = $tags->getAssociation('SpecialTags');
         $this->assertNotEmpty($jointAssoc, 'has many to junction should exist');
         $this->assertInstanceOf('Cake\ORM\Association\HasMany', $jointAssoc);
         $this->assertEquals('target_foreign_key', $jointAssoc->foreignKey());

+ 3 - 3
tests/TestCase/ORM/AssociationProxyTest.php

@@ -57,8 +57,8 @@ class AssociationProxyTest extends TestCase
         $this->assertTrue(isset($articles->authors));
         $this->assertTrue(isset($articles->comments));
         $this->assertFalse(isset($articles->posts));
-        $this->assertSame($articles->association('authors'), $articles->authors);
-        $this->assertSame($articles->association('comments'), $articles->comments);
+        $this->assertSame($articles->getAssociation('authors'), $articles->authors);
+        $this->assertSame($articles->getAssociation('comments'), $articles->comments);
     }
 
     /**
@@ -132,7 +132,7 @@ class AssociationProxyTest extends TestCase
         $articles->belongsTo('authors');
         $authors->hasMany('comments');
         $this->assertTrue(isset($articles->authors->comments));
-        $this->assertSame($authors->association('comments'), $articles->authors->comments);
+        $this->assertSame($authors->getAssociation('comments'), $articles->authors->comments);
     }
 
     /**

+ 1 - 1
tests/TestCase/ORM/Behavior/CounterCacheBehaviorTest.php

@@ -440,7 +440,7 @@ class CounterCacheBehaviorTest extends TestCase
             'bindingKey' => ['category_id', 'user_id'],
             'foreignKey' => ['category_id', 'user_id']
         ]);
-        $this->post->association('UserCategoryPosts')->target($this->userCategoryPosts);
+        $this->post->getAssociation('UserCategoryPosts')->target($this->userCategoryPosts);
         $this->post->addBehavior('CounterCache', [
             'UserCategoryPosts' => ['post_count']
         ]);

+ 3 - 3
tests/TestCase/ORM/CompositeKeysTest.php

@@ -212,7 +212,7 @@ class CompositeKeyTest extends TestCase
             ->toArray();
         $expected[0]['articles'] = [];
         $this->assertEquals($expected, $results);
-        $this->assertEquals($table->association('SiteArticles')->strategy(), $strategy);
+        $this->assertEquals($table->getAssociation('SiteArticles')->strategy(), $strategy);
     }
 
     /**
@@ -301,7 +301,7 @@ class CompositeKeyTest extends TestCase
             ],
         ];
         $this->assertEquals($expected, $results);
-        $this->assertEquals($articles->association('SiteTags')->strategy(), $strategy);
+        $this->assertEquals($articles->getAssociation('SiteTags')->strategy(), $strategy);
     }
 
     /**
@@ -481,7 +481,7 @@ class CompositeKeyTest extends TestCase
         $entity = $table->get([3, 2]);
         $result = $table->delete($entity);
 
-        $query = $table->association('SiteArticles')->find('all', [
+        $query = $table->getAssociation('SiteArticles')->find('all', [
             'conditions' => [
                 'author_id' => $entity->id,
                 'site_id' => $entity->site_id

+ 2 - 2
tests/TestCase/ORM/QueryRegressionTest.php

@@ -1704,9 +1704,9 @@ class QueryRegressionTest extends TestCase
         $this->loadFixtures('Authors', 'Articles', 'Tags', 'ArticlesTags');
         $table = TableRegistry::get('authors');
         $table->hasMany('articles');
-        $articles = $table->association('articles')->target();
+        $articles = $table->getAssociation('articles')->target();
         $articles->hasMany('articlesTags');
-        $tags = $articles->association('articlesTags')->target()->belongsTo('tags');
+        $tags = $articles->getAssociation('articlesTags')->target()->belongsTo('tags');
 
         $tags->target()->getEventManager()->on('Model.beforeFind', function ($e, $query) {
             return $query->formatResults(function ($results) {

+ 8 - 8
tests/TestCase/ORM/QueryTest.php

@@ -273,7 +273,7 @@ class QueryTest extends TestCase
             ->toArray();
         $expected[0]['articles'] = [];
         $this->assertEquals($expected, $results);
-        $this->assertEquals($table->association('articles')->strategy(), $strategy);
+        $this->assertEquals($table->getAssociation('articles')->strategy(), $strategy);
     }
 
     /**
@@ -657,7 +657,7 @@ class QueryTest extends TestCase
             ],
         ];
         $this->assertEquals($expected, $results);
-        $this->assertEquals($table->association('Tags')->strategy(), $strategy);
+        $this->assertEquals($table->getAssociation('Tags')->strategy(), $strategy);
     }
 
     /**
@@ -2323,7 +2323,7 @@ class QueryTest extends TestCase
     {
         $table = TableRegistry::get('ArticlesTags');
         $table->belongsTo('Articles');
-        $table->association('Articles')->target()->belongsTo('Authors');
+        $table->getAssociation('Articles')->target()->belongsTo('Authors');
 
         $builder = function ($q) {
             return $q
@@ -2371,9 +2371,9 @@ class QueryTest extends TestCase
     {
         $table = TableRegistry::get('authors');
         $table->hasMany('articles');
-        $articles = $table->association('articles')->target();
+        $articles = $table->getAssociation('articles')->target();
         $articles->hasMany('articlesTags');
-        $articles->association('articlesTags')->target()->belongsTo('tags');
+        $articles->getAssociation('articlesTags')->target()->belongsTo('tags');
 
         $query = $table->find()->contain(['articles.articlesTags.tags' => function ($q) {
             return $q->formatResults(function ($results) {
@@ -2447,7 +2447,7 @@ class QueryTest extends TestCase
     {
         $table = TableRegistry::get('ArticlesTags');
         $table->belongsTo('Articles');
-        $table->association('Articles')->target()->belongsTo('Authors');
+        $table->getAssociation('Articles')->target()->belongsTo('Authors');
 
         $query = $table->find()
             ->order(['Articles.id' => 'ASC'])
@@ -2469,9 +2469,9 @@ class QueryTest extends TestCase
     {
         $table = TableRegistry::get('authors');
         $table->hasMany('articles');
-        $articles = $table->association('articles')->target();
+        $articles = $table->getAssociation('articles')->target();
         $articles->hasMany('articlesTags');
-        $articles->association('articlesTags')->target()->belongsTo('tags');
+        $articles->getAssociation('articlesTags')->target()->belongsTo('tags');
 
         $query = $table->find()->matching('articles.articlesTags', function ($q) {
             return $q->matching('tags', function ($q) {

+ 9 - 9
tests/TestCase/ORM/RulesCheckerIntegrationTest.php

@@ -65,12 +65,12 @@ class RulesCheckerIntegrationTest extends TestCase
 
         $table = TableRegistry::get('articles');
         $table->belongsTo('authors');
-        $table->association('authors')
+        $table->getAssociation('authors')
             ->target()
             ->rulesChecker()
             ->add(
                 function (Entity $author, array $options) use ($table) {
-                    $this->assertSame($options['repository'], $table->association('authors')->target());
+                    $this->assertSame($options['repository'], $table->getAssociation('authors')->target());
 
                     return false;
                 },
@@ -104,7 +104,7 @@ class RulesCheckerIntegrationTest extends TestCase
 
         $table = TableRegistry::get('authors');
         $table->hasOne('articles');
-        $table->association('articles')
+        $table->getAssociation('articles')
             ->target()
             ->rulesChecker()
             ->add(
@@ -149,7 +149,7 @@ class RulesCheckerIntegrationTest extends TestCase
 
         $table = TableRegistry::get('authors');
         $table->hasMany('articles');
-        $table->association('articles')
+        $table->getAssociation('articles')
             ->target()
             ->rulesChecker()
             ->add(
@@ -198,7 +198,7 @@ class RulesCheckerIntegrationTest extends TestCase
 
         $table = TableRegistry::get('authors');
         $table->hasMany('articles');
-        $table->association('articles')
+        $table->getAssociation('articles')
             ->target()
             ->rulesChecker()
             ->add(
@@ -240,7 +240,7 @@ class RulesCheckerIntegrationTest extends TestCase
         ];
         $table = TableRegistry::get('articles');
         $table->belongsToMany('tags');
-        $table->association('tags')
+        $table->getAssociation('tags')
             ->junction()
             ->rulesChecker()
             ->add(function (Entity $entity) {
@@ -280,7 +280,7 @@ class RulesCheckerIntegrationTest extends TestCase
         ];
         $table = TableRegistry::get('articles');
         $table->belongsToMany('tags');
-        $table->association('tags')
+        $table->getAssociation('tags')
             ->junction()
             ->rulesChecker()
             ->add(function (Entity $entity) {
@@ -1319,8 +1319,8 @@ class RulesCheckerIntegrationTest extends TestCase
 
         $table = TableRegistry::get('authors');
         $table->hasMany('articles');
-        $table->association('articles')->belongsTo('authors');
-        $checker = $table->association('articles')->target()->rulesChecker();
+        $table->getAssociation('articles')->belongsTo('authors');
+        $checker = $table->getAssociation('articles')->target()->rulesChecker();
         $checker->add(function ($entity, $options) use ($checker) {
             $rule = $checker->existsIn('author_id', 'authors');
             $id = $entity->author_id;

+ 40 - 16
tests/TestCase/ORM/TableTest.php

@@ -511,6 +511,30 @@ class TableTest extends TestCase
     }
 
     /**
+     * Test that the getAssociation() method supports the dot syntax.
+     *
+     * @return void
+     */
+    public function testAssociationDotSyntax()
+    {
+        $groups = TableRegistry::get('Groups');
+        $members = TableRegistry::get('Members');
+        $groupsMembers = TableRegistry::get('GroupsMembers');
+
+        $groups->belongsToMany('Members');
+        $groups->hasMany('GroupsMembers');
+        $groupsMembers->belongsTo('Members');
+        $members->belongsToMany('Groups');
+
+        $association = $groups->getAssociation('GroupsMembers.Members.Groups');
+        $this->assertInstanceOf(BelongsToMany::class, $association);
+        $this->assertSame(
+            $groups->getAssociation('GroupsMembers')->getAssociation('Members')->getAssociation('Groups'),
+            $association
+        );
+    }
+
+    /**
      * Tests that belongsTo() creates and configures correctly the association
      *
      * @return void
@@ -521,7 +545,7 @@ class TableTest extends TestCase
         $table = new Table(['table' => 'dates']);
         $belongsTo = $table->belongsTo('user', $options);
         $this->assertInstanceOf('Cake\ORM\Association\BelongsTo', $belongsTo);
-        $this->assertSame($belongsTo, $table->association('user'));
+        $this->assertSame($belongsTo, $table->getAssociation('user'));
         $this->assertEquals('user', $belongsTo->name());
         $this->assertEquals('fake_id', $belongsTo->foreignKey());
         $this->assertEquals(['a' => 'b'], $belongsTo->conditions());
@@ -539,7 +563,7 @@ class TableTest extends TestCase
         $table = new Table(['table' => 'users']);
         $hasOne = $table->hasOne('profile', $options);
         $this->assertInstanceOf('Cake\ORM\Association\HasOne', $hasOne);
-        $this->assertSame($hasOne, $table->association('profile'));
+        $this->assertSame($hasOne, $table->getAssociation('profile'));
         $this->assertEquals('profile', $hasOne->name());
         $this->assertEquals('user_id', $hasOne->foreignKey());
         $this->assertEquals(['b' => 'c'], $hasOne->conditions());
@@ -670,7 +694,7 @@ class TableTest extends TestCase
         $table = new Table(['table' => 'authors']);
         $hasMany = $table->hasMany('article', $options);
         $this->assertInstanceOf('Cake\ORM\Association\HasMany', $hasMany);
-        $this->assertSame($hasMany, $table->association('article'));
+        $this->assertSame($hasMany, $table->getAssociation('article'));
         $this->assertEquals('article', $hasMany->name());
         $this->assertEquals('author_id', $hasMany->foreignKey());
         $this->assertEquals(['b' => 'c'], $hasMany->conditions());
@@ -788,7 +812,7 @@ class TableTest extends TestCase
         $table = new Table(['table' => 'authors', 'connection' => $this->connection]);
         $belongsToMany = $table->belongsToMany('tag', $options);
         $this->assertInstanceOf('Cake\ORM\Association\BelongsToMany', $belongsToMany);
-        $this->assertSame($belongsToMany, $table->association('tag'));
+        $this->assertSame($belongsToMany, $table->getAssociation('tag'));
         $this->assertEquals('tag', $belongsToMany->name());
         $this->assertEquals('thing_id', $belongsToMany->foreignKey());
         $this->assertEquals(['b' => 'c'], $belongsToMany->conditions());
@@ -1924,7 +1948,7 @@ class TableTest extends TestCase
             ]
         );
         $authors->hasMany('Articles', ['saveStrategy' => 'append']);
-        $this->assertEquals('append', $authors->association('articles')->saveStrategy());
+        $this->assertEquals('append', $authors->getAssociation('articles')->saveStrategy());
     }
 
     /**
@@ -3022,7 +3046,7 @@ class TableTest extends TestCase
         $entity = $table->get(1);
         $result = $table->delete($entity);
 
-        $articles = $table->association('articles')->target();
+        $articles = $table->getAssociation('articles')->target();
         $query = $articles->find('all', [
             'conditions' => [
                 'author_id' => $entity->id
@@ -3067,7 +3091,7 @@ class TableTest extends TestCase
         $entity = $query->first();
         $result = $table->delete($entity);
 
-        $articles = $table->association('articles')->target();
+        $articles = $table->getAssociation('articles')->target();
         $query = $articles->find('all')->where(['author_id' => $entity->id]);
         $this->assertCount(2, $query->execute(), 'Should find rows.');
     }
@@ -3088,7 +3112,7 @@ class TableTest extends TestCase
         $entity = $query->first();
         $table->delete($entity);
 
-        $junction = $table->association('tags')->junction();
+        $junction = $table->getAssociation('tags')->junction();
         $query = $junction->find('all')->where(['article_id' => 1]);
         $this->assertNull($query->all()->first(), 'Should not find any rows.');
     }
@@ -4347,7 +4371,7 @@ class TableTest extends TestCase
         $tags[] = $newTag;
 
         $tagsTable->save($newTag);
-        $table->association('tags')->link($article, $tags);
+        $table->getAssociation('tags')->link($article, $tags);
 
         $this->assertEquals($article->tags, $tags);
         foreach ($tags as $tag) {
@@ -4867,7 +4891,7 @@ class TableTest extends TestCase
             ->where(['id' => 1])
             ->contain(['tags'])->first();
 
-        $table->association('tags')->unlink($article, [$article->tags[0]]);
+        $table->getAssociation('tags')->unlink($article, [$article->tags[0]]);
         $this->assertCount(1, $article->tags);
         $this->assertEquals(2, $article->tags[0]->get('id'));
         $this->assertFalse($article->isDirty('tags'));
@@ -4889,7 +4913,7 @@ class TableTest extends TestCase
         $tags[] = new \TestApp\Model\Entity\Tag(['id' => 1], $options);
         $tags[] = new \TestApp\Model\Entity\Tag(['id' => 2], $options);
 
-        $table->association('tags')->unlink($article, $tags);
+        $table->getAssociation('tags')->unlink($article, $tags);
         $left = $table->find('all')->where(['id' => 1])->contain(['tags'])->first();
         $this->assertEmpty($left->tags);
     }
@@ -4916,7 +4940,7 @@ class TableTest extends TestCase
             'tag_id' => 2
         ], $options);
 
-        $table->association('tags')->unlink($article, $tags);
+        $table->getAssociation('tags')->unlink($article, $tags);
         $left = $table->find('all')->where(['id' => 1])->contain(['tags'])->first();
         $this->assertEmpty($left->tags);
     }
@@ -4938,7 +4962,7 @@ class TableTest extends TestCase
         $tags[] = new \TestApp\Model\Entity\Tag(['id' => 3], $options);
         $tags[] = new \TestApp\Model\Entity\Tag(['name' => 'foo']);
 
-        $table->association('tags')->replaceLinks($article, $tags);
+        $table->getAssociation('tags')->replaceLinks($article, $tags);
         $this->assertEquals(2, $article->tags[0]->id);
         $this->assertEquals(3, $article->tags[1]->id);
         $this->assertEquals(4, $article->tags[2]->id);
@@ -4966,7 +4990,7 @@ class TableTest extends TestCase
         $article = new Entity(['id' => 1], $options);
         $tags = [];
 
-        $table->association('tags')->replaceLinks($article, $tags);
+        $table->getAssociation('tags')->replaceLinks($article, $tags);
         $this->assertSame($tags, $article->tags);
         $article = $table->find('all')->where(['id' => 1])->contain(['tags'])->first();
         $this->assertEmpty($article->tags);
@@ -4995,7 +5019,7 @@ class TableTest extends TestCase
         ], $options);
         $tags[] = new \TestApp\Model\Entity\Tag(['id' => 3], $options);
 
-        $table->association('tags')->replaceLinks($article, $tags);
+        $table->getAssociation('tags')->replaceLinks($article, $tags);
         $this->assertSame($tags, $article->tags);
         $article = $table->find('all')->where(['id' => 1])->contain(['tags'])->first();
         $this->assertCount(2, $article->tags);
@@ -6091,7 +6115,7 @@ class TableTest extends TestCase
         $eventManager = $table->getEventManager();
 
         $associationBeforeFindCount = 0;
-        $table->association('authors')->target()->getEventManager()->on(
+        $table->getAssociation('authors')->target()->getEventManager()->on(
             'Model.beforeFind',
             function (Event $event, Query $query, ArrayObject $options, $primary) use (&$associationBeforeFindCount) {
                 $this->assertTrue(is_bool($primary));