MarshallerTest.php 102 KB

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