AssociationTest.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754
  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\Core\Plugin;
  17. use Cake\ORM\Table;
  18. use Cake\TestSuite\TestCase;
  19. /**
  20. * A Test double used to assert that default tables are created
  21. */
  22. class TestTable extends Table
  23. {
  24. public function initialize(array $config = [])
  25. {
  26. $this->setSchema(['id' => ['type' => 'integer']]);
  27. }
  28. public function findPublished($query)
  29. {
  30. return $query->applyOptions(['this' => 'worked']);
  31. }
  32. }
  33. /**
  34. * Tests Association class
  35. */
  36. class AssociationTest extends TestCase
  37. {
  38. /**
  39. * @var \Cake\ORM\Association|\PHPUnit_Framework_MockObject_MockObject
  40. */
  41. public $association;
  42. /**
  43. * Set up
  44. *
  45. * @return void
  46. */
  47. public function setUp()
  48. {
  49. parent::setUp();
  50. $this->source = new TestTable;
  51. $config = [
  52. 'className' => '\Cake\Test\TestCase\ORM\TestTable',
  53. 'foreignKey' => 'a_key',
  54. 'conditions' => ['field' => 'value'],
  55. 'dependent' => true,
  56. 'sourceTable' => $this->source,
  57. 'joinType' => 'INNER'
  58. ];
  59. $this->association = $this->getMockBuilder('\Cake\ORM\Association')
  60. ->setMethods([
  61. '_options', 'attachTo', '_joinCondition', 'cascadeDelete', 'isOwningSide',
  62. 'saveAssociated', 'eagerLoader', 'type', 'requiresKeys'
  63. ])
  64. ->setConstructorArgs(['Foo', $config])
  65. ->getMock();
  66. }
  67. /**
  68. * Tear down
  69. *
  70. * @return void
  71. */
  72. public function tearDown()
  73. {
  74. parent::tearDown();
  75. $this->getTableLocator()->clear();
  76. }
  77. /**
  78. * Tests that _options acts as a callback where subclasses can add their own
  79. * initialization code based on the passed configuration array
  80. *
  81. * @return void
  82. */
  83. public function testOptionsIsCalled()
  84. {
  85. $options = ['foo' => 'bar'];
  86. $this->association->expects($this->once())->method('_options')->with($options);
  87. $this->association->__construct('Name', $options);
  88. }
  89. /**
  90. * Tests that name() returns the correct configure association name
  91. *
  92. * @group deprecated
  93. * @return void
  94. */
  95. public function testName()
  96. {
  97. $this->deprecated(function () {
  98. $this->assertEquals('Foo', $this->association->name());
  99. $this->association->name('Bar');
  100. $this->assertEquals('Bar', $this->association->name());
  101. });
  102. }
  103. /**
  104. * Tests that setName()
  105. *
  106. * @return void
  107. */
  108. public function testSetName()
  109. {
  110. $this->assertEquals('Foo', $this->association->getName());
  111. $this->assertSame($this->association, $this->association->setName('Bar'));
  112. $this->assertEquals('Bar', $this->association->getName());
  113. }
  114. /**
  115. * Tests that setName() succeeds before the target table is resolved.
  116. *
  117. * @return void
  118. */
  119. public function testSetNameBeforeTarget()
  120. {
  121. $this->association->setName('Bar');
  122. $this->assertEquals('Bar', $this->association->getName());
  123. }
  124. /**
  125. * Tests that setName() fails after the target table is resolved.
  126. *
  127. * @return void
  128. */
  129. public function testSetNameAfterTarger()
  130. {
  131. $this->expectException(\InvalidArgumentException::class);
  132. $this->expectExceptionMessage('Association name does not match target table alias.');
  133. $this->association->getTarget();
  134. $this->association->setName('Bar');
  135. }
  136. /**
  137. * Tests that setName() succeeds if name equals target table alias.
  138. *
  139. * @return void
  140. */
  141. public function testSetNameToTargetAlias()
  142. {
  143. $alias = $this->association->getTarget()->getAlias();
  144. $this->association->setName($alias);
  145. $this->assertEquals($alias, $this->association->getName());
  146. }
  147. /**
  148. * Tests that className() returns the correct association className
  149. *
  150. * @return void
  151. */
  152. public function testClassName()
  153. {
  154. $this->assertEquals('\Cake\Test\TestCase\ORM\TestTable', $this->association->className());
  155. }
  156. /**
  157. * Tests that className() returns the correct (unnormalized) className
  158. *
  159. * @return void
  160. */
  161. public function testClassNameUnnormalized()
  162. {
  163. $config = [
  164. 'className' => 'Test',
  165. ];
  166. $this->association = $this->getMockBuilder('\Cake\ORM\Association')
  167. ->setMethods([
  168. '_options', 'attachTo', '_joinCondition', 'cascadeDelete', 'isOwningSide',
  169. 'saveAssociated', 'eagerLoader', 'type', 'requiresKeys'
  170. ])
  171. ->setConstructorArgs(['Foo', $config])
  172. ->getMock();
  173. $this->assertEquals('Test', $this->association->className());
  174. }
  175. /**
  176. * Tests that an exception is thrown when invalid target table is fetched
  177. * from a registry.
  178. *
  179. * @return void
  180. */
  181. public function testInvalidTableFetchedFromRegistry()
  182. {
  183. $this->expectException(\RuntimeException::class);
  184. $this->getTableLocator()->get('Test');
  185. $config = [
  186. 'className' => '\Cake\Test\TestCase\ORM\TestTable',
  187. ];
  188. $this->association = $this->getMockBuilder('\Cake\ORM\Association')
  189. ->setMethods([
  190. '_options', 'attachTo', '_joinCondition', 'cascadeDelete', 'isOwningSide',
  191. 'saveAssociated', 'eagerLoader', 'type', 'requiresKeys'
  192. ])
  193. ->setConstructorArgs(['Test', $config])
  194. ->getMock();
  195. $this->association->getTarget();
  196. }
  197. /**
  198. * Tests that a descendant table could be fetched from a registry.
  199. *
  200. * @return void
  201. */
  202. public function testTargetTableDescendant()
  203. {
  204. $this->getTableLocator()->get('Test', [
  205. 'className' => '\Cake\Test\TestCase\ORM\TestTable'
  206. ]);
  207. $className = '\Cake\ORM\Table';
  208. $config = [
  209. 'className' => $className,
  210. ];
  211. $this->association = $this->getMockBuilder('\Cake\ORM\Association')
  212. ->setMethods([
  213. '_options', 'attachTo', '_joinCondition', 'cascadeDelete', 'isOwningSide',
  214. 'saveAssociated', 'eagerLoader', 'type', 'requiresKeys'
  215. ])
  216. ->setConstructorArgs(['Test', $config])
  217. ->getMock();
  218. $target = $this->association->getTarget();
  219. $this->assertInstanceOf($className, $target);
  220. }
  221. /**
  222. * Tests that cascadeCallbacks() returns the correct configured value
  223. *
  224. * @group deprecated
  225. * @return void
  226. */
  227. public function testCascadeCallbacks()
  228. {
  229. $this->deprecated(function () {
  230. $this->assertFalse($this->association->cascadeCallbacks());
  231. $this->association->cascadeCallbacks(true);
  232. $this->assertTrue($this->association->cascadeCallbacks());
  233. });
  234. }
  235. /**
  236. * Tests that cascadeCallbacks() returns the correct configured value
  237. *
  238. * @return void
  239. */
  240. public function testSetCascadeCallbacks()
  241. {
  242. $this->assertFalse($this->association->getCascadeCallbacks());
  243. $this->assertSame($this->association, $this->association->setCascadeCallbacks(true));
  244. $this->assertTrue($this->association->getCascadeCallbacks());
  245. }
  246. /**
  247. * Tests the bindingKey method as a setter/getter
  248. *
  249. * @group deprecated
  250. * @return void
  251. */
  252. public function testBindingKey()
  253. {
  254. $this->deprecated(function () {
  255. $this->association->bindingKey('foo_id');
  256. $this->assertEquals('foo_id', $this->association->bindingKey());
  257. });
  258. }
  259. /**
  260. * Tests the bindingKey method as a setter/getter
  261. *
  262. * @return void
  263. */
  264. public function testSetBindingKey()
  265. {
  266. $this->assertSame($this->association, $this->association->setBindingKey('foo_id'));
  267. $this->assertEquals('foo_id', $this->association->getBindingKey());
  268. }
  269. /**
  270. * Tests the bindingKey() method when called with its defaults
  271. *
  272. * @return void
  273. */
  274. public function testBindingKeyDefault()
  275. {
  276. $this->source->setPrimaryKey(['id', 'site_id']);
  277. $this->association
  278. ->expects($this->once())
  279. ->method('isOwningSide')
  280. ->will($this->returnValue(true));
  281. $result = $this->association->getBindingKey();
  282. $this->assertEquals(['id', 'site_id'], $result);
  283. }
  284. /**
  285. * Tests the bindingKey() method when the association source is not the
  286. * owning side
  287. *
  288. * @return void
  289. */
  290. public function testBindingDefaultNoOwningSide()
  291. {
  292. $target = new Table;
  293. $target->setPrimaryKey(['foo', 'site_id']);
  294. $this->association->setTarget($target);
  295. $this->association
  296. ->expects($this->once())
  297. ->method('isOwningSide')
  298. ->will($this->returnValue(false));
  299. $result = $this->association->getBindingKey();
  300. $this->assertEquals(['foo', 'site_id'], $result);
  301. }
  302. /**
  303. * Tests that name() returns the correct configured value
  304. *
  305. * @group deprecated
  306. * @return void
  307. */
  308. public function testForeignKey()
  309. {
  310. $this->deprecated(function () {
  311. $this->assertEquals('a_key', $this->association->foreignKey());
  312. $this->association->foreignKey('another_key');
  313. $this->assertEquals('another_key', $this->association->foreignKey());
  314. });
  315. }
  316. /**
  317. * Tests setForeignKey()
  318. *
  319. * @return void
  320. */
  321. public function testSetForeignKey()
  322. {
  323. $this->assertEquals('a_key', $this->association->getForeignKey());
  324. $this->assertSame($this->association, $this->association->setForeignKey('another_key'));
  325. $this->assertEquals('another_key', $this->association->getForeignKey());
  326. }
  327. /**
  328. * Tests that conditions() returns the correct configured value
  329. *
  330. * @group deprecated
  331. * @return void
  332. */
  333. public function testConditions()
  334. {
  335. $this->deprecated(function () {
  336. $this->assertEquals(['field' => 'value'], $this->association->conditions());
  337. $conds = ['another_key' => 'another value'];
  338. $this->association->conditions($conds);
  339. $this->assertEquals($conds, $this->association->conditions());
  340. });
  341. }
  342. /**
  343. * Tests setConditions()
  344. *
  345. * @return void
  346. */
  347. public function testSetConditions()
  348. {
  349. $this->assertEquals(['field' => 'value'], $this->association->getConditions());
  350. $conds = ['another_key' => 'another value'];
  351. $this->assertSame($this->association, $this->association->setConditions($conds));
  352. $this->assertEquals($conds, $this->association->getConditions());
  353. }
  354. /**
  355. * Tests that canBeJoined() returns the correct configured value
  356. *
  357. * @return void
  358. */
  359. public function testCanBeJoined()
  360. {
  361. $this->assertTrue($this->association->canBeJoined());
  362. }
  363. /**
  364. * Tests that target() returns the correct Table object
  365. *
  366. * @group deprecated
  367. * @return void
  368. */
  369. public function testTarget()
  370. {
  371. $this->deprecated(function () {
  372. $table = $this->association->target();
  373. $this->assertInstanceOf(__NAMESPACE__ . '\TestTable', $table);
  374. $other = new Table;
  375. $this->association->target($other);
  376. $this->assertSame($other, $this->association->target());
  377. });
  378. }
  379. /**
  380. * Tests that setTarget()
  381. *
  382. * @return void
  383. */
  384. public function testSetTarget()
  385. {
  386. $table = $this->association->getTarget();
  387. $this->assertInstanceOf(__NAMESPACE__ . '\TestTable', $table);
  388. $other = new Table;
  389. $this->assertSame($this->association, $this->association->setTarget($other));
  390. $this->assertSame($other, $this->association->getTarget());
  391. }
  392. /**
  393. * Tests that target() returns the correct Table object for plugins
  394. *
  395. * @return void
  396. */
  397. public function testTargetPlugin()
  398. {
  399. Plugin::load('TestPlugin');
  400. $config = [
  401. 'className' => 'TestPlugin.Comments',
  402. 'foreignKey' => 'a_key',
  403. 'conditions' => ['field' => 'value'],
  404. 'dependent' => true,
  405. 'sourceTable' => $this->source,
  406. 'joinType' => 'INNER'
  407. ];
  408. $this->association = $this->getMockBuilder('\Cake\ORM\Association')
  409. ->setMethods([
  410. 'type', 'eagerLoader', 'cascadeDelete', 'isOwningSide', 'saveAssociated',
  411. 'requiresKeys'
  412. ])
  413. ->setConstructorArgs(['ThisAssociationName', $config])
  414. ->getMock();
  415. $table = $this->association->getTarget();
  416. $this->assertInstanceOf('TestPlugin\Model\Table\CommentsTable', $table);
  417. $this->assertTrue(
  418. $this->getTableLocator()->exists('TestPlugin.ThisAssociationName'),
  419. 'The association class will use this registry key'
  420. );
  421. $this->assertFalse($this->getTableLocator()->exists('TestPlugin.Comments'), 'The association class will NOT use this key');
  422. $this->assertFalse($this->getTableLocator()->exists('Comments'), 'Should also not be set');
  423. $this->assertFalse($this->getTableLocator()->exists('ThisAssociationName'), 'Should also not be set');
  424. $plugin = $this->getTableLocator()->get('TestPlugin.ThisAssociationName');
  425. $this->assertSame($table, $plugin, 'Should be an instance of TestPlugin.Comments');
  426. $this->assertSame('TestPlugin.ThisAssociationName', $table->getRegistryAlias());
  427. $this->assertSame('comments', $table->getTable());
  428. $this->assertSame('ThisAssociationName', $table->getAlias());
  429. }
  430. /**
  431. * Tests that source() returns the correct Table object
  432. *
  433. * @group deprecated
  434. * @return void
  435. */
  436. public function testSource()
  437. {
  438. $this->deprecated(function () {
  439. $table = $this->association->source();
  440. $this->assertSame($this->source, $table);
  441. $other = new Table;
  442. $this->association->source($other);
  443. $this->assertSame($other, $this->association->source());
  444. });
  445. }
  446. /**
  447. * Tests that source() returns the correct Table object
  448. *
  449. * @return void
  450. */
  451. public function testSetSource()
  452. {
  453. $table = $this->association->getSource();
  454. $this->assertSame($this->source, $table);
  455. $other = new Table;
  456. $this->assertSame($this->association, $this->association->setSource($other));
  457. $this->assertSame($other, $this->association->getSource());
  458. }
  459. /**
  460. * Tests joinType method
  461. *
  462. * @group deprecated
  463. * @return void
  464. */
  465. public function testJoinType()
  466. {
  467. $this->deprecated(function () {
  468. $this->assertEquals('INNER', $this->association->joinType());
  469. $this->association->joinType('LEFT');
  470. $this->assertEquals('LEFT', $this->association->joinType());
  471. });
  472. }
  473. /**
  474. * Tests setJoinType method
  475. *
  476. * @return void
  477. */
  478. public function testSetJoinType()
  479. {
  480. $this->assertEquals('INNER', $this->association->getJoinType());
  481. $this->assertSame($this->association, $this->association->setJoinType('LEFT'));
  482. $this->assertEquals('LEFT', $this->association->getJoinType());
  483. }
  484. /**
  485. * Tests dependent method
  486. *
  487. * @group deprecated
  488. * @return void
  489. */
  490. public function testDependent()
  491. {
  492. $this->deprecated(function () {
  493. $this->assertTrue($this->association->dependent());
  494. $this->association->dependent(false);
  495. $this->assertFalse($this->association->dependent());
  496. });
  497. }
  498. /**
  499. * Tests property method
  500. *
  501. * @group deprecated
  502. * @return void
  503. */
  504. public function testProperty()
  505. {
  506. $this->deprecated(function () {
  507. $this->assertEquals('foo', $this->association->property());
  508. $this->association->property('thing');
  509. $this->assertEquals('thing', $this->association->property());
  510. });
  511. }
  512. /**
  513. * Tests property method
  514. *
  515. * @return void
  516. */
  517. public function testSetProperty()
  518. {
  519. $this->assertEquals('foo', $this->association->getProperty());
  520. $this->assertSame($this->association, $this->association->setProperty('thing'));
  521. $this->assertEquals('thing', $this->association->getProperty());
  522. }
  523. /**
  524. * Test that warning is shown if property name clashes with table field.
  525. *
  526. * @return void
  527. */
  528. public function testPropertyNameClash()
  529. {
  530. $this->expectException(\PHPUnit\Framework\Error\Warning::class);
  531. $this->expectExceptionMessageRegExp('/^Association property name "foo" clashes with field of same name of table "test"/');
  532. $this->source->setSchema(['foo' => ['type' => 'string']]);
  533. $this->assertEquals('foo', $this->association->getProperty());
  534. }
  535. /**
  536. * Test that warning is not shown if "propertyName" option is explicitly specified.
  537. *
  538. * @return void
  539. */
  540. public function testPropertyNameExplicitySet()
  541. {
  542. $this->source->setSchema(['foo' => ['type' => 'string']]);
  543. $config = [
  544. 'className' => '\Cake\Test\TestCase\ORM\TestTable',
  545. 'foreignKey' => 'a_key',
  546. 'conditions' => ['field' => 'value'],
  547. 'dependent' => true,
  548. 'sourceTable' => $this->source,
  549. 'joinType' => 'INNER',
  550. 'propertyName' => 'foo'
  551. ];
  552. $association = $this->getMockBuilder('\Cake\ORM\Association')
  553. ->setMethods([
  554. '_options', 'attachTo', '_joinCondition', 'cascadeDelete', 'isOwningSide',
  555. 'saveAssociated', 'eagerLoader', 'type', 'requiresKeys'
  556. ])
  557. ->setConstructorArgs(['Foo', $config])
  558. ->getMock();
  559. $this->assertEquals('foo', $association->getProperty());
  560. }
  561. /**
  562. * Tests strategy method
  563. *
  564. * @group deprecated
  565. * @return void
  566. */
  567. public function testStrategy()
  568. {
  569. $this->deprecated(function () {
  570. $this->assertEquals('join', $this->association->strategy());
  571. $this->association->strategy('select');
  572. $this->assertEquals('select', $this->association->strategy());
  573. $this->association->strategy('subquery');
  574. $this->assertEquals('subquery', $this->association->strategy());
  575. });
  576. }
  577. /**
  578. * Tests strategy method
  579. *
  580. * @return void
  581. */
  582. public function testSetStrategy()
  583. {
  584. $this->assertEquals('join', $this->association->getStrategy());
  585. $this->association->setStrategy('select');
  586. $this->assertEquals('select', $this->association->getStrategy());
  587. $this->association->setStrategy('subquery');
  588. $this->assertEquals('subquery', $this->association->getStrategy());
  589. }
  590. /**
  591. * Tests that providing an invalid strategy throws an exception
  592. *
  593. * @return void
  594. */
  595. public function testInvalidStrategy()
  596. {
  597. $this->expectException(\InvalidArgumentException::class);
  598. $this->association->setStrategy('anotherThing');
  599. }
  600. /**
  601. * Tests test finder() method as getter and setter
  602. *
  603. * @group deprecated
  604. * @return void
  605. */
  606. public function testFinderMethod()
  607. {
  608. $this->deprecated(function () {
  609. $this->assertEquals('all', $this->association->finder());
  610. $this->assertEquals('published', $this->association->finder('published'));
  611. $this->assertEquals('published', $this->association->finder());
  612. });
  613. }
  614. /**
  615. * Tests test setFinder() method
  616. *
  617. * @return void
  618. */
  619. public function testSetFinderMethod()
  620. {
  621. $this->assertEquals('all', $this->association->getFinder());
  622. $this->assertSame($this->association, $this->association->setFinder('published'));
  623. $this->assertEquals('published', $this->association->getFinder());
  624. }
  625. /**
  626. * Tests that `finder` is a valid option for the association constructor
  627. *
  628. * @return void
  629. */
  630. public function testFinderInConstructor()
  631. {
  632. $config = [
  633. 'className' => '\Cake\Test\TestCase\ORM\TestTable',
  634. 'foreignKey' => 'a_key',
  635. 'conditions' => ['field' => 'value'],
  636. 'dependent' => true,
  637. 'sourceTable' => $this->source,
  638. 'joinType' => 'INNER',
  639. 'finder' => 'published'
  640. ];
  641. $assoc = $this->getMockBuilder('\Cake\ORM\Association')
  642. ->setMethods([
  643. 'type', 'eagerLoader', 'cascadeDelete', 'isOwningSide', 'saveAssociated',
  644. 'requiresKeys'
  645. ])
  646. ->setConstructorArgs(['Foo', $config])
  647. ->getMock();
  648. $this->assertEquals('published', $assoc->getFinder());
  649. }
  650. /**
  651. * Tests that the defined custom finder is used when calling find
  652. * in the association
  653. *
  654. * @return void
  655. */
  656. public function testCustomFinderIsUsed()
  657. {
  658. $this->association->setFinder('published');
  659. $this->assertEquals(
  660. ['this' => 'worked'],
  661. $this->association->find()->getOptions()
  662. );
  663. }
  664. /**
  665. * Tests that `locator` is a valid option for the association constructor
  666. *
  667. * @return void
  668. */
  669. public function testLocatorInConstructor()
  670. {
  671. $locator = $this->getMockBuilder('Cake\ORM\Locator\LocatorInterface')->getMock();
  672. $config = [
  673. 'className' => '\Cake\Test\TestCase\ORM\TestTable',
  674. 'tableLocator' => $locator
  675. ];
  676. $assoc = $this->getMockBuilder('\Cake\ORM\Association')
  677. ->setMethods([
  678. 'type', 'eagerLoader', 'cascadeDelete', 'isOwningSide', 'saveAssociated',
  679. 'requiresKeys'
  680. ])
  681. ->setConstructorArgs(['Foo', $config])
  682. ->getMock();
  683. $this->assertEquals($locator, $assoc->getTableLocator());
  684. }
  685. }