HasOneTest.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  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\Query;
  22. use Cake\ORM\Table;
  23. use Cake\ORM\TableRegistry;
  24. use Cake\TestSuite\TestCase;
  25. /**
  26. * Tests HasOne class
  27. *
  28. */
  29. class HasOneTest extends TestCase
  30. {
  31. /**
  32. * Set up
  33. *
  34. * @return void
  35. */
  36. public function setUp()
  37. {
  38. parent::setUp();
  39. $this->user = TableRegistry::get('Users', [
  40. 'schema' => [
  41. 'id' => ['type' => 'integer'],
  42. 'username' => ['type' => 'string'],
  43. '_constraints' => [
  44. 'primary' => ['type' => 'primary', 'columns' => ['id']]
  45. ]
  46. ]
  47. ]);
  48. $this->profile = TableRegistry::get('Profiles', [
  49. 'schema' => [
  50. 'id' => ['type' => 'integer'],
  51. 'first_name' => ['type' => 'string'],
  52. 'user_id' => ['type' => 'integer'],
  53. '_constraints' => [
  54. 'primary' => ['type' => 'primary', 'columns' => ['id']]
  55. ]
  56. ]
  57. ]);
  58. $this->profilesTypeMap = new TypeMap([
  59. 'Profiles.id' => 'integer',
  60. 'id' => 'integer',
  61. 'Profiles.first_name' => 'string',
  62. 'first_name' => 'string',
  63. 'Profiles.user_id' => 'integer',
  64. 'user_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->getMock('\Cake\ORM\Query', ['join', 'select'], [null, null]);
  96. $config = [
  97. 'foreignKey' => 'user_id',
  98. 'sourceTable' => $this->user,
  99. 'targetTable' => $this->profile,
  100. 'conditions' => ['Profiles.is_active' => true]
  101. ];
  102. $association = new HasOne('Profiles', $config);
  103. $field = new IdentifierExpression('Profiles.user_id');
  104. $query->expects($this->once())->method('join')->with([
  105. 'Profiles' => [
  106. 'conditions' => new QueryExpression([
  107. 'Profiles.is_active' => true,
  108. ['Users.id' => $field],
  109. ], $this->profilesTypeMap),
  110. 'type' => 'LEFT',
  111. 'table' => 'profiles'
  112. ]
  113. ]);
  114. $query->expects($this->once())->method('select')->with([
  115. 'Profiles__id' => 'Profiles.id',
  116. 'Profiles__first_name' => 'Profiles.first_name',
  117. 'Profiles__user_id' => 'Profiles.user_id'
  118. ]);
  119. $association->attachTo($query);
  120. }
  121. /**
  122. * Tests that it is possible to avoid fields inclusion for the associated table
  123. *
  124. * @return void
  125. */
  126. public function testAttachToNoFields()
  127. {
  128. $query = $this->getMock('\Cake\ORM\Query', ['join', 'select'], [null, null]);
  129. $config = [
  130. 'sourceTable' => $this->user,
  131. 'targetTable' => $this->profile,
  132. 'conditions' => ['Profiles.is_active' => true]
  133. ];
  134. $association = new HasOne('Profiles', $config);
  135. $field = new IdentifierExpression('Profiles.user_id');
  136. $query->expects($this->once())->method('join')->with([
  137. 'Profiles' => [
  138. 'conditions' => new QueryExpression([
  139. 'Profiles.is_active' => true,
  140. ['Users.id' => $field],
  141. ], $this->profilesTypeMap),
  142. 'type' => 'LEFT',
  143. 'table' => 'profiles'
  144. ]
  145. ]);
  146. $query->expects($this->never())->method('select');
  147. $association->attachTo($query, ['includeFields' => false]);
  148. }
  149. /**
  150. * Tests that using hasOne with a table having a multi column primary
  151. * key will work if the foreign key is passed
  152. *
  153. * @return void
  154. */
  155. public function testAttachToMultiPrimaryKey()
  156. {
  157. $query = $this->getMock('\Cake\ORM\Query', ['join', 'select'], [null, null]);
  158. $config = [
  159. 'sourceTable' => $this->user,
  160. 'targetTable' => $this->profile,
  161. 'conditions' => ['Profiles.is_active' => true],
  162. 'foreignKey' => ['user_id', 'user_site_id']
  163. ];
  164. $this->user->primaryKey(['id', 'site_id']);
  165. $association = new HasOne('Profiles', $config);
  166. $field1 = new IdentifierExpression('Profiles.user_id');
  167. $field2 = new IdentifierExpression('Profiles.user_site_id');
  168. $query->expects($this->once())->method('join')->with([
  169. 'Profiles' => [
  170. 'conditions' => new QueryExpression([
  171. 'Profiles.is_active' => true,
  172. ['Users.id' => $field1, 'Users.site_id' => $field2],
  173. ], $this->profilesTypeMap),
  174. 'type' => 'LEFT',
  175. 'table' => 'profiles'
  176. ]
  177. ]);
  178. $query->expects($this->never())->method('select');
  179. $association->attachTo($query, ['includeFields' => false]);
  180. }
  181. /**
  182. * Tests that using hasOne with a table having a multi column primary
  183. * key will work if the foreign key is passed
  184. *
  185. * @expectedException \RuntimeException
  186. * @expectedExceptionMessage Cannot match provided foreignKey for "Profiles", got "(user_id)" but expected foreign key for "(id, site_id)"
  187. * @return void
  188. */
  189. public function testAttachToMultiPrimaryKeyMistmatch()
  190. {
  191. $query = $this->getMock('\Cake\ORM\Query', ['join', 'select'], [null, null]);
  192. $config = [
  193. 'sourceTable' => $this->user,
  194. 'targetTable' => $this->profile,
  195. 'conditions' => ['Profiles.is_active' => true],
  196. ];
  197. $this->user->primaryKey(['id', 'site_id']);
  198. $association = new HasOne('Profiles', $config);
  199. $association->attachTo($query, ['includeFields' => false]);
  200. }
  201. /**
  202. * Test that saveAssociated() ignores non entity values.
  203. *
  204. * @return void
  205. */
  206. public function testSaveAssociatedOnlyEntities()
  207. {
  208. $mock = $this->getMock('Cake\ORM\Table', [], [], '', false);
  209. $config = [
  210. 'sourceTable' => $this->user,
  211. 'targetTable' => $mock,
  212. ];
  213. $mock->expects($this->never())
  214. ->method('saveAssociated');
  215. $entity = new Entity([
  216. 'username' => 'Mark',
  217. 'email' => 'mark@example.com',
  218. 'profile' => ['twitter' => '@cakephp']
  219. ]);
  220. $association = new HasOne('Profiles', $config);
  221. $result = $association->saveAssociated($entity);
  222. $this->assertSame($result, $entity);
  223. }
  224. /**
  225. * Tests that property is being set using the constructor options.
  226. *
  227. * @return void
  228. */
  229. public function testPropertyOption()
  230. {
  231. $config = ['propertyName' => 'thing_placeholder'];
  232. $association = new hasOne('Thing', $config);
  233. $this->assertEquals('thing_placeholder', $association->property());
  234. }
  235. /**
  236. * Test that plugin names are omitted from property()
  237. *
  238. * @return void
  239. */
  240. public function testPropertyNoPlugin()
  241. {
  242. $mock = $this->getMock('Cake\ORM\Table', [], [], '', false);
  243. $config = [
  244. 'sourceTable' => $this->user,
  245. 'targetTable' => $mock,
  246. ];
  247. $association = new HasOne('Contacts.Profiles', $config);
  248. $this->assertEquals('profile', $association->property());
  249. }
  250. /**
  251. * Tests that attaching an association to a query will trigger beforeFind
  252. * for the target table
  253. *
  254. * @return void
  255. */
  256. public function testAttachToBeforeFind()
  257. {
  258. $query = $this->getMock('\Cake\ORM\Query', ['join', 'select'], [null, null]);
  259. $config = [
  260. 'foreignKey' => 'user_id',
  261. 'sourceTable' => $this->user,
  262. 'targetTable' => $this->profile,
  263. ];
  264. $listener = $this->getMock('stdClass', ['__invoke']);
  265. $this->profile->eventManager()->attach($listener, 'Model.beforeFind');
  266. $association = new HasOne('Profiles', $config);
  267. $listener->expects($this->once())->method('__invoke')
  268. ->with(
  269. $this->isInstanceOf('\Cake\Event\Event'),
  270. $this->isInstanceOf('\Cake\ORM\Query'),
  271. $this->isInstanceOf('\ArrayObject'),
  272. false
  273. );
  274. $association->attachTo($query);
  275. }
  276. /**
  277. * Tests that attaching an association to a query will trigger beforeFind
  278. * for the target table
  279. *
  280. * @return void
  281. */
  282. public function testAttachToBeforeFindExtraOptions()
  283. {
  284. $query = $this->getMock('\Cake\ORM\Query', ['join', 'select'], [null, null]);
  285. $config = [
  286. 'foreignKey' => 'user_id',
  287. 'sourceTable' => $this->user,
  288. 'targetTable' => $this->profile,
  289. ];
  290. $listener = $this->getMock('stdClass', ['__invoke']);
  291. $this->profile->eventManager()->attach($listener, 'Model.beforeFind');
  292. $association = new HasOne('Profiles', $config);
  293. $opts = new \ArrayObject(['something' => 'more']);
  294. $listener->expects($this->once())->method('__invoke')
  295. ->with(
  296. $this->isInstanceOf('\Cake\Event\Event'),
  297. $this->isInstanceOf('\Cake\ORM\Query'),
  298. $opts,
  299. false
  300. );
  301. $association->attachTo($query, ['queryBuilder' => function ($q) {
  302. return $q->applyOptions(['something' => 'more']);
  303. }]);
  304. }
  305. }