| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503 |
- <?php
- /**
- * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
- * Copyright (c) Cake Software Foundation, Inc. (https://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. (https://cakefoundation.org)
- * @link https://cakephp.org CakePHP(tm) Project
- * @since 3.0.0
- * @license https://opensource.org/licenses/mit-license.php MIT License
- */
- namespace Cake\Test\TestCase\ORM\Association;
- use Cake\Database\Expression\QueryExpression;
- use Cake\Datasource\ConnectionManager;
- use Cake\Event\Event;
- use Cake\ORM\Association\BelongsTo;
- use Cake\ORM\Association\BelongsToMany;
- use Cake\ORM\Association\HasMany;
- use Cake\ORM\Entity;
- use Cake\ORM\Table;
- use Cake\TestSuite\TestCase;
- /**
- * Tests BelongsToMany class
- */
- class BelongsToManyTest extends TestCase
- {
- /**
- * Fixtures
- *
- * @var array
- */
- public $fixtures = ['core.Articles', 'core.SpecialTags', 'core.ArticlesTags', 'core.Tags'];
- /**
- * Set up
- *
- * @return void
- */
- public function setUp()
- {
- parent::setUp();
- $this->tag = $this->getMockBuilder('Cake\ORM\Table')
- ->setMethods(['find', 'delete'])
- ->setConstructorArgs([['alias' => 'Tags', 'table' => 'tags']])
- ->getMock();
- $this->tag->setSchema([
- 'id' => ['type' => 'integer'],
- 'name' => ['type' => 'string'],
- '_constraints' => [
- 'primary' => ['type' => 'primary', 'columns' => ['id']],
- ],
- ]);
- $this->article = $this->getMockBuilder('Cake\ORM\Table')
- ->setMethods(['find', 'delete'])
- ->setConstructorArgs([['alias' => 'Articles', 'table' => 'articles']])
- ->getMock();
- $this->article->setSchema([
- 'id' => ['type' => 'integer'],
- 'name' => ['type' => 'string'],
- '_constraints' => [
- 'primary' => ['type' => 'primary', 'columns' => ['id']],
- ],
- ]);
- }
- /**
- * Tests setForeignKey()
- *
- * @return void
- */
- public function testSetForeignKey()
- {
- $assoc = new BelongsToMany('Test', [
- 'sourceTable' => $this->article,
- 'targetTable' => $this->tag,
- ]);
- $this->assertEquals('article_id', $assoc->getForeignKey());
- $this->assertSame($assoc, $assoc->setForeignKey('another_key'));
- $this->assertEquals('another_key', $assoc->getForeignKey());
- }
- /**
- * Tests that foreignKey() returns the correct configured value
- *
- * @group deprecated
- * @return void
- */
- public function testForeignKey()
- {
- $this->deprecated(function () {
- $assoc = new BelongsToMany('Test', [
- 'sourceTable' => $this->article,
- 'targetTable' => $this->tag,
- ]);
- $this->assertEquals('article_id', $assoc->foreignKey());
- $this->assertEquals('another_key', $assoc->foreignKey('another_key'));
- $this->assertEquals('another_key', $assoc->foreignKey());
- });
- }
- /**
- * Tests that the association reports it can be joined
- *
- * @return void
- */
- public function testCanBeJoined()
- {
- $assoc = new BelongsToMany('Test');
- $this->assertFalse($assoc->canBeJoined());
- }
- /**
- * Tests sort() method
- *
- * @group deprecated
- * @return void
- */
- public function testSort()
- {
- $this->deprecated(function () {
- $assoc = new BelongsToMany('Test');
- $this->assertNull($assoc->sort());
- $assoc->sort(['id' => 'ASC']);
- $this->assertEquals(['id' => 'ASC'], $assoc->sort());
- });
- }
- /**
- * Tests setSort() method
- *
- * @return void
- */
- public function testSetSort()
- {
- $assoc = new BelongsToMany('Test');
- $this->assertNull($assoc->getSort());
- $assoc->setSort(['id' => 'ASC']);
- $this->assertEquals(['id' => 'ASC'], $assoc->getSort());
- }
- /**
- * Tests requiresKeys() method
- *
- * @return void
- */
- public function testRequiresKeys()
- {
- $assoc = new BelongsToMany('Test');
- $this->assertTrue($assoc->requiresKeys());
- $assoc->setStrategy(BelongsToMany::STRATEGY_SUBQUERY);
- $this->assertFalse($assoc->requiresKeys());
- $assoc->setStrategy(BelongsToMany::STRATEGY_SELECT);
- $this->assertTrue($assoc->requiresKeys());
- }
- /**
- * Tests that BelongsToMany can't use the join strategy
- *
- * @return void
- */
- public function testStrategyFailure()
- {
- $this->expectException(\InvalidArgumentException::class);
- $this->expectExceptionMessage('Invalid strategy "join" was provided');
- $assoc = new BelongsToMany('Test');
- $assoc->setStrategy(BelongsToMany::STRATEGY_JOIN);
- }
- /**
- * Tests the junction method
- *
- * @return void
- */
- public function testJunction()
- {
- $assoc = new BelongsToMany('Test', [
- 'sourceTable' => $this->article,
- 'targetTable' => $this->tag,
- 'strategy' => 'subquery',
- ]);
- $junction = $assoc->junction();
- $this->assertInstanceOf(Table::class, $junction);
- $this->assertEquals('ArticlesTags', $junction->getAlias());
- $this->assertEquals('articles_tags', $junction->getTable());
- $this->assertSame($this->article, $junction->getAssociation('Articles')->getTarget());
- $this->assertSame($this->tag, $junction->getAssociation('Tags')->getTarget());
- $this->assertInstanceOf(BelongsTo::class, $junction->getAssociation('Articles'));
- $this->assertInstanceOf(BelongsTo::class, $junction->getAssociation('Tags'));
- $this->assertSame($junction, $this->tag->getAssociation('ArticlesTags')->getTarget());
- $this->assertSame($this->article, $this->tag->getAssociation('Articles')->getTarget());
- $this->assertInstanceOf(BelongsToMany::class, $this->tag->getAssociation('Articles'));
- $this->assertInstanceOf(HasMany::class, $this->tag->getAssociation('ArticlesTags'));
- $this->assertSame($junction, $assoc->junction());
- $junction2 = $this->getTableLocator()->get('Foos');
- $assoc->junction($junction2);
- $this->assertSame($junction2, $assoc->junction());
- $assoc->junction('ArticlesTags');
- $this->assertSame($junction, $assoc->junction());
- $this->assertSame($assoc->getStrategy(), $this->tag->getAssociation('Articles')->getStrategy());
- $this->assertSame($assoc->getStrategy(), $this->tag->getAssociation('ArticlesTags')->getStrategy());
- $this->assertSame($assoc->getStrategy(), $this->article->getAssociation('ArticlesTags')->getStrategy());
- $this->assertSame($this->article->getPrimaryKey(), $junction->getAssociation('Articles')->getBindingKey());
- $this->assertSame($this->tag->getPrimaryKey(), $junction->getAssociation('Tags')->getBindingKey());
- }
- /**
- * Tests the junction passes the source connection name on.
- *
- * @return void
- */
- public function testJunctionConnection()
- {
- $mock = $this->getMockBuilder('Cake\Database\Connection')
- ->setMethods(['setDriver'])
- ->setConstructorArgs(['name' => 'other_source'])
- ->getMock();
- ConnectionManager::setConfig('other_source', $mock);
- $this->article->setConnection(ConnectionManager::get('other_source'));
- $assoc = new BelongsToMany('Test', [
- 'sourceTable' => $this->article,
- 'targetTable' => $this->tag,
- ]);
- $junction = $assoc->junction();
- $this->assertSame($mock, $junction->getConnection());
- ConnectionManager::drop('other_source');
- }
- /**
- * Tests the junction method custom keys
- *
- * @return void
- */
- public function testJunctionCustomKeys()
- {
- $this->article->belongsToMany('Tags', [
- 'joinTable' => 'articles_tags',
- 'foreignKey' => 'article',
- 'targetForeignKey' => 'tag',
- ]);
- $this->tag->belongsToMany('Articles', [
- 'joinTable' => 'articles_tags',
- 'foreignKey' => 'tag',
- 'targetForeignKey' => 'article',
- ]);
- $junction = $this->article->getAssociation('Tags')->junction();
- $this->assertEquals('article', $junction->getAssociation('Articles')->getForeignKey());
- $this->assertEquals('article', $this->article->getAssociation('ArticlesTags')->getForeignKey());
- $junction = $this->tag->getAssociation('Articles')->junction();
- $this->assertEquals('tag', $junction->getAssociation('Tags')->getForeignKey());
- $this->assertEquals('tag', $this->tag->getAssociation('ArticlesTags')->getForeignKey());
- }
- /**
- * Tests it is possible to set the table name for the join table
- *
- * @return void
- */
- public function testJunctionWithDefaultTableName()
- {
- $assoc = new BelongsToMany('Test', [
- 'sourceTable' => $this->article,
- 'targetTable' => $this->tag,
- 'joinTable' => 'tags_articles',
- ]);
- $junction = $assoc->junction();
- $this->assertEquals('TagsArticles', $junction->getAlias());
- $this->assertEquals('tags_articles', $junction->getTable());
- }
- /**
- * Tests saveStrategy
- *
- * @group deprecated
- * @return void
- */
- public function testSaveStrategy()
- {
- $this->deprecated(function () {
- $assoc = new BelongsToMany('Test');
- $this->assertEquals(BelongsToMany::SAVE_REPLACE, $assoc->saveStrategy());
- $assoc->saveStrategy(BelongsToMany::SAVE_APPEND);
- $this->assertEquals(BelongsToMany::SAVE_APPEND, $assoc->saveStrategy());
- $assoc->saveStrategy(BelongsToMany::SAVE_REPLACE);
- $this->assertEquals(BelongsToMany::SAVE_REPLACE, $assoc->saveStrategy());
- });
- }
- /**
- * Tests saveStrategy
- *
- * @return void
- */
- public function testSetSaveStrategy()
- {
- $assoc = new BelongsToMany('Test');
- $this->assertEquals(BelongsToMany::SAVE_REPLACE, $assoc->getSaveStrategy());
- $assoc->setSaveStrategy(BelongsToMany::SAVE_APPEND);
- $this->assertEquals(BelongsToMany::SAVE_APPEND, $assoc->getSaveStrategy());
- $assoc->setSaveStrategy(BelongsToMany::SAVE_REPLACE);
- $this->assertEquals(BelongsToMany::SAVE_REPLACE, $assoc->getSaveStrategy());
- }
- /**
- * Tests that it is possible to pass the saveAssociated strategy in the constructor
- *
- * @return void
- */
- public function testSaveStrategyInOptions()
- {
- $assoc = new BelongsToMany('Test', ['saveStrategy' => BelongsToMany::SAVE_APPEND]);
- $this->assertEquals(BelongsToMany::SAVE_APPEND, $assoc->getSaveStrategy());
- }
- /**
- * Tests that passing an invalid strategy will throw an exception
- *
- * @return void
- */
- public function testSaveStrategyInvalid()
- {
- $this->expectException(\InvalidArgumentException::class);
- $this->expectExceptionMessage('Invalid save strategy "depsert"');
- $assoc = new BelongsToMany('Test', ['saveStrategy' => 'depsert']);
- }
- /**
- * Test cascading deletes.
- *
- * @return void
- */
- public function testCascadeDelete()
- {
- $articleTag = $this->getMockBuilder('Cake\ORM\Table')
- ->setMethods(['deleteAll'])
- ->getMock();
- $config = [
- 'sourceTable' => $this->article,
- 'targetTable' => $this->tag,
- 'sort' => ['id' => 'ASC'],
- ];
- $association = new BelongsToMany('Tags', $config);
- $association->junction($articleTag);
- $this->article
- ->getAssociation($articleTag->getAlias())
- ->setConditions(['click_count' => 3]);
- $articleTag->expects($this->once())
- ->method('deleteAll')
- ->with([
- 'click_count' => 3,
- 'article_id' => 1,
- ]);
- $entity = new Entity(['id' => 1, 'name' => 'PHP']);
- $association->cascadeDelete($entity);
- }
- /**
- * Test cascading deletes with dependent=false
- *
- * @return void
- */
- public function testCascadeDeleteDependent()
- {
- $articleTag = $this->getMockBuilder('Cake\ORM\Table')
- ->setMethods(['delete', 'deleteAll'])
- ->getMock();
- $config = [
- 'sourceTable' => $this->article,
- 'targetTable' => $this->tag,
- 'dependent' => false,
- 'sort' => ['id' => 'ASC'],
- ];
- $association = new BelongsToMany('Tags', $config);
- $association->junction($articleTag);
- $this->article
- ->getAssociation($articleTag->getAlias())
- ->setConditions(['click_count' => 3]);
- $articleTag->expects($this->never())
- ->method('deleteAll');
- $articleTag->expects($this->never())
- ->method('delete');
- $entity = new Entity(['id' => 1, 'name' => 'PHP']);
- $association->cascadeDelete($entity);
- }
- /**
- * Test cascading deletes with callbacks.
- *
- * @return void
- */
- public function testCascadeDeleteWithCallbacks()
- {
- $articleTag = $this->getTableLocator()->get('ArticlesTags');
- $config = [
- 'sourceTable' => $this->article,
- 'targetTable' => $this->tag,
- 'cascadeCallbacks' => true,
- ];
- $association = new BelongsToMany('Tag', $config);
- $association->junction($articleTag);
- $this->article->getAssociation($articleTag->getAlias());
- $counter = $this->getMockBuilder('StdClass')
- ->setMethods(['__invoke'])
- ->getMock();
- $counter->expects($this->exactly(2))->method('__invoke');
- $articleTag->getEventManager()->on('Model.beforeDelete', $counter);
- $this->assertEquals(2, $articleTag->find()->where(['article_id' => 1])->count());
- $entity = new Entity(['id' => 1, 'name' => 'PHP']);
- $association->cascadeDelete($entity);
- $this->assertEquals(0, $articleTag->find()->where(['article_id' => 1])->count());
- }
- /**
- * Test linking entities having a non persisted source entity
- *
- * @return void
- */
- public function testLinkWithNotPersistedSource()
- {
- $this->expectException(\InvalidArgumentException::class);
- $this->expectExceptionMessage('Source entity needs to be persisted before links can be created or removed');
- $config = [
- 'sourceTable' => $this->article,
- 'targetTable' => $this->tag,
- 'joinTable' => 'tags_articles',
- ];
- $assoc = new BelongsToMany('Test', $config);
- $entity = new Entity(['id' => 1]);
- $tags = [new Entity(['id' => 2]), new Entity(['id' => 3])];
- $assoc->link($entity, $tags);
- }
- /**
- * Test liking entities having a non persisted target entity
- *
- * @return void
- */
- public function testLinkWithNotPersistedTarget()
- {
- $this->expectException(\InvalidArgumentException::class);
- $this->expectExceptionMessage('Cannot link entities that have not been persisted yet');
- $config = [
- 'sourceTable' => $this->article,
- 'targetTable' => $this->tag,
- 'joinTable' => 'tags_articles',
- ];
- $assoc = new BelongsToMany('Test', $config);
- $entity = new Entity(['id' => 1], ['markNew' => false]);
- $tags = [new Entity(['id' => 2]), new Entity(['id' => 3])];
- $assoc->link($entity, $tags);
- }
- /**
- * Tests that linking entities will persist correctly with append strategy
- *
- * @return void
- */
- public function testLinkSuccessSaveAppend()
- {
- $articles = $this->getTableLocator()->get('Articles');
- $tags = $this->getTableLocator()->get('Tags');
- $config = [
- 'sourceTable' => $articles,
- 'targetTable' => $tags,
- 'joinTable' => 'articles_tags',
- 'saveStrategy' => BelongsToMany::SAVE_APPEND,
- ];
- $assoc = $articles->belongsToMany('Tags', $config);
- // Load without tags as that is a main use case for append strategies
- $article = $articles->get(1);
- $opts = ['markNew' => false];
- $tags = [
- new Entity(['id' => 2, 'name' => 'add'], $opts),
- new Entity(['id' => 3, 'name' => 'adder'], $opts),
- ];
- $this->assertTrue($assoc->link($article, $tags));
- $this->assertCount(2, $article->tags, 'In-memory tags are incorrect');
- $this->assertSame([2, 3], collection($article->tags)->extract('id')->toList());
- $article = $articles->get(1, ['contain' => ['Tags']]);
- $this->assertCount(3, $article->tags, 'Persisted tags are wrong');
- $this->assertSame([1, 2, 3], collection($article->tags)->extract('id')->toList());
- }
- /**
- * Tests that linking the same tag to multiple articles works
- *
- * @return void
- */
- public function testLinkSaveAppendSharedTarget()
- {
- $articles = $this->getTableLocator()->get('Articles');
- $tags = $this->getTableLocator()->get('Tags');
- $articlesTags = $this->getTableLocator()->get('ArticlesTags');
- $articlesTags->deleteAll('1=1');
- $config = [
- 'sourceTable' => $articles,
- 'targetTable' => $tags,
- 'joinTable' => 'articles_tags',
- 'saveStrategy' => BelongsToMany::SAVE_APPEND,
- ];
- $assoc = $articles->belongsToMany('Tags', $config);
- $articleOne = $articles->get(1);
- $articleTwo = $articles->get(2);
- $tagTwo = $tags->get(2);
- $tagThree = $tags->get(3);
- $this->assertTrue($assoc->link($articleOne, [$tagThree, $tagTwo]));
- $this->assertTrue($assoc->link($articleTwo, [$tagThree]));
- $this->assertCount(2, $articleOne->tags, 'In-memory tags are incorrect');
- $this->assertSame([3, 2], collection($articleOne->tags)->extract('id')->toList());
- $this->assertCount(1, $articleTwo->tags, 'In-memory tags are incorrect');
- $this->assertSame([3], collection($articleTwo->tags)->extract('id')->toList());
- $rows = $articlesTags->find()->all();
- $this->assertCount(3, $rows, '3 link rows should be created.');
- }
- /**
- * Tests that liking entities will validate data and pass on to _saveLinks
- *
- * @return void
- */
- public function testLinkSuccessWithMocks()
- {
- $connection = ConnectionManager::get('test');
- $joint = $this->getMockBuilder('\Cake\ORM\Table')
- ->setMethods(['save', 'getPrimaryKey'])
- ->setConstructorArgs([['alias' => 'ArticlesTags', 'connection' => $connection]])
- ->getMock();
- $config = [
- 'sourceTable' => $this->article,
- 'targetTable' => $this->tag,
- 'through' => $joint,
- 'joinTable' => 'tags_articles',
- ];
- $assoc = new BelongsToMany('Test', $config);
- $opts = ['markNew' => false];
- $entity = new Entity(['id' => 1], $opts);
- $tags = [new Entity(['id' => 2], $opts), new Entity(['id' => 3], $opts)];
- $saveOptions = ['foo' => 'bar'];
- $joint->method('getPrimaryKey')
- ->will($this->returnValue(['article_id', 'tag_id']));
- $joint->expects($this->at(1))
- ->method('save')
- ->will($this->returnCallback(function ($e, $opts) use ($entity) {
- $expected = ['article_id' => 1, 'tag_id' => 2];
- $this->assertEquals($expected, $e->toArray());
- $this->assertEquals(['foo' => 'bar'], $opts);
- $this->assertTrue($e->isNew());
- return $entity;
- }));
- $joint->expects($this->at(2))
- ->method('save')
- ->will($this->returnCallback(function ($e, $opts) use ($entity) {
- $expected = ['article_id' => 1, 'tag_id' => 3];
- $this->assertEquals($expected, $e->toArray());
- $this->assertEquals(['foo' => 'bar'], $opts);
- $this->assertTrue($e->isNew());
- return $entity;
- }));
- $this->assertTrue($assoc->link($entity, $tags, $saveOptions));
- $this->assertSame($entity->test, $tags);
- }
- /**
- * Tests that linking entities will set the junction table registry alias
- *
- * @return void
- */
- public function testLinkSetSourceToJunctionEntities()
- {
- $connection = ConnectionManager::get('test');
- $joint = $this->getMockBuilder('\Cake\ORM\Table')
- ->setMethods(['save', 'getPrimaryKey'])
- ->setConstructorArgs([['alias' => 'ArticlesTags', 'connection' => $connection]])
- ->getMock();
- $joint->setRegistryAlias('Plugin.ArticlesTags');
- $config = [
- 'sourceTable' => $this->article,
- 'targetTable' => $this->tag,
- 'through' => $joint,
- ];
- $assoc = new BelongsToMany('Tags', $config);
- $opts = ['markNew' => false];
- $entity = new Entity(['id' => 1], $opts);
- $tags = [new Entity(['id' => 2], $opts)];
- $joint->method('getPrimaryKey')
- ->will($this->returnValue(['article_id', 'tag_id']));
- $joint->expects($this->once())
- ->method('save')
- ->will($this->returnCallback(function (Entity $e, $opts) {
- $this->assertSame('Plugin.ArticlesTags', $e->getSource());
- return $e;
- }));
- $this->assertTrue($assoc->link($entity, $tags));
- $this->assertSame($entity->tags, $tags);
- $this->assertSame('Plugin.ArticlesTags', $entity->tags[0]->get('_joinData')->getSource());
- }
- /**
- * Test liking entities having a non persisted source entity
- *
- * @return void
- */
- public function testUnlinkWithNotPersistedSource()
- {
- $this->expectException(\InvalidArgumentException::class);
- $this->expectExceptionMessage('Source entity needs to be persisted before links can be created or removed');
- $config = [
- 'sourceTable' => $this->article,
- 'targetTable' => $this->tag,
- 'joinTable' => 'tags_articles',
- ];
- $assoc = new BelongsToMany('Test', $config);
- $entity = new Entity(['id' => 1]);
- $tags = [new Entity(['id' => 2]), new Entity(['id' => 3])];
- $assoc->unlink($entity, $tags);
- }
- /**
- * Test liking entities having a non persisted target entity
- *
- * @return void
- */
- public function testUnlinkWithNotPersistedTarget()
- {
- $this->expectException(\InvalidArgumentException::class);
- $this->expectExceptionMessage('Cannot link entities that have not been persisted');
- $config = [
- 'sourceTable' => $this->article,
- 'targetTable' => $this->tag,
- 'joinTable' => 'tags_articles',
- ];
- $assoc = new BelongsToMany('Test', $config);
- $entity = new Entity(['id' => 1], ['markNew' => false]);
- $tags = [new Entity(['id' => 2]), new Entity(['id' => 3])];
- $assoc->unlink($entity, $tags);
- }
- /**
- * Tests that unlinking calls the right methods
- *
- * @return void
- */
- public function testUnlinkSuccess()
- {
- $joint = $this->getTableLocator()->get('SpecialTags');
- $articles = $this->getTableLocator()->get('Articles');
- $tags = $this->getTableLocator()->get('Tags');
- $assoc = $articles->belongsToMany('Tags', [
- 'sourceTable' => $articles,
- 'targetTable' => $tags,
- 'through' => $joint,
- 'joinTable' => 'special_tags',
- ]);
- $entity = $articles->get(2, ['contain' => 'Tags']);
- $initial = $entity->tags;
- $this->assertCount(1, $initial);
- $this->assertTrue($assoc->unlink($entity, $entity->tags));
- $this->assertEmpty($entity->get('tags'), 'Property should be empty');
- $new = $articles->get(2, ['contain' => 'Tags']);
- $this->assertCount(0, $new->tags, 'DB should be clean');
- $this->assertSame(3, $tags->find()->count(), 'Tags should still exist');
- }
- /**
- * Tests that unlinking with last parameter set to false
- * will not remove entities from the association property
- *
- * @return void
- */
- public function testUnlinkWithoutPropertyClean()
- {
- $joint = $this->getTableLocator()->get('SpecialTags');
- $articles = $this->getTableLocator()->get('Articles');
- $tags = $this->getTableLocator()->get('Tags');
- $assoc = $articles->belongsToMany('Tags', [
- 'sourceTable' => $articles,
- 'targetTable' => $tags,
- 'through' => $joint,
- 'joinTable' => 'special_tags',
- 'conditions' => ['SpecialTags.highlighted' => true],
- ]);
- $entity = $articles->get(2, ['contain' => 'Tags']);
- $initial = $entity->tags;
- $this->assertCount(1, $initial);
- $this->assertTrue($assoc->unlink($entity, $initial, ['cleanProperty' => false]));
- $this->assertNotEmpty($entity->get('tags'), 'Property should not be empty');
- $this->assertEquals($initial, $entity->get('tags'), 'Property should be untouched');
- $new = $articles->get(2, ['contain' => 'Tags']);
- $this->assertCount(0, $new->tags, 'DB should be clean');
- }
- /**
- * Tests that replaceLink requires the sourceEntity to have primaryKey values
- * for the source entity
- *
- * @return void
- */
- public function testReplaceWithMissingPrimaryKey()
- {
- $this->expectException(\InvalidArgumentException::class);
- $this->expectExceptionMessage('Could not find primary key value for source entity');
- $config = [
- 'sourceTable' => $this->article,
- 'targetTable' => $this->tag,
- 'joinTable' => 'tags_articles',
- ];
- $assoc = new BelongsToMany('Test', $config);
- $entity = new Entity(['foo' => 1], ['markNew' => false]);
- $tags = [new Entity(['id' => 2]), new Entity(['id' => 3])];
- $assoc->replaceLinks($entity, $tags);
- }
- /**
- * Test that replaceLinks() can saveAssociated an empty set, removing all rows.
- *
- * @return void
- */
- public function testReplaceLinksUpdateToEmptySet()
- {
- $joint = $this->getTableLocator()->get('ArticlesTags');
- $articles = $this->getTableLocator()->get('Articles');
- $tags = $this->getTableLocator()->get('Tags');
- $assoc = $articles->belongsToMany('Tags', [
- 'sourceTable' => $articles,
- 'targetTable' => $tags,
- 'through' => $joint,
- 'joinTable' => 'articles_tags',
- ]);
- $entity = $articles->get(1, ['contain' => 'Tags']);
- $this->assertCount(2, $entity->tags);
- $assoc->replaceLinks($entity, []);
- $this->assertSame([], $entity->tags, 'Property should be empty');
- $this->assertFalse($entity->isDirty('tags'), 'Property should be cleaned');
- $new = $articles->get(1, ['contain' => 'Tags']);
- $this->assertSame([], $entity->tags, 'Should not be data in db');
- }
- /**
- * Tests that replaceLinks will delete entities not present in the passed,
- * array, maintain those are already persisted and were passed and also
- * insert the rest.
- *
- * @return void
- */
- public function testReplaceLinkSuccess()
- {
- $joint = $this->getTableLocator()->get('ArticlesTags');
- $articles = $this->getTableLocator()->get('Articles');
- $tags = $this->getTableLocator()->get('Tags');
- $assoc = $articles->belongsToMany('Tags', [
- 'sourceTable' => $articles,
- 'targetTable' => $tags,
- 'through' => $joint,
- 'joinTable' => 'articles_tags',
- ]);
- $entity = $articles->get(1, ['contain' => 'Tags']);
- // 1=existing, 2=removed, 3=new link, & new tag
- $tagData = [
- new Entity(['id' => 1], ['markNew' => false]),
- new Entity(['id' => 3]),
- new Entity(['name' => 'net new']),
- ];
- $result = $assoc->replaceLinks($entity, $tagData, ['associated' => false]);
- $this->assertTrue($result);
- $this->assertSame($tagData, $entity->tags, 'Tags should match replaced objects');
- $this->assertFalse($entity->isDirty('tags'), 'Should be clean');
- $fresh = $articles->get(1, ['contain' => 'Tags']);
- $this->assertCount(3, $fresh->tags, 'Records should be in db');
- $this->assertNotEmpty($tags->get(2), 'Unlinked tag should still exist');
- }
- /**
- * Tests that replaceLinks() will contain() the target table when
- * there are conditions present on the association.
- *
- * In this case the replacement will fail because the association conditions
- * hide the fixture data.
- *
- * @return void
- */
- public function testReplaceLinkWithConditions()
- {
- $joint = $this->getTableLocator()->get('SpecialTags');
- $articles = $this->getTableLocator()->get('Articles');
- $tags = $this->getTableLocator()->get('Tags');
- $assoc = $articles->belongsToMany('Tags', [
- 'sourceTable' => $articles,
- 'targetTable' => $tags,
- 'through' => $joint,
- 'joinTable' => 'special_tags',
- 'conditions' => ['SpecialTags.highlighted' => true],
- ]);
- $entity = $articles->get(1, ['contain' => 'Tags']);
- $result = $assoc->replaceLinks($entity, [], ['associated' => false]);
- $this->assertTrue($result);
- $this->assertSame([], $entity->tags, 'Tags should match replaced objects');
- $this->assertFalse($entity->isDirty('tags'), 'Should be clean');
- $fresh = $articles->get(1, ['contain' => 'Tags']);
- $this->assertCount(0, $fresh->tags, 'Association should be empty');
- $jointCount = $joint->find()->where(['article_id' => 1])->count();
- $this->assertSame(1, $jointCount, 'Non matching joint record should remain.');
- }
- /**
- * Tests replaceLinks with failing domain rules and new link targets.
- *
- * @return void
- */
- public function testReplaceLinkFailingDomainRules()
- {
- $articles = $this->getTableLocator()->get('Articles');
- $tags = $this->getTableLocator()->get('Tags');
- $tags->getEventManager()->on('Model.buildRules', function (Event $event, $rules) {
- $rules->add(function () {
- return false;
- }, 'rule', ['errorField' => 'name', 'message' => 'Bad data']);
- });
- $assoc = $articles->belongsToMany('Tags', [
- 'sourceTable' => $articles,
- 'targetTable' => $tags,
- 'through' => $this->getTableLocator()->get('ArticlesTags'),
- 'joinTable' => 'articles_tags',
- ]);
- $entity = $articles->get(1, ['contain' => 'Tags']);
- $originalCount = count($entity->tags);
- $tags = [
- new Entity(['name' => 'tag99', 'description' => 'Best tag']),
- ];
- $result = $assoc->replaceLinks($entity, $tags);
- $this->assertFalse($result, 'replace should have failed.');
- $this->assertNotEmpty($tags[0]->getErrors(), 'Bad entity should have errors.');
- $entity = $articles->get(1, ['contain' => 'Tags']);
- $this->assertCount($originalCount, $entity->tags, 'Should not have changed.');
- $this->assertEquals('tag1', $entity->tags[0]->name);
- }
- /**
- * Provider for empty values
- *
- * @return array
- */
- public function emptyProvider()
- {
- return [
- [''],
- [false],
- [null],
- [[]],
- ];
- }
- /**
- * Test that saveAssociated() fails on non-empty, non-iterable value
- *
- * @return void
- */
- public function testSaveAssociatedNotEmptyNotIterable()
- {
- $this->expectException(\InvalidArgumentException::class);
- $this->expectExceptionMessage('Could not save tags, it cannot be traversed');
- $articles = $this->getTableLocator()->get('Articles');
- $assoc = $articles->belongsToMany('Tags', [
- 'saveStrategy' => BelongsToMany::SAVE_APPEND,
- 'joinTable' => 'articles_tags',
- ]);
- $entity = new Entity([
- 'id' => 1,
- 'tags' => 'oh noes',
- ], ['markNew' => true]);
- $assoc->saveAssociated($entity);
- }
- /**
- * Test that saving an empty set on create works.
- *
- * @dataProvider emptyProvider
- * @return void
- */
- public function testSaveAssociatedEmptySetSuccess($value)
- {
- $table = $this->getMockBuilder('Cake\ORM\Table')
- ->setMethods(['table'])
- ->getMock();
- $table->setSchema([]);
- $assoc = $this->getMockBuilder('\Cake\ORM\Association\BelongsToMany')
- ->setMethods(['_saveTarget', 'replaceLinks'])
- ->setConstructorArgs(['tags', ['sourceTable' => $table]])
- ->getMock();
- $entity = new Entity([
- 'id' => 1,
- 'tags' => $value,
- ], ['markNew' => true]);
- $assoc->setSaveStrategy(BelongsToMany::SAVE_REPLACE);
- $assoc->expects($this->never())
- ->method('replaceLinks');
- $assoc->expects($this->never())
- ->method('_saveTarget');
- $this->assertSame($entity, $assoc->saveAssociated($entity));
- }
- /**
- * Test that saving an empty set on update works.
- *
- * @dataProvider emptyProvider
- * @return void
- */
- public function testSaveAssociatedEmptySetUpdateSuccess($value)
- {
- $table = $this->getMockBuilder('Cake\ORM\Table')
- ->setMethods(['table'])
- ->getMock();
- $table->setSchema([]);
- $assoc = $this->getMockBuilder('\Cake\ORM\Association\BelongsToMany')
- ->setMethods(['_saveTarget', 'replaceLinks'])
- ->setConstructorArgs(['tags', ['sourceTable' => $table]])
- ->getMock();
- $entity = new Entity([
- 'id' => 1,
- 'tags' => $value,
- ], ['markNew' => false]);
- $assoc->setSaveStrategy(BelongsToMany::SAVE_REPLACE);
- $assoc->expects($this->once())
- ->method('replaceLinks')
- ->with($entity, [])
- ->will($this->returnValue(true));
- $assoc->expects($this->never())
- ->method('_saveTarget');
- $this->assertSame($entity, $assoc->saveAssociated($entity));
- }
- /**
- * Tests saving with replace strategy returning true
- *
- * @return void
- */
- public function testSaveAssociatedWithReplace()
- {
- $table = $this->getMockBuilder('Cake\ORM\Table')
- ->setMethods(['table'])
- ->getMock();
- $table->setSchema([]);
- $assoc = $this->getMockBuilder('\Cake\ORM\Association\BelongsToMany')
- ->setMethods(['replaceLinks'])
- ->setConstructorArgs(['tags', ['sourceTable' => $table]])
- ->getMock();
- $entity = new Entity([
- 'id' => 1,
- 'tags' => [
- new Entity(['name' => 'foo']),
- ],
- ]);
- $options = ['foo' => 'bar'];
- $assoc->setSaveStrategy(BelongsToMany::SAVE_REPLACE);
- $assoc->expects($this->once())->method('replaceLinks')
- ->with($entity, $entity->tags, $options)
- ->will($this->returnValue(true));
- $this->assertSame($entity, $assoc->saveAssociated($entity, $options));
- }
- /**
- * Tests saving with replace strategy returning true
- *
- * @return void
- */
- public function testSaveAssociatedWithReplaceReturnFalse()
- {
- $table = $this->getMockBuilder('Cake\ORM\Table')
- ->setMethods(['table'])
- ->getMock();
- $table->setSchema([]);
- $assoc = $this->getMockBuilder('\Cake\ORM\Association\BelongsToMany')
- ->setMethods(['replaceLinks'])
- ->setConstructorArgs(['tags', ['sourceTable' => $table]])
- ->getMock();
- $entity = new Entity([
- 'id' => 1,
- 'tags' => [
- new Entity(['name' => 'foo']),
- ],
- ]);
- $options = ['foo' => 'bar'];
- $assoc->setSaveStrategy(BelongsToMany::SAVE_REPLACE);
- $assoc->expects($this->once())->method('replaceLinks')
- ->with($entity, $entity->tags, $options)
- ->will($this->returnValue(false));
- $this->assertFalse($assoc->saveAssociated($entity, $options));
- }
- /**
- * Test that saveAssociated() ignores non entity values.
- *
- * @return void
- */
- public function testSaveAssociatedOnlyEntitiesAppend()
- {
- $connection = ConnectionManager::get('test');
- $mock = $this->getMockBuilder('Cake\ORM\Table')
- ->setMethods(['saveAssociated', 'schema'])
- ->setConstructorArgs([['table' => 'tags', 'connection' => $connection]])
- ->getMock();
- $mock->setPrimaryKey('id');
- $config = [
- 'sourceTable' => $this->article,
- 'targetTable' => $mock,
- 'saveStrategy' => BelongsToMany::SAVE_APPEND,
- ];
- $entity = new Entity([
- 'id' => 1,
- 'title' => 'First Post',
- 'tags' => [
- ['tag' => 'nope'],
- new Entity(['tag' => 'cakephp']),
- ],
- ]);
- $mock->expects($this->never())
- ->method('saveAssociated');
- $association = new BelongsToMany('Tags', $config);
- $association->saveAssociated($entity);
- }
- /**
- * Tests that targetForeignKey() returns the correct configured value
- *
- * @group deprecated
- * @return void
- */
- public function testTargetForeignKey()
- {
- $this->deprecated(function () {
- $assoc = new BelongsToMany('Test', [
- 'sourceTable' => $this->article,
- 'targetTable' => $this->tag,
- ]);
- $this->assertEquals('tag_id', $assoc->targetForeignKey());
- $this->assertEquals('another_key', $assoc->targetForeignKey('another_key'));
- $this->assertEquals('another_key', $assoc->targetForeignKey());
- $assoc = new BelongsToMany('Test', [
- 'sourceTable' => $this->article,
- 'targetTable' => $this->tag,
- 'targetForeignKey' => 'foo',
- ]);
- $this->assertEquals('foo', $assoc->targetForeignKey());
- });
- }
- /**
- * Tests that setTargetForeignKey() returns the correct configured value
- *
- * @return void
- */
- public function testSetTargetForeignKey()
- {
- $assoc = new BelongsToMany('Test', [
- 'sourceTable' => $this->article,
- 'targetTable' => $this->tag,
- ]);
- $this->assertEquals('tag_id', $assoc->getTargetForeignKey());
- $assoc->setTargetForeignKey('another_key');
- $this->assertEquals('another_key', $assoc->getTargetForeignKey());
- $assoc = new BelongsToMany('Test', [
- 'sourceTable' => $this->article,
- 'targetTable' => $this->tag,
- 'targetForeignKey' => 'foo',
- ]);
- $this->assertEquals('foo', $assoc->getTargetForeignKey());
- }
- /**
- * Tests that custom foreignKeys are properly transmitted to involved associations
- * when they are customized
- *
- * @return void
- */
- public function testJunctionWithCustomForeignKeys()
- {
- $assoc = new BelongsToMany('Test', [
- 'sourceTable' => $this->article,
- 'targetTable' => $this->tag,
- 'foreignKey' => 'Art',
- 'targetForeignKey' => 'Tag',
- ]);
- $junction = $assoc->junction();
- $this->assertEquals('Art', $junction->getAssociation('Articles')->getForeignKey());
- $this->assertEquals('Tag', $junction->getAssociation('Tags')->getForeignKey());
- $inverseRelation = $this->tag->getAssociation('Articles');
- $this->assertEquals('Tag', $inverseRelation->getForeignKey());
- $this->assertEquals('Art', $inverseRelation->getTargetForeignKey());
- }
- /**
- * Tests that property is being set using the constructor options.
- *
- * @return void
- */
- public function testPropertyOption()
- {
- $config = ['propertyName' => 'thing_placeholder'];
- $association = new BelongsToMany('Thing', $config);
- $this->assertEquals('thing_placeholder', $association->getProperty());
- }
- /**
- * Test that plugin names are omitted from property()
- *
- * @return void
- */
- public function testPropertyNoPlugin()
- {
- $mock = $this->getMockBuilder('Cake\ORM\Table')
- ->disableOriginalConstructor()
- ->getMock();
- $config = [
- 'sourceTable' => $this->article,
- 'targetTable' => $mock,
- ];
- $association = new BelongsToMany('Contacts.Tags', $config);
- $this->assertEquals('tags', $association->getProperty());
- }
- /**
- * Test that the generated associations are correct.
- *
- * @return void
- */
- public function testGeneratedAssociations()
- {
- $articles = $this->getTableLocator()->get('Articles');
- $tags = $this->getTableLocator()->get('Tags');
- $conditions = ['SpecialTags.highlighted' => true];
- $assoc = $articles->belongsToMany('Tags', [
- 'sourceTable' => $articles,
- 'targetTable' => $tags,
- 'foreignKey' => 'foreign_key',
- 'targetForeignKey' => 'target_foreign_key',
- 'through' => 'SpecialTags',
- 'conditions' => $conditions,
- ]);
- // Generate associations
- $assoc->junction();
- $tagAssoc = $articles->getAssociation('Tags');
- $this->assertNotEmpty($tagAssoc, 'btm should exist');
- $this->assertEquals($conditions, $tagAssoc->getConditions());
- $this->assertEquals('target_foreign_key', $tagAssoc->getTargetForeignKey());
- $this->assertEquals('foreign_key', $tagAssoc->getForeignKey());
- $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->getForeignKey());
- $articleAssoc = $tags->getAssociation('Articles');
- $this->assertNotEmpty($articleAssoc, 'reverse btm should exist');
- $this->assertInstanceOf('Cake\ORM\Association\BelongsToMany', $articleAssoc);
- $this->assertEquals($conditions, $articleAssoc->getConditions());
- $this->assertEquals('foreign_key', $articleAssoc->getTargetForeignKey(), 'keys should swap');
- $this->assertEquals('target_foreign_key', $articleAssoc->getForeignKey(), 'keys should swap');
- $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->getForeignKey());
- }
- /**
- * Tests that eager loading requires association keys
- *
- * @return void
- */
- public function testEagerLoadingRequiresPrimaryKey()
- {
- $this->expectException(\RuntimeException::class);
- $this->expectExceptionMessage('The "tags" table does not define a primary key');
- $table = $this->getTableLocator()->get('Articles');
- $tags = $this->getTableLocator()->get('Tags');
- $tags->getSchema()->dropConstraint('primary');
- $table->belongsToMany('Tags');
- $table->find()->contain('Tags')->first();
- }
- /**
- * Tests that fetching belongsToMany association will not force
- * all fields being returned, but instead will honor the select() clause
- *
- * @see https://github.com/cakephp/cakephp/issues/7916
- * @return void
- */
- public function testEagerLoadingBelongsToManyLimitedFields()
- {
- $table = $this->getTableLocator()->get('Articles');
- $table->belongsToMany('Tags');
- $result = $table
- ->find()
- ->contain(['Tags' => function ($q) {
- return $q->select(['id']);
- }])
- ->first();
- $this->assertNotEmpty($result->tags[0]->id);
- $this->assertEmpty($result->tags[0]->name);
- $result = $table
- ->find()
- ->contain([
- 'Tags' => [
- 'fields' => [
- 'Tags.name',
- ],
- ],
- ])
- ->first();
- $this->assertNotEmpty($result->tags[0]->name);
- $this->assertEmpty($result->tags[0]->id);
- }
- /**
- * Tests that fetching belongsToMany association will retain autoFields(true) if it was used.
- *
- * @see https://github.com/cakephp/cakephp/issues/8052
- * @return void
- */
- public function testEagerLoadingBelongsToManyLimitedFieldsWithAutoFields()
- {
- $table = $this->getTableLocator()->get('Articles');
- $table->belongsToMany('Tags');
- $result = $table
- ->find()
- ->contain(['Tags' => function ($q) {
- return $q->select(['two' => $q->newExpr('1 + 1')])->enableAutoFields(true);
- }])
- ->first();
- $this->assertNotEmpty($result->tags[0]->two, 'Should have computed field');
- $this->assertNotEmpty($result->tags[0]->name, 'Should have standard field');
- }
- /**
- * Test that association proxy find() applies joins when conditions are involved.
- *
- * @return void
- */
- public function testAssociationProxyFindWithConditions()
- {
- $table = $this->getTableLocator()->get('Articles');
- $table->belongsToMany('Tags', [
- 'foreignKey' => 'article_id',
- 'associationForeignKey' => 'tag_id',
- 'conditions' => ['SpecialTags.highlighted' => true],
- 'through' => 'SpecialTags',
- ]);
- $query = $table->Tags->find();
- $result = $query->toArray();
- $this->assertCount(1, $result);
- $this->assertEquals(1, $result[0]->id);
- }
- /**
- * Test that association proxy find() applies complex conditions
- *
- * @return void
- */
- public function testAssociationProxyFindWithComplexConditions()
- {
- $table = $this->getTableLocator()->get('Articles');
- $table->belongsToMany('Tags', [
- 'foreignKey' => 'article_id',
- 'associationForeignKey' => 'tag_id',
- 'conditions' => [
- 'OR' => [
- 'SpecialTags.highlighted' => true,
- ],
- ],
- 'through' => 'SpecialTags',
- ]);
- $query = $table->Tags->find();
- $result = $query->toArray();
- $this->assertCount(1, $result);
- $this->assertEquals(1, $result[0]->id);
- }
- /**
- * Test that matching() works on belongsToMany associations.
- *
- * @return void
- */
- public function testBelongsToManyAssociationWithArrayConditions()
- {
- $table = $this->getTableLocator()->get('Articles');
- $table->belongsToMany('Tags', [
- 'foreignKey' => 'article_id',
- 'associationForeignKey' => 'tag_id',
- 'conditions' => ['SpecialTags.highlighted' => true],
- 'through' => 'SpecialTags',
- ]);
- $query = $table->find()->matching('Tags', function ($q) {
- return $q->where(['Tags.name' => 'tag1']);
- });
- $results = $query->toArray();
- $this->assertCount(1, $results);
- $this->assertNotEmpty($results[0]->_matchingData);
- }
- /**
- * Test that matching() works on belongsToMany associations.
- *
- * @return void
- */
- public function testBelongsToManyAssociationWithExpressionConditions()
- {
- $table = $this->getTableLocator()->get('Articles');
- $table->belongsToMany('Tags', [
- 'foreignKey' => 'article_id',
- 'associationForeignKey' => 'tag_id',
- 'conditions' => [new QueryExpression("name LIKE 'tag%'")],
- 'through' => 'SpecialTags',
- ]);
- $query = $table->find()->matching('Tags', function ($q) {
- return $q->where(['Tags.name' => 'tag1']);
- });
- $results = $query->toArray();
- $this->assertCount(1, $results);
- $this->assertNotEmpty($results[0]->_matchingData);
- }
- /**
- * Test that association proxy find() with matching resolves joins correctly
- *
- * @return void
- */
- public function testAssociationProxyFindWithConditionsMatching()
- {
- $table = $this->getTableLocator()->get('Articles');
- $table->belongsToMany('Tags', [
- 'foreignKey' => 'article_id',
- 'associationForeignKey' => 'tag_id',
- 'conditions' => ['SpecialTags.highlighted' => true],
- 'through' => 'SpecialTags',
- ]);
- $query = $table->Tags->find()->matching('Articles', function ($query) {
- return $query->where(['Articles.id' => 1]);
- });
- // The inner join on special_tags excludes the results.
- $this->assertEquals(0, $query->count());
- }
- /**
- * Test custom binding key for target table association
- *
- * @return void
- */
- public function testCustomTargetBindingKeyContain()
- {
- $this->getTableLocator()->get('ArticlesTags')
- ->belongsTo('SpecialTags', [
- 'bindingKey' => 'tag_id',
- 'foreignKey' => 'tag_id',
- ]);
- $table = $this->getTableLocator()->get('Articles');
- $table->belongsToMany('SpecialTags', [
- 'through' => 'ArticlesTags',
- 'targetForeignKey' => 'tag_id',
- ]);
- $results = $table->find()
- ->contain('SpecialTags', function ($query) {
- return $query->order(['SpecialTags.tag_id']);
- })
- ->where(['id' => 2])
- ->toArray();
- $this->assertCount(1, $results);
- $this->assertCount(2, $results[0]->special_tags);
- $this->assertSame(2, $results[0]->special_tags[0]->id);
- $this->assertSame(1, $results[0]->special_tags[0]->tag_id);
- $this->assertSame(1, $results[0]->special_tags[1]->id);
- $this->assertSame(3, $results[0]->special_tags[1]->tag_id);
- }
- /**
- * Test custom binding key for target table association
- *
- * @return void
- */
- public function testCustomTargetBindingKeyLink()
- {
- $this->getTableLocator()->get('ArticlesTags')
- ->belongsTo('SpecialTags', [
- 'bindingKey' => 'tag_id',
- 'foreignKey' => 'tag_id',
- ]);
- $table = $this->getTableLocator()->get('Articles');
- $table->belongsToMany('SpecialTags', [
- 'through' => 'ArticlesTags',
- 'targetForeignKey' => 'tag_id',
- ]);
- $specialTag = $table->SpecialTags->newEntity([
- 'article_id' => 2,
- 'tag_id' => 2,
- ]);
- $table->SpecialTags->save($specialTag);
- $article = $table->get(2);
- $this->assertTrue($table->SpecialTags->link($article, [$specialTag]));
- $results = $table->find()
- ->contain('SpecialTags')
- ->where(['id' => 2])
- ->toArray();
- $this->assertCount(1, $results);
- $this->assertCount(3, $results[0]->special_tags);
- }
- }
|