MarshallerTest.php 109 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467
  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\Expression\IdentifierExpression;
  17. use Cake\Event\Event;
  18. use Cake\I18n\Time;
  19. use Cake\ORM\Entity;
  20. use Cake\ORM\Marshaller;
  21. use Cake\ORM\Table;
  22. use Cake\TestSuite\TestCase;
  23. use Cake\Validation\Validator;
  24. /**
  25. * Test entity for mass assignment.
  26. */
  27. class OpenEntity extends Entity
  28. {
  29. protected $_accessible = [
  30. '*' => true,
  31. ];
  32. }
  33. /**
  34. * Test entity for mass assignment.
  35. */
  36. class Tag extends Entity
  37. {
  38. protected $_accessible = [
  39. 'tag' => true,
  40. ];
  41. }
  42. /**
  43. * Test entity for mass assignment.
  44. */
  45. class ProtectedArticle extends Entity
  46. {
  47. protected $_accessible = [
  48. 'title' => true,
  49. 'body' => true
  50. ];
  51. }
  52. /**
  53. * Test stub for greedy find operations.
  54. */
  55. class GreedyCommentsTable extends Table
  56. {
  57. /**
  58. * initialize hook
  59. *
  60. * @param array $config Config data.
  61. * @return void
  62. */
  63. public function initialize(array $config)
  64. {
  65. $this->setTable('comments');
  66. $this->setAlias('Comments');
  67. }
  68. /**
  69. * Overload find to cause issues.
  70. *
  71. * @param string $type Find type
  72. * @param array $options find options
  73. * @return object
  74. */
  75. public function find($type = 'all', $options = [])
  76. {
  77. if (empty($options['conditions'])) {
  78. $options['conditions'] = [];
  79. }
  80. $options['conditions'] = array_merge($options['conditions'], ['Comments.published' => 'Y']);
  81. return parent::find($type, $options);
  82. }
  83. }
  84. /**
  85. * Marshaller test case
  86. */
  87. class MarshallerTest extends TestCase
  88. {
  89. public $fixtures = [
  90. 'core.Articles',
  91. 'core.ArticlesTags',
  92. 'core.Comments',
  93. 'core.SpecialTags',
  94. 'core.Tags',
  95. 'core.Users'
  96. ];
  97. /**
  98. * @var Table
  99. */
  100. protected $articles;
  101. /**
  102. * @var Table
  103. */
  104. protected $comments;
  105. /**
  106. * @var Table
  107. */
  108. protected $users;
  109. /**
  110. * @var Table
  111. */
  112. protected $tags;
  113. /**
  114. * @var Table
  115. */
  116. protected $articleTags;
  117. /**
  118. * setup
  119. *
  120. * @return void
  121. */
  122. public function setUp()
  123. {
  124. parent::setUp();
  125. $this->articles = $this->getTableLocator()->get('Articles');
  126. $this->articles->belongsTo('Users', [
  127. 'foreignKey' => 'author_id'
  128. ]);
  129. $this->articles->hasMany('Comments');
  130. $this->articles->belongsToMany('Tags');
  131. $this->comments = $this->getTableLocator()->get('Comments');
  132. $this->users = $this->getTableLocator()->get('Users');
  133. $this->tags = $this->getTableLocator()->get('Tags');
  134. $this->articleTags = $this->getTableLocator()->get('ArticlesTags');
  135. $this->comments->belongsTo('Articles');
  136. $this->comments->belongsTo('Users');
  137. $this->articles->setEntityClass(__NAMESPACE__ . '\OpenEntity');
  138. $this->comments->setEntityClass(__NAMESPACE__ . '\OpenEntity');
  139. $this->users->setEntityClass(__NAMESPACE__ . '\OpenEntity');
  140. $this->tags->setEntityClass(__NAMESPACE__ . '\OpenEntity');
  141. $this->articleTags->setEntityClass(__NAMESPACE__ . '\OpenEntity');
  142. }
  143. /**
  144. * Teardown
  145. *
  146. * @return void
  147. */
  148. public function tearDown()
  149. {
  150. parent::tearDown();
  151. $this->getTableLocator()->clear();
  152. unset($this->articles, $this->comments, $this->users, $this->tags);
  153. }
  154. /**
  155. * Test one() in a simple use.
  156. *
  157. * @return void
  158. */
  159. public function testOneSimple()
  160. {
  161. $data = [
  162. 'title' => 'My title',
  163. 'body' => 'My content',
  164. 'author_id' => 1,
  165. 'not_in_schema' => true
  166. ];
  167. $marshall = new Marshaller($this->articles);
  168. $result = $marshall->one($data, []);
  169. $this->assertInstanceOf('Cake\ORM\Entity', $result);
  170. $this->assertEquals($data, $result->toArray());
  171. $this->assertTrue($result->isDirty(), 'Should be a dirty entity.');
  172. $this->assertTrue($result->isNew(), 'Should be new');
  173. $this->assertEquals('Articles', $result->getSource());
  174. }
  175. /**
  176. * Test that marshalling an entity with '' for pk values results
  177. * in no pk value being set.
  178. *
  179. * @return void
  180. */
  181. public function testOneEmptyStringPrimaryKey()
  182. {
  183. $data = [
  184. 'id' => '',
  185. 'username' => 'superuser',
  186. 'password' => 'root',
  187. 'created' => new Time('2013-10-10 00:00'),
  188. 'updated' => new Time('2013-10-10 00:00')
  189. ];
  190. $marshall = new Marshaller($this->articles);
  191. $result = $marshall->one($data, []);
  192. $this->assertFalse($result->isDirty('id'));
  193. $this->assertNull($result->id);
  194. }
  195. /**
  196. * Test marshalling datetime/date field.
  197. *
  198. * @return void
  199. */
  200. public function testOneWithDatetimeField()
  201. {
  202. $data = [
  203. 'comment' => 'My Comment text',
  204. 'created' => [
  205. 'year' => '2014',
  206. 'month' => '2',
  207. 'day' => 14
  208. ]
  209. ];
  210. $marshall = new Marshaller($this->comments);
  211. $result = $marshall->one($data, []);
  212. $this->assertEquals(new Time('2014-02-14 00:00:00'), $result->created);
  213. $data['created'] = [
  214. 'year' => '2014',
  215. 'month' => '2',
  216. 'day' => 14,
  217. 'hour' => 9,
  218. 'minute' => 25,
  219. 'meridian' => 'pm'
  220. ];
  221. $result = $marshall->one($data, []);
  222. $this->assertEquals(new Time('2014-02-14 21:25:00'), $result->created);
  223. $data['created'] = [
  224. 'year' => '2014',
  225. 'month' => '2',
  226. 'day' => 14,
  227. 'hour' => 9,
  228. 'minute' => 25,
  229. ];
  230. $result = $marshall->one($data, []);
  231. $this->assertEquals(new Time('2014-02-14 09:25:00'), $result->created);
  232. $data['created'] = '2014-02-14 09:25:00';
  233. $result = $marshall->one($data, []);
  234. $this->assertEquals(new Time('2014-02-14 09:25:00'), $result->created);
  235. $data['created'] = 1392387900;
  236. $result = $marshall->one($data, []);
  237. $this->assertEquals($data['created'], $result->created->getTimestamp());
  238. }
  239. /**
  240. * Ensure that marshalling casts reasonably.
  241. *
  242. * @return void
  243. */
  244. public function testOneOnlyCastMatchingData()
  245. {
  246. $data = [
  247. 'title' => 'My title',
  248. 'body' => 'My content',
  249. 'author_id' => 'derp',
  250. 'created' => 'fale'
  251. ];
  252. $this->articles->setEntityClass(__NAMESPACE__ . '\OpenEntity');
  253. $marshall = new Marshaller($this->articles);
  254. $result = $marshall->one($data, []);
  255. $this->assertSame($data['title'], $result->title);
  256. $this->assertNull($result->author_id, 'No cast on bad data.');
  257. $this->assertSame($data['created'], $result->created, 'No cast on bad data.');
  258. }
  259. /**
  260. * Test one() follows mass-assignment rules.
  261. *
  262. * @return void
  263. */
  264. public function testOneAccessibleProperties()
  265. {
  266. $data = [
  267. 'title' => 'My title',
  268. 'body' => 'My content',
  269. 'author_id' => 1,
  270. 'not_in_schema' => true
  271. ];
  272. $this->articles->setEntityClass(__NAMESPACE__ . '\ProtectedArticle');
  273. $marshall = new Marshaller($this->articles);
  274. $result = $marshall->one($data, []);
  275. $this->assertInstanceOf(__NAMESPACE__ . '\ProtectedArticle', $result);
  276. $this->assertNull($result->author_id);
  277. $this->assertNull($result->not_in_schema);
  278. }
  279. /**
  280. * Test one() supports accessibleFields option
  281. *
  282. * @return void
  283. */
  284. public function testOneAccessibleFieldsOption()
  285. {
  286. $data = [
  287. 'title' => 'My title',
  288. 'body' => 'My content',
  289. 'author_id' => 1,
  290. 'not_in_schema' => true
  291. ];
  292. $this->articles->setEntityClass(__NAMESPACE__ . '\ProtectedArticle');
  293. $marshall = new Marshaller($this->articles);
  294. $result = $marshall->one($data, ['accessibleFields' => ['body' => false]]);
  295. $this->assertNull($result->body);
  296. $result = $marshall->one($data, ['accessibleFields' => ['author_id' => true]]);
  297. $this->assertEquals($data['author_id'], $result->author_id);
  298. $this->assertNull($result->not_in_schema);
  299. $result = $marshall->one($data, ['accessibleFields' => ['*' => true]]);
  300. $this->assertEquals($data['author_id'], $result->author_id);
  301. $this->assertTrue($result->not_in_schema);
  302. }
  303. /**
  304. * Test one() with an invalid association
  305. *
  306. * @return void
  307. */
  308. public function testOneInvalidAssociation()
  309. {
  310. $this->expectException(\InvalidArgumentException::class);
  311. $this->expectExceptionMessage('Cannot marshal data for "Derp" association. It is not associated with "Articles".');
  312. $data = [
  313. 'title' => 'My title',
  314. 'body' => 'My content',
  315. 'derp' => [
  316. 'id' => 1,
  317. 'username' => 'mark',
  318. ]
  319. ];
  320. $marshall = new Marshaller($this->articles);
  321. $marshall->one($data, [
  322. 'associated' => ['Derp']
  323. ]);
  324. }
  325. /**
  326. * Test that one() correctly handles an association beforeMarshal
  327. * making the association empty.
  328. *
  329. * @return void
  330. */
  331. public function testOneAssociationBeforeMarshalMutation()
  332. {
  333. $users = $this->getTableLocator()->get('Users');
  334. $articles = $this->getTableLocator()->get('Articles');
  335. $users->hasOne('Articles', [
  336. 'foreignKey' => 'author_id'
  337. ]);
  338. $articles->getEventManager()->on('Model.beforeMarshal', function ($event, $data, $options) {
  339. // Blank the association, so it doesn't become dirty.
  340. unset($data['not_a_real_field']);
  341. });
  342. $data = [
  343. 'username' => 'Jen',
  344. 'article' => [
  345. 'not_a_real_field' => 'whatever'
  346. ]
  347. ];
  348. $marshall = new Marshaller($users);
  349. $entity = $marshall->one($data, ['associated' => ['Articles']]);
  350. $this->assertTrue($entity->isDirty('username'));
  351. $this->assertFalse($entity->isDirty('article'));
  352. // Ensure consistency with merge()
  353. $entity = new Entity([
  354. 'username' => 'Jenny',
  355. ]);
  356. // Make the entity think it is new.
  357. $entity->setAccess('*', true);
  358. $entity->clean();
  359. $entity = $marshall->merge($entity, $data, ['associated' => ['Articles']]);
  360. $this->assertTrue($entity->isDirty('username'));
  361. $this->assertFalse($entity->isDirty('article'));
  362. }
  363. /**
  364. * Test one() supports accessibleFields option for associations
  365. *
  366. * @return void
  367. */
  368. public function testOneAccessibleFieldsOptionForAssociations()
  369. {
  370. $data = [
  371. 'title' => 'My title',
  372. 'body' => 'My content',
  373. 'user' => [
  374. 'id' => 1,
  375. 'username' => 'mark',
  376. ]
  377. ];
  378. $this->articles->setEntityClass(__NAMESPACE__ . '\ProtectedArticle');
  379. $this->users->setEntityClass(__NAMESPACE__ . '\ProtectedArticle');
  380. $marshall = new Marshaller($this->articles);
  381. $result = $marshall->one($data, [
  382. 'associated' => [
  383. 'Users' => ['accessibleFields' => ['id' => true]]
  384. ],
  385. 'accessibleFields' => ['body' => false, 'user' => true]
  386. ]);
  387. $this->assertNull($result->body);
  388. $this->assertNull($result->user->username);
  389. $this->assertEquals(1, $result->user->id);
  390. }
  391. /**
  392. * test one() with a wrapping model name.
  393. *
  394. * @return void
  395. */
  396. public function testOneWithAdditionalName()
  397. {
  398. $data = [
  399. 'title' => 'Original Title',
  400. 'Articles' => [
  401. 'title' => 'My title',
  402. 'body' => 'My content',
  403. 'author_id' => 1,
  404. 'not_in_schema' => true,
  405. 'user' => [
  406. 'username' => 'mark',
  407. ]
  408. ]
  409. ];
  410. $marshall = new Marshaller($this->articles);
  411. $result = $marshall->one($data, ['associated' => ['Users']]);
  412. $this->assertInstanceOf('Cake\ORM\Entity', $result);
  413. $this->assertTrue($result->isDirty(), 'Should be a dirty entity.');
  414. $this->assertTrue($result->isNew(), 'Should be new');
  415. $this->assertFalse($result->has('Articles'), 'No prefixed field.');
  416. $this->assertEquals($data['title'], $result->title, 'Data from prefix should be merged.');
  417. $this->assertEquals($data['Articles']['user']['username'], $result->user->username);
  418. }
  419. /**
  420. * test one() with association data.
  421. *
  422. * @return void
  423. */
  424. public function testOneAssociationsSingle()
  425. {
  426. $data = [
  427. 'title' => 'My title',
  428. 'body' => 'My content',
  429. 'author_id' => 1,
  430. 'comments' => [
  431. ['comment' => 'First post', 'user_id' => 2],
  432. ['comment' => 'Second post', 'user_id' => 2],
  433. ],
  434. 'user' => [
  435. 'username' => 'mark',
  436. 'password' => 'secret'
  437. ]
  438. ];
  439. $marshall = new Marshaller($this->articles);
  440. $result = $marshall->one($data, ['associated' => ['Users']]);
  441. $this->assertEquals($data['title'], $result->title);
  442. $this->assertEquals($data['body'], $result->body);
  443. $this->assertEquals($data['author_id'], $result->author_id);
  444. $this->assertInternalType('array', $result->comments);
  445. $this->assertEquals($data['comments'], $result->comments);
  446. $this->assertTrue($result->isDirty('comments'));
  447. $this->assertInstanceOf('Cake\ORM\Entity', $result->user);
  448. $this->assertTrue($result->isDirty('user'));
  449. $this->assertEquals($data['user']['username'], $result->user->username);
  450. $this->assertEquals($data['user']['password'], $result->user->password);
  451. }
  452. /**
  453. * test one() with association data.
  454. *
  455. * @return void
  456. */
  457. public function testOneAssociationsMany()
  458. {
  459. $data = [
  460. 'title' => 'My title',
  461. 'body' => 'My content',
  462. 'author_id' => 1,
  463. 'comments' => [
  464. ['comment' => 'First post', 'user_id' => 2],
  465. ['comment' => 'Second post', 'user_id' => 2],
  466. ],
  467. 'user' => [
  468. 'username' => 'mark',
  469. 'password' => 'secret'
  470. ]
  471. ];
  472. $marshall = new Marshaller($this->articles);
  473. $result = $marshall->one($data, ['associated' => ['Comments']]);
  474. $this->assertEquals($data['title'], $result->title);
  475. $this->assertEquals($data['body'], $result->body);
  476. $this->assertEquals($data['author_id'], $result->author_id);
  477. $this->assertInternalType('array', $result->comments);
  478. $this->assertCount(2, $result->comments);
  479. $this->assertInstanceOf('Cake\ORM\Entity', $result->comments[0]);
  480. $this->assertInstanceOf('Cake\ORM\Entity', $result->comments[1]);
  481. $this->assertEquals($data['comments'][0]['comment'], $result->comments[0]->comment);
  482. $this->assertInternalType('array', $result->user);
  483. $this->assertEquals($data['user'], $result->user);
  484. }
  485. /**
  486. * Test building the _joinData entity for belongstomany associations.
  487. *
  488. * @return void
  489. */
  490. public function testOneBelongsToManyJoinData()
  491. {
  492. $data = [
  493. 'title' => 'My title',
  494. 'body' => 'My content',
  495. 'author_id' => 1,
  496. 'tags' => [
  497. ['tag' => 'news', '_joinData' => ['active' => 1]],
  498. ['tag' => 'cakephp', '_joinData' => ['active' => 0]],
  499. ],
  500. ];
  501. $marshall = new Marshaller($this->articles);
  502. $result = $marshall->one($data, [
  503. 'associated' => ['Tags']
  504. ]);
  505. $this->assertEquals($data['title'], $result->title);
  506. $this->assertEquals($data['body'], $result->body);
  507. $this->assertInternalType('array', $result->tags);
  508. $this->assertInstanceOf('Cake\ORM\Entity', $result->tags[0]);
  509. $this->assertEquals($data['tags'][0]['tag'], $result->tags[0]->tag);
  510. $this->assertInstanceOf(
  511. 'Cake\ORM\Entity',
  512. $result->tags[0]->_joinData,
  513. '_joinData should be an entity.'
  514. );
  515. $this->assertEquals(
  516. $data['tags'][0]['_joinData']['active'],
  517. $result->tags[0]->_joinData->active,
  518. '_joinData should be an entity.'
  519. );
  520. }
  521. /**
  522. * Test that the onlyIds option restricts to only accepting ids for belongs to many associations.
  523. *
  524. * @return void
  525. */
  526. public function testOneBelongsToManyOnlyIdsRejectArray()
  527. {
  528. $data = [
  529. 'title' => 'My title',
  530. 'body' => 'My content',
  531. 'author_id' => 1,
  532. 'tags' => [
  533. ['tag' => 'news'],
  534. ['tag' => 'cakephp'],
  535. ],
  536. ];
  537. $marshall = new Marshaller($this->articles);
  538. $result = $marshall->one($data, [
  539. 'associated' => ['Tags' => ['onlyIds' => true]]
  540. ]);
  541. $this->assertEmpty($result->tags, 'Only ids should be marshalled.');
  542. }
  543. /**
  544. * Test that the onlyIds option restricts to only accepting ids for belongs to many associations.
  545. *
  546. * @return void
  547. */
  548. public function testOneBelongsToManyOnlyIdsWithIds()
  549. {
  550. $data = [
  551. 'title' => 'My title',
  552. 'body' => 'My content',
  553. 'author_id' => 1,
  554. 'tags' => [
  555. '_ids' => [1, 2],
  556. ['tag' => 'news'],
  557. ],
  558. ];
  559. $marshall = new Marshaller($this->articles);
  560. $result = $marshall->one($data, [
  561. 'associated' => ['Tags' => ['onlyIds' => true]]
  562. ]);
  563. $this->assertCount(2, $result->tags, 'Ids should be marshalled.');
  564. }
  565. /**
  566. * Test marshalling nested associations on the _joinData structure.
  567. *
  568. * @return void
  569. */
  570. public function testOneBelongsToManyJoinDataAssociated()
  571. {
  572. $data = [
  573. 'title' => 'My title',
  574. 'body' => 'My content',
  575. 'author_id' => 1,
  576. 'tags' => [
  577. [
  578. 'tag' => 'news',
  579. '_joinData' => [
  580. 'active' => 1,
  581. 'user' => ['username' => 'Bill'],
  582. ]
  583. ],
  584. [
  585. 'tag' => 'cakephp',
  586. '_joinData' => [
  587. 'active' => 0,
  588. 'user' => ['username' => 'Mark'],
  589. ]
  590. ],
  591. ],
  592. ];
  593. $articlesTags = $this->getTableLocator()->get('ArticlesTags');
  594. $articlesTags->belongsTo('Users');
  595. $marshall = new Marshaller($this->articles);
  596. $result = $marshall->one($data, ['associated' => ['Tags._joinData.Users']]);
  597. $this->assertInstanceOf(
  598. 'Cake\ORM\Entity',
  599. $result->tags[0]->_joinData->user,
  600. 'joinData should contain a user entity.'
  601. );
  602. $this->assertEquals('Bill', $result->tags[0]->_joinData->user->username);
  603. $this->assertInstanceOf(
  604. 'Cake\ORM\Entity',
  605. $result->tags[1]->_joinData->user,
  606. 'joinData should contain a user entity.'
  607. );
  608. $this->assertEquals('Mark', $result->tags[1]->_joinData->user->username);
  609. }
  610. /**
  611. * Test one() with with id and _joinData.
  612. *
  613. * @return void
  614. */
  615. public function testOneBelongsToManyJoinDataAssociatedWithIds()
  616. {
  617. $data = [
  618. 'title' => 'My title',
  619. 'body' => 'My content',
  620. 'author_id' => 1,
  621. 'tags' => [
  622. 3 => [
  623. 'id' => 1,
  624. '_joinData' => [
  625. 'active' => 1,
  626. 'user' => ['username' => 'MyLux'],
  627. ]
  628. ],
  629. 5 => [
  630. 'id' => 2,
  631. '_joinData' => [
  632. 'active' => 0,
  633. 'user' => ['username' => 'IronFall'],
  634. ]
  635. ],
  636. ],
  637. ];
  638. $articlesTags = $this->getTableLocator()->get('ArticlesTags');
  639. $tags = $this->getTableLocator()->get('Tags');
  640. $t1 = $tags->find('all')->where(['id' => 1])->first();
  641. $t2 = $tags->find('all')->where(['id' => 2])->first();
  642. $articlesTags->belongsTo('Users');
  643. $marshall = new Marshaller($this->articles);
  644. $result = $marshall->one($data, ['associated' => ['Tags._joinData.Users']]);
  645. $this->assertInstanceOf(
  646. 'Cake\ORM\Entity',
  647. $result->tags[0]
  648. );
  649. $this->assertInstanceOf(
  650. 'Cake\ORM\Entity',
  651. $result->tags[1]
  652. );
  653. $this->assertInstanceOf(
  654. 'Cake\ORM\Entity',
  655. $result->tags[0]->_joinData->user
  656. );
  657. $this->assertInstanceOf(
  658. 'Cake\ORM\Entity',
  659. $result->tags[1]->_joinData->user
  660. );
  661. $this->assertFalse($result->tags[0]->isNew(), 'Should not be new, as id is in db.');
  662. $this->assertFalse($result->tags[1]->isNew(), 'Should not be new, as id is in db.');
  663. $this->assertEquals($t1->tag, $result->tags[0]->tag);
  664. $this->assertEquals($t2->tag, $result->tags[1]->tag);
  665. $this->assertEquals($data['tags'][3]['_joinData']['user']['username'], $result->tags[0]->_joinData->user->username);
  666. $this->assertEquals($data['tags'][5]['_joinData']['user']['username'], $result->tags[1]->_joinData->user->username);
  667. }
  668. /**
  669. * Test belongsToMany association with mixed data and _joinData
  670. *
  671. * @return void
  672. */
  673. public function testOneBelongsToManyWithMixedJoinData()
  674. {
  675. $data = [
  676. 'title' => 'My title',
  677. 'body' => 'My content',
  678. 'author_id' => 1,
  679. 'tags' => [
  680. [
  681. 'id' => 1,
  682. '_joinData' => [
  683. 'active' => 0,
  684. ]
  685. ],
  686. [
  687. 'name' => 'tag5',
  688. '_joinData' => [
  689. 'active' => 1,
  690. ]
  691. ]
  692. ]
  693. ];
  694. $marshall = new Marshaller($this->articles);
  695. $result = $marshall->one($data, ['associated' => ['Tags._joinData']]);
  696. $this->assertEquals($data['tags'][0]['id'], $result->tags[0]->id);
  697. $this->assertEquals($data['tags'][1]['name'], $result->tags[1]->name);
  698. $this->assertEquals(0, $result->tags[0]->_joinData->active);
  699. $this->assertEquals(1, $result->tags[1]->_joinData->active);
  700. }
  701. public function testOneBelongsToManyWithNestedAssociations()
  702. {
  703. $this->tags->belongsToMany('Articles');
  704. $data = [
  705. 'name' => 'new tag',
  706. 'articles' => [
  707. // This nested article exists, and we want to update it.
  708. [
  709. 'id' => 1,
  710. 'title' => 'New tagged article',
  711. 'body' => 'New tagged article',
  712. 'user' => [
  713. 'id' => 1,
  714. 'username' => 'newuser'
  715. ],
  716. 'comments' => [
  717. ['comment' => 'New comment', 'user_id' => 1],
  718. ['comment' => 'Second comment', 'user_id' => 1],
  719. ]
  720. ]
  721. ]
  722. ];
  723. $marshaller = new Marshaller($this->tags);
  724. $tag = $marshaller->one($data, ['associated' => ['Articles.Users', 'Articles.Comments']]);
  725. $this->assertNotEmpty($tag->articles);
  726. $this->assertCount(1, $tag->articles);
  727. $this->assertTrue($tag->isDirty('articles'), 'Updated prop should be dirty');
  728. $this->assertInstanceOf('Cake\ORM\Entity', $tag->articles[0]);
  729. $this->assertSame('New tagged article', $tag->articles[0]->title);
  730. $this->assertFalse($tag->articles[0]->isNew());
  731. $this->assertNotEmpty($tag->articles[0]->user);
  732. $this->assertInstanceOf('Cake\ORM\Entity', $tag->articles[0]->user);
  733. $this->assertTrue($tag->articles[0]->isDirty('user'), 'Updated prop should be dirty');
  734. $this->assertSame('newuser', $tag->articles[0]->user->username);
  735. $this->assertTrue($tag->articles[0]->user->isNew());
  736. $this->assertNotEmpty($tag->articles[0]->comments);
  737. $this->assertCount(2, $tag->articles[0]->comments);
  738. $this->assertTrue($tag->articles[0]->isDirty('comments'), 'Updated prop should be dirty');
  739. $this->assertInstanceOf('Cake\ORM\Entity', $tag->articles[0]->comments[0]);
  740. $this->assertTrue($tag->articles[0]->comments[0]->isNew());
  741. $this->assertTrue($tag->articles[0]->comments[1]->isNew());
  742. }
  743. /**
  744. * Test belongsToMany association with mixed data and _joinData
  745. *
  746. * @return void
  747. */
  748. public function testBelongsToManyAddingNewExisting()
  749. {
  750. $this->tags->setEntityClass(__NAMESPACE__ . '\Tag');
  751. $data = [
  752. 'title' => 'My title',
  753. 'body' => 'My content',
  754. 'author_id' => 1,
  755. 'tags' => [
  756. [
  757. 'id' => 1,
  758. '_joinData' => [
  759. 'active' => 0,
  760. ]
  761. ],
  762. ]
  763. ];
  764. $marshall = new Marshaller($this->articles);
  765. $result = $marshall->one($data, ['associated' => ['Tags._joinData']]);
  766. $data = [
  767. 'title' => 'New Title',
  768. 'tags' => [
  769. [
  770. 'id' => 1,
  771. '_joinData' => [
  772. 'active' => 0,
  773. ]
  774. ],
  775. [
  776. 'id' => 2,
  777. '_joinData' => [
  778. 'active' => 1,
  779. ]
  780. ]
  781. ]
  782. ];
  783. $result = $marshall->merge($result, $data, ['associated' => ['Tags._joinData']]);
  784. $this->assertEquals($data['title'], $result->title);
  785. $this->assertEquals($data['tags'][0]['id'], $result->tags[0]->id);
  786. $this->assertEquals($data['tags'][1]['id'], $result->tags[1]->id);
  787. $this->assertNotEmpty($result->tags[0]->_joinData);
  788. $this->assertNotEmpty($result->tags[1]->_joinData);
  789. $this->assertTrue($result->isDirty('tags'), 'Modified prop should be dirty');
  790. $this->assertEquals(0, $result->tags[0]->_joinData->active);
  791. $this->assertEquals(1, $result->tags[1]->_joinData->active);
  792. }
  793. /**
  794. * Test belongsToMany association with mixed data and _joinData
  795. *
  796. * @return void
  797. */
  798. public function testBelongsToManyWithMixedJoinDataOutOfOrder()
  799. {
  800. $data = [
  801. 'title' => 'My title',
  802. 'body' => 'My content',
  803. 'author_id' => 1,
  804. 'tags' => [
  805. [
  806. 'name' => 'tag5',
  807. '_joinData' => [
  808. 'active' => 1,
  809. ]
  810. ],
  811. [
  812. 'id' => 1,
  813. '_joinData' => [
  814. 'active' => 0,
  815. ]
  816. ],
  817. [
  818. 'name' => 'tag3',
  819. '_joinData' => [
  820. 'active' => 1,
  821. ]
  822. ],
  823. ]
  824. ];
  825. $marshall = new Marshaller($this->articles);
  826. $result = $marshall->one($data, ['associated' => ['Tags._joinData']]);
  827. $this->assertEquals($data['tags'][0]['name'], $result->tags[0]->name);
  828. $this->assertEquals($data['tags'][1]['id'], $result->tags[1]->id);
  829. $this->assertEquals($data['tags'][2]['name'], $result->tags[2]->name);
  830. $this->assertEquals(1, $result->tags[0]->_joinData->active);
  831. $this->assertEquals(0, $result->tags[1]->_joinData->active);
  832. $this->assertEquals(1, $result->tags[2]->_joinData->active);
  833. }
  834. /**
  835. * Test belongsToMany association with scalars
  836. *
  837. * @return void
  838. */
  839. public function testBelongsToManyInvalidData()
  840. {
  841. $data = [
  842. 'title' => 'My title',
  843. 'body' => 'My content',
  844. 'author_id' => 1,
  845. 'tags' => [
  846. 'id' => 1
  847. ]
  848. ];
  849. $article = $this->articles->newEntity($data, [
  850. 'associated' => ['Tags']
  851. ]);
  852. $this->assertEmpty($article->tags, 'No entity should be created');
  853. $data['tags'] = 1;
  854. $article = $this->articles->newEntity($data, [
  855. 'associated' => ['Tags']
  856. ]);
  857. $this->assertEmpty($article->tags, 'No entity should be created');
  858. }
  859. /**
  860. * Test belongsToMany association with mixed data array
  861. *
  862. * @return void
  863. */
  864. public function testBelongsToManyWithMixedData()
  865. {
  866. $data = [
  867. 'title' => 'My title',
  868. 'body' => 'My content',
  869. 'author_id' => 1,
  870. 'tags' => [
  871. [
  872. 'name' => 'tag4'
  873. ],
  874. [
  875. 'name' => 'tag5'
  876. ],
  877. [
  878. 'id' => 1
  879. ]
  880. ]
  881. ];
  882. $tags = $this->getTableLocator()->get('Tags');
  883. $marshaller = new Marshaller($this->articles);
  884. $article = $marshaller->one($data, ['associated' => ['Tags']]);
  885. $this->assertEquals($data['tags'][0]['name'], $article->tags[0]->name);
  886. $this->assertEquals($data['tags'][1]['name'], $article->tags[1]->name);
  887. $this->assertEquals($article->tags[2], $tags->get(1));
  888. $this->assertTrue($article->tags[0]->isNew());
  889. $this->assertTrue($article->tags[1]->isNew());
  890. $this->assertFalse($article->tags[2]->isNew());
  891. $tagCount = $tags->find()->count();
  892. $this->articles->save($article);
  893. $this->assertEquals($tagCount + 2, $tags->find()->count());
  894. }
  895. /**
  896. * Test belongsToMany association with the ForceNewTarget to force saving
  897. * new records on the target tables with BTM relationships when the primaryKey(s)
  898. * of the target table is specified.
  899. *
  900. * @return void
  901. */
  902. public function testBelongsToManyWithForceNew()
  903. {
  904. $data = [
  905. 'title' => 'Fourth Article',
  906. 'body' => 'Fourth Article Body',
  907. 'author_id' => 1,
  908. 'tags' => [
  909. [
  910. 'id' => 3
  911. ],
  912. [
  913. 'id' => 4,
  914. 'name' => 'tag4'
  915. ]
  916. ]
  917. ];
  918. $marshaller = new Marshaller($this->articles);
  919. $article = $marshaller->one($data, [
  920. 'associated' => ['Tags'],
  921. 'forceNew' => true
  922. ]);
  923. $this->assertFalse($article->tags[0]->isNew(), 'The tag should not be new');
  924. $this->assertTrue($article->tags[1]->isNew(), 'The tag should be new');
  925. $this->assertSame('tag4', $article->tags[1]->name, 'Property should match request data.');
  926. }
  927. /**
  928. * Test HasMany association with _ids attribute
  929. *
  930. * @return void
  931. */
  932. public function testOneHasManyWithIds()
  933. {
  934. $data = [
  935. 'title' => 'article',
  936. 'body' => 'some content',
  937. 'comments' => [
  938. '_ids' => [1, 2]
  939. ]
  940. ];
  941. $marshaller = new Marshaller($this->articles);
  942. $article = $marshaller->one($data, ['associated' => ['Comments']]);
  943. $this->assertEquals($article->comments[0], $this->comments->get(1));
  944. $this->assertEquals($article->comments[1], $this->comments->get(2));
  945. }
  946. /**
  947. * Test that the onlyIds option restricts to only accepting ids for hasmany associations.
  948. *
  949. * @return void
  950. */
  951. public function testOneHasManyOnlyIdsRejectArray()
  952. {
  953. $data = [
  954. 'title' => 'article',
  955. 'body' => 'some content',
  956. 'comments' => [
  957. ['comment' => 'first comment'],
  958. ['comment' => 'second comment'],
  959. ]
  960. ];
  961. $marshaller = new Marshaller($this->articles);
  962. $article = $marshaller->one($data, [
  963. 'associated' => ['Comments' => ['onlyIds' => true]]
  964. ]);
  965. $this->assertEmpty($article->comments);
  966. }
  967. /**
  968. * Test that the onlyIds option restricts to only accepting ids for hasmany associations.
  969. *
  970. * @return void
  971. */
  972. public function testOneHasManyOnlyIdsWithIds()
  973. {
  974. $data = [
  975. 'title' => 'article',
  976. 'body' => 'some content',
  977. 'comments' => [
  978. '_ids' => [1, 2],
  979. ['comment' => 'first comment'],
  980. ]
  981. ];
  982. $marshaller = new Marshaller($this->articles);
  983. $article = $marshaller->one($data, [
  984. 'associated' => ['Comments' => ['onlyIds' => true]]
  985. ]);
  986. $this->assertCount(2, $article->comments);
  987. }
  988. /**
  989. * Test HasMany association with invalid data
  990. *
  991. * @return void
  992. */
  993. public function testOneHasManyInvalidData()
  994. {
  995. $data = [
  996. 'title' => 'new title',
  997. 'body' => 'some content',
  998. 'comments' => [
  999. 'id' => 1
  1000. ]
  1001. ];
  1002. $marshaller = new Marshaller($this->articles);
  1003. $article = $marshaller->one($data, ['associated' => ['Comments']]);
  1004. $this->assertEmpty($article->comments);
  1005. $data['comments'] = 1;
  1006. $article = $marshaller->one($data, ['associated' => ['Comments']]);
  1007. $this->assertEmpty($article->comments);
  1008. }
  1009. /**
  1010. * Test one() with deeper associations.
  1011. *
  1012. * @return void
  1013. */
  1014. public function testOneDeepAssociations()
  1015. {
  1016. $data = [
  1017. 'comment' => 'First post',
  1018. 'user_id' => 2,
  1019. 'article' => [
  1020. 'title' => 'Article title',
  1021. 'body' => 'Article body',
  1022. 'user' => [
  1023. 'username' => 'mark',
  1024. 'password' => 'secret'
  1025. ],
  1026. ]
  1027. ];
  1028. $marshall = new Marshaller($this->comments);
  1029. $result = $marshall->one($data, ['associated' => ['Articles.Users']]);
  1030. $this->assertEquals(
  1031. $data['article']['title'],
  1032. $result->article->title
  1033. );
  1034. $this->assertEquals(
  1035. $data['article']['user']['username'],
  1036. $result->article->user->username
  1037. );
  1038. }
  1039. /**
  1040. * Test many() with a simple set of data.
  1041. *
  1042. * @return void
  1043. */
  1044. public function testManySimple()
  1045. {
  1046. $data = [
  1047. ['comment' => 'First post', 'user_id' => 2],
  1048. ['comment' => 'Second post', 'user_id' => 2],
  1049. ];
  1050. $marshall = new Marshaller($this->comments);
  1051. $result = $marshall->many($data);
  1052. $this->assertCount(2, $result);
  1053. $this->assertInstanceOf('Cake\ORM\Entity', $result[0]);
  1054. $this->assertInstanceOf('Cake\ORM\Entity', $result[1]);
  1055. $this->assertEquals($data[0]['comment'], $result[0]->comment);
  1056. $this->assertEquals($data[1]['comment'], $result[1]->comment);
  1057. }
  1058. /**
  1059. * Test many() with some invalid data
  1060. *
  1061. * @return void
  1062. */
  1063. public function testManyInvalidData()
  1064. {
  1065. $data = [
  1066. ['id' => 2, 'comment' => 'Changed 2', 'user_id' => 2],
  1067. ['id' => 1, 'comment' => 'Changed 1', 'user_id' => 1],
  1068. '_csrfToken' => 'abc123',
  1069. ];
  1070. $marshall = new Marshaller($this->comments);
  1071. $result = $marshall->many($data);
  1072. $this->assertCount(2, $result);
  1073. }
  1074. /**
  1075. * test many() with nested associations.
  1076. *
  1077. * @return void
  1078. */
  1079. public function testManyAssociations()
  1080. {
  1081. $data = [
  1082. [
  1083. 'comment' => 'First post',
  1084. 'user_id' => 2,
  1085. 'user' => [
  1086. 'username' => 'mark',
  1087. ],
  1088. ],
  1089. [
  1090. 'comment' => 'Second post',
  1091. 'user_id' => 2,
  1092. 'user' => [
  1093. 'username' => 'jose',
  1094. ],
  1095. ],
  1096. ];
  1097. $marshall = new Marshaller($this->comments);
  1098. $result = $marshall->many($data, ['associated' => ['Users']]);
  1099. $this->assertCount(2, $result);
  1100. $this->assertInstanceOf('Cake\ORM\Entity', $result[0]);
  1101. $this->assertInstanceOf('Cake\ORM\Entity', $result[1]);
  1102. $this->assertEquals(
  1103. $data[0]['user']['username'],
  1104. $result[0]->user->username
  1105. );
  1106. $this->assertEquals(
  1107. $data[1]['user']['username'],
  1108. $result[1]->user->username
  1109. );
  1110. }
  1111. /**
  1112. * Test if exception is raised when called with [associated => NonExistingAssociation]
  1113. * Previously such association were simply ignored
  1114. *
  1115. * @return void
  1116. */
  1117. public function testManyInvalidAssociation()
  1118. {
  1119. $this->expectException(\InvalidArgumentException::class);
  1120. $data = [
  1121. [
  1122. 'comment' => 'First post',
  1123. 'user_id' => 2,
  1124. 'user' => [
  1125. 'username' => 'mark',
  1126. ],
  1127. ],
  1128. [
  1129. 'comment' => 'Second post',
  1130. 'user_id' => 2,
  1131. 'user' => [
  1132. 'username' => 'jose',
  1133. ],
  1134. ],
  1135. ];
  1136. $marshall = new Marshaller($this->comments);
  1137. $marshall->many($data, ['associated' => ['Users', 'People']]);
  1138. }
  1139. /**
  1140. * Test generating a list of entities from a list of ids.
  1141. *
  1142. * @return void
  1143. */
  1144. public function testOneGenerateBelongsToManyEntitiesFromIds()
  1145. {
  1146. $data = [
  1147. 'title' => 'Haz tags',
  1148. 'body' => 'Some content here',
  1149. 'tags' => ['_ids' => '']
  1150. ];
  1151. $marshall = new Marshaller($this->articles);
  1152. $result = $marshall->one($data, ['associated' => ['Tags']]);
  1153. $this->assertCount(0, $result->tags);
  1154. $data = [
  1155. 'title' => 'Haz tags',
  1156. 'body' => 'Some content here',
  1157. 'tags' => ['_ids' => false]
  1158. ];
  1159. $result = $marshall->one($data, ['associated' => ['Tags']]);
  1160. $this->assertCount(0, $result->tags);
  1161. $data = [
  1162. 'title' => 'Haz tags',
  1163. 'body' => 'Some content here',
  1164. 'tags' => ['_ids' => null]
  1165. ];
  1166. $result = $marshall->one($data, ['associated' => ['Tags']]);
  1167. $this->assertCount(0, $result->tags);
  1168. $data = [
  1169. 'title' => 'Haz tags',
  1170. 'body' => 'Some content here',
  1171. 'tags' => ['_ids' => []]
  1172. ];
  1173. $result = $marshall->one($data, ['associated' => ['Tags']]);
  1174. $this->assertCount(0, $result->tags);
  1175. $data = [
  1176. 'title' => 'Haz tags',
  1177. 'body' => 'Some content here',
  1178. 'tags' => ['_ids' => [1, 2, 3]]
  1179. ];
  1180. $marshall = new Marshaller($this->articles);
  1181. $result = $marshall->one($data, ['associated' => ['Tags']]);
  1182. $this->assertCount(3, $result->tags);
  1183. $this->assertInstanceOf('Cake\ORM\Entity', $result->tags[0]);
  1184. $this->assertInstanceOf('Cake\ORM\Entity', $result->tags[1]);
  1185. $this->assertInstanceOf('Cake\ORM\Entity', $result->tags[2]);
  1186. }
  1187. /**
  1188. * Test merge() in a simple use.
  1189. *
  1190. * @return void
  1191. */
  1192. public function testMergeSimple()
  1193. {
  1194. $data = [
  1195. 'title' => 'My title',
  1196. 'author_id' => 1,
  1197. 'not_in_schema' => true
  1198. ];
  1199. $marshall = new Marshaller($this->articles);
  1200. $entity = new Entity([
  1201. 'title' => 'Foo',
  1202. 'body' => 'My Content'
  1203. ]);
  1204. $entity->setAccess('*', true);
  1205. $entity->isNew(false);
  1206. $entity->clean();
  1207. $result = $marshall->merge($entity, $data, []);
  1208. $this->assertSame($entity, $result);
  1209. $this->assertEquals($data + ['body' => 'My Content'], $result->toArray());
  1210. $this->assertTrue($result->isDirty(), 'Should be a dirty entity.');
  1211. $this->assertFalse($result->isNew(), 'Should not change the entity state');
  1212. }
  1213. /**
  1214. * Test merge() with accessibleFields options
  1215. *
  1216. * @return void
  1217. */
  1218. public function testMergeAccessibleFields()
  1219. {
  1220. $data = [
  1221. 'title' => 'My title',
  1222. 'body' => 'New content',
  1223. 'author_id' => 1,
  1224. 'not_in_schema' => true
  1225. ];
  1226. $marshall = new Marshaller($this->articles);
  1227. $entity = new Entity([
  1228. 'title' => 'Foo',
  1229. 'body' => 'My Content'
  1230. ]);
  1231. $entity->setAccess('*', false);
  1232. $entity->isNew(false);
  1233. $entity->clean();
  1234. $result = $marshall->merge($entity, $data, ['accessibleFields' => ['body' => true]]);
  1235. $this->assertSame($entity, $result);
  1236. $this->assertEquals(['title' => 'Foo', 'body' => 'New content'], $result->toArray());
  1237. $this->assertTrue($entity->isAccessible('body'));
  1238. }
  1239. /**
  1240. * Provides empty values.
  1241. *
  1242. * @return array
  1243. */
  1244. public function emptyProvider()
  1245. {
  1246. return [
  1247. [0],
  1248. ['0'],
  1249. ];
  1250. }
  1251. /**
  1252. * Test merging empty values into an entity.
  1253. *
  1254. * @dataProvider emptyProvider
  1255. * @return void
  1256. */
  1257. public function testMergeFalseyValues($value)
  1258. {
  1259. $marshall = new Marshaller($this->articles);
  1260. $entity = new Entity();
  1261. $entity->setAccess('*', true);
  1262. $entity->clean();
  1263. $entity = $marshall->merge($entity, ['author_id' => $value]);
  1264. $this->assertTrue($entity->isDirty('author_id'), 'Field should be dirty');
  1265. $this->assertSame(0, $entity->get('author_id'), 'Value should be zero');
  1266. }
  1267. /**
  1268. * Test merge() doesn't dirty values that were null and are null again.
  1269. *
  1270. * @return void
  1271. */
  1272. public function testMergeUnchangedNullValue()
  1273. {
  1274. $data = [
  1275. 'title' => 'My title',
  1276. 'author_id' => 1,
  1277. 'body' => null,
  1278. ];
  1279. $marshall = new Marshaller($this->articles);
  1280. $entity = new Entity([
  1281. 'title' => 'Foo',
  1282. 'body' => null
  1283. ]);
  1284. $entity->setAccess('*', true);
  1285. $entity->isNew(false);
  1286. $entity->clean();
  1287. $result = $marshall->merge($entity, $data, []);
  1288. $this->assertFalse($entity->isDirty('body'), 'unchanged null should not be dirty');
  1289. }
  1290. /**
  1291. * Tests that merge respects the entity accessible methods
  1292. *
  1293. * @return void
  1294. */
  1295. public function testMergeWhitelist()
  1296. {
  1297. $data = [
  1298. 'title' => 'My title',
  1299. 'author_id' => 1,
  1300. 'not_in_schema' => true
  1301. ];
  1302. $marshall = new Marshaller($this->articles);
  1303. $entity = new Entity([
  1304. 'title' => 'Foo',
  1305. 'body' => 'My Content'
  1306. ]);
  1307. $entity->setAccess('*', false);
  1308. $entity->setAccess('author_id', true);
  1309. $entity->isNew(false);
  1310. $entity->clean();
  1311. $result = $marshall->merge($entity, $data, []);
  1312. $expected = [
  1313. 'title' => 'Foo',
  1314. 'body' => 'My Content',
  1315. 'author_id' => 1
  1316. ];
  1317. $this->assertEquals($expected, $result->toArray());
  1318. }
  1319. /**
  1320. * Test merge() with an invalid association
  1321. *
  1322. * @return void
  1323. */
  1324. public function testMergeInvalidAssociation()
  1325. {
  1326. $this->expectException(\InvalidArgumentException::class);
  1327. $this->expectExceptionMessage('Cannot marshal data for "Derp" association. It is not associated with "Articles".');
  1328. $data = [
  1329. 'title' => 'My title',
  1330. 'body' => 'My content',
  1331. 'derp' => [
  1332. 'id' => 1,
  1333. 'username' => 'mark',
  1334. ]
  1335. ];
  1336. $article = new Entity([
  1337. 'title' => 'title for post',
  1338. 'body' => 'body',
  1339. ]);
  1340. $marshall = new Marshaller($this->articles);
  1341. $marshall->merge($article, $data, [
  1342. 'associated' => ['Derp']
  1343. ]);
  1344. }
  1345. /**
  1346. * Test merge when fields contains an association.
  1347. *
  1348. * @param $fields
  1349. * @return void
  1350. */
  1351. public function testMergeWithSingleAssociationAndFields()
  1352. {
  1353. $user = new Entity([
  1354. 'username' => 'user',
  1355. ]);
  1356. $article = new Entity([
  1357. 'title' => 'title for post',
  1358. 'body' => 'body',
  1359. 'user' => $user,
  1360. ]);
  1361. $user->setAccess('*', true);
  1362. $article->setAccess('*', true);
  1363. $data = [
  1364. 'title' => 'Chelsea',
  1365. 'user' => [
  1366. 'username' => 'dee'
  1367. ]
  1368. ];
  1369. $marshall = new Marshaller($this->articles);
  1370. $marshall->merge($article, $data, [
  1371. 'fields' => ['title', 'user'],
  1372. 'associated' => ['Users' => []]
  1373. ]);
  1374. $this->assertSame($user, $article->user);
  1375. $this->assertTrue($article->isDirty('user'));
  1376. }
  1377. /**
  1378. * Tests that fields with the same value are not marked as dirty
  1379. *
  1380. * @return void
  1381. */
  1382. public function testMergeDirty()
  1383. {
  1384. $marshall = new Marshaller($this->articles);
  1385. $entity = new Entity([
  1386. 'title' => 'Foo',
  1387. 'author_id' => 1
  1388. ]);
  1389. $data = [
  1390. 'title' => 'Foo',
  1391. 'author_id' => 1,
  1392. 'crazy' => true
  1393. ];
  1394. $entity->setAccess('*', true);
  1395. $entity->clean();
  1396. $result = $marshall->merge($entity, $data, []);
  1397. $expected = [
  1398. 'title' => 'Foo',
  1399. 'author_id' => 1,
  1400. 'crazy' => true
  1401. ];
  1402. $this->assertEquals($expected, $result->toArray());
  1403. $this->assertFalse($entity->isDirty('title'));
  1404. $this->assertFalse($entity->isDirty('author_id'));
  1405. $this->assertTrue($entity->isDirty('crazy'));
  1406. }
  1407. /**
  1408. * Tests merging data into an associated entity
  1409. *
  1410. * @return void
  1411. */
  1412. public function testMergeWithSingleAssociation()
  1413. {
  1414. $user = new Entity([
  1415. 'username' => 'mark',
  1416. 'password' => 'secret'
  1417. ]);
  1418. $entity = new Entity([
  1419. 'title' => 'My Title',
  1420. 'user' => $user
  1421. ]);
  1422. $user->setAccess('*', true);
  1423. $entity->setAccess('*', true);
  1424. $entity->clean();
  1425. $data = [
  1426. 'body' => 'My Content',
  1427. 'user' => [
  1428. 'password' => 'not a secret'
  1429. ]
  1430. ];
  1431. $marshall = new Marshaller($this->articles);
  1432. $marshall->merge($entity, $data, ['associated' => ['Users']]);
  1433. $this->assertTrue($entity->isDirty('user'), 'association should be dirty');
  1434. $this->assertTrue($entity->isDirty('body'), 'body should be dirty');
  1435. $this->assertEquals('My Content', $entity->body);
  1436. $this->assertSame($user, $entity->user);
  1437. $this->assertEquals('mark', $entity->user->username);
  1438. $this->assertEquals('not a secret', $entity->user->password);
  1439. }
  1440. /**
  1441. * Tests that new associated entities can be created when merging data into
  1442. * a parent entity
  1443. *
  1444. * @return void
  1445. */
  1446. public function testMergeCreateAssociation()
  1447. {
  1448. $entity = new Entity([
  1449. 'title' => 'My Title'
  1450. ]);
  1451. $entity->setAccess('*', true);
  1452. $entity->clean();
  1453. $data = [
  1454. 'body' => 'My Content',
  1455. 'user' => [
  1456. 'username' => 'mark',
  1457. 'password' => 'not a secret'
  1458. ]
  1459. ];
  1460. $marshall = new Marshaller($this->articles);
  1461. $marshall->merge($entity, $data, ['associated' => ['Users']]);
  1462. $this->assertEquals('My Content', $entity->body);
  1463. $this->assertInstanceOf('Cake\ORM\Entity', $entity->user);
  1464. $this->assertEquals('mark', $entity->user->username);
  1465. $this->assertEquals('not a secret', $entity->user->password);
  1466. $this->assertTrue($entity->isDirty('user'));
  1467. $this->assertTrue($entity->isDirty('body'));
  1468. $this->assertTrue($entity->user->isNew());
  1469. }
  1470. /**
  1471. * Test merge when an association has been replaced with null
  1472. *
  1473. * @return void
  1474. */
  1475. public function testMergeAssociationNullOut()
  1476. {
  1477. $user = new Entity([
  1478. 'id' => 1,
  1479. 'username' => 'user',
  1480. ]);
  1481. $article = new Entity([
  1482. 'title' => 'title for post',
  1483. 'user_id' => 1,
  1484. 'user' => $user,
  1485. ]);
  1486. $user->setAccess('*', true);
  1487. $article->setAccess('*', true);
  1488. $data = [
  1489. 'title' => 'Chelsea',
  1490. 'user_id' => '',
  1491. 'user' => ''
  1492. ];
  1493. $marshall = new Marshaller($this->articles);
  1494. $marshall->merge($article, $data, [
  1495. 'associated' => ['Users']
  1496. ]);
  1497. $this->assertNull($article->user);
  1498. $this->assertSame('', $article->user_id);
  1499. $this->assertTrue($article->isDirty('user'));
  1500. }
  1501. /**
  1502. * Tests merging one to many associations
  1503. *
  1504. * @return void
  1505. */
  1506. public function testMergeMultipleAssociations()
  1507. {
  1508. $user = new Entity(['username' => 'mark', 'password' => 'secret']);
  1509. $comment1 = new Entity(['id' => 1, 'comment' => 'A comment']);
  1510. $comment2 = new Entity(['id' => 2, 'comment' => 'Another comment']);
  1511. $entity = new Entity([
  1512. 'title' => 'My Title',
  1513. 'user' => $user,
  1514. 'comments' => [$comment1, $comment2]
  1515. ]);
  1516. $user->setAccess('*', true);
  1517. $comment1->setAccess('*', true);
  1518. $comment2->setAccess('*', true);
  1519. $entity->setAccess('*', true);
  1520. $entity->clean();
  1521. $data = [
  1522. 'title' => 'Another title',
  1523. 'user' => ['password' => 'not so secret'],
  1524. 'comments' => [
  1525. ['comment' => 'Extra comment 1'],
  1526. ['id' => 2, 'comment' => 'Altered comment 2'],
  1527. ['id' => 1, 'comment' => 'Altered comment 1'],
  1528. ['id' => 3, 'comment' => 'Extra comment 3'],
  1529. ['id' => 4, 'comment' => 'Extra comment 4'],
  1530. ['comment' => 'Extra comment 2']
  1531. ]
  1532. ];
  1533. $marshall = new Marshaller($this->articles);
  1534. $result = $marshall->merge($entity, $data, ['associated' => ['Users', 'Comments']]);
  1535. $this->assertSame($entity, $result);
  1536. $this->assertSame($user, $result->user);
  1537. $this->assertTrue($result->isDirty('user'), 'association should be dirty');
  1538. $this->assertEquals('not so secret', $entity->user->password);
  1539. $this->assertTrue($result->isDirty('comments'));
  1540. $this->assertSame($comment1, $entity->comments[0]);
  1541. $this->assertSame($comment2, $entity->comments[1]);
  1542. $this->assertEquals('Altered comment 1', $entity->comments[0]->comment);
  1543. $this->assertEquals('Altered comment 2', $entity->comments[1]->comment);
  1544. $thirdComment = $this->articles->Comments
  1545. ->find()
  1546. ->where(['id' => 3])
  1547. ->enableHydration(false)
  1548. ->first();
  1549. $this->assertEquals(
  1550. ['comment' => 'Extra comment 3'] + $thirdComment,
  1551. $entity->comments[2]->toArray()
  1552. );
  1553. $forthComment = $this->articles->Comments
  1554. ->find()
  1555. ->where(['id' => 4])
  1556. ->enableHydration(false)
  1557. ->first();
  1558. $this->assertEquals(
  1559. ['comment' => 'Extra comment 4'] + $forthComment,
  1560. $entity->comments[3]->toArray()
  1561. );
  1562. $this->assertEquals(
  1563. ['comment' => 'Extra comment 1'],
  1564. $entity->comments[4]->toArray()
  1565. );
  1566. $this->assertEquals(
  1567. ['comment' => 'Extra comment 2'],
  1568. $entity->comments[5]->toArray()
  1569. );
  1570. }
  1571. /**
  1572. * Tests that merging data to a hasMany association with _ids works.
  1573. *
  1574. * @return void
  1575. */
  1576. public function testMergeHasManyEntitiesFromIds()
  1577. {
  1578. $entity = $this->articles->get(1, ['contain' => ['Comments']]);
  1579. $this->assertNotEmpty($entity->comments);
  1580. $marshall = new Marshaller($this->articles);
  1581. $data = ['comments' => ['_ids' => [1, 2, 3]]];
  1582. $result = $marshall->merge($entity, $data, ['associated' => ['Comments']]);
  1583. $this->assertCount(3, $result->comments);
  1584. $this->assertTrue($result->isDirty('comments'), 'Updated prop should be dirty');
  1585. $this->assertInstanceOf(Entity::class, $result->comments[0]);
  1586. $this->assertEquals(1, $result->comments[0]->id);
  1587. $this->assertInstanceOf(Entity::class, $result->comments[1]);
  1588. $this->assertEquals(2, $result->comments[1]->id);
  1589. $this->assertInstanceOf(Entity::class, $result->comments[2]);
  1590. $this->assertEquals(3, $result->comments[2]->id);
  1591. }
  1592. /**
  1593. * Tests that merging data to a hasMany association using onlyIds restricts operations.
  1594. *
  1595. * @return void
  1596. */
  1597. public function testMergeHasManyEntitiesFromIdsOnlyIds()
  1598. {
  1599. $entity = $this->articles->get(1, ['contain' => ['Comments']]);
  1600. $this->assertNotEmpty($entity->comments);
  1601. $marshall = new Marshaller($this->articles);
  1602. $data = [
  1603. 'comments' => [
  1604. '_ids' => [1],
  1605. [
  1606. 'comment' => 'Nope'
  1607. ]
  1608. ]
  1609. ];
  1610. $result = $marshall->merge($entity, $data, ['associated' => ['Comments' => ['onlyIds' => true]]]);
  1611. $this->assertCount(1, $result->comments);
  1612. $this->assertTrue($result->isDirty('comments'), 'Updated prop should be dirty');
  1613. $this->assertInstanceOf(Entity::class, $result->comments[0]);
  1614. $this->assertNotEquals('Nope', $result->comments[0]);
  1615. }
  1616. /**
  1617. * Tests that merging data to an entity containing belongsToMany and _ids
  1618. * will just overwrite the data
  1619. *
  1620. * @return void
  1621. */
  1622. public function testMergeBelongsToManyEntitiesFromIds()
  1623. {
  1624. $entity = new Entity([
  1625. 'title' => 'Haz tags',
  1626. 'body' => 'Some content here',
  1627. 'tags' => [
  1628. new Entity(['id' => 1, 'name' => 'Cake']),
  1629. new Entity(['id' => 2, 'name' => 'PHP'])
  1630. ]
  1631. ]);
  1632. $data = [
  1633. 'title' => 'Haz moar tags',
  1634. 'tags' => ['_ids' => [1, 2, 3]]
  1635. ];
  1636. $entity->setAccess('*', true);
  1637. $entity->clean();
  1638. $marshall = new Marshaller($this->articles);
  1639. $result = $marshall->merge($entity, $data, ['associated' => ['Tags']]);
  1640. $this->assertCount(3, $result->tags);
  1641. $this->assertTrue($result->isDirty('tags'), 'Updated prop should be dirty');
  1642. $this->assertInstanceOf('Cake\ORM\Entity', $result->tags[0]);
  1643. $this->assertInstanceOf('Cake\ORM\Entity', $result->tags[1]);
  1644. $this->assertInstanceOf('Cake\ORM\Entity', $result->tags[2]);
  1645. }
  1646. /**
  1647. * Tests that merging data to an entity containing belongsToMany and _ids
  1648. * will not generate conflicting queries when associations are automatically selected
  1649. *
  1650. * @return void
  1651. */
  1652. public function testMergeFromIdsWithAutoAssociation()
  1653. {
  1654. $entity = new Entity([
  1655. 'title' => 'Haz tags',
  1656. 'body' => 'Some content here',
  1657. 'tags' => [
  1658. new Entity(['id' => 1, 'name' => 'Cake']),
  1659. new Entity(['id' => 2, 'name' => 'PHP'])
  1660. ]
  1661. ]);
  1662. $data = [
  1663. 'title' => 'Haz moar tags',
  1664. 'tags' => ['_ids' => [1, 2, 3]]
  1665. ];
  1666. $entity->setAccess('*', true);
  1667. $entity->clean();
  1668. // Adding a forced join to have another table with the same column names
  1669. $this->articles->Tags->getEventManager()->on('Model.beforeFind', function ($e, $query) {
  1670. $left = new IdentifierExpression('Tags.id');
  1671. $right = new IdentifierExpression('a.id');
  1672. $query->leftJoin(['a' => 'tags'], $query->newExpr()->eq($left, $right));
  1673. });
  1674. $marshall = new Marshaller($this->articles);
  1675. $result = $marshall->merge($entity, $data, ['associated' => ['Tags']]);
  1676. $this->assertCount(3, $result->tags);
  1677. $this->assertTrue($result->isDirty('tags'));
  1678. }
  1679. /**
  1680. * Tests that merging data to an entity containing belongsToMany and _ids
  1681. * with additional association conditions works.
  1682. *
  1683. * @return void
  1684. */
  1685. public function testMergeBelongsToManyFromIdsWithConditions()
  1686. {
  1687. $this->articles->belongsToMany('Tags', [
  1688. 'conditions' => ['ArticleTags.article_id' => 1]
  1689. ]);
  1690. $entity = new Entity([
  1691. 'title' => 'No tags',
  1692. 'body' => 'Some content here',
  1693. 'tags' => []
  1694. ]);
  1695. $data = [
  1696. 'title' => 'Haz moar tags',
  1697. 'tags' => ['_ids' => [1, 2, 3]]
  1698. ];
  1699. $entity->setAccess('*', true);
  1700. $entity->clean();
  1701. $marshall = new Marshaller($this->articles);
  1702. $result = $marshall->merge($entity, $data, ['associated' => ['Tags']]);
  1703. $this->assertCount(3, $result->tags);
  1704. $this->assertTrue($result->isDirty('tags'));
  1705. $this->assertInstanceOf('Cake\ORM\Entity', $result->tags[0]);
  1706. $this->assertInstanceOf('Cake\ORM\Entity', $result->tags[1]);
  1707. $this->assertInstanceOf('Cake\ORM\Entity', $result->tags[2]);
  1708. }
  1709. /**
  1710. * Tests that merging data to an entity containing belongsToMany as an array
  1711. * with additional association conditions works.
  1712. *
  1713. * @return void
  1714. */
  1715. public function testMergeBelongsToManyFromArrayWithConditions()
  1716. {
  1717. $this->articles->belongsToMany('Tags', [
  1718. 'conditions' => ['ArticleTags.article_id' => 1]
  1719. ]);
  1720. $this->articles->Tags->getEventManager()
  1721. ->on('Model.beforeFind', function (Event $event, $query) use (&$called) {
  1722. $called = true;
  1723. return $query->where(['Tags.id >=' => 1]);
  1724. });
  1725. $entity = new Entity([
  1726. 'title' => 'No tags',
  1727. 'body' => 'Some content here',
  1728. 'tags' => []
  1729. ]);
  1730. $data = [
  1731. 'title' => 'Haz moar tags',
  1732. 'tags' => [
  1733. ['id' => 1],
  1734. ['id' => 2]
  1735. ]
  1736. ];
  1737. $entity->setAccess('*', true);
  1738. $marshall = new Marshaller($this->articles);
  1739. $result = $marshall->merge($entity, $data, ['associated' => ['Tags']]);
  1740. $this->assertCount(2, $result->tags);
  1741. $this->assertInstanceOf('Cake\ORM\Entity', $result->tags[0]);
  1742. $this->assertInstanceOf('Cake\ORM\Entity', $result->tags[1]);
  1743. $this->assertTrue($called);
  1744. }
  1745. /**
  1746. * Tests that merging data to an entity containing belongsToMany and _ids
  1747. * will ignore empty values.
  1748. *
  1749. * @return void
  1750. */
  1751. public function testMergeBelongsToManyEntitiesFromIdsEmptyValue()
  1752. {
  1753. $entity = new Entity([
  1754. 'title' => 'Haz tags',
  1755. 'body' => 'Some content here',
  1756. 'tags' => [
  1757. new Entity(['id' => 1, 'name' => 'Cake']),
  1758. new Entity(['id' => 2, 'name' => 'PHP'])
  1759. ]
  1760. ]);
  1761. $data = [
  1762. 'title' => 'Haz moar tags',
  1763. 'tags' => ['_ids' => '']
  1764. ];
  1765. $entity->setAccess('*', true);
  1766. $marshall = new Marshaller($this->articles);
  1767. $result = $marshall->merge($entity, $data, ['associated' => ['Tags']]);
  1768. $this->assertCount(0, $result->tags);
  1769. $data = [
  1770. 'title' => 'Haz moar tags',
  1771. 'tags' => ['_ids' => false]
  1772. ];
  1773. $result = $marshall->merge($entity, $data, ['associated' => ['Tags']]);
  1774. $this->assertCount(0, $result->tags);
  1775. $data = [
  1776. 'title' => 'Haz moar tags',
  1777. 'tags' => ['_ids' => null]
  1778. ];
  1779. $result = $marshall->merge($entity, $data, ['associated' => ['Tags']]);
  1780. $this->assertCount(0, $result->tags);
  1781. $this->assertTrue($result->isDirty('tags'));
  1782. }
  1783. /**
  1784. * Test that the ids option restricts to only accepting ids for belongs to many associations.
  1785. *
  1786. * @return void
  1787. */
  1788. public function testMergeBelongsToManyOnlyIdsRejectArray()
  1789. {
  1790. $entity = new Entity([
  1791. 'title' => 'Haz tags',
  1792. 'body' => 'Some content here',
  1793. 'tags' => [
  1794. new Entity(['id' => 1, 'name' => 'Cake']),
  1795. new Entity(['id' => 2, 'name' => 'PHP'])
  1796. ]
  1797. ]);
  1798. $data = [
  1799. 'title' => 'Haz moar tags',
  1800. 'tags' => [
  1801. ['name' => 'new'],
  1802. ['name' => 'awesome']
  1803. ]
  1804. ];
  1805. $entity->setAccess('*', true);
  1806. $marshall = new Marshaller($this->articles);
  1807. $result = $marshall->merge($entity, $data, [
  1808. 'associated' => ['Tags' => ['onlyIds' => true]]
  1809. ]);
  1810. $this->assertCount(0, $result->tags);
  1811. $this->assertTrue($result->isDirty('tags'));
  1812. }
  1813. /**
  1814. * Test that the ids option restricts to only accepting ids for belongs to many associations.
  1815. *
  1816. * @return void
  1817. */
  1818. public function testMergeBelongsToManyOnlyIdsWithIds()
  1819. {
  1820. $entity = new Entity([
  1821. 'title' => 'Haz tags',
  1822. 'body' => 'Some content here',
  1823. 'tags' => [
  1824. new Entity(['id' => 1, 'name' => 'Cake']),
  1825. new Entity(['id' => 2, 'name' => 'PHP'])
  1826. ]
  1827. ]);
  1828. $data = [
  1829. 'title' => 'Haz moar tags',
  1830. 'tags' => [
  1831. '_ids' => [3]
  1832. ]
  1833. ];
  1834. $entity->setAccess('*', true);
  1835. $marshall = new Marshaller($this->articles);
  1836. $result = $marshall->merge($entity, $data, [
  1837. 'associated' => ['Tags' => ['ids' => true]]
  1838. ]);
  1839. $this->assertCount(1, $result->tags);
  1840. $this->assertEquals('tag3', $result->tags[0]->name);
  1841. $this->assertTrue($result->isDirty('tags'));
  1842. }
  1843. /**
  1844. * Test that invalid _joinData (scalar data) is not marshalled.
  1845. *
  1846. * @return void
  1847. */
  1848. public function testMergeBelongsToManyJoinDataScalar()
  1849. {
  1850. $this->getTableLocator()->clear();
  1851. $articles = $this->getTableLocator()->get('Articles');
  1852. $articles->belongsToMany('Tags', [
  1853. 'through' => 'SpecialTags'
  1854. ]);
  1855. $entity = $articles->get(1, ['contain' => 'Tags']);
  1856. $data = [
  1857. 'title' => 'Haz data',
  1858. 'tags' => [
  1859. ['id' => 3, 'tag' => 'Cake', '_joinData' => 'Invalid'],
  1860. ]
  1861. ];
  1862. $marshall = new Marshaller($articles);
  1863. $result = $marshall->merge($entity, $data, ['associated' => 'Tags._joinData']);
  1864. $articles->save($entity, ['associated' => ['Tags._joinData']]);
  1865. $this->assertFalse($entity->tags[0]->isDirty('_joinData'));
  1866. $this->assertEmpty($entity->tags[0]->_joinData);
  1867. }
  1868. /**
  1869. * Test merging the _joinData entity for belongstomany associations when * is not
  1870. * accessible.
  1871. *
  1872. * @return void
  1873. */
  1874. public function testMergeBelongsToManyJoinDataNotAccessible()
  1875. {
  1876. $this->getTableLocator()->clear();
  1877. $articles = $this->getTableLocator()->get('Articles');
  1878. $articles->belongsToMany('Tags', [
  1879. 'through' => 'SpecialTags'
  1880. ]);
  1881. $entity = $articles->get(1, ['contain' => 'Tags']);
  1882. // Make only specific fields accessible, but not _joinData.
  1883. $entity->tags[0]->setAccess('*', false);
  1884. $entity->tags[0]->setAccess(['article_id', 'tag_id'], true);
  1885. $data = [
  1886. 'title' => 'Haz data',
  1887. 'tags' => [
  1888. ['id' => 3, 'tag' => 'Cake', '_joinData' => ['highlighted' => '1', 'author_id' => '99']],
  1889. ]
  1890. ];
  1891. $marshall = new Marshaller($articles);
  1892. $result = $marshall->merge($entity, $data, ['associated' => 'Tags._joinData']);
  1893. $this->assertTrue($entity->isDirty('tags'), 'Association data changed');
  1894. $this->assertTrue($entity->tags[0]->isDirty('_joinData'));
  1895. $this->assertTrue($result->tags[0]->_joinData->isDirty('author_id'), 'Field not modified');
  1896. $this->assertTrue($result->tags[0]->_joinData->isDirty('highlighted'), 'Field not modified');
  1897. $this->assertSame(99, $result->tags[0]->_joinData->author_id);
  1898. $this->assertTrue($result->tags[0]->_joinData->highlighted);
  1899. }
  1900. /**
  1901. * Test that _joinData is marshalled consistently with both
  1902. * new and existing records
  1903. *
  1904. * @return void
  1905. */
  1906. public function testMergeBelongsToManyHandleJoinDataConsistently()
  1907. {
  1908. $this->getTableLocator()->clear();
  1909. $articles = $this->getTableLocator()->get('Articles');
  1910. $articles->belongsToMany('Tags', [
  1911. 'through' => 'SpecialTags'
  1912. ]);
  1913. $entity = $articles->get(1);
  1914. $data = [
  1915. 'title' => 'Haz data',
  1916. 'tags' => [
  1917. ['id' => 3, 'tag' => 'Cake', '_joinData' => ['highlighted' => true]],
  1918. ]
  1919. ];
  1920. $marshall = new Marshaller($articles);
  1921. $result = $marshall->merge($entity, $data, ['associated' => 'Tags']);
  1922. $this->assertTrue($entity->isDirty('tags'));
  1923. $this->assertInstanceOf('Cake\ORM\Entity', $result->tags[0]->_joinData);
  1924. $this->assertTrue($result->tags[0]->_joinData->highlighted);
  1925. // Also ensure merge() overwrites existing data.
  1926. $entity = $articles->get(1, ['contain' => 'Tags']);
  1927. $data = [
  1928. 'title' => 'Haz data',
  1929. 'tags' => [
  1930. ['id' => 3, 'tag' => 'Cake', '_joinData' => ['highlighted' => true]],
  1931. ]
  1932. ];
  1933. $marshall = new Marshaller($articles);
  1934. $result = $marshall->merge($entity, $data, ['associated' => 'Tags']);
  1935. $this->assertTrue($entity->isDirty('tags'), 'association data changed');
  1936. $this->assertInstanceOf('Cake\ORM\Entity', $result->tags[0]->_joinData);
  1937. $this->assertTrue($result->tags[0]->_joinData->highlighted);
  1938. }
  1939. /**
  1940. * Test merging belongsToMany data doesn't create 'new' entities.
  1941. *
  1942. * @return void
  1943. */
  1944. public function testMergeBelongsToManyJoinDataAssociatedWithIds()
  1945. {
  1946. $data = [
  1947. 'title' => 'My title',
  1948. 'tags' => [
  1949. [
  1950. 'id' => 1,
  1951. '_joinData' => [
  1952. 'active' => 1,
  1953. 'user' => ['username' => 'MyLux'],
  1954. ]
  1955. ],
  1956. [
  1957. 'id' => 2,
  1958. '_joinData' => [
  1959. 'active' => 0,
  1960. 'user' => ['username' => 'IronFall'],
  1961. ]
  1962. ],
  1963. ],
  1964. ];
  1965. $articlesTags = $this->getTableLocator()->get('ArticlesTags');
  1966. $articlesTags->belongsTo('Users');
  1967. $marshall = new Marshaller($this->articles);
  1968. $article = $this->articles->get(1, ['associated' => 'Tags']);
  1969. $result = $marshall->merge($article, $data, ['associated' => ['Tags._joinData.Users']]);
  1970. $this->assertTrue($result->isDirty('tags'));
  1971. $this->assertInstanceOf('Cake\ORM\Entity', $result->tags[0]);
  1972. $this->assertInstanceOf('Cake\ORM\Entity', $result->tags[1]);
  1973. $this->assertInstanceOf('Cake\ORM\Entity', $result->tags[0]->_joinData->user);
  1974. $this->assertInstanceOf('Cake\ORM\Entity', $result->tags[1]->_joinData->user);
  1975. $this->assertFalse($result->tags[0]->isNew(), 'Should not be new, as id is in db.');
  1976. $this->assertFalse($result->tags[1]->isNew(), 'Should not be new, as id is in db.');
  1977. $this->assertEquals(1, $result->tags[0]->id);
  1978. $this->assertEquals(2, $result->tags[1]->id);
  1979. $this->assertEquals(1, $result->tags[0]->_joinData->active);
  1980. $this->assertEquals(0, $result->tags[1]->_joinData->active);
  1981. $this->assertEquals(
  1982. $data['tags'][0]['_joinData']['user']['username'],
  1983. $result->tags[0]->_joinData->user->username
  1984. );
  1985. $this->assertEquals(
  1986. $data['tags'][1]['_joinData']['user']['username'],
  1987. $result->tags[1]->_joinData->user->username
  1988. );
  1989. }
  1990. /**
  1991. * Test merging the _joinData entity for belongstomany associations.
  1992. *
  1993. * @return void
  1994. */
  1995. public function testMergeBelongsToManyJoinData()
  1996. {
  1997. $data = [
  1998. 'title' => 'My title',
  1999. 'body' => 'My content',
  2000. 'author_id' => 1,
  2001. 'tags' => [
  2002. [
  2003. 'id' => 1,
  2004. 'tag' => 'news',
  2005. '_joinData' => [
  2006. 'active' => 0
  2007. ]
  2008. ],
  2009. [
  2010. 'id' => 2,
  2011. 'tag' => 'cakephp',
  2012. '_joinData' => [
  2013. 'active' => 0
  2014. ]
  2015. ],
  2016. ],
  2017. ];
  2018. $options = ['associated' => ['Tags._joinData']];
  2019. $marshall = new Marshaller($this->articles);
  2020. $entity = $marshall->one($data, $options);
  2021. $entity->setAccess('*', true);
  2022. $data = [
  2023. 'title' => 'Haz data',
  2024. 'tags' => [
  2025. ['id' => 1, 'tag' => 'Cake', '_joinData' => ['foo' => 'bar']],
  2026. ['tag' => 'new tag', '_joinData' => ['active' => 1, 'foo' => 'baz']]
  2027. ]
  2028. ];
  2029. $tag1 = $entity->tags[0];
  2030. $result = $marshall->merge($entity, $data, $options);
  2031. $this->assertEquals($data['title'], $result->title);
  2032. $this->assertEquals('My content', $result->body);
  2033. $this->assertTrue($result->isDirty('tags'));
  2034. $this->assertSame($tag1, $entity->tags[0]);
  2035. $this->assertSame($tag1->_joinData, $entity->tags[0]->_joinData);
  2036. $this->assertSame(
  2037. ['active' => 0, 'foo' => 'bar'],
  2038. $entity->tags[0]->_joinData->toArray()
  2039. );
  2040. $this->assertSame(
  2041. ['active' => 1, 'foo' => 'baz'],
  2042. $entity->tags[1]->_joinData->toArray()
  2043. );
  2044. $this->assertEquals('new tag', $entity->tags[1]->tag);
  2045. $this->assertTrue($entity->tags[0]->isDirty('_joinData'));
  2046. $this->assertTrue($entity->tags[1]->isDirty('_joinData'));
  2047. }
  2048. /**
  2049. * Test merging associations inside _joinData
  2050. *
  2051. * @return void
  2052. */
  2053. public function testMergeJoinDataAssociations()
  2054. {
  2055. $data = [
  2056. 'title' => 'My title',
  2057. 'body' => 'My content',
  2058. 'author_id' => 1,
  2059. 'tags' => [
  2060. [
  2061. 'id' => 1,
  2062. 'tag' => 'news',
  2063. '_joinData' => [
  2064. 'active' => 0,
  2065. 'user' => ['username' => 'Bill']
  2066. ]
  2067. ],
  2068. [
  2069. 'id' => 2,
  2070. 'tag' => 'cakephp',
  2071. '_joinData' => [
  2072. 'active' => 0
  2073. ]
  2074. ],
  2075. ]
  2076. ];
  2077. $articlesTags = $this->getTableLocator()->get('ArticlesTags');
  2078. $articlesTags->belongsTo('Users');
  2079. $options = ['associated' => ['Tags._joinData.Users']];
  2080. $marshall = new Marshaller($this->articles);
  2081. $entity = $marshall->one($data, $options);
  2082. $entity->setAccess('*', true);
  2083. $data = [
  2084. 'title' => 'Haz data',
  2085. 'tags' => [
  2086. [
  2087. 'id' => 1,
  2088. 'tag' => 'news',
  2089. '_joinData' => [
  2090. 'foo' => 'bar',
  2091. 'user' => ['password' => 'secret']
  2092. ]
  2093. ],
  2094. [
  2095. 'id' => 2,
  2096. '_joinData' => [
  2097. 'active' => 1,
  2098. 'foo' => 'baz',
  2099. 'user' => ['username' => 'ber']
  2100. ]
  2101. ]
  2102. ]
  2103. ];
  2104. $tag1 = $entity->tags[0];
  2105. $result = $marshall->merge($entity, $data, $options);
  2106. $this->assertEquals($data['title'], $result->title);
  2107. $this->assertEquals('My content', $result->body);
  2108. $this->assertTrue($entity->isDirty('tags'));
  2109. $this->assertSame($tag1, $entity->tags[0]);
  2110. $this->assertTrue($tag1->isDirty('_joinData'));
  2111. $this->assertSame($tag1->_joinData, $entity->tags[0]->_joinData);
  2112. $this->assertEquals('Bill', $entity->tags[0]->_joinData->user->username);
  2113. $this->assertEquals('secret', $entity->tags[0]->_joinData->user->password);
  2114. $this->assertEquals('ber', $entity->tags[1]->_joinData->user->username);
  2115. }
  2116. /**
  2117. * Tests that merging belongsToMany association doesn't erase _joinData
  2118. * on existing objects.
  2119. *
  2120. * @return void
  2121. */
  2122. public function testMergeBelongsToManyIdsRetainJoinData()
  2123. {
  2124. $this->articles->belongsToMany('Tags');
  2125. $entity = $this->articles->get(1, ['contain' => ['Tags']]);
  2126. $entity->setAccess('*', true);
  2127. $original = $entity->tags[0]->_joinData;
  2128. $this->assertInstanceOf('Cake\ORM\Entity', $entity->tags[0]->_joinData);
  2129. $data = [
  2130. 'title' => 'Haz moar tags',
  2131. 'tags' => [
  2132. ['id' => 1],
  2133. ]
  2134. ];
  2135. $marshall = new Marshaller($this->articles);
  2136. $result = $marshall->merge($entity, $data, ['associated' => ['Tags']]);
  2137. $this->assertCount(1, $result->tags);
  2138. $this->assertTrue($result->isDirty('tags'));
  2139. $this->assertInstanceOf('Cake\ORM\Entity', $result->tags[0]);
  2140. $this->assertInstanceOf('Cake\ORM\Entity', $result->tags[0]->_joinData);
  2141. $this->assertSame($original, $result->tags[0]->_joinData, 'Should be same object');
  2142. }
  2143. /**
  2144. * Test mergeMany() with a simple set of data.
  2145. *
  2146. * @return void
  2147. */
  2148. public function testMergeManySimple()
  2149. {
  2150. $entities = [
  2151. new OpenEntity(['id' => 1, 'comment' => 'First post', 'user_id' => 2]),
  2152. new OpenEntity(['id' => 2, 'comment' => 'Second post', 'user_id' => 2])
  2153. ];
  2154. $entities[0]->clean();
  2155. $entities[1]->clean();
  2156. $data = [
  2157. ['id' => 2, 'comment' => 'Changed 2', 'user_id' => 2],
  2158. ['id' => 1, 'comment' => 'Changed 1', 'user_id' => 1]
  2159. ];
  2160. $marshall = new Marshaller($this->comments);
  2161. $result = $marshall->mergeMany($entities, $data);
  2162. $this->assertSame($entities[0], $result[0]);
  2163. $this->assertSame($entities[1], $result[1]);
  2164. $this->assertEquals('Changed 1', $result[0]->comment);
  2165. $this->assertEquals(1, $result[0]->user_id);
  2166. $this->assertEquals('Changed 2', $result[1]->comment);
  2167. $this->assertTrue($result[0]->isDirty('user_id'));
  2168. $this->assertFalse($result[1]->isDirty('user_id'));
  2169. }
  2170. /**
  2171. * Test mergeMany() with some invalid data
  2172. *
  2173. * @return void
  2174. */
  2175. public function testMergeManyInvalidData()
  2176. {
  2177. $entities = [
  2178. new OpenEntity(['id' => 1, 'comment' => 'First post', 'user_id' => 2]),
  2179. new OpenEntity(['id' => 2, 'comment' => 'Second post', 'user_id' => 2])
  2180. ];
  2181. $entities[0]->clean();
  2182. $entities[1]->clean();
  2183. $data = [
  2184. ['id' => 2, 'comment' => 'Changed 2', 'user_id' => 2],
  2185. ['id' => 1, 'comment' => 'Changed 1', 'user_id' => 1],
  2186. '_csrfToken' => 'abc123',
  2187. ];
  2188. $marshall = new Marshaller($this->comments);
  2189. $result = $marshall->mergeMany($entities, $data);
  2190. $this->assertSame($entities[0], $result[0]);
  2191. $this->assertSame($entities[1], $result[1]);
  2192. }
  2193. /**
  2194. * Tests that only records found in the data array are returned, those that cannot
  2195. * be matched are discarded
  2196. *
  2197. * @return void
  2198. */
  2199. public function testMergeManyWithAppend()
  2200. {
  2201. $entities = [
  2202. new OpenEntity(['comment' => 'First post', 'user_id' => 2]),
  2203. new OpenEntity(['id' => 2, 'comment' => 'Second post', 'user_id' => 2])
  2204. ];
  2205. $entities[0]->clean();
  2206. $entities[1]->clean();
  2207. $data = [
  2208. ['id' => 2, 'comment' => 'Changed 2', 'user_id' => 2],
  2209. ['id' => 1, 'comment' => 'Comment 1', 'user_id' => 1]
  2210. ];
  2211. $marshall = new Marshaller($this->comments);
  2212. $result = $marshall->mergeMany($entities, $data);
  2213. $this->assertCount(2, $result);
  2214. $this->assertNotSame($entities[0], $result[0]);
  2215. $this->assertSame($entities[1], $result[0]);
  2216. $this->assertEquals('Changed 2', $result[0]->comment);
  2217. $this->assertEquals('Comment 1', $result[1]->comment);
  2218. }
  2219. /**
  2220. * Test that mergeMany() handles composite key associations properly.
  2221. *
  2222. * The articles_tags table has a composite primary key, and should be
  2223. * handled correctly.
  2224. *
  2225. * @return void
  2226. */
  2227. public function testMergeManyCompositeKey()
  2228. {
  2229. $articlesTags = $this->getTableLocator()->get('ArticlesTags');
  2230. $entities = [
  2231. new OpenEntity(['article_id' => 1, 'tag_id' => 2]),
  2232. new OpenEntity(['article_id' => 1, 'tag_id' => 1]),
  2233. ];
  2234. $entities[0]->clean();
  2235. $entities[1]->clean();
  2236. $data = [
  2237. ['article_id' => 1, 'tag_id' => 1],
  2238. ['article_id' => 1, 'tag_id' => 2]
  2239. ];
  2240. $marshall = new Marshaller($articlesTags);
  2241. $result = $marshall->mergeMany($entities, $data);
  2242. $this->assertCount(2, $result, 'Should have two records');
  2243. $this->assertSame($entities[0], $result[0], 'Should retain object');
  2244. $this->assertSame($entities[1], $result[1], 'Should retain object');
  2245. }
  2246. /**
  2247. * Test mergeMany() with forced contain to ensure aliases are used in queries.
  2248. *
  2249. * @return void
  2250. */
  2251. public function testMergeManyExistingQueryAliases()
  2252. {
  2253. $entities = [
  2254. new OpenEntity(['id' => 1, 'comment' => 'First post', 'user_id' => 2], ['markClean' => true]),
  2255. ];
  2256. $data = [
  2257. ['id' => 1, 'comment' => 'Changed 1', 'user_id' => 1],
  2258. ['id' => 2, 'comment' => 'Changed 2', 'user_id' => 2],
  2259. ];
  2260. $this->comments->getEventManager()->on('Model.beforeFind', function (Event $event, $query) {
  2261. return $query->contain(['Articles']);
  2262. });
  2263. $marshall = new Marshaller($this->comments);
  2264. $result = $marshall->mergeMany($entities, $data);
  2265. $this->assertSame($entities[0], $result[0]);
  2266. }
  2267. /**
  2268. * Test mergeMany() when the exist check returns nothing.
  2269. *
  2270. * @return void
  2271. */
  2272. public function testMergeManyExistQueryFails()
  2273. {
  2274. $entities = [
  2275. new Entity(['id' => 1, 'comment' => 'First post', 'user_id' => 2]),
  2276. new Entity(['id' => 2, 'comment' => 'Second post', 'user_id' => 2])
  2277. ];
  2278. $entities[0]->clean();
  2279. $entities[1]->clean();
  2280. $data = [
  2281. ['id' => 2, 'comment' => 'Changed 2', 'user_id' => 2],
  2282. ['id' => 1, 'comment' => 'Changed 1', 'user_id' => 1],
  2283. ['id' => 3, 'comment' => 'New 1'],
  2284. ];
  2285. $comments = $this->getTableLocator()->get('GreedyComments', [
  2286. 'className' => __NAMESPACE__ . '\\GreedyCommentsTable'
  2287. ]);
  2288. $marshall = new Marshaller($comments);
  2289. $result = $marshall->mergeMany($entities, $data);
  2290. $this->assertCount(3, $result);
  2291. $this->assertEquals('Changed 1', $result[0]->comment);
  2292. $this->assertEquals(1, $result[0]->user_id);
  2293. $this->assertEquals('Changed 2', $result[1]->comment);
  2294. $this->assertEquals('New 1', $result[2]->comment);
  2295. }
  2296. /**
  2297. * Tests merge with data types that need to be marshalled
  2298. *
  2299. * @return void
  2300. */
  2301. public function testMergeComplexType()
  2302. {
  2303. $entity = new Entity(
  2304. ['comment' => 'My Comment text'],
  2305. ['markNew' => false, 'markClean' => true]
  2306. );
  2307. $data = [
  2308. 'created' => [
  2309. 'year' => '2014',
  2310. 'month' => '2',
  2311. 'day' => 14
  2312. ]
  2313. ];
  2314. $marshall = new Marshaller($this->comments);
  2315. $result = $marshall->merge($entity, $data);
  2316. $this->assertInstanceOf('DateTime', $entity->created);
  2317. $this->assertEquals('2014-02-14', $entity->created->format('Y-m-d'));
  2318. }
  2319. /**
  2320. * Tests that it is possible to pass a fields option to the marshaller
  2321. *
  2322. * @group deprecated
  2323. * @return void
  2324. */
  2325. public function testOneWithFieldList()
  2326. {
  2327. $this->deprecated(function () {
  2328. $data = [
  2329. 'title' => 'My title',
  2330. 'body' => 'My content',
  2331. 'author_id' => null
  2332. ];
  2333. $marshall = new Marshaller($this->articles);
  2334. $result = $marshall->one($data, ['fieldList' => ['title', 'author_id']]);
  2335. $this->assertInstanceOf('Cake\ORM\Entity', $result);
  2336. unset($data['body']);
  2337. $this->assertEquals($data, $result->toArray());
  2338. });
  2339. }
  2340. /**
  2341. * Tests that it is possible to pass a fields option to the marshaller
  2342. *
  2343. * @return void
  2344. */
  2345. public function testOneWithFields()
  2346. {
  2347. $data = [
  2348. 'title' => 'My title',
  2349. 'body' => 'My content',
  2350. 'author_id' => null
  2351. ];
  2352. $marshall = new Marshaller($this->articles);
  2353. $result = $marshall->one($data, ['fields' => ['title', 'author_id']]);
  2354. $this->assertInstanceOf('Cake\ORM\Entity', $result);
  2355. unset($data['body']);
  2356. $this->assertEquals($data, $result->toArray());
  2357. }
  2358. /**
  2359. * Test one() with translations
  2360. *
  2361. * @return void
  2362. */
  2363. public function testOneWithTranslations()
  2364. {
  2365. $this->articles->addBehavior('Translate', [
  2366. 'fields' => ['title', 'body']
  2367. ]);
  2368. $data = [
  2369. 'author_id' => 1,
  2370. '_translations' => [
  2371. 'en' => [
  2372. 'title' => 'English Title',
  2373. 'body' => 'English Content'
  2374. ],
  2375. 'es' => [
  2376. 'title' => 'Titulo Español',
  2377. 'body' => 'Contenido Español'
  2378. ]
  2379. ],
  2380. 'user' => [
  2381. 'id' => 1,
  2382. 'username' => 'mark'
  2383. ]
  2384. ];
  2385. $marshall = new Marshaller($this->articles);
  2386. $result = $marshall->one($data, ['associated' => ['Users']]);
  2387. $this->assertEmpty($result->getErrors());
  2388. $this->assertEquals(1, $result->author_id);
  2389. $this->assertInstanceOf(__NAMESPACE__ . '\OpenEntity', $result->user);
  2390. $this->assertEquals('mark', $result->user->username);
  2391. $translations = $result->get('_translations');
  2392. $this->assertCount(2, $translations);
  2393. $this->assertInstanceOf(__NAMESPACE__ . '\OpenEntity', $translations['en']);
  2394. $this->assertInstanceOf(__NAMESPACE__ . '\OpenEntity', $translations['es']);
  2395. $this->assertEquals($data['_translations']['en'], $translations['en']->toArray());
  2396. }
  2397. /**
  2398. * Tests that it is possible to pass a fields option to the merge method
  2399. *
  2400. * @group deprecated
  2401. * @return void
  2402. */
  2403. public function testMergeWithFieldList()
  2404. {
  2405. $this->deprecated(function () {
  2406. $data = [
  2407. 'title' => 'My title',
  2408. 'body' => null,
  2409. 'author_id' => 1
  2410. ];
  2411. $marshall = new Marshaller($this->articles);
  2412. $entity = new Entity([
  2413. 'title' => 'Foo',
  2414. 'body' => 'My content',
  2415. 'author_id' => 2
  2416. ]);
  2417. $entity->setAccess('*', false);
  2418. $entity->isNew(false);
  2419. $entity->clean();
  2420. $result = $marshall->merge($entity, $data, ['fieldList' => ['title', 'body']]);
  2421. $expected = [
  2422. 'title' => 'My title',
  2423. 'body' => null,
  2424. 'author_id' => 2
  2425. ];
  2426. $this->assertSame($entity, $result);
  2427. $this->assertEquals($expected, $result->toArray());
  2428. $this->assertFalse($entity->isAccessible('*'));
  2429. });
  2430. }
  2431. /**
  2432. * Tests that it is possible to pass a fields option to the merge method
  2433. *
  2434. * @return void
  2435. */
  2436. public function testMergeWithFields()
  2437. {
  2438. $data = [
  2439. 'title' => 'My title',
  2440. 'body' => null,
  2441. 'author_id' => 1
  2442. ];
  2443. $marshall = new Marshaller($this->articles);
  2444. $entity = new Entity([
  2445. 'title' => 'Foo',
  2446. 'body' => 'My content',
  2447. 'author_id' => 2
  2448. ]);
  2449. $entity->setAccess('*', false);
  2450. $entity->isNew(false);
  2451. $entity->clean();
  2452. $result = $marshall->merge($entity, $data, ['fields' => ['title', 'body']]);
  2453. $expected = [
  2454. 'title' => 'My title',
  2455. 'body' => null,
  2456. 'author_id' => 2
  2457. ];
  2458. $this->assertSame($entity, $result);
  2459. $this->assertEquals($expected, $result->toArray());
  2460. $this->assertFalse($entity->isAccessible('*'));
  2461. }
  2462. /**
  2463. * Test that many() also receives a fields option
  2464. *
  2465. * @return void
  2466. */
  2467. public function testManyFields()
  2468. {
  2469. $data = [
  2470. ['comment' => 'First post', 'user_id' => 2, 'foo' => 'bar'],
  2471. ['comment' => 'Second post', 'user_id' => 2, 'foo' => 'bar'],
  2472. ];
  2473. $marshall = new Marshaller($this->comments);
  2474. $result = $marshall->many($data, ['fields' => ['comment', 'user_id']]);
  2475. $this->assertCount(2, $result);
  2476. unset($data[0]['foo'], $data[1]['foo']);
  2477. $this->assertEquals($data[0], $result[0]->toArray());
  2478. $this->assertEquals($data[1], $result[1]->toArray());
  2479. }
  2480. /**
  2481. * Test that many() also receives a fields option
  2482. *
  2483. * @return void
  2484. */
  2485. public function testMergeManyFields()
  2486. {
  2487. $entities = [
  2488. new OpenEntity(['id' => 1, 'comment' => 'First post', 'user_id' => 2]),
  2489. new OpenEntity(['id' => 2, 'comment' => 'Second post', 'user_id' => 2])
  2490. ];
  2491. $entities[0]->clean();
  2492. $entities[1]->clean();
  2493. $data = [
  2494. ['id' => 2, 'comment' => 'Changed 2', 'user_id' => 10],
  2495. ['id' => 1, 'comment' => 'Changed 1', 'user_id' => 20]
  2496. ];
  2497. $marshall = new Marshaller($this->comments);
  2498. $result = $marshall->mergeMany($entities, $data, ['fields' => ['id', 'comment']]);
  2499. $this->assertSame($entities[0], $result[0]);
  2500. $this->assertSame($entities[1], $result[1]);
  2501. $expected = ['id' => 2, 'comment' => 'Changed 2', 'user_id' => 2];
  2502. $this->assertEquals($expected, $entities[1]->toArray());
  2503. $expected = ['id' => 1, 'comment' => 'Changed 1', 'user_id' => 2];
  2504. $this->assertEquals($expected, $entities[0]->toArray());
  2505. }
  2506. /**
  2507. * test marshalling association data while passing a fields
  2508. *
  2509. * @return void
  2510. */
  2511. public function testAssociationsFields()
  2512. {
  2513. $data = [
  2514. 'title' => 'My title',
  2515. 'body' => 'My content',
  2516. 'author_id' => 1,
  2517. 'user' => [
  2518. 'username' => 'mark',
  2519. 'password' => 'secret',
  2520. 'foo' => 'bar'
  2521. ]
  2522. ];
  2523. $marshall = new Marshaller($this->articles);
  2524. $result = $marshall->one($data, [
  2525. 'fields' => ['title', 'body', 'user'],
  2526. 'associated' => [
  2527. 'Users' => ['fields' => ['username', 'foo']]
  2528. ]
  2529. ]);
  2530. $this->assertEquals($data['title'], $result->title);
  2531. $this->assertEquals($data['body'], $result->body);
  2532. $this->assertNull($result->author_id);
  2533. $this->assertInstanceOf('Cake\ORM\Entity', $result->user);
  2534. $this->assertEquals($data['user']['username'], $result->user->username);
  2535. $this->assertNull($result->user->password);
  2536. }
  2537. /**
  2538. * Tests merging associated data with a fields
  2539. *
  2540. * @return void
  2541. */
  2542. public function testMergeAssociationWithfields()
  2543. {
  2544. $user = new Entity([
  2545. 'username' => 'mark',
  2546. 'password' => 'secret'
  2547. ]);
  2548. $entity = new Entity([
  2549. 'tile' => 'My Title',
  2550. 'user' => $user
  2551. ]);
  2552. $user->setAccess('*', true);
  2553. $entity->setAccess('*', true);
  2554. $data = [
  2555. 'body' => 'My Content',
  2556. 'something' => 'else',
  2557. 'user' => [
  2558. 'password' => 'not a secret',
  2559. 'extra' => 'data'
  2560. ]
  2561. ];
  2562. $marshall = new Marshaller($this->articles);
  2563. $marshall->merge($entity, $data, [
  2564. 'fields' => ['something'],
  2565. 'associated' => ['Users' => ['fields' => ['extra']]]
  2566. ]);
  2567. $this->assertNull($entity->body);
  2568. $this->assertEquals('else', $entity->something);
  2569. $this->assertSame($user, $entity->user);
  2570. $this->assertEquals('mark', $entity->user->username);
  2571. $this->assertEquals('secret', $entity->user->password);
  2572. $this->assertEquals('data', $entity->user->extra);
  2573. $this->assertTrue($entity->isDirty('user'));
  2574. }
  2575. /**
  2576. * Test marshalling nested associations on the _joinData structure
  2577. * while having a fields
  2578. *
  2579. * @return void
  2580. */
  2581. public function testJoinDataWhiteList()
  2582. {
  2583. $data = [
  2584. 'title' => 'My title',
  2585. 'body' => 'My content',
  2586. 'author_id' => 1,
  2587. 'tags' => [
  2588. [
  2589. 'tag' => 'news',
  2590. '_joinData' => [
  2591. 'active' => 1,
  2592. 'crazy' => 'data',
  2593. 'user' => ['username' => 'Bill'],
  2594. ]
  2595. ],
  2596. [
  2597. 'tag' => 'cakephp',
  2598. '_joinData' => [
  2599. 'active' => 0,
  2600. 'crazy' => 'stuff',
  2601. 'user' => ['username' => 'Mark'],
  2602. ]
  2603. ],
  2604. ],
  2605. ];
  2606. $articlesTags = $this->getTableLocator()->get('ArticlesTags');
  2607. $articlesTags->belongsTo('Users');
  2608. $marshall = new Marshaller($this->articles);
  2609. $result = $marshall->one($data, [
  2610. 'associated' => [
  2611. 'Tags._joinData' => ['fields' => ['active', 'user']],
  2612. 'Tags._joinData.Users'
  2613. ]
  2614. ]);
  2615. $this->assertInstanceOf(
  2616. 'Cake\ORM\Entity',
  2617. $result->tags[0]->_joinData->user,
  2618. 'joinData should contain a user entity.'
  2619. );
  2620. $this->assertEquals('Bill', $result->tags[0]->_joinData->user->username);
  2621. $this->assertInstanceOf(
  2622. 'Cake\ORM\Entity',
  2623. $result->tags[1]->_joinData->user,
  2624. 'joinData should contain a user entity.'
  2625. );
  2626. $this->assertEquals('Mark', $result->tags[1]->_joinData->user->username);
  2627. $this->assertNull($result->tags[0]->_joinData->crazy);
  2628. $this->assertNull($result->tags[1]->_joinData->crazy);
  2629. }
  2630. /**
  2631. * Test merging the _joinData entity for belongstomany associations
  2632. * while passing a whitelist
  2633. *
  2634. * @return void
  2635. */
  2636. public function testMergeJoinDataWithFields()
  2637. {
  2638. $data = [
  2639. 'title' => 'My title',
  2640. 'body' => 'My content',
  2641. 'author_id' => 1,
  2642. 'tags' => [
  2643. [
  2644. 'id' => 1,
  2645. 'tag' => 'news',
  2646. '_joinData' => [
  2647. 'active' => 0
  2648. ]
  2649. ],
  2650. [
  2651. 'id' => 2,
  2652. 'tag' => 'cakephp',
  2653. '_joinData' => [
  2654. 'active' => 0
  2655. ]
  2656. ],
  2657. ],
  2658. ];
  2659. $options = ['associated' => ['Tags' => ['associated' => ['_joinData']]]];
  2660. $marshall = new Marshaller($this->articles);
  2661. $entity = $marshall->one($data, $options);
  2662. $entity->setAccess('*', true);
  2663. $data = [
  2664. 'title' => 'Haz data',
  2665. 'tags' => [
  2666. ['id' => 1, 'tag' => 'Cake', '_joinData' => ['foo' => 'bar', 'crazy' => 'something']],
  2667. ['tag' => 'new tag', '_joinData' => ['active' => 1, 'foo' => 'baz']]
  2668. ]
  2669. ];
  2670. $tag1 = $entity->tags[0];
  2671. $result = $marshall->merge($entity, $data, [
  2672. 'associated' => ['Tags._joinData' => ['fields' => ['foo']]]
  2673. ]);
  2674. $this->assertEquals($data['title'], $result->title);
  2675. $this->assertEquals('My content', $result->body);
  2676. $this->assertSame($tag1, $entity->tags[0]);
  2677. $this->assertSame($tag1->_joinData, $entity->tags[0]->_joinData);
  2678. $this->assertSame(
  2679. ['active' => 0, 'foo' => 'bar'],
  2680. $entity->tags[0]->_joinData->toArray()
  2681. );
  2682. $this->assertSame(
  2683. ['foo' => 'baz'],
  2684. $entity->tags[1]->_joinData->toArray()
  2685. );
  2686. $this->assertEquals('new tag', $entity->tags[1]->tag);
  2687. $this->assertTrue($entity->tags[0]->isDirty('_joinData'));
  2688. $this->assertTrue($entity->tags[1]->isDirty('_joinData'));
  2689. }
  2690. /**
  2691. * Tests marshalling with validation errors
  2692. *
  2693. * @return void
  2694. */
  2695. public function testValidationFail()
  2696. {
  2697. $data = [
  2698. 'title' => 'Thing',
  2699. 'body' => 'hey'
  2700. ];
  2701. $this->articles->getValidator()->requirePresence('thing');
  2702. $marshall = new Marshaller($this->articles);
  2703. $entity = $marshall->one($data);
  2704. $this->assertNotEmpty($entity->getError('thing'));
  2705. }
  2706. /**
  2707. * Test that invalid validate options raise exceptions
  2708. *
  2709. * @return void
  2710. */
  2711. public function testValidateInvalidType()
  2712. {
  2713. $this->expectException(\RuntimeException::class);
  2714. $data = ['title' => 'foo'];
  2715. $marshaller = new Marshaller($this->articles);
  2716. $marshaller->one($data, [
  2717. 'validate' => ['derp'],
  2718. ]);
  2719. }
  2720. /**
  2721. * Tests that associations are validated and custom validators can be used
  2722. *
  2723. * @return void
  2724. */
  2725. public function testValidateWithAssociationsAndCustomValidator()
  2726. {
  2727. $data = [
  2728. 'title' => 'foo',
  2729. 'body' => 'bar',
  2730. 'user' => [
  2731. 'name' => 'Susan'
  2732. ],
  2733. 'comments' => [
  2734. [
  2735. 'comment' => 'foo'
  2736. ]
  2737. ]
  2738. ];
  2739. $validator = (new Validator)->add('body', 'numeric', ['rule' => 'numeric']);
  2740. $this->articles->setValidator('custom', $validator);
  2741. $validator2 = (new Validator)->requirePresence('thing');
  2742. $this->articles->Users->setValidator('customThing', $validator2);
  2743. $this->articles->Comments->setValidator('default', $validator2);
  2744. $entity = (new Marshaller($this->articles))->one($data, [
  2745. 'validate' => 'custom',
  2746. 'associated' => ['Users', 'Comments']
  2747. ]);
  2748. $this->assertNotEmpty($entity->getError('body'), 'custom was not used');
  2749. $this->assertNull($entity->body);
  2750. $this->assertEmpty($entity->user->getError('thing'));
  2751. $this->assertNotEmpty($entity->comments[0]->getError('thing'));
  2752. $entity = (new Marshaller($this->articles))->one($data, [
  2753. 'validate' => 'custom',
  2754. 'associated' => ['Users' => ['validate' => 'customThing'], 'Comments']
  2755. ]);
  2756. $this->assertNotEmpty($entity->getError('body'));
  2757. $this->assertNull($entity->body);
  2758. $this->assertNotEmpty($entity->user->getError('thing'), 'customThing was not used');
  2759. $this->assertNotEmpty($entity->comments[0]->getError('thing'));
  2760. }
  2761. /**
  2762. * Tests that validation can be bypassed
  2763. *
  2764. * @return void
  2765. */
  2766. public function testSkipValidation()
  2767. {
  2768. $data = [
  2769. 'title' => 'foo',
  2770. 'body' => 'bar',
  2771. 'user' => [
  2772. 'name' => 'Susan'
  2773. ],
  2774. ];
  2775. $validator = (new Validator)->requirePresence('thing');
  2776. $this->articles->setValidator('default', $validator);
  2777. $this->articles->Users->setValidator('default', $validator);
  2778. $entity = (new Marshaller($this->articles))->one($data, [
  2779. 'validate' => false,
  2780. 'associated' => ['Users']
  2781. ]);
  2782. $this->assertEmpty($entity->getError('thing'));
  2783. $this->assertNotEmpty($entity->user->getError('thing'));
  2784. $entity = (new Marshaller($this->articles))->one($data, [
  2785. 'associated' => ['Users' => ['validate' => false]]
  2786. ]);
  2787. $this->assertNotEmpty($entity->getError('thing'));
  2788. $this->assertEmpty($entity->user->getError('thing'));
  2789. }
  2790. /**
  2791. * Tests that it is possible to pass a validator directly in the options
  2792. *
  2793. * @return void
  2794. */
  2795. public function testPassingCustomValidator()
  2796. {
  2797. $data = [
  2798. 'title' => 'Thing',
  2799. 'body' => 'hey'
  2800. ];
  2801. $validator = clone $this->articles->getValidator();
  2802. $validator->requirePresence('thing');
  2803. $marshall = new Marshaller($this->articles);
  2804. $entity = $marshall->one($data, ['validate' => $validator]);
  2805. $this->assertNotEmpty($entity->getError('thing'));
  2806. }
  2807. /**
  2808. * Tests that invalid property is being filled when data cannot be patched into an entity.
  2809. *
  2810. * @return void
  2811. */
  2812. public function testValidationWithInvalidFilled()
  2813. {
  2814. $data = [
  2815. 'title' => 'foo',
  2816. 'number' => 'bar',
  2817. ];
  2818. $validator = (new Validator)->add('number', 'numeric', ['rule' => 'numeric']);
  2819. $marshall = new Marshaller($this->articles);
  2820. $entity = $marshall->one($data, ['validate' => $validator]);
  2821. $this->assertNotEmpty($entity->getError('number'));
  2822. $this->assertNull($entity->number);
  2823. $this->assertSame(['number' => 'bar'], $entity->getInvalid());
  2824. }
  2825. /**
  2826. * Test merge with validation error
  2827. *
  2828. * @return void
  2829. */
  2830. public function testMergeWithValidation()
  2831. {
  2832. $data = [
  2833. 'title' => 'My title',
  2834. 'author_id' => 'foo',
  2835. ];
  2836. $marshall = new Marshaller($this->articles);
  2837. $entity = new Entity([
  2838. 'id' => 1,
  2839. 'title' => 'Foo',
  2840. 'body' => 'My Content',
  2841. 'author_id' => 1
  2842. ]);
  2843. $this->assertEmpty($entity->getInvalid());
  2844. $entity->setAccess('*', true);
  2845. $entity->isNew(false);
  2846. $entity->clean();
  2847. $this->articles->getValidator()
  2848. ->requirePresence('thing', 'update')
  2849. ->requirePresence('id', 'update')
  2850. ->add('author_id', 'numeric', ['rule' => 'numeric'])
  2851. ->add('id', 'numeric', ['rule' => 'numeric', 'on' => 'update']);
  2852. $expected = clone $entity;
  2853. $result = $marshall->merge($expected, $data, []);
  2854. $this->assertSame($expected, $result);
  2855. $this->assertSame(1, $result->author_id);
  2856. $this->assertNotEmpty($result->getError('thing'));
  2857. $this->assertEmpty($result->getError('id'));
  2858. $this->articles->getValidator()->requirePresence('thing', 'create');
  2859. $result = $marshall->merge($entity, $data, []);
  2860. $this->assertEmpty($result->getError('thing'));
  2861. $this->assertSame(['author_id' => 'foo'], $result->getInvalid());
  2862. }
  2863. /**
  2864. * Test merge with validation and create or update validation rules
  2865. *
  2866. * @return void
  2867. */
  2868. public function testMergeWithCreate()
  2869. {
  2870. $data = [
  2871. 'title' => 'My title',
  2872. 'author_id' => 'foo',
  2873. ];
  2874. $marshall = new Marshaller($this->articles);
  2875. $entity = new Entity([
  2876. 'title' => 'Foo',
  2877. 'body' => 'My Content',
  2878. 'author_id' => 1
  2879. ]);
  2880. $entity->setAccess('*', true);
  2881. $entity->isNew(true);
  2882. $entity->clean();
  2883. $this->articles->getValidator()
  2884. ->requirePresence('thing', 'update')
  2885. ->add('author_id', 'numeric', ['rule' => 'numeric', 'on' => 'update']);
  2886. $expected = clone $entity;
  2887. $result = $marshall->merge($expected, $data, []);
  2888. $this->assertEmpty($result->getError('author_id'));
  2889. $this->assertEmpty($result->getError('thing'));
  2890. $entity->clean();
  2891. $entity->isNew(false);
  2892. $result = $marshall->merge($entity, $data, []);
  2893. $this->assertNotEmpty($result->getError('author_id'));
  2894. $this->assertNotEmpty($result->getError('thing'));
  2895. }
  2896. /**
  2897. * Test merge() with translate behavior integration
  2898. *
  2899. * @return void
  2900. */
  2901. public function testMergeWithTranslations()
  2902. {
  2903. $this->articles->addBehavior('Translate', [
  2904. 'fields' => ['title', 'body']
  2905. ]);
  2906. $data = [
  2907. 'author_id' => 1,
  2908. '_translations' => [
  2909. 'en' => [
  2910. 'title' => 'English Title',
  2911. 'body' => 'English Content'
  2912. ],
  2913. 'es' => [
  2914. 'title' => 'Titulo Español',
  2915. 'body' => 'Contenido Español'
  2916. ]
  2917. ]
  2918. ];
  2919. $marshall = new Marshaller($this->articles);
  2920. $entity = $this->articles->newEntity();
  2921. $result = $marshall->merge($entity, $data, []);
  2922. $this->assertSame($entity, $result);
  2923. $this->assertEmpty($result->getErrors());
  2924. $this->assertTrue($result->isDirty('_translations'));
  2925. $translations = $result->get('_translations');
  2926. $this->assertCount(2, $translations);
  2927. $this->assertInstanceOf(__NAMESPACE__ . '\OpenEntity', $translations['en']);
  2928. $this->assertInstanceOf(__NAMESPACE__ . '\OpenEntity', $translations['es']);
  2929. $this->assertEquals($data['_translations']['en'], $translations['en']->toArray());
  2930. }
  2931. /**
  2932. * Test Model.beforeMarshal event.
  2933. *
  2934. * @return void
  2935. */
  2936. public function testBeforeMarshalEvent()
  2937. {
  2938. $data = [
  2939. 'title' => 'My title',
  2940. 'body' => 'My content',
  2941. 'user' => [
  2942. 'name' => 'Robert',
  2943. 'username' => 'rob'
  2944. ]
  2945. ];
  2946. $marshall = new Marshaller($this->articles);
  2947. $this->articles->getEventManager()->on(
  2948. 'Model.beforeMarshal',
  2949. function ($e, $data, $options) {
  2950. $this->assertArrayHasKey('validate', $options);
  2951. $data['title'] = 'Modified title';
  2952. $data['user']['username'] = 'robert';
  2953. $options['associated'] = ['Users'];
  2954. }
  2955. );
  2956. $entity = $marshall->one($data);
  2957. $this->assertEquals('Modified title', $entity->title);
  2958. $this->assertEquals('My content', $entity->body);
  2959. $this->assertEquals('Robert', $entity->user->name);
  2960. $this->assertEquals('robert', $entity->user->username);
  2961. }
  2962. /**
  2963. * Test Model.beforeMarshal event on associated tables.
  2964. *
  2965. * @return void
  2966. */
  2967. public function testBeforeMarshalEventOnAssociations()
  2968. {
  2969. $data = [
  2970. 'title' => 'My title',
  2971. 'body' => 'My content',
  2972. 'author_id' => 1,
  2973. 'user' => [
  2974. 'username' => 'mark',
  2975. 'password' => 'secret'
  2976. ],
  2977. 'comments' => [
  2978. ['comment' => 'First post', 'user_id' => 2],
  2979. ['comment' => 'Second post', 'user_id' => 2],
  2980. ],
  2981. 'tags' => [
  2982. ['tag' => 'news', '_joinData' => ['active' => 1]],
  2983. ['tag' => 'cakephp', '_joinData' => ['active' => 0]],
  2984. ],
  2985. ];
  2986. $marshall = new Marshaller($this->articles);
  2987. // Assert event options are correct
  2988. $this->articles->users->getEventManager()->on(
  2989. 'Model.beforeMarshal',
  2990. function ($e, $data, $options) {
  2991. $this->assertArrayHasKey('validate', $options);
  2992. $this->assertTrue($options['validate']);
  2993. $this->assertArrayHasKey('associated', $options);
  2994. $this->assertSame([], $options['associated']);
  2995. $this->assertArrayHasKey('association', $options);
  2996. $this->assertInstanceOf('Cake\ORM\Association', $options['association']);
  2997. }
  2998. );
  2999. $this->articles->users->getEventManager()->on(
  3000. 'Model.beforeMarshal',
  3001. function ($e, $data, $options) {
  3002. $data['secret'] = 'h45h3d';
  3003. }
  3004. );
  3005. $this->articles->comments->getEventManager()->on(
  3006. 'Model.beforeMarshal',
  3007. function ($e, $data) {
  3008. $data['comment'] .= ' (modified)';
  3009. }
  3010. );
  3011. $this->articles->tags->getEventManager()->on(
  3012. 'Model.beforeMarshal',
  3013. function ($e, $data) {
  3014. $data['tag'] .= ' (modified)';
  3015. }
  3016. );
  3017. $this->articles->tags->junction()->getEventManager()->on(
  3018. 'Model.beforeMarshal',
  3019. function ($e, $data) {
  3020. $data['modified_by'] = 1;
  3021. }
  3022. );
  3023. $entity = $marshall->one($data, [
  3024. 'associated' => ['Users', 'Comments', 'Tags']
  3025. ]);
  3026. $this->assertEquals('h45h3d', $entity->user->secret);
  3027. $this->assertEquals('First post (modified)', $entity->comments[0]->comment);
  3028. $this->assertEquals('Second post (modified)', $entity->comments[1]->comment);
  3029. $this->assertEquals('news (modified)', $entity->tags[0]->tag);
  3030. $this->assertEquals('cakephp (modified)', $entity->tags[1]->tag);
  3031. $this->assertEquals(1, $entity->tags[0]->_joinData->modified_by);
  3032. $this->assertEquals(1, $entity->tags[1]->_joinData->modified_by);
  3033. }
  3034. /**
  3035. * Tests that patching an association resulting in no changes, will
  3036. * not mark the parent entity as dirty
  3037. *
  3038. * @return void
  3039. */
  3040. public function testAssociationNoChanges()
  3041. {
  3042. $options = ['markClean' => true, 'isNew' => false];
  3043. $entity = new Entity([
  3044. 'title' => 'My Title',
  3045. 'user' => new Entity([
  3046. 'username' => 'mark',
  3047. 'password' => 'not a secret'
  3048. ], $options)
  3049. ], $options);
  3050. $data = [
  3051. 'body' => 'My Content',
  3052. 'user' => [
  3053. 'username' => 'mark',
  3054. 'password' => 'not a secret'
  3055. ]
  3056. ];
  3057. $marshall = new Marshaller($this->articles);
  3058. $marshall->merge($entity, $data, ['associated' => ['Users']]);
  3059. $this->assertEquals('My Content', $entity->body);
  3060. $this->assertInstanceOf('Cake\ORM\Entity', $entity->user);
  3061. $this->assertEquals('mark', $entity->user->username);
  3062. $this->assertEquals('not a secret', $entity->user->password);
  3063. $this->assertFalse($entity->isDirty('user'));
  3064. $this->assertTrue($entity->user->isNew());
  3065. }
  3066. /**
  3067. * Test that primary key meta data is being read from the table
  3068. * and not the schema reflection when handling belongsToMany associations.
  3069. *
  3070. * @return void
  3071. */
  3072. public function testEnsurePrimaryKeyBeingReadFromTableForHandlingEmptyStringPrimaryKey()
  3073. {
  3074. $data = [
  3075. 'id' => ''
  3076. ];
  3077. $articles = $this->getTableLocator()->get('Articles');
  3078. $articles->getSchema()->dropConstraint('primary');
  3079. $articles->setPrimaryKey('id');
  3080. $marshall = new Marshaller($articles);
  3081. $result = $marshall->one($data);
  3082. $this->assertFalse($result->isDirty('id'));
  3083. $this->assertNull($result->id);
  3084. }
  3085. /**
  3086. * Test that primary key meta data is being read from the table
  3087. * and not the schema reflection when handling belongsToMany associations.
  3088. *
  3089. * @return void
  3090. */
  3091. public function testEnsurePrimaryKeyBeingReadFromTableWhenLoadingBelongsToManyRecordsByPrimaryKey()
  3092. {
  3093. $data = [
  3094. 'tags' => [
  3095. [
  3096. 'id' => 1
  3097. ],
  3098. [
  3099. 'id' => 2
  3100. ]
  3101. ]
  3102. ];
  3103. $tags = $this->getTableLocator()->get('Tags');
  3104. $tags->getSchema()->dropConstraint('primary');
  3105. $tags->setPrimaryKey('id');
  3106. $marshall = new Marshaller($this->articles);
  3107. $result = $marshall->one($data, ['associated' => ['Tags']]);
  3108. $expected = [
  3109. 'tags' => [
  3110. [
  3111. 'id' => 1,
  3112. 'name' => 'tag1',
  3113. 'description' => 'A big description',
  3114. 'created' => new Time('2016-01-01 00:00'),
  3115. ],
  3116. [
  3117. 'id' => 2,
  3118. 'name' => 'tag2',
  3119. 'description' => 'Another big description',
  3120. 'created' => new Time('2016-01-01 00:00'),
  3121. ]
  3122. ]
  3123. ];
  3124. $this->assertEquals($expected, $result->toArray());
  3125. }
  3126. }