MarshallerTest.php 112 KB

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