AssociationTest.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755
  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. $this->loadPlugins(['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. Plugin::unload();
  430. }
  431. /**
  432. * Tests that source() returns the correct Table object
  433. *
  434. * @group deprecated
  435. * @return void
  436. */
  437. public function testSource()
  438. {
  439. $this->deprecated(function () {
  440. $table = $this->association->source();
  441. $this->assertSame($this->source, $table);
  442. $other = new Table;
  443. $this->association->source($other);
  444. $this->assertSame($other, $this->association->source());
  445. });
  446. }
  447. /**
  448. * Tests that source() returns the correct Table object
  449. *
  450. * @return void
  451. */
  452. public function testSetSource()
  453. {
  454. $table = $this->association->getSource();
  455. $this->assertSame($this->source, $table);
  456. $other = new Table;
  457. $this->assertSame($this->association, $this->association->setSource($other));
  458. $this->assertSame($other, $this->association->getSource());
  459. }
  460. /**
  461. * Tests joinType method
  462. *
  463. * @group deprecated
  464. * @return void
  465. */
  466. public function testJoinType()
  467. {
  468. $this->deprecated(function () {
  469. $this->assertEquals('INNER', $this->association->joinType());
  470. $this->association->joinType('LEFT');
  471. $this->assertEquals('LEFT', $this->association->joinType());
  472. });
  473. }
  474. /**
  475. * Tests setJoinType method
  476. *
  477. * @return void
  478. */
  479. public function testSetJoinType()
  480. {
  481. $this->assertEquals('INNER', $this->association->getJoinType());
  482. $this->assertSame($this->association, $this->association->setJoinType('LEFT'));
  483. $this->assertEquals('LEFT', $this->association->getJoinType());
  484. }
  485. /**
  486. * Tests dependent method
  487. *
  488. * @group deprecated
  489. * @return void
  490. */
  491. public function testDependent()
  492. {
  493. $this->deprecated(function () {
  494. $this->assertTrue($this->association->dependent());
  495. $this->association->dependent(false);
  496. $this->assertFalse($this->association->dependent());
  497. });
  498. }
  499. /**
  500. * Tests property method
  501. *
  502. * @group deprecated
  503. * @return void
  504. */
  505. public function testProperty()
  506. {
  507. $this->deprecated(function () {
  508. $this->assertEquals('foo', $this->association->property());
  509. $this->association->property('thing');
  510. $this->assertEquals('thing', $this->association->property());
  511. });
  512. }
  513. /**
  514. * Tests property method
  515. *
  516. * @return void
  517. */
  518. public function testSetProperty()
  519. {
  520. $this->assertEquals('foo', $this->association->getProperty());
  521. $this->assertSame($this->association, $this->association->setProperty('thing'));
  522. $this->assertEquals('thing', $this->association->getProperty());
  523. }
  524. /**
  525. * Test that warning is shown if property name clashes with table field.
  526. *
  527. * @return void
  528. */
  529. public function testPropertyNameClash()
  530. {
  531. $this->expectException(\PHPUnit\Framework\Error\Warning::class);
  532. $this->expectExceptionMessageRegExp('/^Association property name "foo" clashes with field of same name of table "test"/');
  533. $this->source->setSchema(['foo' => ['type' => 'string']]);
  534. $this->assertEquals('foo', $this->association->getProperty());
  535. }
  536. /**
  537. * Test that warning is not shown if "propertyName" option is explicitly specified.
  538. *
  539. * @return void
  540. */
  541. public function testPropertyNameExplicitySet()
  542. {
  543. $this->source->setSchema(['foo' => ['type' => 'string']]);
  544. $config = [
  545. 'className' => '\Cake\Test\TestCase\ORM\TestTable',
  546. 'foreignKey' => 'a_key',
  547. 'conditions' => ['field' => 'value'],
  548. 'dependent' => true,
  549. 'sourceTable' => $this->source,
  550. 'joinType' => 'INNER',
  551. 'propertyName' => 'foo'
  552. ];
  553. $association = $this->getMockBuilder('\Cake\ORM\Association')
  554. ->setMethods([
  555. '_options', 'attachTo', '_joinCondition', 'cascadeDelete', 'isOwningSide',
  556. 'saveAssociated', 'eagerLoader', 'type', 'requiresKeys'
  557. ])
  558. ->setConstructorArgs(['Foo', $config])
  559. ->getMock();
  560. $this->assertEquals('foo', $association->getProperty());
  561. }
  562. /**
  563. * Tests strategy method
  564. *
  565. * @group deprecated
  566. * @return void
  567. */
  568. public function testStrategy()
  569. {
  570. $this->deprecated(function () {
  571. $this->assertEquals('join', $this->association->strategy());
  572. $this->association->strategy('select');
  573. $this->assertEquals('select', $this->association->strategy());
  574. $this->association->strategy('subquery');
  575. $this->assertEquals('subquery', $this->association->strategy());
  576. });
  577. }
  578. /**
  579. * Tests strategy method
  580. *
  581. * @return void
  582. */
  583. public function testSetStrategy()
  584. {
  585. $this->assertEquals('join', $this->association->getStrategy());
  586. $this->association->setStrategy('select');
  587. $this->assertEquals('select', $this->association->getStrategy());
  588. $this->association->setStrategy('subquery');
  589. $this->assertEquals('subquery', $this->association->getStrategy());
  590. }
  591. /**
  592. * Tests that providing an invalid strategy throws an exception
  593. *
  594. * @return void
  595. */
  596. public function testInvalidStrategy()
  597. {
  598. $this->expectException(\InvalidArgumentException::class);
  599. $this->association->setStrategy('anotherThing');
  600. }
  601. /**
  602. * Tests test finder() method as getter and setter
  603. *
  604. * @group deprecated
  605. * @return void
  606. */
  607. public function testFinderMethod()
  608. {
  609. $this->deprecated(function () {
  610. $this->assertEquals('all', $this->association->finder());
  611. $this->assertEquals('published', $this->association->finder('published'));
  612. $this->assertEquals('published', $this->association->finder());
  613. });
  614. }
  615. /**
  616. * Tests test setFinder() method
  617. *
  618. * @return void
  619. */
  620. public function testSetFinderMethod()
  621. {
  622. $this->assertEquals('all', $this->association->getFinder());
  623. $this->assertSame($this->association, $this->association->setFinder('published'));
  624. $this->assertEquals('published', $this->association->getFinder());
  625. }
  626. /**
  627. * Tests that `finder` is a valid option for the association constructor
  628. *
  629. * @return void
  630. */
  631. public function testFinderInConstructor()
  632. {
  633. $config = [
  634. 'className' => '\Cake\Test\TestCase\ORM\TestTable',
  635. 'foreignKey' => 'a_key',
  636. 'conditions' => ['field' => 'value'],
  637. 'dependent' => true,
  638. 'sourceTable' => $this->source,
  639. 'joinType' => 'INNER',
  640. 'finder' => 'published'
  641. ];
  642. $assoc = $this->getMockBuilder('\Cake\ORM\Association')
  643. ->setMethods([
  644. 'type', 'eagerLoader', 'cascadeDelete', 'isOwningSide', 'saveAssociated',
  645. 'requiresKeys'
  646. ])
  647. ->setConstructorArgs(['Foo', $config])
  648. ->getMock();
  649. $this->assertEquals('published', $assoc->getFinder());
  650. }
  651. /**
  652. * Tests that the defined custom finder is used when calling find
  653. * in the association
  654. *
  655. * @return void
  656. */
  657. public function testCustomFinderIsUsed()
  658. {
  659. $this->association->setFinder('published');
  660. $this->assertEquals(
  661. ['this' => 'worked'],
  662. $this->association->find()->getOptions()
  663. );
  664. }
  665. /**
  666. * Tests that `locator` is a valid option for the association constructor
  667. *
  668. * @return void
  669. */
  670. public function testLocatorInConstructor()
  671. {
  672. $locator = $this->getMockBuilder('Cake\ORM\Locator\LocatorInterface')->getMock();
  673. $config = [
  674. 'className' => '\Cake\Test\TestCase\ORM\TestTable',
  675. 'tableLocator' => $locator
  676. ];
  677. $assoc = $this->getMockBuilder('\Cake\ORM\Association')
  678. ->setMethods([
  679. 'type', 'eagerLoader', 'cascadeDelete', 'isOwningSide', 'saveAssociated',
  680. 'requiresKeys'
  681. ])
  682. ->setConstructorArgs(['Foo', $config])
  683. ->getMock();
  684. $this->assertEquals($locator, $assoc->getTableLocator());
  685. }
  686. }