HasOneTest.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (http://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. (http://cakefoundation.org)
  11. * @link http://cakephp.org CakePHP(tm) Project
  12. * @since 3.0.0
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Test\TestCase\ORM\Association;
  16. use Cake\Database\Expression\IdentifierExpression;
  17. use Cake\Database\Expression\QueryExpression;
  18. use Cake\Database\TypeMap;
  19. use Cake\ORM\Association\HasOne;
  20. use Cake\ORM\Entity;
  21. use Cake\ORM\TableRegistry;
  22. use Cake\TestSuite\TestCase;
  23. /**
  24. * Tests HasOne class
  25. */
  26. class HasOneTest extends TestCase
  27. {
  28. /**
  29. * Set up
  30. *
  31. * @return void
  32. */
  33. public function setUp()
  34. {
  35. parent::setUp();
  36. $this->user = TableRegistry::get('Users', [
  37. 'schema' => [
  38. 'id' => ['type' => 'integer'],
  39. 'username' => ['type' => 'string'],
  40. '_constraints' => [
  41. 'primary' => ['type' => 'primary', 'columns' => ['id']]
  42. ]
  43. ]
  44. ]);
  45. $this->profile = TableRegistry::get('Profiles', [
  46. 'schema' => [
  47. 'id' => ['type' => 'integer'],
  48. 'first_name' => ['type' => 'string'],
  49. 'user_id' => ['type' => 'integer'],
  50. '_constraints' => [
  51. 'primary' => ['type' => 'primary', 'columns' => ['id']]
  52. ]
  53. ]
  54. ]);
  55. $this->profilesTypeMap = new TypeMap([
  56. 'Profiles.id' => 'integer',
  57. 'id' => 'integer',
  58. 'Profiles.first_name' => 'string',
  59. 'first_name' => 'string',
  60. 'Profiles.user_id' => 'integer',
  61. 'user_id' => 'integer',
  62. 'Profiles__first_name' => 'string',
  63. 'Profiles__user_id' => 'integer',
  64. 'Profiles__id' => 'integer',
  65. ]);
  66. }
  67. /**
  68. * Tear down
  69. *
  70. * @return void
  71. */
  72. public function tearDown()
  73. {
  74. parent::tearDown();
  75. TableRegistry::clear();
  76. }
  77. /**
  78. * Tests that the association reports it can be joined
  79. *
  80. * @return void
  81. */
  82. public function testCanBeJoined()
  83. {
  84. $assoc = new HasOne('Test');
  85. $this->assertTrue($assoc->canBeJoined());
  86. }
  87. /**
  88. * Tests that the correct join and fields are attached to a query depending on
  89. * the association config
  90. *
  91. * @return void
  92. */
  93. public function testAttachTo()
  94. {
  95. $query = $this->getMockBuilder('\Cake\ORM\Query')
  96. ->setMethods(['join', 'select'])
  97. ->setConstructorArgs([null, null])
  98. ->getMock();
  99. $config = [
  100. 'foreignKey' => 'user_id',
  101. 'sourceTable' => $this->user,
  102. 'targetTable' => $this->profile,
  103. 'conditions' => ['Profiles.is_active' => true]
  104. ];
  105. $association = new HasOne('Profiles', $config);
  106. $field = new IdentifierExpression('Profiles.user_id');
  107. $query->expects($this->once())->method('join')->with([
  108. 'Profiles' => [
  109. 'conditions' => new QueryExpression([
  110. 'Profiles.is_active' => true,
  111. ['Users.id' => $field],
  112. ], $this->profilesTypeMap),
  113. 'type' => 'LEFT',
  114. 'table' => 'profiles'
  115. ]
  116. ]);
  117. $query->expects($this->once())->method('select')->with([
  118. 'Profiles__id' => 'Profiles.id',
  119. 'Profiles__first_name' => 'Profiles.first_name',
  120. 'Profiles__user_id' => 'Profiles.user_id'
  121. ]);
  122. $association->attachTo($query);
  123. $this->assertEquals(
  124. 'string',
  125. $query->typeMap()->type('Profiles__first_name'),
  126. 'Associations should map types.'
  127. );
  128. }
  129. /**
  130. * Tests that it is possible to avoid fields inclusion for the associated table
  131. *
  132. * @return void
  133. */
  134. public function testAttachToNoFields()
  135. {
  136. $query = $this->getMockBuilder('\Cake\ORM\Query')
  137. ->setMethods(['join', 'select'])
  138. ->setConstructorArgs([null, null])
  139. ->getMock();
  140. $config = [
  141. 'sourceTable' => $this->user,
  142. 'targetTable' => $this->profile,
  143. 'conditions' => ['Profiles.is_active' => true]
  144. ];
  145. $association = new HasOne('Profiles', $config);
  146. $field = new IdentifierExpression('Profiles.user_id');
  147. $query->expects($this->once())->method('join')->with([
  148. 'Profiles' => [
  149. 'conditions' => new QueryExpression([
  150. 'Profiles.is_active' => true,
  151. ['Users.id' => $field],
  152. ], $this->profilesTypeMap),
  153. 'type' => 'LEFT',
  154. 'table' => 'profiles'
  155. ]
  156. ]);
  157. $query->expects($this->never())->method('select');
  158. $association->attachTo($query, ['includeFields' => false]);
  159. }
  160. /**
  161. * Tests that using hasOne with a table having a multi column primary
  162. * key will work if the foreign key is passed
  163. *
  164. * @return void
  165. */
  166. public function testAttachToMultiPrimaryKey()
  167. {
  168. $query = $this->getMockBuilder('\Cake\ORM\Query')
  169. ->setMethods(['join', 'select'])
  170. ->setConstructorArgs([null, null])
  171. ->getMock();
  172. $config = [
  173. 'sourceTable' => $this->user,
  174. 'targetTable' => $this->profile,
  175. 'conditions' => ['Profiles.is_active' => true],
  176. 'foreignKey' => ['user_id', 'user_site_id']
  177. ];
  178. $this->user->primaryKey(['id', 'site_id']);
  179. $association = new HasOne('Profiles', $config);
  180. $field1 = new IdentifierExpression('Profiles.user_id');
  181. $field2 = new IdentifierExpression('Profiles.user_site_id');
  182. $query->expects($this->once())->method('join')->with([
  183. 'Profiles' => [
  184. 'conditions' => new QueryExpression([
  185. 'Profiles.is_active' => true,
  186. ['Users.id' => $field1, 'Users.site_id' => $field2],
  187. ], $this->profilesTypeMap),
  188. 'type' => 'LEFT',
  189. 'table' => 'profiles'
  190. ]
  191. ]);
  192. $query->expects($this->never())->method('select');
  193. $association->attachTo($query, ['includeFields' => false]);
  194. }
  195. /**
  196. * Tests that using hasOne with a table having a multi column primary
  197. * key will work if the foreign key is passed
  198. *
  199. * @expectedException \RuntimeException
  200. * @expectedExceptionMessage Cannot match provided foreignKey for "Profiles", got "(user_id)" but expected foreign key for "(id, site_id)"
  201. * @return void
  202. */
  203. public function testAttachToMultiPrimaryKeyMistmatch()
  204. {
  205. $query = $this->getMockBuilder('\Cake\ORM\Query')
  206. ->setMethods(['join', 'select'])
  207. ->setConstructorArgs([null, null])
  208. ->getMock();
  209. $config = [
  210. 'sourceTable' => $this->user,
  211. 'targetTable' => $this->profile,
  212. 'conditions' => ['Profiles.is_active' => true],
  213. ];
  214. $this->user->primaryKey(['id', 'site_id']);
  215. $association = new HasOne('Profiles', $config);
  216. $association->attachTo($query, ['includeFields' => false]);
  217. }
  218. /**
  219. * Test that saveAssociated() ignores non entity values.
  220. *
  221. * @return void
  222. */
  223. public function testSaveAssociatedOnlyEntities()
  224. {
  225. $mock = $this->getMockBuilder('Cake\ORM\Table')
  226. ->setMethods(['saveAssociated'])
  227. ->disableOriginalConstructor()
  228. ->getMock();
  229. $config = [
  230. 'sourceTable' => $this->user,
  231. 'targetTable' => $mock,
  232. ];
  233. $mock->expects($this->never())
  234. ->method('saveAssociated');
  235. $entity = new Entity([
  236. 'username' => 'Mark',
  237. 'email' => 'mark@example.com',
  238. 'profile' => ['twitter' => '@cakephp']
  239. ]);
  240. $association = new HasOne('Profiles', $config);
  241. $result = $association->saveAssociated($entity);
  242. $this->assertSame($result, $entity);
  243. }
  244. /**
  245. * Tests that property is being set using the constructor options.
  246. *
  247. * @return void
  248. */
  249. public function testPropertyOption()
  250. {
  251. $config = ['propertyName' => 'thing_placeholder'];
  252. $association = new hasOne('Thing', $config);
  253. $this->assertEquals('thing_placeholder', $association->property());
  254. }
  255. /**
  256. * Test that plugin names are omitted from property()
  257. *
  258. * @return void
  259. */
  260. public function testPropertyNoPlugin()
  261. {
  262. $mock = $this->getMockBuilder('Cake\ORM\Table')
  263. ->disableOriginalConstructor()
  264. ->getMock();
  265. $config = [
  266. 'sourceTable' => $this->user,
  267. 'targetTable' => $mock,
  268. ];
  269. $association = new HasOne('Contacts.Profiles', $config);
  270. $this->assertEquals('profile', $association->property());
  271. }
  272. /**
  273. * Tests that attaching an association to a query will trigger beforeFind
  274. * for the target table
  275. *
  276. * @return void
  277. */
  278. public function testAttachToBeforeFind()
  279. {
  280. $query = $this->getMockBuilder('\Cake\ORM\Query')
  281. ->setMethods(['join', 'select'])
  282. ->setConstructorArgs([null, null])
  283. ->getMock();
  284. $config = [
  285. 'foreignKey' => 'user_id',
  286. 'sourceTable' => $this->user,
  287. 'targetTable' => $this->profile,
  288. ];
  289. $listener = $this->getMockBuilder('stdClass')
  290. ->setMethods(['__invoke'])
  291. ->getMock();
  292. $this->profile->eventManager()->attach($listener, 'Model.beforeFind');
  293. $association = new HasOne('Profiles', $config);
  294. $listener->expects($this->once())->method('__invoke')
  295. ->with(
  296. $this->isInstanceOf('\Cake\Event\Event'),
  297. $this->isInstanceOf('\Cake\ORM\Query'),
  298. $this->isInstanceOf('\ArrayObject'),
  299. false
  300. );
  301. $association->attachTo($query);
  302. }
  303. /**
  304. * Tests that attaching an association to a query will trigger beforeFind
  305. * for the target table
  306. *
  307. * @return void
  308. */
  309. public function testAttachToBeforeFindExtraOptions()
  310. {
  311. $query = $this->getMockBuilder('\Cake\ORM\Query')
  312. ->setMethods(['join', 'select'])
  313. ->setConstructorArgs([null, null])
  314. ->getMock();
  315. $config = [
  316. 'foreignKey' => 'user_id',
  317. 'sourceTable' => $this->user,
  318. 'targetTable' => $this->profile,
  319. ];
  320. $listener = $this->getMockBuilder('stdClass')
  321. ->setMethods(['__invoke'])
  322. ->getMock();
  323. $this->profile->eventManager()->attach($listener, 'Model.beforeFind');
  324. $association = new HasOne('Profiles', $config);
  325. $opts = new \ArrayObject(['something' => 'more']);
  326. $listener->expects($this->once())->method('__invoke')
  327. ->with(
  328. $this->isInstanceOf('\Cake\Event\Event'),
  329. $this->isInstanceOf('\Cake\ORM\Query'),
  330. $opts,
  331. false
  332. );
  333. $association->attachTo($query, ['queryBuilder' => function ($q) {
  334. return $q->applyOptions(['something' => 'more']);
  335. }]);
  336. }
  337. }