CompositeKeysTest.php 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  11. * @link https://cakephp.org CakePHP(tm) Project
  12. * @since 3.0.0
  13. * @license https://opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Test\TestCase\ORM;
  16. use Cake\Database\Driver\Sqlite;
  17. use Cake\Database\Driver\Sqlserver;
  18. use Cake\Datasource\ConnectionManager;
  19. use Cake\ORM\Entity;
  20. use Cake\ORM\Marshaller;
  21. use Cake\ORM\Query;
  22. use Cake\ORM\Table;
  23. use Cake\ORM\TableRegistry;
  24. use Cake\TestSuite\TestCase;
  25. use PDO;
  26. /**
  27. * Test entity for mass assignment.
  28. */
  29. class OpenArticleEntity extends Entity
  30. {
  31. protected $_accessible = [
  32. '*' => true
  33. ];
  34. }
  35. /**
  36. * Integration tests for table operations involving composite keys
  37. */
  38. class CompositeKeyTest extends TestCase
  39. {
  40. /**
  41. * Fixture to be used
  42. *
  43. * @var array
  44. */
  45. public $fixtures = [
  46. 'core.composite_increments',
  47. 'core.site_articles',
  48. 'core.site_articles_tags',
  49. 'core.site_authors',
  50. 'core.site_tags'
  51. ];
  52. /**
  53. * setUp method
  54. *
  55. * @return void
  56. */
  57. public function setUp()
  58. {
  59. parent::setUp();
  60. $this->connection = ConnectionManager::get('test');
  61. }
  62. /**
  63. * Data provider for the two types of strategies HasOne implements
  64. *
  65. * @return void
  66. */
  67. public function strategiesProviderHasOne()
  68. {
  69. return [['join'], ['select']];
  70. }
  71. /**
  72. * Data provider for the two types of strategies HasMany implements
  73. *
  74. * @return void
  75. */
  76. public function strategiesProviderHasMany()
  77. {
  78. return [['subquery'], ['select']];
  79. }
  80. /**
  81. * Data provider for the two types of strategies BelongsTo implements
  82. *
  83. * @return void
  84. */
  85. public function strategiesProviderBelongsTo()
  86. {
  87. return [['join'], ['select']];
  88. }
  89. /**
  90. * Data provider for the two types of strategies BelongsToMany implements
  91. *
  92. * @return void
  93. */
  94. public function strategiesProviderBelongsToMany()
  95. {
  96. return [['subquery'], ['select']];
  97. }
  98. /**
  99. * Test that you cannot save rows with composite keys if some columns are missing.
  100. *
  101. * @group save
  102. * @return void
  103. */
  104. public function testSaveNewErrorCompositeKeyNoIncrement()
  105. {
  106. $this->expectException(\RuntimeException::class);
  107. $this->expectExceptionMessage('Cannot insert row, some of the primary key values are missing');
  108. $articles = TableRegistry::get('SiteArticles');
  109. $article = $articles->newEntity(['site_id' => 1, 'author_id' => 1, 'title' => 'testing']);
  110. $articles->save($article);
  111. }
  112. /**
  113. * Test that saving into composite primary keys where one column is missing & autoIncrement works.
  114. *
  115. * SQLite is skipped because it doesn't support autoincrement composite keys.
  116. *
  117. * @group save
  118. * @return void
  119. */
  120. public function testSaveNewCompositeKeyIncrement()
  121. {
  122. $this->skipIfSqlite();
  123. $table = TableRegistry::get('CompositeIncrements');
  124. $thing = $table->newEntity(['account_id' => 3, 'name' => 'new guy']);
  125. $this->assertSame($thing, $table->save($thing));
  126. $this->assertNotEmpty($thing->id, 'Primary key should have been populated');
  127. $this->assertSame(3, $thing->account_id);
  128. }
  129. /**
  130. * Tests that HasMany associations are correctly eager loaded and results
  131. * correctly nested when multiple foreignKeys are used
  132. *
  133. * @dataProvider strategiesProviderHasMany
  134. * @return void
  135. */
  136. public function testHasManyEager($strategy)
  137. {
  138. $table = TableRegistry::get('SiteAuthors');
  139. $table->hasMany('SiteArticles', [
  140. 'propertyName' => 'articles',
  141. 'strategy' => $strategy,
  142. 'sort' => ['SiteArticles.id' => 'asc'],
  143. 'foreignKey' => ['author_id', 'site_id']
  144. ]);
  145. $query = new Query($this->connection, $table);
  146. $results = $query->select()
  147. ->contain('SiteArticles')
  148. ->hydrate(false)
  149. ->toArray();
  150. $expected = [
  151. [
  152. 'id' => 1,
  153. 'name' => 'mark',
  154. 'site_id' => 1,
  155. 'articles' => [
  156. [
  157. 'id' => 1,
  158. 'title' => 'First Article',
  159. 'body' => 'First Article Body',
  160. 'author_id' => 1,
  161. 'site_id' => 1
  162. ]
  163. ]
  164. ],
  165. [
  166. 'id' => 2,
  167. 'name' => 'juan',
  168. 'site_id' => 2,
  169. 'articles' => [],
  170. ],
  171. [
  172. 'id' => 3,
  173. 'name' => 'jose',
  174. 'site_id' => 2,
  175. 'articles' => [
  176. [
  177. 'id' => 2,
  178. 'title' => 'Second Article',
  179. 'body' => 'Second Article Body',
  180. 'author_id' => 3,
  181. 'site_id' => 2
  182. ]
  183. ]
  184. ],
  185. [
  186. 'id' => 4,
  187. 'name' => 'andy',
  188. 'site_id' => 1,
  189. 'articles' => [],
  190. ]
  191. ];
  192. $this->assertEquals($expected, $results);
  193. $results = $query->repository($table)
  194. ->select()
  195. ->contain(['SiteArticles' => ['conditions' => ['SiteArticles.id' => 2]]])
  196. ->hydrate(false)
  197. ->toArray();
  198. $expected[0]['articles'] = [];
  199. $this->assertEquals($expected, $results);
  200. $this->assertEquals($table->association('SiteArticles')->strategy(), $strategy);
  201. }
  202. /**
  203. * Tests that BelongsToMany associations are correctly eager loaded when multiple
  204. * foreignKeys are used
  205. *
  206. * @dataProvider strategiesProviderBelongsToMany
  207. * @return void
  208. */
  209. public function testBelongsToManyEager($strategy)
  210. {
  211. $articles = TableRegistry::get('SiteArticles');
  212. $tags = TableRegistry::get('SiteTags');
  213. $junction = TableRegistry::get('SiteArticlesTags');
  214. $articles->belongsToMany('SiteTags', [
  215. 'strategy' => $strategy,
  216. 'targetTable' => $tags,
  217. 'propertyName' => 'tags',
  218. 'through' => 'SiteArticlesTags',
  219. 'sort' => ['SiteTags.id' => 'asc'],
  220. 'foreignKey' => ['article_id', 'site_id'],
  221. 'targetForeignKey' => ['tag_id', 'site_id']
  222. ]);
  223. $query = new Query($this->connection, $articles);
  224. $results = $query->select()->contain('SiteTags')->hydrate(false)->toArray();
  225. $expected = [
  226. [
  227. 'id' => 1,
  228. 'author_id' => 1,
  229. 'title' => 'First Article',
  230. 'body' => 'First Article Body',
  231. 'site_id' => 1,
  232. 'tags' => [
  233. [
  234. 'id' => 1,
  235. 'name' => 'tag1',
  236. '_joinData' => ['article_id' => 1, 'tag_id' => 1, 'site_id' => 1],
  237. 'site_id' => 1
  238. ],
  239. [
  240. 'id' => 3,
  241. 'name' => 'tag3',
  242. '_joinData' => ['article_id' => 1, 'tag_id' => 3, 'site_id' => 1],
  243. 'site_id' => 1
  244. ]
  245. ]
  246. ],
  247. [
  248. 'id' => 2,
  249. 'title' => 'Second Article',
  250. 'body' => 'Second Article Body',
  251. 'author_id' => 3,
  252. 'site_id' => 2,
  253. 'tags' => [
  254. [
  255. 'id' => 4,
  256. 'name' => 'tag4',
  257. '_joinData' => ['article_id' => 2, 'tag_id' => 4, 'site_id' => 2],
  258. 'site_id' => 2
  259. ]
  260. ]
  261. ],
  262. [
  263. 'id' => 3,
  264. 'title' => 'Third Article',
  265. 'body' => 'Third Article Body',
  266. 'author_id' => 1,
  267. 'site_id' => 2,
  268. 'tags' => [],
  269. ],
  270. [
  271. 'id' => 4,
  272. 'title' => 'Fourth Article',
  273. 'body' => 'Fourth Article Body',
  274. 'author_id' => 3,
  275. 'site_id' => 1,
  276. 'tags' => [
  277. [
  278. 'id' => 1,
  279. 'name' => 'tag1',
  280. '_joinData' => ['article_id' => 4, 'tag_id' => 1, 'site_id' => 1],
  281. 'site_id' => 1
  282. ]
  283. ]
  284. ],
  285. ];
  286. $this->assertEquals($expected, $results);
  287. $this->assertEquals($articles->association('SiteTags')->strategy(), $strategy);
  288. }
  289. /**
  290. * Tests loading belongsTo with composite keys
  291. *
  292. * @dataProvider strategiesProviderBelongsTo
  293. * @return void
  294. */
  295. public function testBelongsToEager($strategy)
  296. {
  297. $table = TableRegistry::get('SiteArticles');
  298. $table->belongsTo('SiteAuthors', [
  299. 'propertyName' => 'author',
  300. 'strategy' => $strategy,
  301. 'foreignKey' => ['author_id', 'site_id']
  302. ]);
  303. $query = new Query($this->connection, $table);
  304. $results = $query->select()
  305. ->where(['SiteArticles.id IN' => [1, 2]])
  306. ->contain('SiteAuthors')
  307. ->hydrate(false)
  308. ->toArray();
  309. $expected = [
  310. [
  311. 'id' => 1,
  312. 'author_id' => 1,
  313. 'site_id' => 1,
  314. 'title' => 'First Article',
  315. 'body' => 'First Article Body',
  316. 'author' => [
  317. 'id' => 1,
  318. 'name' => 'mark',
  319. 'site_id' => 1
  320. ]
  321. ],
  322. [
  323. 'id' => 2,
  324. 'author_id' => 3,
  325. 'site_id' => 2,
  326. 'title' => 'Second Article',
  327. 'body' => 'Second Article Body',
  328. 'author' => [
  329. 'id' => 3,
  330. 'name' => 'jose',
  331. 'site_id' => 2
  332. ]
  333. ]
  334. ];
  335. $this->assertEquals($expected, $results);
  336. }
  337. /**
  338. * Tests loading hasOne with composite keys
  339. *
  340. * @dataProvider strategiesProviderHasOne
  341. * @return void
  342. */
  343. public function testHasOneEager($strategy)
  344. {
  345. $table = TableRegistry::get('SiteAuthors');
  346. $table->hasOne('SiteArticles', [
  347. 'propertyName' => 'first_article',
  348. 'strategy' => $strategy,
  349. 'foreignKey' => ['author_id', 'site_id']
  350. ]);
  351. $query = new Query($this->connection, $table);
  352. $results = $query->select()
  353. ->where(['SiteAuthors.id IN' => [1, 3]])
  354. ->contain('SiteArticles')
  355. ->hydrate(false)
  356. ->toArray();
  357. $expected = [
  358. [
  359. 'id' => 1,
  360. 'name' => 'mark',
  361. 'site_id' => 1,
  362. 'first_article' => [
  363. 'id' => 1,
  364. 'author_id' => 1,
  365. 'site_id' => 1,
  366. 'title' => 'First Article',
  367. 'body' => 'First Article Body'
  368. ]
  369. ],
  370. [
  371. 'id' => 3,
  372. 'name' => 'jose',
  373. 'site_id' => 2,
  374. 'first_article' => [
  375. 'id' => 2,
  376. 'author_id' => 3,
  377. 'site_id' => 2,
  378. 'title' => 'Second Article',
  379. 'body' => 'Second Article Body'
  380. ]
  381. ]
  382. ];
  383. $this->assertEquals($expected, $results);
  384. }
  385. /**
  386. * Tests that it is possible to insert a new row using the save method
  387. * if the entity has composite primary key
  388. *
  389. * @group save
  390. * @return void
  391. */
  392. public function testSaveNewEntity()
  393. {
  394. $entity = new Entity([
  395. 'id' => 5,
  396. 'site_id' => 1,
  397. 'title' => 'Fifth Article',
  398. 'body' => 'Fifth Article Body',
  399. 'author_id' => 3,
  400. ]);
  401. $table = TableRegistry::get('SiteArticles');
  402. $this->assertSame($entity, $table->save($entity));
  403. $this->assertEquals($entity->id, 5);
  404. $row = $table->find('all')->where(['id' => 5, 'site_id' => 1])->first();
  405. $this->assertEquals($entity->toArray(), $row->toArray());
  406. }
  407. /**
  408. * Tests that it is possible to insert a new row using the save method
  409. * if the entity has composite primary key
  410. *
  411. * @group save
  412. * @return void
  413. */
  414. public function testSaveNewEntityMissingKey()
  415. {
  416. $this->expectException(\RuntimeException::class);
  417. $this->expectExceptionMessage('Cannot insert row, some of the primary key values are missing. Got (5, ), expecting (id, site_id)');
  418. $entity = new Entity([
  419. 'id' => 5,
  420. 'title' => 'Fifth Article',
  421. 'body' => 'Fifth Article Body',
  422. 'author_id' => 3,
  423. ]);
  424. $table = TableRegistry::get('SiteArticles');
  425. $table->save($entity);
  426. }
  427. /**
  428. * Test simple delete with composite primary key
  429. *
  430. * @return void
  431. */
  432. public function testDelete()
  433. {
  434. $table = TableRegistry::get('SiteAuthors');
  435. $table->save(new Entity(['id' => 1, 'site_id' => 2]));
  436. $entity = $table->get([1, 1]);
  437. $result = $table->delete($entity);
  438. $this->assertTrue($result);
  439. $this->assertEquals(4, $table->find('all')->count());
  440. $this->assertEmpty($table->find()->where(['id' => 1, 'site_id' => 1])->first());
  441. }
  442. /**
  443. * Test delete with dependent records having composite keys
  444. *
  445. * @return void
  446. */
  447. public function testDeleteDependent()
  448. {
  449. $table = TableRegistry::get('SiteAuthors');
  450. $table->hasMany('SiteArticles', [
  451. 'foreignKey' => ['author_id', 'site_id'],
  452. 'dependent' => true,
  453. ]);
  454. $entity = $table->get([3, 2]);
  455. $result = $table->delete($entity);
  456. $query = $table->association('SiteArticles')->find('all', [
  457. 'conditions' => [
  458. 'author_id' => $entity->id,
  459. 'site_id' => $entity->site_id
  460. ]
  461. ]);
  462. $this->assertNull($query->all()->first(), 'Should not find any rows.');
  463. }
  464. /**
  465. * Test generating a list of entities from a list of composite ids
  466. *
  467. * @return void
  468. */
  469. public function testOneGenerateBelongsToManyEntitiesFromIds()
  470. {
  471. $articles = TableRegistry::get('SiteArticles');
  472. $articles->entityClass(__NAMESPACE__ . '\OpenArticleEntity');
  473. $tags = TableRegistry::get('SiteTags');
  474. $junction = TableRegistry::get('SiteArticlesTags');
  475. $articles->belongsToMany('SiteTags', [
  476. 'targetTable' => $tags,
  477. 'propertyName' => 'tags',
  478. 'through' => 'SiteArticlesTags',
  479. 'foreignKey' => ['article_id', 'site_id'],
  480. 'targetForeignKey' => ['tag_id', 'site_id']
  481. ]);
  482. $data = [
  483. 'title' => 'Haz tags',
  484. 'body' => 'Some content here',
  485. 'tags' => ['_ids' => [[1, 1], [2, 2], [3, 1]]]
  486. ];
  487. $marshall = new Marshaller($articles);
  488. $result = $marshall->one($data, ['associated' => ['SiteTags']]);
  489. $this->assertCount(3, $result->tags);
  490. $this->assertInstanceOf('Cake\ORM\Entity', $result->tags[0]);
  491. $this->assertInstanceOf('Cake\ORM\Entity', $result->tags[1]);
  492. $this->assertInstanceOf('Cake\ORM\Entity', $result->tags[2]);
  493. $data = [
  494. 'title' => 'Haz tags',
  495. 'body' => 'Some content here',
  496. 'tags' => ['_ids' => [1, 2, 3]]
  497. ];
  498. $marshall = new Marshaller($articles);
  499. $result = $marshall->one($data, ['associated' => ['SiteTags']]);
  500. $this->assertEmpty($result->tags);
  501. }
  502. /**
  503. * Tests find('list') with composite keys
  504. *
  505. * @return void
  506. */
  507. public function testFindListCompositeKeys()
  508. {
  509. $table = new Table([
  510. 'table' => 'site_authors',
  511. 'connection' => $this->connection,
  512. ]);
  513. $table->displayField('name');
  514. $query = $table->find('list')
  515. ->hydrate(false)
  516. ->order('id');
  517. $expected = [
  518. '1;1' => 'mark',
  519. '2;2' => 'juan',
  520. '3;2' => 'jose',
  521. '4;1' => 'andy'
  522. ];
  523. $this->assertEquals($expected, $query->toArray());
  524. $table->displayField(['name', 'site_id']);
  525. $query = $table->find('list')
  526. ->hydrate(false)
  527. ->order('id');
  528. $expected = [
  529. '1;1' => 'mark;1',
  530. '2;2' => 'juan;2',
  531. '3;2' => 'jose;2',
  532. '4;1' => 'andy;1'
  533. ];
  534. $this->assertEquals($expected, $query->toArray());
  535. $query = $table->find('list', ['groupField' => ['site_id', 'site_id']])
  536. ->hydrate(false)
  537. ->order('id');
  538. $expected = [
  539. '1;1' => [
  540. '1;1' => 'mark;1',
  541. '4;1' => 'andy;1'
  542. ],
  543. '2;2' => [
  544. '2;2' => 'juan;2',
  545. '3;2' => 'jose;2'
  546. ]
  547. ];
  548. $this->assertEquals($expected, $query->toArray());
  549. }
  550. /**
  551. * Tests find('threaded') with composite keys
  552. *
  553. * @return void
  554. */
  555. public function testFindThreadedCompositeKeys()
  556. {
  557. $table = TableRegistry::get('SiteAuthors');
  558. $query = $this->getMockBuilder('\Cake\ORM\Query')
  559. ->setMethods(['_addDefaultFields', 'execute'])
  560. ->setConstructorArgs([null, $table])
  561. ->getMock();
  562. $items = new \Cake\Datasource\ResultSetDecorator([
  563. ['id' => 1, 'name' => 'a', 'site_id' => 1, 'parent_id' => null],
  564. ['id' => 2, 'name' => 'a', 'site_id' => 2, 'parent_id' => null],
  565. ['id' => 3, 'name' => 'a', 'site_id' => 1, 'parent_id' => 1],
  566. ['id' => 4, 'name' => 'a', 'site_id' => 2, 'parent_id' => 2],
  567. ['id' => 5, 'name' => 'a', 'site_id' => 2, 'parent_id' => 4],
  568. ['id' => 6, 'name' => 'a', 'site_id' => 1, 'parent_id' => 2],
  569. ['id' => 7, 'name' => 'a', 'site_id' => 1, 'parent_id' => 3],
  570. ['id' => 8, 'name' => 'a', 'site_id' => 2, 'parent_id' => 4],
  571. ]);
  572. $query->find('threaded', ['parentField' => ['parent_id', 'site_id']]);
  573. $formatter = $query->formatResults()[0];
  574. $expected = [
  575. [
  576. 'id' => 1,
  577. 'name' => 'a',
  578. 'site_id' => 1,
  579. 'parent_id' => null,
  580. 'children' => [
  581. [
  582. 'id' => 3,
  583. 'name' => 'a',
  584. 'site_id' => 1,
  585. 'parent_id' => 1,
  586. 'children' => [
  587. [
  588. 'id' => 7,
  589. 'name' => 'a',
  590. 'site_id' => 1,
  591. 'parent_id' => 3,
  592. 'children' => []
  593. ]
  594. ]
  595. ]
  596. ]
  597. ],
  598. [
  599. 'id' => 2,
  600. 'name' => 'a',
  601. 'site_id' => 2,
  602. 'parent_id' => null,
  603. 'children' => [
  604. [
  605. 'id' => 4,
  606. 'name' => 'a',
  607. 'site_id' => 2,
  608. 'parent_id' => 2,
  609. 'children' => [
  610. [
  611. 'id' => 5,
  612. 'name' => 'a',
  613. 'site_id' => 2,
  614. 'parent_id' => 4,
  615. 'children' => []
  616. ],
  617. [
  618. 'id' => 8,
  619. 'name' => 'a',
  620. 'site_id' => 2,
  621. 'parent_id' => 4,
  622. 'children' => []
  623. ]
  624. ]
  625. ]
  626. ]
  627. ],
  628. [
  629. 'id' => 6,
  630. 'name' => 'a',
  631. 'site_id' => 1,
  632. 'parent_id' => 2,
  633. 'children' => []
  634. ]
  635. ];
  636. $this->assertEquals($expected, $formatter($items)->toArray());
  637. }
  638. /**
  639. * Tests that loadInto() is capable of handling composite primary keys
  640. *
  641. * @return void
  642. */
  643. public function testLoadInto()
  644. {
  645. $table = TableRegistry::get('SiteAuthors');
  646. $tags = TableRegistry::get('SiteTags');
  647. $table->hasMany('SiteArticles', [
  648. 'foreignKey' => ['author_id', 'site_id'],
  649. ]);
  650. $author = $table->get([1, 1]);
  651. $result = $table->loadInto($author, ['SiteArticles']);
  652. $this->assertSame($author, $result);
  653. $this->assertNotEmpty($result->site_articles);
  654. $expected = $table->get([1, 1], ['contain' => ['SiteArticles']]);
  655. $this->assertEquals($expected, $result);
  656. }
  657. /**
  658. * Tests that loadInto() is capable of handling composite primary keys
  659. * when loading belongsTo associations
  660. *
  661. * @return void
  662. */
  663. public function testLoadIntoWithBelongsTo()
  664. {
  665. $table = TableRegistry::get('SiteArticles');
  666. $table->belongsTo('SiteAuthors', [
  667. 'foreignKey' => ['author_id', 'site_id'],
  668. ]);
  669. $author = $table->get([2, 2]);
  670. $result = $table->loadInto($author, ['SiteAuthors']);
  671. $this->assertSame($author, $result);
  672. $this->assertNotEmpty($result->site_author);
  673. $expected = $table->get([2, 2], ['contain' => ['SiteAuthors']]);
  674. $this->assertEquals($expected, $result);
  675. }
  676. /**
  677. * Tests that loadInto() is capable of handling composite primary keys
  678. * when loading into multiple entities
  679. *
  680. * @return void
  681. */
  682. public function testLoadIntoMany()
  683. {
  684. $table = TableRegistry::get('SiteAuthors');
  685. $tags = TableRegistry::get('SiteTags');
  686. $table->hasMany('SiteArticles', [
  687. 'foreignKey' => ['author_id', 'site_id'],
  688. ]);
  689. $authors = $table->find()->toList();
  690. $result = $table->loadInto($authors, ['SiteArticles']);
  691. foreach ($authors as $k => $v) {
  692. $this->assertSame($result[$k], $v);
  693. }
  694. $expected = $table->find('all', ['contain' => ['SiteArticles']])->toList();
  695. $this->assertEquals($expected, $result);
  696. }
  697. /**
  698. * Tests notMatching() with a belongsToMany association
  699. *
  700. * @return void
  701. */
  702. public function testNotMatchingBelongsToMany()
  703. {
  704. $driver = $this->connection->driver();
  705. if ($driver instanceof Sqlserver) {
  706. $this->markTestSkipped('Sqlserver does not support the requirements of this test.');
  707. } elseif ($driver instanceof Sqlite) {
  708. $serverVersion = $driver->connection()->getAttribute(PDO::ATTR_SERVER_VERSION);
  709. if (version_compare($serverVersion, '3.15.0', '<')) {
  710. $this->markTestSkipped("Sqlite ($serverVersion) does not support the requirements of this test.");
  711. }
  712. }
  713. $articles = TableRegistry::get('SiteArticles');
  714. $articles->belongsToMany('SiteTags', [
  715. 'through' => 'SiteArticlesTags',
  716. 'foreignKey' => ['article_id', 'site_id'],
  717. 'targetForeignKey' => ['tag_id', 'site_id']
  718. ]);
  719. $results = $articles->find()
  720. ->hydrate(false)
  721. ->notMatching('SiteTags')
  722. ->toArray();
  723. $expected = [
  724. [
  725. 'id' => 3,
  726. 'author_id' => 1,
  727. 'site_id' => 2,
  728. 'title' => 'Third Article',
  729. 'body' => 'Third Article Body',
  730. ],
  731. ];
  732. $this->assertEquals($expected, $results);
  733. }
  734. /**
  735. * Helper method to skip tests when connection is SQLite.
  736. *
  737. * @return void
  738. */
  739. public function skipIfSqlite()
  740. {
  741. $this->skipIf(
  742. $this->connection->driver() instanceof Sqlite,
  743. 'SQLite does not support the requirements of this test.'
  744. );
  745. }
  746. }