TableTest.php 216 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  5. * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  6. *
  7. * Licensed under The MIT License
  8. * For full copyright and license information, please see the LICENSE.txt
  9. * Redistributions of files must retain the above copyright notice.
  10. *
  11. * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  12. * @link https://cakephp.org CakePHP(tm) Project
  13. * @since 3.0.0
  14. * @license https://opensource.org/licenses/mit-license.php MIT License
  15. */
  16. namespace Cake\Test\TestCase\ORM;
  17. use ArrayObject;
  18. use Cake\Collection\Collection;
  19. use Cake\Database\Exception;
  20. use Cake\Database\Expression\QueryExpression;
  21. use Cake\Database\Schema\TableSchema;
  22. use Cake\Database\StatementInterface;
  23. use Cake\Database\TypeMap;
  24. use Cake\Datasource\ConnectionManager;
  25. use Cake\Datasource\EntityInterface;
  26. use Cake\Event\EventInterface;
  27. use Cake\Event\EventManager;
  28. use Cake\I18n\Time;
  29. use Cake\ORM\Association\BelongsTo;
  30. use Cake\ORM\Association\BelongsToMany;
  31. use Cake\ORM\Association\HasMany;
  32. use Cake\ORM\Association\HasOne;
  33. use Cake\ORM\AssociationCollection;
  34. use Cake\ORM\Entity;
  35. use Cake\ORM\Exception\PersistenceFailedException;
  36. use Cake\ORM\Query;
  37. use Cake\ORM\RulesChecker;
  38. use Cake\ORM\SaveOptionsBuilder;
  39. use Cake\ORM\Table;
  40. use Cake\TestSuite\TestCase;
  41. use Cake\Utility\Hash;
  42. use Cake\Validation\Validator;
  43. use InvalidArgumentException;
  44. use RuntimeException;
  45. use TestApp\Model\Entity\ProtectedEntity;
  46. use TestApp\Model\Entity\VirtualUser;
  47. use TestApp\Model\Table\UsersTable;
  48. /**
  49. * Tests Table class
  50. */
  51. class TableTest extends TestCase
  52. {
  53. protected $fixtures = [
  54. 'core.Articles',
  55. 'core.Tags',
  56. 'core.ArticlesTags',
  57. 'core.Authors',
  58. 'core.Categories',
  59. 'core.Comments',
  60. 'core.Groups',
  61. 'core.GroupsMembers',
  62. 'core.Members',
  63. 'core.PolymorphicTagged',
  64. 'core.SiteArticles',
  65. 'core.Users',
  66. ];
  67. /**
  68. * Handy variable containing the next primary key that will be inserted in the
  69. * users table
  70. *
  71. * @var int
  72. */
  73. public static $nextUserId = 5;
  74. public function setUp(): void
  75. {
  76. parent::setUp();
  77. $this->connection = ConnectionManager::get('test');
  78. static::setAppNamespace();
  79. $this->usersTypeMap = new TypeMap([
  80. 'Users.id' => 'integer',
  81. 'id' => 'integer',
  82. 'Users__id' => 'integer',
  83. 'Users.username' => 'string',
  84. 'Users__username' => 'string',
  85. 'username' => 'string',
  86. 'Users.password' => 'string',
  87. 'Users__password' => 'string',
  88. 'password' => 'string',
  89. 'Users.created' => 'timestamp',
  90. 'Users__created' => 'timestamp',
  91. 'created' => 'timestamp',
  92. 'Users.updated' => 'timestamp',
  93. 'Users__updated' => 'timestamp',
  94. 'updated' => 'timestamp',
  95. ]);
  96. $config = $this->connection->config();
  97. if (strpos($config['driver'], 'Postgres') !== false) {
  98. $this->usersTypeMap = new TypeMap([
  99. 'Users.id' => 'integer',
  100. 'id' => 'integer',
  101. 'Users__id' => 'integer',
  102. 'Users.username' => 'string',
  103. 'Users__username' => 'string',
  104. 'username' => 'string',
  105. 'Users.password' => 'string',
  106. 'Users__password' => 'string',
  107. 'password' => 'string',
  108. 'Users.created' => 'timestampfractional',
  109. 'Users__created' => 'timestampfractional',
  110. 'created' => 'timestampfractional',
  111. 'Users.updated' => 'timestampfractional',
  112. 'Users__updated' => 'timestampfractional',
  113. 'updated' => 'timestampfractional',
  114. ]);
  115. } elseif (strpos($config['driver'], 'Sqlserver') !== false) {
  116. $this->usersTypeMap = new TypeMap([
  117. 'Users.id' => 'integer',
  118. 'id' => 'integer',
  119. 'Users__id' => 'integer',
  120. 'Users.username' => 'string',
  121. 'Users__username' => 'string',
  122. 'username' => 'string',
  123. 'Users.password' => 'string',
  124. 'Users__password' => 'string',
  125. 'password' => 'string',
  126. 'Users.created' => 'datetimefractional',
  127. 'Users__created' => 'datetimefractional',
  128. 'created' => 'datetimefractional',
  129. 'Users.updated' => 'datetimefractional',
  130. 'Users__updated' => 'datetimefractional',
  131. 'updated' => 'datetimefractional',
  132. ]);
  133. }
  134. $this->articlesTypeMap = new TypeMap([
  135. 'Articles.id' => 'integer',
  136. 'Articles__id' => 'integer',
  137. 'id' => 'integer',
  138. 'Articles.title' => 'string',
  139. 'Articles__title' => 'string',
  140. 'title' => 'string',
  141. 'Articles.author_id' => 'integer',
  142. 'Articles__author_id' => 'integer',
  143. 'author_id' => 'integer',
  144. 'Articles.body' => 'text',
  145. 'Articles__body' => 'text',
  146. 'body' => 'text',
  147. 'Articles.published' => 'string',
  148. 'Articles__published' => 'string',
  149. 'published' => 'string',
  150. ]);
  151. }
  152. /**
  153. * teardown method
  154. *
  155. * @return void
  156. */
  157. public function tearDown(): void
  158. {
  159. parent::tearDown();
  160. $this->getTableLocator()->clear();
  161. $this->clearPlugins();
  162. }
  163. /**
  164. * Tests the table method
  165. *
  166. * @return void
  167. */
  168. public function testTableMethod()
  169. {
  170. $table = new Table(['table' => 'users']);
  171. $this->assertSame('users', $table->getTable());
  172. $table = new UsersTable();
  173. $this->assertSame('users', $table->getTable());
  174. /** @var \Cake\ORM\Table|\PHPUnit\Framework\MockObject\MockObject $table */
  175. $table = $this->getMockBuilder(Table::class)
  176. ->setMethods(['find'])
  177. ->setMockClassName('SpecialThingsTable')
  178. ->getMock();
  179. $this->assertSame('special_things', $table->getTable());
  180. $table = new Table(['alias' => 'LoveBoats']);
  181. $this->assertSame('love_boats', $table->getTable());
  182. $table->setTable('other');
  183. $this->assertSame('other', $table->getTable());
  184. $table->setTable('database.other');
  185. $this->assertSame('database.other', $table->getTable());
  186. }
  187. /**
  188. * Tests the setAlias method
  189. *
  190. * @return void
  191. */
  192. public function testSetAlias()
  193. {
  194. $table = new Table(['alias' => 'users']);
  195. $this->assertSame('users', $table->getAlias());
  196. $table = new Table(['table' => 'stuffs']);
  197. $this->assertSame('stuffs', $table->getAlias());
  198. $table = new UsersTable();
  199. $this->assertSame('Users', $table->getAlias());
  200. /** @var \Cake\ORM\Table|\PHPUnit\Framework\MockObject\MockObject $table */
  201. $table = $this->getMockBuilder(Table::class)
  202. ->setMethods(['find'])
  203. ->setMockClassName('SpecialThingTable')
  204. ->getMock();
  205. $this->assertSame('SpecialThing', $table->getAlias());
  206. $table->setAlias('AnotherOne');
  207. $this->assertSame('AnotherOne', $table->getAlias());
  208. }
  209. /**
  210. * Test that aliasField() works.
  211. *
  212. * @return void
  213. */
  214. public function testAliasField()
  215. {
  216. $table = new Table(['alias' => 'Users']);
  217. $this->assertSame('Users.id', $table->aliasField('id'));
  218. $this->assertSame('Users.id', $table->aliasField('Users.id'));
  219. }
  220. /**
  221. * Tests setConnection method
  222. *
  223. * @return void
  224. */
  225. public function testSetConnection()
  226. {
  227. $table = new Table(['table' => 'users']);
  228. $this->assertSame($this->connection, $table->getConnection());
  229. $this->assertSame($table, $table->setConnection($this->connection));
  230. $this->assertSame($this->connection, $table->getConnection());
  231. }
  232. /**
  233. * Tests primaryKey method
  234. *
  235. * @return void
  236. */
  237. public function testSetPrimaryKey()
  238. {
  239. $table = new Table([
  240. 'table' => 'users',
  241. 'schema' => [
  242. 'id' => ['type' => 'integer'],
  243. '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]],
  244. ],
  245. ]);
  246. $this->assertSame('id', $table->getPrimaryKey());
  247. $this->assertSame($table, $table->setPrimaryKey('thingID'));
  248. $this->assertSame('thingID', $table->getPrimaryKey());
  249. $table->setPrimaryKey(['thingID', 'user_id']);
  250. $this->assertEquals(['thingID', 'user_id'], $table->getPrimaryKey());
  251. }
  252. /**
  253. * Tests that name will be selected as a displayField
  254. *
  255. * @return void
  256. */
  257. public function testDisplayFieldName()
  258. {
  259. $table = new Table([
  260. 'table' => 'users',
  261. 'schema' => [
  262. 'foo' => ['type' => 'string'],
  263. 'name' => ['type' => 'string'],
  264. ],
  265. ]);
  266. $this->assertSame('name', $table->getDisplayField());
  267. }
  268. /**
  269. * Tests that title will be selected as a displayField
  270. *
  271. * @return void
  272. */
  273. public function testDisplayFieldTitle()
  274. {
  275. $table = new Table([
  276. 'table' => 'users',
  277. 'schema' => [
  278. 'foo' => ['type' => 'string'],
  279. 'title' => ['type' => 'string'],
  280. ],
  281. ]);
  282. $this->assertSame('title', $table->getDisplayField());
  283. }
  284. /**
  285. * Tests that no displayField will fallback to primary key
  286. *
  287. * @return void
  288. */
  289. public function testDisplayFallback()
  290. {
  291. $table = new Table([
  292. 'table' => 'users',
  293. 'schema' => [
  294. 'id' => ['type' => 'string'],
  295. 'foo' => ['type' => 'string'],
  296. '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]],
  297. ],
  298. ]);
  299. $this->assertSame('id', $table->getDisplayField());
  300. }
  301. /**
  302. * Tests that displayField can be changed
  303. *
  304. * @return void
  305. */
  306. public function testDisplaySet()
  307. {
  308. $table = new Table([
  309. 'table' => 'users',
  310. 'schema' => [
  311. 'id' => ['type' => 'string'],
  312. 'foo' => ['type' => 'string'],
  313. '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]],
  314. ],
  315. ]);
  316. $this->assertSame('id', $table->getDisplayField());
  317. $table->setDisplayField('foo');
  318. $this->assertSame('foo', $table->getDisplayField());
  319. }
  320. /**
  321. * Tests schema method
  322. *
  323. * @return void
  324. */
  325. public function testSetSchema()
  326. {
  327. $schema = $this->connection->getSchemaCollection()->describe('users');
  328. $table = new Table([
  329. 'table' => 'users',
  330. 'connection' => $this->connection,
  331. ]);
  332. $this->assertEquals($schema, $table->getSchema());
  333. $table = new Table(['table' => 'stuff']);
  334. $table->setSchema($schema);
  335. $this->assertSame($schema, $table->getSchema());
  336. $table = new Table(['table' => 'another']);
  337. $schema = ['id' => ['type' => 'integer']];
  338. $table->setSchema($schema);
  339. $this->assertEquals(
  340. new TableSchema('another', $schema),
  341. $table->getSchema()
  342. );
  343. }
  344. /**
  345. * Tests that _initializeSchema can be used to alter the database schema
  346. *
  347. * @return void
  348. */
  349. public function testSchemaInitialize()
  350. {
  351. $schema = $this->connection->getSchemaCollection()->describe('users');
  352. $table = $this->getMockBuilder(Table::class)
  353. ->setMethods(['_initializeSchema'])
  354. ->setConstructorArgs([['table' => 'users', 'connection' => $this->connection]])
  355. ->getMock();
  356. $table->expects($this->once())
  357. ->method('_initializeSchema')
  358. ->with($schema)
  359. ->will($this->returnCallback(function ($schema) {
  360. $schema->setColumnType('username', 'integer');
  361. return $schema;
  362. }));
  363. $result = $table->getSchema();
  364. $schema->setColumnType('username', 'integer');
  365. $this->assertEquals($schema, $result);
  366. $this->assertEquals($schema, $table->getSchema(), '_initializeSchema should be called once');
  367. }
  368. /**
  369. * Tests that all fields for a table are added by default in a find when no
  370. * other fields are specified
  371. *
  372. * @return void
  373. */
  374. public function testFindAllNoFieldsAndNoHydration()
  375. {
  376. $table = new Table([
  377. 'table' => 'users',
  378. 'connection' => $this->connection,
  379. ]);
  380. $results = $table
  381. ->find('all')
  382. ->where(['id IN' => [1, 2]])
  383. ->order('id')
  384. ->enableHydration(false)
  385. ->toArray();
  386. $expected = [
  387. [
  388. 'id' => 1,
  389. 'username' => 'mariano',
  390. 'password' => '$2a$10$u05j8FjsvLBNdfhBhc21LOuVMpzpabVXQ9OpC2wO3pSO0q6t7HHMO',
  391. 'created' => new Time('2007-03-17 01:16:23'),
  392. 'updated' => new Time('2007-03-17 01:18:31'),
  393. ],
  394. [
  395. 'id' => 2,
  396. 'username' => 'nate',
  397. 'password' => '$2a$10$u05j8FjsvLBNdfhBhc21LOuVMpzpabVXQ9OpC2wO3pSO0q6t7HHMO',
  398. 'created' => new Time('2008-03-17 01:18:23'),
  399. 'updated' => new Time('2008-03-17 01:20:31'),
  400. ],
  401. ];
  402. $this->assertEquals($expected, $results);
  403. }
  404. /**
  405. * Tests that it is possible to select only a few fields when finding over a table
  406. *
  407. * @return void
  408. */
  409. public function testFindAllSomeFieldsNoHydration()
  410. {
  411. $table = new Table([
  412. 'table' => 'users',
  413. 'connection' => $this->connection,
  414. ]);
  415. $results = $table->find('all')
  416. ->select(['username', 'password'])
  417. ->enableHydration(false)
  418. ->order('username')->toArray();
  419. $expected = [
  420. ['username' => 'garrett', 'password' => '$2a$10$u05j8FjsvLBNdfhBhc21LOuVMpzpabVXQ9OpC2wO3pSO0q6t7HHMO'],
  421. ['username' => 'larry', 'password' => '$2a$10$u05j8FjsvLBNdfhBhc21LOuVMpzpabVXQ9OpC2wO3pSO0q6t7HHMO'],
  422. ['username' => 'mariano', 'password' => '$2a$10$u05j8FjsvLBNdfhBhc21LOuVMpzpabVXQ9OpC2wO3pSO0q6t7HHMO'],
  423. ['username' => 'nate', 'password' => '$2a$10$u05j8FjsvLBNdfhBhc21LOuVMpzpabVXQ9OpC2wO3pSO0q6t7HHMO'],
  424. ];
  425. $this->assertSame($expected, $results);
  426. $results = $table->find('all')
  427. ->select(['foo' => 'username', 'password'])
  428. ->order('username')
  429. ->enableHydration(false)
  430. ->toArray();
  431. $expected = [
  432. ['foo' => 'garrett', 'password' => '$2a$10$u05j8FjsvLBNdfhBhc21LOuVMpzpabVXQ9OpC2wO3pSO0q6t7HHMO'],
  433. ['foo' => 'larry', 'password' => '$2a$10$u05j8FjsvLBNdfhBhc21LOuVMpzpabVXQ9OpC2wO3pSO0q6t7HHMO'],
  434. ['foo' => 'mariano', 'password' => '$2a$10$u05j8FjsvLBNdfhBhc21LOuVMpzpabVXQ9OpC2wO3pSO0q6t7HHMO'],
  435. ['foo' => 'nate', 'password' => '$2a$10$u05j8FjsvLBNdfhBhc21LOuVMpzpabVXQ9OpC2wO3pSO0q6t7HHMO'],
  436. ];
  437. $this->assertSame($expected, $results);
  438. }
  439. /**
  440. * Tests that the query will automatically casts complex conditions to the correct
  441. * types when the columns belong to the default table
  442. *
  443. * @return void
  444. */
  445. public function testFindAllConditionAutoTypes()
  446. {
  447. $table = new Table([
  448. 'table' => 'users',
  449. 'connection' => $this->connection,
  450. ]);
  451. $query = $table->find('all')
  452. ->select(['id', 'username'])
  453. ->where(['created >=' => new Time('2010-01-22 00:00')])
  454. ->enableHydration(false)
  455. ->order('id');
  456. $expected = [
  457. ['id' => 3, 'username' => 'larry'],
  458. ['id' => 4, 'username' => 'garrett'],
  459. ];
  460. $this->assertSame($expected, $query->toArray());
  461. $query = $table->find()
  462. ->enableHydration(false)
  463. ->select(['id', 'username'])
  464. ->where(['OR' => [
  465. 'created >=' => new Time('2010-01-22 00:00'),
  466. 'users.created' => new Time('2008-03-17 01:18:23'),
  467. ]])
  468. ->order('id');
  469. $expected = [
  470. ['id' => 2, 'username' => 'nate'],
  471. ['id' => 3, 'username' => 'larry'],
  472. ['id' => 4, 'username' => 'garrett'],
  473. ];
  474. $this->assertSame($expected, $query->toArray());
  475. }
  476. /**
  477. * Test that beforeFind events can mutate the query.
  478. *
  479. * @return void
  480. */
  481. public function testFindBeforeFindEventMutateQuery()
  482. {
  483. $table = new Table([
  484. 'table' => 'users',
  485. 'connection' => $this->connection,
  486. ]);
  487. $table->getEventManager()->on(
  488. 'Model.beforeFind',
  489. function (EventInterface $event, $query, $options) {
  490. $query->limit(1);
  491. }
  492. );
  493. $result = $table->find('all')->all();
  494. $this->assertCount(1, $result, 'Should only have 1 record, limit 1 applied.');
  495. }
  496. /**
  497. * Test that beforeFind events are fired and can stop the find and
  498. * return custom results.
  499. *
  500. * @return void
  501. */
  502. public function testFindBeforeFindEventOverrideReturn()
  503. {
  504. $table = new Table([
  505. 'table' => 'users',
  506. 'connection' => $this->connection,
  507. ]);
  508. $expected = ['One', 'Two', 'Three'];
  509. $table->getEventManager()->on(
  510. 'Model.beforeFind',
  511. function (EventInterface $event, $query, $options) use ($expected) {
  512. $query->setResult($expected);
  513. $event->stopPropagation();
  514. }
  515. );
  516. $query = $table->find('all');
  517. $query->limit(1);
  518. $this->assertEquals($expected, $query->all()->toArray());
  519. }
  520. /**
  521. * Test that the getAssociation() method supports the dot syntax.
  522. *
  523. * @return void
  524. */
  525. public function testAssociationDotSyntax()
  526. {
  527. $groups = $this->getTableLocator()->get('Groups');
  528. $members = $this->getTableLocator()->get('Members');
  529. $groupsMembers = $this->getTableLocator()->get('GroupsMembers');
  530. $groups->belongsToMany('Members');
  531. $groups->hasMany('GroupsMembers');
  532. $groupsMembers->belongsTo('Members');
  533. $members->belongsToMany('Groups');
  534. $association = $groups->getAssociation('GroupsMembers.Members.Groups');
  535. $this->assertInstanceOf(BelongsToMany::class, $association);
  536. $this->assertSame(
  537. $groups->getAssociation('GroupsMembers')->getAssociation('Members')->getAssociation('Groups'),
  538. $association
  539. );
  540. }
  541. /**
  542. * Tests that the getAssociation() method throws an exception on non-existent ones.
  543. *
  544. * @return void
  545. */
  546. public function testGetAssociationNonExistent()
  547. {
  548. $this->expectException(InvalidArgumentException::class);
  549. $this->getTableLocator()->get('Groups')->getAssociation('FooBar');
  550. }
  551. /**
  552. * Tests that belongsTo() creates and configures correctly the association
  553. *
  554. * @return void
  555. */
  556. public function testBelongsTo()
  557. {
  558. $options = ['foreignKey' => 'fake_id', 'conditions' => ['a' => 'b']];
  559. $table = new Table(['table' => 'dates']);
  560. $belongsTo = $table->belongsTo('user', $options);
  561. $this->assertInstanceOf(BelongsTo::class, $belongsTo);
  562. $this->assertSame($belongsTo, $table->getAssociation('user'));
  563. $this->assertSame('user', $belongsTo->getName());
  564. $this->assertSame('fake_id', $belongsTo->getForeignKey());
  565. $this->assertEquals(['a' => 'b'], $belongsTo->getConditions());
  566. $this->assertSame($table, $belongsTo->getSource());
  567. }
  568. /**
  569. * Tests that hasOne() creates and configures correctly the association
  570. *
  571. * @return void
  572. */
  573. public function testHasOne()
  574. {
  575. $options = ['foreignKey' => 'user_id', 'conditions' => ['b' => 'c']];
  576. $table = new Table(['table' => 'users']);
  577. $hasOne = $table->hasOne('profile', $options);
  578. $this->assertInstanceOf(HasOne::class, $hasOne);
  579. $this->assertSame($hasOne, $table->getAssociation('profile'));
  580. $this->assertSame('profile', $hasOne->getName());
  581. $this->assertSame('user_id', $hasOne->getForeignKey());
  582. $this->assertEquals(['b' => 'c'], $hasOne->getConditions());
  583. $this->assertSame($table, $hasOne->getSource());
  584. }
  585. /**
  586. * Test has one with a plugin model
  587. *
  588. * @return void
  589. */
  590. public function testHasOnePlugin()
  591. {
  592. $options = ['className' => 'TestPlugin.Comments'];
  593. $table = new Table(['table' => 'users']);
  594. $hasOne = $table->hasOne('Comments', $options);
  595. $this->assertInstanceOf(HasOne::class, $hasOne);
  596. $this->assertSame('Comments', $hasOne->getName());
  597. $hasOneTable = $hasOne->getTarget();
  598. $this->assertSame('Comments', $hasOne->getAlias());
  599. $this->assertSame('TestPlugin.Comments', $hasOne->getRegistryAlias());
  600. $options = ['className' => 'TestPlugin.Comments'];
  601. $table = new Table(['table' => 'users']);
  602. $hasOne = $table->hasOne('TestPlugin.Comments', $options);
  603. $this->assertInstanceOf(HasOne::class, $hasOne);
  604. $this->assertSame('Comments', $hasOne->getName());
  605. $hasOneTable = $hasOne->getTarget();
  606. $this->assertSame('Comments', $hasOne->getAlias());
  607. $this->assertSame('TestPlugin.Comments', $hasOne->getRegistryAlias());
  608. }
  609. /**
  610. * testNoneUniqueAssociationsSameClass
  611. *
  612. * @return void
  613. */
  614. public function testNoneUniqueAssociationsSameClass()
  615. {
  616. $Users = new Table(['table' => 'users']);
  617. $options = ['className' => 'Comments'];
  618. $Users->hasMany('Comments', $options);
  619. $Articles = new Table(['table' => 'articles']);
  620. $options = ['className' => 'Comments'];
  621. $Articles->hasMany('Comments', $options);
  622. $Categories = new Table(['table' => 'categories']);
  623. $options = ['className' => 'TestPlugin.Comments'];
  624. $Categories->hasMany('Comments', $options);
  625. $this->assertInstanceOf(Table::class, $Users->Comments->getTarget());
  626. $this->assertInstanceOf(Table::class, $Articles->Comments->getTarget());
  627. $this->assertInstanceOf('TestPlugin\Model\Table\CommentsTable', $Categories->Comments->getTarget());
  628. }
  629. /**
  630. * Test associations which refer to the same table multiple times
  631. *
  632. * @return void
  633. */
  634. public function testSelfJoinAssociations()
  635. {
  636. $Categories = $this->getTableLocator()->get('Categories');
  637. $options = ['className' => 'Categories'];
  638. $Categories->hasMany('Children', ['foreignKey' => 'parent_id'] + $options);
  639. $Categories->belongsTo('Parent', $options);
  640. $this->assertSame('categories', $Categories->Children->getTarget()->getTable());
  641. $this->assertSame('categories', $Categories->Parent->getTarget()->getTable());
  642. $this->assertSame('Children', $Categories->Children->getAlias());
  643. $this->assertSame('Children', $Categories->Children->getTarget()->getAlias());
  644. $this->assertSame('Parent', $Categories->Parent->getAlias());
  645. $this->assertSame('Parent', $Categories->Parent->getTarget()->getAlias());
  646. $expected = [
  647. 'id' => 2,
  648. 'parent_id' => 1,
  649. 'name' => 'Category 1.1',
  650. 'parent' => [
  651. 'id' => 1,
  652. 'parent_id' => 0,
  653. 'name' => 'Category 1',
  654. ],
  655. 'children' => [
  656. [
  657. 'id' => 7,
  658. 'parent_id' => 2,
  659. 'name' => 'Category 1.1.1',
  660. ],
  661. [
  662. 'id' => 8,
  663. 'parent_id' => 2,
  664. 'name' => 'Category 1.1.2',
  665. ],
  666. ],
  667. ];
  668. $fields = ['id', 'parent_id', 'name'];
  669. $result = $Categories->find('all')
  670. ->select(['Categories.id', 'Categories.parent_id', 'Categories.name'])
  671. ->contain(['Children' => ['fields' => $fields], 'Parent' => ['fields' => $fields]])
  672. ->where(['Categories.id' => 2])
  673. ->first()
  674. ->toArray();
  675. $this->assertSame($expected, $result);
  676. }
  677. /**
  678. * Tests that hasMany() creates and configures correctly the association
  679. *
  680. * @return void
  681. */
  682. public function testHasMany()
  683. {
  684. $options = [
  685. 'foreignKey' => 'author_id',
  686. 'conditions' => ['b' => 'c'],
  687. 'sort' => ['foo' => 'asc'],
  688. ];
  689. $table = new Table(['table' => 'authors']);
  690. $hasMany = $table->hasMany('article', $options);
  691. $this->assertInstanceOf(HasMany::class, $hasMany);
  692. $this->assertSame($hasMany, $table->getAssociation('article'));
  693. $this->assertSame('article', $hasMany->getName());
  694. $this->assertSame('author_id', $hasMany->getForeignKey());
  695. $this->assertEquals(['b' => 'c'], $hasMany->getConditions());
  696. $this->assertEquals(['foo' => 'asc'], $hasMany->getSort());
  697. $this->assertSame($table, $hasMany->getSource());
  698. }
  699. /**
  700. * testHasManyWithClassName
  701. *
  702. * @return void
  703. */
  704. public function testHasManyWithClassName()
  705. {
  706. $table = $this->getTableLocator()->get('Articles');
  707. $table->hasMany('Comments', [
  708. 'className' => 'Comments',
  709. 'conditions' => ['published' => 'Y'],
  710. ]);
  711. $table->hasMany('UnapprovedComments', [
  712. 'className' => 'Comments',
  713. 'conditions' => ['published' => 'N'],
  714. 'propertyName' => 'unaproved_comments',
  715. ]);
  716. $expected = [
  717. 'id' => 1,
  718. 'title' => 'First Article',
  719. 'unaproved_comments' => [
  720. [
  721. 'id' => 4,
  722. 'article_id' => 1,
  723. 'comment' => 'Fourth Comment for First Article',
  724. ],
  725. ],
  726. 'comments' => [
  727. [
  728. 'id' => 1,
  729. 'article_id' => 1,
  730. 'comment' => 'First Comment for First Article',
  731. ],
  732. [
  733. 'id' => 2,
  734. 'article_id' => 1,
  735. 'comment' => 'Second Comment for First Article',
  736. ],
  737. [
  738. 'id' => 3,
  739. 'article_id' => 1,
  740. 'comment' => 'Third Comment for First Article',
  741. ],
  742. ],
  743. ];
  744. $result = $table->find()
  745. ->select(['id', 'title'])
  746. ->contain([
  747. 'Comments' => ['fields' => ['id', 'article_id', 'comment']],
  748. 'UnapprovedComments' => ['fields' => ['id', 'article_id', 'comment']],
  749. ])
  750. ->where(['id' => 1])
  751. ->first();
  752. $this->assertSame($expected, $result->toArray());
  753. }
  754. /**
  755. * Ensure associations use the plugin-prefixed model
  756. *
  757. * @return void
  758. */
  759. public function testHasManyPluginOverlap()
  760. {
  761. $this->getTableLocator()->get('Comments');
  762. $this->loadPlugins(['TestPlugin']);
  763. $table = new Table(['table' => 'authors']);
  764. $table->hasMany('TestPlugin.Comments');
  765. $comments = $table->Comments->getTarget();
  766. $this->assertInstanceOf('TestPlugin\Model\Table\CommentsTable', $comments);
  767. }
  768. /**
  769. * Ensure associations use the plugin-prefixed model
  770. * even if specified with config
  771. *
  772. * @return void
  773. */
  774. public function testHasManyPluginOverlapConfig()
  775. {
  776. $this->getTableLocator()->get('Comments');
  777. $this->loadPlugins(['TestPlugin']);
  778. $table = new Table(['table' => 'authors']);
  779. $table->hasMany('Comments', ['className' => 'TestPlugin.Comments']);
  780. $comments = $table->Comments->getTarget();
  781. $this->assertInstanceOf('TestPlugin\Model\Table\CommentsTable', $comments);
  782. }
  783. /**
  784. * Tests that BelongsToMany() creates and configures correctly the association
  785. *
  786. * @return void
  787. */
  788. public function testBelongsToMany()
  789. {
  790. $options = [
  791. 'foreignKey' => 'thing_id',
  792. 'joinTable' => 'things_tags',
  793. 'conditions' => ['b' => 'c'],
  794. 'sort' => ['foo' => 'asc'],
  795. ];
  796. $table = new Table(['table' => 'authors', 'connection' => $this->connection]);
  797. $belongsToMany = $table->belongsToMany('tag', $options);
  798. $this->assertInstanceOf(BelongsToMany::class, $belongsToMany);
  799. $this->assertSame($belongsToMany, $table->getAssociation('tag'));
  800. $this->assertSame('tag', $belongsToMany->getName());
  801. $this->assertSame('thing_id', $belongsToMany->getForeignKey());
  802. $this->assertEquals(['b' => 'c'], $belongsToMany->getConditions());
  803. $this->assertEquals(['foo' => 'asc'], $belongsToMany->getSort());
  804. $this->assertSame($table, $belongsToMany->getSource());
  805. $this->assertSame('things_tags', $belongsToMany->junction()->getTable());
  806. }
  807. /**
  808. * Test addAssociations()
  809. *
  810. * @return void
  811. */
  812. public function testAddAssociations()
  813. {
  814. $params = [
  815. 'belongsTo' => [
  816. 'users' => ['foreignKey' => 'fake_id', 'conditions' => ['a' => 'b']],
  817. ],
  818. 'hasOne' => ['profiles'],
  819. 'hasMany' => ['authors'],
  820. 'belongsToMany' => [
  821. 'tags' => [
  822. 'joinTable' => 'things_tags',
  823. 'conditions' => [
  824. 'Tags.starred' => true,
  825. ],
  826. ],
  827. ],
  828. ];
  829. $table = new Table(['table' => 'dates']);
  830. $result = $table->addAssociations($params);
  831. $this->assertSame($table, $result);
  832. $associations = $table->associations();
  833. $belongsTo = $associations->get('users');
  834. $this->assertInstanceOf('Cake\ORM\Association\BelongsTo', $belongsTo);
  835. $this->assertSame('users', $belongsTo->getName());
  836. $this->assertSame('fake_id', $belongsTo->getForeignKey());
  837. $this->assertEquals(['a' => 'b'], $belongsTo->getConditions());
  838. $this->assertSame($table, $belongsTo->getSource());
  839. $hasOne = $associations->get('profiles');
  840. $this->assertInstanceOf(HasOne::class, $hasOne);
  841. $this->assertSame('profiles', $hasOne->getName());
  842. $hasMany = $associations->get('authors');
  843. $this->assertInstanceOf(HasMany::class, $hasMany);
  844. $this->assertSame('authors', $hasMany->getName());
  845. $belongsToMany = $associations->get('tags');
  846. $this->assertInstanceOf(BelongsToMany::class, $belongsToMany);
  847. $this->assertSame('tags', $belongsToMany->getName());
  848. $this->assertSame('things_tags', $belongsToMany->junction()->getTable());
  849. $this->assertSame(['Tags.starred' => true], $belongsToMany->getConditions());
  850. }
  851. /**
  852. * Test basic multi row updates.
  853. *
  854. * @return void
  855. */
  856. public function testUpdateAll()
  857. {
  858. $table = new Table([
  859. 'table' => 'users',
  860. 'connection' => $this->connection,
  861. ]);
  862. $fields = ['username' => 'mark'];
  863. $result = $table->updateAll($fields, ['id <' => 4]);
  864. $this->assertSame(3, $result);
  865. $result = $table->find('all')
  866. ->select(['username'])
  867. ->order(['id' => 'asc'])
  868. ->enableHydration(false)
  869. ->toArray();
  870. $expected = array_fill(0, 3, $fields);
  871. $expected[] = ['username' => 'garrett'];
  872. $this->assertEquals($expected, $result);
  873. }
  874. /**
  875. * Test that exceptions from the Query bubble up.
  876. *
  877. */
  878. public function testUpdateAllFailure()
  879. {
  880. $this->expectException(\Cake\Database\Exception::class);
  881. $table = $this->getMockBuilder(Table::class)
  882. ->setMethods(['query'])
  883. ->setConstructorArgs([['table' => 'users', 'connection' => $this->connection]])
  884. ->getMock();
  885. $query = $this->getMockBuilder('Cake\ORM\Query')
  886. ->setMethods(['execute'])
  887. ->setConstructorArgs([$this->connection, $table])
  888. ->getMock();
  889. $table->expects($this->once())
  890. ->method('query')
  891. ->will($this->returnValue($query));
  892. $query->expects($this->once())
  893. ->method('execute')
  894. ->will($this->throwException(new Exception('Not good')));
  895. $table->updateAll(['username' => 'mark'], []);
  896. }
  897. /**
  898. * Test deleting many records.
  899. *
  900. * @return void
  901. */
  902. public function testDeleteAll()
  903. {
  904. $table = new Table([
  905. 'table' => 'users',
  906. 'connection' => $this->connection,
  907. ]);
  908. $result = $table->deleteAll(['id <' => 4]);
  909. $this->assertSame(3, $result);
  910. $result = $table->find('all')->toArray();
  911. $this->assertCount(1, $result, 'Only one record should remain');
  912. $this->assertEquals(4, $result[0]['id']);
  913. }
  914. /**
  915. * Test deleting many records with conditions using the alias
  916. *
  917. * @return void
  918. */
  919. public function testDeleteAllAliasedConditions()
  920. {
  921. $table = new Table([
  922. 'table' => 'users',
  923. 'alias' => 'Managers',
  924. 'connection' => $this->connection,
  925. ]);
  926. $result = $table->deleteAll(['Managers.id <' => 4]);
  927. $this->assertSame(3, $result);
  928. $result = $table->find('all')->toArray();
  929. $this->assertCount(1, $result, 'Only one record should remain');
  930. $this->assertEquals(4, $result[0]['id']);
  931. }
  932. /**
  933. * Test that exceptions from the Query bubble up.
  934. *
  935. */
  936. public function testDeleteAllFailure()
  937. {
  938. $this->expectException(\Cake\Database\Exception::class);
  939. $table = $this->getMockBuilder(Table::class)
  940. ->setMethods(['query'])
  941. ->setConstructorArgs([['table' => 'users', 'connection' => $this->connection]])
  942. ->getMock();
  943. $query = $this->getMockBuilder('Cake\ORM\Query')
  944. ->setMethods(['execute'])
  945. ->setConstructorArgs([$this->connection, $table])
  946. ->getMock();
  947. $table->expects($this->once())
  948. ->method('query')
  949. ->will($this->returnValue($query));
  950. $query->expects($this->once())
  951. ->method('execute')
  952. ->will($this->throwException(new Exception('Not good')));
  953. $table->deleteAll(['id >' => 4]);
  954. }
  955. /**
  956. * Tests that array options are passed to the query object using applyOptions
  957. *
  958. * @return void
  959. */
  960. public function testFindApplyOptions()
  961. {
  962. $table = $this->getMockBuilder(Table::class)
  963. ->setMethods(['query', 'findAll'])
  964. ->setConstructorArgs([['table' => 'users', 'connection' => $this->connection]])
  965. ->getMock();
  966. $query = $this->getMockBuilder('Cake\ORM\Query')
  967. ->setConstructorArgs([$this->connection, $table])
  968. ->getMock();
  969. $table->expects($this->once())
  970. ->method('query')
  971. ->will($this->returnValue($query));
  972. $options = ['fields' => ['a', 'b'], 'connections' => ['a >' => 1]];
  973. $query->expects($this->any())
  974. ->method('select')
  975. ->will($this->returnSelf());
  976. $query->expects($this->once())->method('getOptions')
  977. ->will($this->returnValue(['connections' => ['a >' => 1]]));
  978. $query->expects($this->once())
  979. ->method('applyOptions')
  980. ->with($options);
  981. $table->expects($this->once())->method('findAll')
  982. ->with($query, ['connections' => ['a >' => 1]]);
  983. $table->find('all', $options);
  984. }
  985. /**
  986. * Tests find('list')
  987. *
  988. * @return void
  989. */
  990. public function testFindListNoHydration()
  991. {
  992. $table = new Table([
  993. 'table' => 'users',
  994. 'connection' => $this->connection,
  995. ]);
  996. $table->setDisplayField('username');
  997. $query = $table->find('list')
  998. ->enableHydration(false)
  999. ->order('id');
  1000. $expected = [
  1001. 1 => 'mariano',
  1002. 2 => 'nate',
  1003. 3 => 'larry',
  1004. 4 => 'garrett',
  1005. ];
  1006. $this->assertSame($expected, $query->toArray());
  1007. $query = $table->find('list', ['fields' => ['id', 'username']])
  1008. ->enableHydration(false)
  1009. ->order('id');
  1010. $expected = [
  1011. 1 => 'mariano',
  1012. 2 => 'nate',
  1013. 3 => 'larry',
  1014. 4 => 'garrett',
  1015. ];
  1016. $this->assertSame($expected, $query->toArray());
  1017. $query = $table->find('list', ['groupField' => 'odd'])
  1018. ->select(['id', 'username', 'odd' => new QueryExpression('id % 2')])
  1019. ->enableHydration(false)
  1020. ->order('id');
  1021. $expected = [
  1022. 1 => [
  1023. 1 => 'mariano',
  1024. 3 => 'larry',
  1025. ],
  1026. 0 => [
  1027. 2 => 'nate',
  1028. 4 => 'garrett',
  1029. ],
  1030. ];
  1031. $this->assertSame($expected, $query->toArray());
  1032. }
  1033. /**
  1034. * Tests find('threaded')
  1035. *
  1036. * @return void
  1037. */
  1038. public function testFindThreadedNoHydration()
  1039. {
  1040. $table = new Table([
  1041. 'table' => 'categories',
  1042. 'connection' => $this->connection,
  1043. ]);
  1044. $expected = [
  1045. [
  1046. 'id' => 1,
  1047. 'parent_id' => 0,
  1048. 'name' => 'Category 1',
  1049. 'children' => [
  1050. [
  1051. 'id' => 2,
  1052. 'parent_id' => 1,
  1053. 'name' => 'Category 1.1',
  1054. 'children' => [
  1055. [
  1056. 'id' => 7,
  1057. 'parent_id' => 2,
  1058. 'name' => 'Category 1.1.1',
  1059. 'children' => [],
  1060. ],
  1061. [
  1062. 'id' => 8,
  1063. 'parent_id' => '2',
  1064. 'name' => 'Category 1.1.2',
  1065. 'children' => [],
  1066. ],
  1067. ],
  1068. ],
  1069. [
  1070. 'id' => 3,
  1071. 'parent_id' => '1',
  1072. 'name' => 'Category 1.2',
  1073. 'children' => [],
  1074. ],
  1075. ],
  1076. ],
  1077. [
  1078. 'id' => 4,
  1079. 'parent_id' => 0,
  1080. 'name' => 'Category 2',
  1081. 'children' => [],
  1082. ],
  1083. [
  1084. 'id' => 5,
  1085. 'parent_id' => 0,
  1086. 'name' => 'Category 3',
  1087. 'children' => [
  1088. [
  1089. 'id' => '6',
  1090. 'parent_id' => '5',
  1091. 'name' => 'Category 3.1',
  1092. 'children' => [],
  1093. ],
  1094. ],
  1095. ],
  1096. ];
  1097. $results = $table->find('all')
  1098. ->select(['id', 'parent_id', 'name'])
  1099. ->enableHydration(false)
  1100. ->find('threaded')
  1101. ->toArray();
  1102. $this->assertEquals($expected, $results);
  1103. }
  1104. /**
  1105. * Tests that finders can be stacked
  1106. *
  1107. * @return void
  1108. */
  1109. public function testStackingFinders()
  1110. {
  1111. $table = $this->getMockBuilder(Table::class)
  1112. ->setMethods(['find', 'findList'])
  1113. ->disableOriginalConstructor()
  1114. ->getMock();
  1115. $params = [$this->connection, $table];
  1116. $query = $this->getMockBuilder('Cake\ORM\Query')
  1117. ->setMethods(['addDefaultTypes'])
  1118. ->setConstructorArgs($params)
  1119. ->getMock();
  1120. $table->expects($this->once())
  1121. ->method('find')
  1122. ->with('threaded', ['order' => ['name' => 'ASC']])
  1123. ->will($this->returnValue($query));
  1124. $table->expects($this->once())
  1125. ->method('findList')
  1126. ->with($query, ['keyPath' => 'id'])
  1127. ->will($this->returnValue($query));
  1128. $result = $table
  1129. ->find('threaded', ['order' => ['name' => 'ASC']])
  1130. ->find('list', ['keyPath' => 'id']);
  1131. $this->assertSame($query, $result);
  1132. }
  1133. /**
  1134. * Tests find('threaded') with hydrated results
  1135. *
  1136. * @return void
  1137. */
  1138. public function testFindThreadedHydrated()
  1139. {
  1140. $table = new Table([
  1141. 'table' => 'categories',
  1142. 'connection' => $this->connection,
  1143. ]);
  1144. $results = $table->find('all')
  1145. ->find('threaded')
  1146. ->select(['id', 'parent_id', 'name'])
  1147. ->toArray();
  1148. $this->assertEquals(1, $results[0]->id);
  1149. $expected = [
  1150. 'id' => 8,
  1151. 'parent_id' => 2,
  1152. 'name' => 'Category 1.1.2',
  1153. 'children' => [],
  1154. ];
  1155. $this->assertEquals($expected, $results[0]->children[0]->children[1]->toArray());
  1156. }
  1157. /**
  1158. * Tests find('list') with hydrated records
  1159. *
  1160. * @return void
  1161. */
  1162. public function testFindListHydrated()
  1163. {
  1164. $table = new Table([
  1165. 'table' => 'users',
  1166. 'connection' => $this->connection,
  1167. ]);
  1168. $table->setDisplayField('username');
  1169. $query = $table
  1170. ->find('list', ['fields' => ['id', 'username']])
  1171. ->order('id');
  1172. $expected = [
  1173. 1 => 'mariano',
  1174. 2 => 'nate',
  1175. 3 => 'larry',
  1176. 4 => 'garrett',
  1177. ];
  1178. $this->assertSame($expected, $query->toArray());
  1179. $query = $table->find('list', ['groupField' => 'odd'])
  1180. ->select(['id', 'username', 'odd' => new QueryExpression('id % 2')])
  1181. ->enableHydration(true)
  1182. ->order('id');
  1183. $expected = [
  1184. 1 => [
  1185. 1 => 'mariano',
  1186. 3 => 'larry',
  1187. ],
  1188. 0 => [
  1189. 2 => 'nate',
  1190. 4 => 'garrett',
  1191. ],
  1192. ];
  1193. $this->assertSame($expected, $query->toArray());
  1194. }
  1195. /**
  1196. * Test that find('list') only selects required fields.
  1197. *
  1198. * @return void
  1199. */
  1200. public function testFindListSelectedFields()
  1201. {
  1202. $table = new Table([
  1203. 'table' => 'users',
  1204. 'connection' => $this->connection,
  1205. ]);
  1206. $table->setDisplayField('username');
  1207. $query = $table->find('list');
  1208. $expected = ['id', 'username'];
  1209. $this->assertSame($expected, $query->clause('select'));
  1210. $query = $table->find('list', ['valueField' => function ($row) {
  1211. return $row->username;
  1212. }]);
  1213. $this->assertEmpty($query->clause('select'));
  1214. $expected = ['odd' => new QueryExpression('id % 2'), 'id', 'username'];
  1215. $query = $table->find('list', [
  1216. 'fields' => $expected,
  1217. 'groupField' => 'odd',
  1218. ]);
  1219. $this->assertSame($expected, $query->clause('select'));
  1220. $articles = new Table([
  1221. 'table' => 'articles',
  1222. 'connection' => $this->connection,
  1223. ]);
  1224. $query = $articles->find('list', ['groupField' => 'author_id']);
  1225. $expected = ['id', 'title', 'author_id'];
  1226. $this->assertSame($expected, $query->clause('select'));
  1227. $query = $articles->find('list', ['valueField' => ['author_id', 'title']])
  1228. ->order('id');
  1229. $expected = ['id', 'author_id', 'title'];
  1230. $this->assertSame($expected, $query->clause('select'));
  1231. $expected = [
  1232. 1 => '1;First Article',
  1233. 2 => '3;Second Article',
  1234. 3 => '1;Third Article',
  1235. ];
  1236. $this->assertSame($expected, $query->toArray());
  1237. }
  1238. /**
  1239. * test that find('list') does not auto add fields to select if using virtual properties
  1240. *
  1241. * @return void
  1242. */
  1243. public function testFindListWithVirtualField()
  1244. {
  1245. $table = new Table([
  1246. 'table' => 'users',
  1247. 'connection' => $this->connection,
  1248. 'entityClass' => VirtualUser::class,
  1249. ]);
  1250. $table->setDisplayField('bonus');
  1251. $query = $table
  1252. ->find('list')
  1253. ->order('id');
  1254. $this->assertEmpty($query->clause('select'));
  1255. $expected = [
  1256. 1 => 'bonus',
  1257. 2 => 'bonus',
  1258. 3 => 'bonus',
  1259. 4 => 'bonus',
  1260. ];
  1261. $this->assertSame($expected, $query->toArray());
  1262. $query = $table->find('list', ['groupField' => 'odd']);
  1263. $this->assertEmpty($query->clause('select'));
  1264. }
  1265. /**
  1266. * Test find('list') with value field from associated table
  1267. *
  1268. * @return void
  1269. */
  1270. public function testFindListWithAssociatedTable()
  1271. {
  1272. $articles = new Table([
  1273. 'table' => 'articles',
  1274. 'connection' => $this->connection,
  1275. ]);
  1276. $articles->belongsTo('Authors');
  1277. $query = $articles->find('list', ['valueField' => 'author.name'])
  1278. ->contain(['Authors'])
  1279. ->order('articles.id');
  1280. $this->assertEmpty($query->clause('select'));
  1281. $expected = [
  1282. 1 => 'mariano',
  1283. 2 => 'larry',
  1284. 3 => 'mariano',
  1285. ];
  1286. $this->assertSame($expected, $query->toArray());
  1287. }
  1288. /**
  1289. * Test the default entityClass.
  1290. *
  1291. * @return void
  1292. */
  1293. public function testEntityClassDefault()
  1294. {
  1295. $table = new Table();
  1296. $this->assertSame('Cake\ORM\Entity', $table->getEntityClass());
  1297. }
  1298. /**
  1299. * Tests that using a simple string for entityClass will try to
  1300. * load the class from the App namespace
  1301. *
  1302. * @return void
  1303. */
  1304. public function testTableClassInApp()
  1305. {
  1306. $class = $this->getMockClass('Cake\ORM\Entity');
  1307. if (!class_exists('TestApp\Model\Entity\TestUser')) {
  1308. class_alias($class, 'TestApp\Model\Entity\TestUser');
  1309. }
  1310. $table = new Table();
  1311. $this->assertSame($table, $table->setEntityClass('TestUser'));
  1312. $this->assertSame('TestApp\Model\Entity\TestUser', $table->getEntityClass());
  1313. }
  1314. /**
  1315. * Test that entity class inflection works for compound nouns
  1316. *
  1317. * @return void
  1318. */
  1319. public function testEntityClassInflection()
  1320. {
  1321. $class = $this->getMockClass('Cake\ORM\Entity');
  1322. if (!class_exists('TestApp\Model\Entity\CustomCookie')) {
  1323. class_alias($class, 'TestApp\Model\Entity\CustomCookie');
  1324. }
  1325. $table = $this->getTableLocator()->get('CustomCookies');
  1326. $this->assertSame('TestApp\Model\Entity\CustomCookie', $table->getEntityClass());
  1327. if (!class_exists('TestApp\Model\Entity\Address')) {
  1328. class_alias($class, 'TestApp\Model\Entity\Address');
  1329. }
  1330. $table = $this->getTableLocator()->get('Addresses');
  1331. $this->assertSame('TestApp\Model\Entity\Address', $table->getEntityClass());
  1332. }
  1333. /**
  1334. * Tests that using a simple string for entityClass will try to
  1335. * load the class from the Plugin namespace when using plugin notation
  1336. *
  1337. * @return void
  1338. */
  1339. public function testTableClassInPlugin()
  1340. {
  1341. $class = $this->getMockClass('Cake\ORM\Entity');
  1342. if (!class_exists('MyPlugin\Model\Entity\SuperUser')) {
  1343. class_alias($class, 'MyPlugin\Model\Entity\SuperUser');
  1344. }
  1345. $table = new Table();
  1346. $this->assertSame($table, $table->setEntityClass('MyPlugin.SuperUser'));
  1347. $this->assertEquals(
  1348. 'MyPlugin\Model\Entity\SuperUser',
  1349. $table->getEntityClass()
  1350. );
  1351. }
  1352. /**
  1353. * Tests that using a simple string for entityClass will throw an exception
  1354. * when the class does not exist in the namespace
  1355. *
  1356. * @return void
  1357. */
  1358. public function testTableClassNonExisting()
  1359. {
  1360. $this->expectException(\Cake\ORM\Exception\MissingEntityException::class);
  1361. $this->expectExceptionMessage('Entity class FooUser could not be found.');
  1362. $table = new Table();
  1363. $table->setEntityClass('FooUser');
  1364. }
  1365. /**
  1366. * Tests getting the entityClass based on conventions for the entity
  1367. * namespace
  1368. *
  1369. * @return void
  1370. */
  1371. public function testTableClassConventionForAPP()
  1372. {
  1373. $table = new \TestApp\Model\Table\ArticlesTable();
  1374. $this->assertSame('TestApp\Model\Entity\Article', $table->getEntityClass());
  1375. }
  1376. /**
  1377. * Tests setting a entity class object using the setter method
  1378. *
  1379. * @return void
  1380. */
  1381. public function testSetEntityClass()
  1382. {
  1383. $table = new Table();
  1384. $class = '\\' . $this->getMockClass('Cake\ORM\Entity');
  1385. $this->assertSame($table, $table->setEntityClass($class));
  1386. $this->assertEquals($class, $table->getEntityClass());
  1387. }
  1388. /**
  1389. * Proves that associations, even though they are lazy loaded, will fetch
  1390. * records using the correct table class and hydrate with the correct entity
  1391. *
  1392. * @return void
  1393. */
  1394. public function testReciprocalBelongsToLoading()
  1395. {
  1396. $table = new \TestApp\Model\Table\ArticlesTable([
  1397. 'connection' => $this->connection,
  1398. ]);
  1399. $result = $table->find('all')->contain(['Authors'])->first();
  1400. $this->assertInstanceOf('TestApp\Model\Entity\Author', $result->author);
  1401. }
  1402. /**
  1403. * Proves that associations, even though they are lazy loaded, will fetch
  1404. * records using the correct table class and hydrate with the correct entity
  1405. *
  1406. * @return void
  1407. */
  1408. public function testReciprocalHasManyLoading()
  1409. {
  1410. $table = new \TestApp\Model\Table\ArticlesTable([
  1411. 'connection' => $this->connection,
  1412. ]);
  1413. $result = $table->find('all')->contain(['Authors' => ['Articles']])->first();
  1414. $this->assertCount(2, $result->author->articles);
  1415. foreach ($result->author->articles as $article) {
  1416. $this->assertInstanceOf('TestApp\Model\Entity\Article', $article);
  1417. }
  1418. }
  1419. /**
  1420. * Tests that the correct table and entity are loaded for the join association in
  1421. * a belongsToMany setup
  1422. *
  1423. * @return void
  1424. */
  1425. public function testReciprocalBelongsToMany()
  1426. {
  1427. $table = new \TestApp\Model\Table\ArticlesTable([
  1428. 'connection' => $this->connection,
  1429. ]);
  1430. $result = $table->find('all')->contain(['Tags'])->first();
  1431. $this->assertInstanceOf('TestApp\Model\Entity\Tag', $result->tags[0]);
  1432. $this->assertInstanceOf(
  1433. 'TestApp\Model\Entity\ArticlesTag',
  1434. $result->tags[0]->_joinData
  1435. );
  1436. }
  1437. /**
  1438. * Tests that recently fetched entities are always clean
  1439. *
  1440. * @return void
  1441. */
  1442. public function testFindCleanEntities()
  1443. {
  1444. $table = new \TestApp\Model\Table\ArticlesTable([
  1445. 'connection' => $this->connection,
  1446. ]);
  1447. $results = $table->find('all')->contain(['Tags', 'Authors'])->toArray();
  1448. $this->assertCount(3, $results);
  1449. foreach ($results as $article) {
  1450. $this->assertFalse($article->isDirty('id'));
  1451. $this->assertFalse($article->isDirty('title'));
  1452. $this->assertFalse($article->isDirty('author_id'));
  1453. $this->assertFalse($article->isDirty('body'));
  1454. $this->assertFalse($article->isDirty('published'));
  1455. $this->assertFalse($article->isDirty('author'));
  1456. $this->assertFalse($article->author->isDirty('id'));
  1457. $this->assertFalse($article->author->isDirty('name'));
  1458. $this->assertFalse($article->isDirty('tag'));
  1459. if ($article->tag) {
  1460. $this->assertFalse($article->tag[0]->_joinData->isDirty('tag_id'));
  1461. }
  1462. }
  1463. }
  1464. /**
  1465. * Tests that recently fetched entities are marked as not new
  1466. *
  1467. * @return void
  1468. */
  1469. public function testFindPersistedEntities()
  1470. {
  1471. $table = new \TestApp\Model\Table\ArticlesTable([
  1472. 'connection' => $this->connection,
  1473. ]);
  1474. $results = $table->find('all')->contain(['tags', 'authors'])->toArray();
  1475. $this->assertCount(3, $results);
  1476. foreach ($results as $article) {
  1477. $this->assertFalse($article->isNew());
  1478. foreach ((array)$article->tag as $tag) {
  1479. $this->assertFalse($tag->isNew());
  1480. $this->assertFalse($tag->_joinData->isNew());
  1481. }
  1482. }
  1483. }
  1484. /**
  1485. * Tests the exists function
  1486. *
  1487. * @return void
  1488. */
  1489. public function testExists()
  1490. {
  1491. $table = $this->getTableLocator()->get('users');
  1492. $this->assertTrue($table->exists(['id' => 1]));
  1493. $this->assertFalse($table->exists(['id' => 501]));
  1494. $this->assertTrue($table->exists(['id' => 3, 'username' => 'larry']));
  1495. }
  1496. /**
  1497. * Test adding a behavior to a table.
  1498. *
  1499. * @return void
  1500. */
  1501. public function testAddBehavior()
  1502. {
  1503. $mock = $this->getMockBuilder('Cake\ORM\BehaviorRegistry')
  1504. ->disableOriginalConstructor()
  1505. ->getMock();
  1506. $mock->expects($this->once())
  1507. ->method('load')
  1508. ->with('Sluggable');
  1509. $table = new Table([
  1510. 'table' => 'articles',
  1511. 'behaviors' => $mock,
  1512. ]);
  1513. $result = $table->addBehavior('Sluggable');
  1514. $this->assertSame($table, $result);
  1515. }
  1516. /**
  1517. * Test adding a behavior that is a duplicate.
  1518. *
  1519. * @return void
  1520. */
  1521. public function testAddBehaviorDuplicate()
  1522. {
  1523. $table = new Table(['table' => 'articles']);
  1524. $this->assertSame($table, $table->addBehavior('Sluggable', ['test' => 'value']));
  1525. $this->assertSame($table, $table->addBehavior('Sluggable', ['test' => 'value']));
  1526. try {
  1527. $table->addBehavior('Sluggable', ['thing' => 'thing']);
  1528. $this->fail('No exception raised');
  1529. } catch (\RuntimeException $e) {
  1530. $this->assertStringContainsString('The "Sluggable" alias has already been loaded', $e->getMessage());
  1531. }
  1532. }
  1533. /**
  1534. * Test removing a behavior from a table.
  1535. *
  1536. * @return void
  1537. */
  1538. public function testRemoveBehavior()
  1539. {
  1540. $mock = $this->getMockBuilder('Cake\ORM\BehaviorRegistry')
  1541. ->disableOriginalConstructor()
  1542. ->getMock();
  1543. $mock->expects($this->once())
  1544. ->method('unload')
  1545. ->with('Sluggable');
  1546. $table = new Table([
  1547. 'table' => 'articles',
  1548. 'behaviors' => $mock,
  1549. ]);
  1550. $result = $table->removeBehavior('Sluggable');
  1551. $this->assertSame($table, $result);
  1552. }
  1553. /**
  1554. * Test adding multiple behaviors to a table.
  1555. *
  1556. * @return void
  1557. */
  1558. public function testAddBehaviors()
  1559. {
  1560. $table = new Table(['table' => 'comments']);
  1561. $behaviors = [
  1562. 'Sluggable',
  1563. 'Timestamp' => [
  1564. 'events' => [
  1565. 'Model.beforeSave' => [
  1566. 'created' => 'new',
  1567. 'updated' => 'always',
  1568. ],
  1569. ],
  1570. ],
  1571. ];
  1572. $this->assertSame($table, $table->addBehaviors($behaviors));
  1573. $this->assertTrue($table->behaviors()->has('Sluggable'));
  1574. $this->assertTrue($table->behaviors()->has('Timestamp'));
  1575. $this->assertSame(
  1576. $behaviors['Timestamp']['events'],
  1577. $table->behaviors()->get('Timestamp')->getConfig('events')
  1578. );
  1579. }
  1580. /**
  1581. * Test getting a behavior instance from a table.
  1582. *
  1583. * @return void
  1584. */
  1585. public function testBehaviors()
  1586. {
  1587. $table = $this->getTableLocator()->get('article');
  1588. $result = $table->behaviors();
  1589. $this->assertInstanceOf('Cake\ORM\BehaviorRegistry', $result);
  1590. }
  1591. /**
  1592. * Test that the getBehavior() method retrieves a behavior from the table registry.
  1593. *
  1594. * @return void
  1595. */
  1596. public function testGetBehavior()
  1597. {
  1598. $table = new Table(['table' => 'comments']);
  1599. $table->addBehavior('Sluggable');
  1600. $this->assertSame($table->behaviors()->get('Sluggable'), $table->getBehavior('Sluggable'));
  1601. }
  1602. /**
  1603. * Test that the getBehavior() method will throw an exception when you try to
  1604. * get a behavior that does not exist.
  1605. *
  1606. * @return void
  1607. */
  1608. public function testGetBehaviorThrowsExceptionForMissingBehavior()
  1609. {
  1610. $table = new Table(['table' => 'comments']);
  1611. $this->expectException(InvalidArgumentException::class);
  1612. $this->expectExceptionMessage('The Sluggable behavior is not defined on ' . get_class($table) . '.');
  1613. $this->assertFalse($table->hasBehavior('Sluggable'));
  1614. $table->getBehavior('Sluggable');
  1615. }
  1616. /**
  1617. * Ensure exceptions are raised on missing behaviors.
  1618. *
  1619. */
  1620. public function testAddBehaviorMissing()
  1621. {
  1622. $this->expectException(\Cake\ORM\Exception\MissingBehaviorException::class);
  1623. $table = $this->getTableLocator()->get('article');
  1624. $this->assertNull($table->addBehavior('NopeNotThere'));
  1625. }
  1626. /**
  1627. * Test mixin methods from behaviors.
  1628. *
  1629. * @return void
  1630. */
  1631. public function testCallBehaviorMethod()
  1632. {
  1633. $table = $this->getTableLocator()->get('article');
  1634. $table->addBehavior('Sluggable');
  1635. $this->assertSame('some-value', $table->slugify('some value'));
  1636. }
  1637. /**
  1638. * Test you can alias a behavior method
  1639. *
  1640. * @return void
  1641. */
  1642. public function testCallBehaviorAliasedMethod()
  1643. {
  1644. $table = $this->getTableLocator()->get('article');
  1645. $table->addBehavior('Sluggable', ['implementedMethods' => ['wednesday' => 'slugify']]);
  1646. $this->assertSame('some-value', $table->wednesday('some value'));
  1647. }
  1648. /**
  1649. * Test finder methods from behaviors.
  1650. *
  1651. * @return void
  1652. */
  1653. public function testCallBehaviorFinder()
  1654. {
  1655. $table = $this->getTableLocator()->get('articles');
  1656. $table->addBehavior('Sluggable');
  1657. $query = $table->find('noSlug');
  1658. $this->assertInstanceOf('Cake\ORM\Query', $query);
  1659. $this->assertNotEmpty($query->clause('where'));
  1660. }
  1661. /**
  1662. * testCallBehaviorAliasedFinder
  1663. *
  1664. * @return void
  1665. */
  1666. public function testCallBehaviorAliasedFinder()
  1667. {
  1668. $table = $this->getTableLocator()->get('articles');
  1669. $table->addBehavior('Sluggable', ['implementedFinders' => ['special' => 'findNoSlug']]);
  1670. $query = $table->find('special');
  1671. $this->assertInstanceOf('Cake\ORM\Query', $query);
  1672. $this->assertNotEmpty($query->clause('where'));
  1673. }
  1674. /**
  1675. * Test implementedEvents
  1676. *
  1677. * @return void
  1678. */
  1679. public function testImplementedEvents()
  1680. {
  1681. /** @var \Cake\ORM\Table|\PHPUnit\Framework\MockObject\MockObject $table */
  1682. $table = $this->getMockBuilder(Table::class)
  1683. ->setMethods([
  1684. 'buildValidator',
  1685. 'beforeMarshal',
  1686. 'beforeFind',
  1687. 'beforeSave',
  1688. 'afterSave',
  1689. 'beforeDelete',
  1690. 'afterDelete',
  1691. 'afterRules',
  1692. ])
  1693. ->getMock();
  1694. $result = $table->implementedEvents();
  1695. $expected = [
  1696. 'Model.beforeMarshal' => 'beforeMarshal',
  1697. 'Model.buildValidator' => 'buildValidator',
  1698. 'Model.beforeFind' => 'beforeFind',
  1699. 'Model.beforeSave' => 'beforeSave',
  1700. 'Model.afterSave' => 'afterSave',
  1701. 'Model.beforeDelete' => 'beforeDelete',
  1702. 'Model.afterDelete' => 'afterDelete',
  1703. 'Model.afterRules' => 'afterRules',
  1704. ];
  1705. $this->assertEquals($expected, $result, 'Events do not match.');
  1706. }
  1707. /**
  1708. * Tests that it is possible to insert a new row using the save method
  1709. *
  1710. * @group save
  1711. * @return void
  1712. */
  1713. public function testSaveNewEntity()
  1714. {
  1715. $entity = new Entity([
  1716. 'username' => 'superuser',
  1717. 'password' => 'root',
  1718. 'created' => new Time('2013-10-10 00:00'),
  1719. 'updated' => new Time('2013-10-10 00:00'),
  1720. ]);
  1721. $table = $this->getTableLocator()->get('users');
  1722. $this->assertSame($entity, $table->save($entity));
  1723. $this->assertEquals($entity->id, self::$nextUserId);
  1724. $row = $table->find()->where(['id' => self::$nextUserId])->first();
  1725. $this->assertEquals($entity->toArray(), $row->toArray());
  1726. }
  1727. /**
  1728. * Test that saving a new empty entity does nothing.
  1729. *
  1730. * @group save
  1731. * @return void
  1732. */
  1733. public function testSaveNewEmptyEntity()
  1734. {
  1735. $entity = new Entity();
  1736. $table = $this->getTableLocator()->get('users');
  1737. $this->assertFalse($table->save($entity));
  1738. }
  1739. /**
  1740. * Test that saving a new empty entity does not call exists.
  1741. *
  1742. * @group save
  1743. * @return void
  1744. */
  1745. public function testSaveNewEntityNoExists()
  1746. {
  1747. /** @var \Cake\ORM\Table|\PHPUnit\Framework\MockObject\MockObject $table */
  1748. $table = $this->getMockBuilder(Table::class)
  1749. ->setMethods(['exists'])
  1750. ->setConstructorArgs([[
  1751. 'connection' => $this->connection,
  1752. 'alias' => 'Users',
  1753. 'table' => 'users',
  1754. ]])
  1755. ->getMock();
  1756. $entity = $table->newEntity(['username' => 'mark']);
  1757. $this->assertTrue($entity->isNew());
  1758. $table->expects($this->never())
  1759. ->method('exists');
  1760. $this->assertSame($entity, $table->save($entity));
  1761. }
  1762. /**
  1763. * Test that saving a new entity with a Primary Key set does call exists.
  1764. *
  1765. * @group save
  1766. * @return void
  1767. */
  1768. public function testSavePrimaryKeyEntityExists()
  1769. {
  1770. $this->skipIfSqlServer();
  1771. /** @var \Cake\ORM\Table|\PHPUnit\Framework\MockObject\MockObject $table */
  1772. $table = $this->getMockBuilder(Table::class)
  1773. ->setMethods(['exists'])
  1774. ->setConstructorArgs([[
  1775. 'connection' => $this->connection,
  1776. 'alias' => 'Users',
  1777. 'table' => 'users',
  1778. ]])
  1779. ->getMock();
  1780. $entity = $table->newEntity(['id' => 20, 'username' => 'mark']);
  1781. $this->assertTrue($entity->isNew());
  1782. $table->expects($this->once())->method('exists');
  1783. $this->assertSame($entity, $table->save($entity));
  1784. }
  1785. /**
  1786. * Test that saving a new entity with a Primary Key set does not call exists when checkExisting is false.
  1787. *
  1788. * @group save
  1789. * @return void
  1790. */
  1791. public function testSavePrimaryKeyEntityNoExists()
  1792. {
  1793. $this->skipIfSqlServer();
  1794. /** @var \Cake\ORM\Table|\PHPUnit\Framework\MockObject\MockObject $table */
  1795. $table = $this->getMockBuilder(Table::class)
  1796. ->setMethods(['exists'])
  1797. ->setConstructorArgs([[
  1798. 'connection' => $this->connection,
  1799. 'alias' => 'Users',
  1800. 'table' => 'users',
  1801. ]])
  1802. ->getMock();
  1803. $entity = $table->newEntity(['id' => 20, 'username' => 'mark']);
  1804. $this->assertTrue($entity->isNew());
  1805. $table->expects($this->never())->method('exists');
  1806. $this->assertSame($entity, $table->save($entity, ['checkExisting' => false]));
  1807. }
  1808. /**
  1809. * Tests that saving an entity will filter out properties that
  1810. * are not present in the table schema when saving
  1811. *
  1812. * @group save
  1813. * @return void
  1814. */
  1815. public function testSaveEntityOnlySchemaFields()
  1816. {
  1817. $entity = new Entity([
  1818. 'username' => 'superuser',
  1819. 'password' => 'root',
  1820. 'crazyness' => 'super crazy value',
  1821. 'created' => new Time('2013-10-10 00:00'),
  1822. 'updated' => new Time('2013-10-10 00:00'),
  1823. ]);
  1824. $table = $this->getTableLocator()->get('users');
  1825. $this->assertSame($entity, $table->save($entity));
  1826. $this->assertEquals($entity->id, self::$nextUserId);
  1827. $row = $table->find('all')->where(['id' => self::$nextUserId])->first();
  1828. $entity->unset('crazyness');
  1829. $this->assertEquals($entity->toArray(), $row->toArray());
  1830. }
  1831. /**
  1832. * Tests that it is possible to modify data from the beforeSave callback
  1833. *
  1834. * @group save
  1835. * @return void
  1836. */
  1837. public function testBeforeSaveModifyData()
  1838. {
  1839. $table = $this->getTableLocator()->get('users');
  1840. $data = new Entity([
  1841. 'username' => 'superuser',
  1842. 'created' => new Time('2013-10-10 00:00'),
  1843. 'updated' => new Time('2013-10-10 00:00'),
  1844. ]);
  1845. $listener = function ($event, EntityInterface $entity, $options) use ($data) {
  1846. $this->assertSame($data, $entity);
  1847. $entity->set('password', 'foo');
  1848. };
  1849. $table->getEventManager()->on('Model.beforeSave', $listener);
  1850. $this->assertSame($data, $table->save($data));
  1851. $this->assertEquals($data->id, self::$nextUserId);
  1852. $row = $table->find('all')->where(['id' => self::$nextUserId])->first();
  1853. $this->assertSame('foo', $row->get('password'));
  1854. }
  1855. /**
  1856. * Tests that it is possible to modify the options array in beforeSave
  1857. *
  1858. * @group save
  1859. * @return void
  1860. */
  1861. public function testBeforeSaveModifyOptions()
  1862. {
  1863. $table = $this->getTableLocator()->get('users');
  1864. $data = new Entity([
  1865. 'username' => 'superuser',
  1866. 'password' => 'foo',
  1867. 'created' => new Time('2013-10-10 00:00'),
  1868. 'updated' => new Time('2013-10-10 00:00'),
  1869. ]);
  1870. $listener1 = function ($event, $entity, $options) {
  1871. $options['crazy'] = true;
  1872. };
  1873. $listener2 = function ($event, $entity, $options) {
  1874. $this->assertTrue($options['crazy']);
  1875. };
  1876. $table->getEventManager()->on('Model.beforeSave', $listener1);
  1877. $table->getEventManager()->on('Model.beforeSave', $listener2);
  1878. $this->assertSame($data, $table->save($data));
  1879. $this->assertEquals($data->id, self::$nextUserId);
  1880. $row = $table->find('all')->where(['id' => self::$nextUserId])->first();
  1881. $this->assertEquals($data->toArray(), $row->toArray());
  1882. }
  1883. /**
  1884. * Tests that it is possible to stop the saving altogether, without implying
  1885. * the save operation failed
  1886. *
  1887. * @group save
  1888. * @return void
  1889. */
  1890. public function testBeforeSaveStopEvent()
  1891. {
  1892. $table = $this->getTableLocator()->get('users');
  1893. $data = new Entity([
  1894. 'username' => 'superuser',
  1895. 'created' => new Time('2013-10-10 00:00'),
  1896. 'updated' => new Time('2013-10-10 00:00'),
  1897. ]);
  1898. $listener = function (EventInterface $event, $entity) {
  1899. $event->stopPropagation();
  1900. return $entity;
  1901. };
  1902. $table->getEventManager()->on('Model.beforeSave', $listener);
  1903. $this->assertSame($data, $table->save($data));
  1904. $this->assertNull($data->id);
  1905. $row = $table->find('all')->where(['id' => self::$nextUserId])->first();
  1906. $this->assertNull($row);
  1907. }
  1908. /**
  1909. * Tests that if beforeSave event is stopped and callback doesn't return any
  1910. * value then save() returns false.
  1911. *
  1912. * @group save
  1913. * @return void
  1914. */
  1915. public function testBeforeSaveStopEventWithNoResult()
  1916. {
  1917. $table = $this->getTableLocator()->get('users');
  1918. $data = new Entity([
  1919. 'username' => 'superuser',
  1920. 'created' => new Time('2013-10-10 00:00'),
  1921. 'updated' => new Time('2013-10-10 00:00'),
  1922. ]);
  1923. $listener = function (EventInterface $event, $entity) {
  1924. $event->stopPropagation();
  1925. };
  1926. $table->getEventManager()->on('Model.beforeSave', $listener);
  1927. $this->assertFalse($table->save($data));
  1928. }
  1929. /**
  1930. * @group save
  1931. * @return void
  1932. */
  1933. public function testBeforeSaveException()
  1934. {
  1935. $this->expectException(RuntimeException::class);
  1936. $this->expectExceptionMessage('The beforeSave callback must return `false` or `EntityInterface` instance. Got `integer` instead.');
  1937. $table = $this->getTableLocator()->get('users');
  1938. $data = new Entity([
  1939. 'username' => 'superuser',
  1940. 'created' => new Time('2013-10-10 00:00'),
  1941. 'updated' => new Time('2013-10-10 00:00'),
  1942. ]);
  1943. $listener = function (EventInterface $event, $entity) {
  1944. $event->stopPropagation();
  1945. return 1;
  1946. };
  1947. $table->getEventManager()->on('Model.beforeSave', $listener);
  1948. $table->save($data);
  1949. }
  1950. /**
  1951. * Asserts that afterSave callback is called on successful save
  1952. *
  1953. * @group save
  1954. * @return void
  1955. */
  1956. public function testAfterSave()
  1957. {
  1958. $table = $this->getTableLocator()->get('users');
  1959. $data = $table->get(1);
  1960. $data->username = 'newusername';
  1961. $called = false;
  1962. $listener = function ($e, EntityInterface $entity, $options) use ($data, &$called) {
  1963. $this->assertSame($data, $entity);
  1964. $this->assertTrue($entity->isDirty());
  1965. $called = true;
  1966. };
  1967. $table->getEventManager()->on('Model.afterSave', $listener);
  1968. $calledAfterCommit = false;
  1969. $listenerAfterCommit = function ($e, EntityInterface $entity, $options) use ($data, &$calledAfterCommit) {
  1970. $this->assertSame($data, $entity);
  1971. $this->assertTrue($entity->isDirty());
  1972. $this->assertNotSame($data->get('username'), $data->getOriginal('username'));
  1973. $calledAfterCommit = true;
  1974. };
  1975. $table->getEventManager()->on('Model.afterSaveCommit', $listenerAfterCommit);
  1976. $this->assertSame($data, $table->save($data));
  1977. $this->assertTrue($called);
  1978. $this->assertTrue($calledAfterCommit);
  1979. }
  1980. /**
  1981. * Asserts that afterSaveCommit is also triggered for non-atomic saves
  1982. *
  1983. * @return void
  1984. */
  1985. public function testAfterSaveCommitForNonAtomic()
  1986. {
  1987. $table = $this->getTableLocator()->get('users');
  1988. $data = new Entity([
  1989. 'username' => 'superuser',
  1990. 'created' => new Time('2013-10-10 00:00'),
  1991. 'updated' => new Time('2013-10-10 00:00'),
  1992. ]);
  1993. $called = false;
  1994. $listener = function ($e, $entity, $options) use ($data, &$called) {
  1995. $this->assertSame($data, $entity);
  1996. $called = true;
  1997. };
  1998. $table->getEventManager()->on('Model.afterSave', $listener);
  1999. $calledAfterCommit = false;
  2000. $listenerAfterCommit = function ($e, $entity, $options) use ($data, &$calledAfterCommit) {
  2001. $calledAfterCommit = true;
  2002. };
  2003. $table->getEventManager()->on('Model.afterSaveCommit', $listenerAfterCommit);
  2004. $this->assertSame($data, $table->save($data, ['atomic' => false]));
  2005. $this->assertEquals($data->id, self::$nextUserId);
  2006. $this->assertTrue($called);
  2007. $this->assertTrue($calledAfterCommit);
  2008. }
  2009. /**
  2010. * Asserts the afterSaveCommit is not triggered if transaction is running.
  2011. *
  2012. * @return void
  2013. */
  2014. public function testAfterSaveCommitWithTransactionRunning()
  2015. {
  2016. $table = $this->getTableLocator()->get('users');
  2017. $data = new Entity([
  2018. 'username' => 'superuser',
  2019. 'created' => new Time('2013-10-10 00:00'),
  2020. 'updated' => new Time('2013-10-10 00:00'),
  2021. ]);
  2022. $called = false;
  2023. $listener = function ($e, $entity, $options) use (&$called) {
  2024. $called = true;
  2025. };
  2026. $table->getEventManager()->on('Model.afterSaveCommit', $listener);
  2027. $this->connection->begin();
  2028. $this->assertSame($data, $table->save($data));
  2029. $this->assertFalse($called);
  2030. $this->connection->commit();
  2031. }
  2032. /**
  2033. * Asserts the afterSaveCommit is not triggered if transaction is running.
  2034. *
  2035. * @return void
  2036. */
  2037. public function testAfterSaveCommitWithNonAtomicAndTransactionRunning()
  2038. {
  2039. $table = $this->getTableLocator()->get('users');
  2040. $data = new Entity([
  2041. 'username' => 'superuser',
  2042. 'created' => new Time('2013-10-10 00:00'),
  2043. 'updated' => new Time('2013-10-10 00:00'),
  2044. ]);
  2045. $called = false;
  2046. $listener = function ($e, $entity, $options) use (&$called) {
  2047. $called = true;
  2048. };
  2049. $table->getEventManager()->on('Model.afterSaveCommit', $listener);
  2050. $this->connection->begin();
  2051. $this->assertSame($data, $table->save($data, ['atomic' => false]));
  2052. $this->assertFalse($called);
  2053. $this->connection->commit();
  2054. }
  2055. /**
  2056. * Asserts that afterSave callback not is called on unsuccessful save
  2057. *
  2058. * @group save
  2059. * @return void
  2060. */
  2061. public function testAfterSaveNotCalled()
  2062. {
  2063. /** @var \Cake\ORM\Table|\PHPUnit\Framework\MockObject\MockObject $table */
  2064. $table = $this->getMockBuilder(Table::class)
  2065. ->setMethods(['query'])
  2066. ->setConstructorArgs([['table' => 'users', 'connection' => $this->connection]])
  2067. ->getMock();
  2068. $query = $this->getMockBuilder('Cake\ORM\Query')
  2069. ->setMethods(['execute', 'addDefaultTypes'])
  2070. ->setConstructorArgs([$this->connection, $table])
  2071. ->getMock();
  2072. $statement = $this->getMockBuilder(StatementInterface::class)->getMock();
  2073. $data = new Entity([
  2074. 'username' => 'superuser',
  2075. 'created' => new Time('2013-10-10 00:00'),
  2076. 'updated' => new Time('2013-10-10 00:00'),
  2077. ]);
  2078. $table->expects($this->once())->method('query')
  2079. ->will($this->returnValue($query));
  2080. $query->expects($this->once())->method('execute')
  2081. ->will($this->returnValue($statement));
  2082. $statement->expects($this->once())->method('rowCount')
  2083. ->will($this->returnValue(0));
  2084. $called = false;
  2085. $listener = function ($e, $entity, $options) use ($data, &$called) {
  2086. $called = true;
  2087. };
  2088. $table->getEventManager()->on('Model.afterSave', $listener);
  2089. $calledAfterCommit = false;
  2090. $listenerAfterCommit = function ($e, $entity, $options) use ($data, &$calledAfterCommit) {
  2091. $calledAfterCommit = true;
  2092. };
  2093. $table->getEventManager()->on('Model.afterSaveCommit', $listenerAfterCommit);
  2094. $this->assertFalse($table->save($data));
  2095. $this->assertFalse($called);
  2096. $this->assertFalse($calledAfterCommit);
  2097. }
  2098. /**
  2099. * Asserts that afterSaveCommit callback is triggered only for primary table
  2100. *
  2101. * @group save
  2102. * @return void
  2103. */
  2104. public function testAfterSaveCommitTriggeredOnlyForPrimaryTable()
  2105. {
  2106. $entity = new Entity([
  2107. 'title' => 'A Title',
  2108. 'body' => 'A body',
  2109. ]);
  2110. $entity->author = new Entity([
  2111. 'name' => 'Jose',
  2112. ]);
  2113. $table = $this->getTableLocator()->get('articles');
  2114. $table->belongsTo('authors');
  2115. $calledForArticle = false;
  2116. $listenerForArticle = function ($e, $entity, $options) use (&$calledForArticle) {
  2117. $calledForArticle = true;
  2118. };
  2119. $table->getEventManager()->on('Model.afterSaveCommit', $listenerForArticle);
  2120. $calledForAuthor = false;
  2121. $listenerForAuthor = function ($e, $entity, $options) use (&$calledForAuthor) {
  2122. $calledForAuthor = true;
  2123. };
  2124. $table->authors->getEventManager()->on('Model.afterSaveCommit', $listenerForAuthor);
  2125. $this->assertSame($entity, $table->save($entity));
  2126. $this->assertFalse($entity->isNew());
  2127. $this->assertFalse($entity->author->isNew());
  2128. $this->assertTrue($calledForArticle);
  2129. $this->assertFalse($calledForAuthor);
  2130. }
  2131. /**
  2132. * Test that you cannot save rows without a primary key.
  2133. *
  2134. * @group save
  2135. * @return void
  2136. */
  2137. public function testSaveNewErrorOnNoPrimaryKey()
  2138. {
  2139. $this->expectException(\RuntimeException::class);
  2140. $this->expectExceptionMessage('Cannot insert row in "users" table, it has no primary key');
  2141. $entity = new Entity(['username' => 'superuser']);
  2142. $table = $this->getTableLocator()->get('users', [
  2143. 'schema' => [
  2144. 'id' => ['type' => 'integer'],
  2145. 'username' => ['type' => 'string'],
  2146. ],
  2147. ]);
  2148. $table->save($entity);
  2149. }
  2150. /**
  2151. * Tests that save is wrapped around a transaction
  2152. *
  2153. * @group save
  2154. * @return void
  2155. */
  2156. public function testAtomicSave()
  2157. {
  2158. $config = ConnectionManager::getConfig('test');
  2159. $connection = $this->getMockBuilder('Cake\Database\Connection')
  2160. ->setMethods(['begin', 'commit', 'inTransaction'])
  2161. ->setConstructorArgs([$config])
  2162. ->getMock();
  2163. $connection->setDriver($this->connection->getDriver());
  2164. /** @var \Cake\ORM\Table|\PHPUnit\Framework\MockObject\MockObject $table */
  2165. $table = $this->getMockBuilder(Table::class)
  2166. ->setMethods(['getConnection'])
  2167. ->setConstructorArgs([['table' => 'users']])
  2168. ->getMock();
  2169. $table->expects($this->any())->method('getConnection')
  2170. ->will($this->returnValue($connection));
  2171. $connection->expects($this->once())->method('begin');
  2172. $connection->expects($this->once())->method('commit');
  2173. $connection->expects($this->any())->method('inTransaction')->will($this->returnValue(true));
  2174. $data = new Entity([
  2175. 'username' => 'superuser',
  2176. 'created' => new Time('2013-10-10 00:00'),
  2177. 'updated' => new Time('2013-10-10 00:00'),
  2178. ]);
  2179. $this->assertSame($data, $table->save($data));
  2180. }
  2181. /**
  2182. * Tests that save will rollback the transaction in the case of an exception
  2183. *
  2184. * @group save
  2185. * @return void
  2186. */
  2187. public function testAtomicSaveRollback()
  2188. {
  2189. $this->expectException(\PDOException::class);
  2190. $connection = $this->getMockBuilder('Cake\Database\Connection')
  2191. ->setMethods(['begin', 'rollback'])
  2192. ->setConstructorArgs([ConnectionManager::getConfig('test')])
  2193. ->getMock();
  2194. $connection->setDriver(ConnectionManager::get('test')->getDriver());
  2195. /** @var \Cake\ORM\Table|\PHPUnit\Framework\MockObject\MockObject $table */
  2196. $table = $this->getMockBuilder(Table::class)
  2197. ->setMethods(['query', 'getConnection'])
  2198. ->setConstructorArgs([['table' => 'users']])
  2199. ->getMock();
  2200. $query = $this->getMockBuilder('Cake\ORM\Query')
  2201. ->setMethods(['execute', 'addDefaultTypes'])
  2202. ->setConstructorArgs([$connection, $table])
  2203. ->getMock();
  2204. $table->expects($this->any())->method('getConnection')
  2205. ->will($this->returnValue($connection));
  2206. $table->expects($this->once())->method('query')
  2207. ->will($this->returnValue($query));
  2208. $connection->expects($this->once())->method('begin');
  2209. $connection->expects($this->once())->method('rollback');
  2210. $query->expects($this->once())->method('execute')
  2211. ->will($this->throwException(new \PDOException()));
  2212. $data = new Entity([
  2213. 'username' => 'superuser',
  2214. 'created' => new Time('2013-10-10 00:00'),
  2215. 'updated' => new Time('2013-10-10 00:00'),
  2216. ]);
  2217. $table->save($data);
  2218. }
  2219. /**
  2220. * Tests that save will rollback the transaction in the case of an exception
  2221. *
  2222. * @group save
  2223. * @return void
  2224. */
  2225. public function testAtomicSaveRollbackOnFailure()
  2226. {
  2227. $connection = $this->getMockBuilder('Cake\Database\Connection')
  2228. ->setMethods(['begin', 'rollback'])
  2229. ->setConstructorArgs([ConnectionManager::getConfig('test')])
  2230. ->getMock();
  2231. $connection->setDriver(ConnectionManager::get('test')->getDriver());
  2232. /** @var \Cake\ORM\Table|\PHPUnit\Framework\MockObject\MockObject $table */
  2233. $table = $this->getMockBuilder(Table::class)
  2234. ->setMethods(['query', 'getConnection', 'exists'])
  2235. ->setConstructorArgs([['table' => 'users']])
  2236. ->getMock();
  2237. $query = $this->getMockBuilder('Cake\ORM\Query')
  2238. ->setMethods(['execute', 'addDefaultTypes'])
  2239. ->setConstructorArgs([$connection, $table])
  2240. ->getMock();
  2241. $table->expects($this->any())->method('getConnection')
  2242. ->will($this->returnValue($connection));
  2243. $table->expects($this->once())->method('query')
  2244. ->will($this->returnValue($query));
  2245. $statement = $this->getMockBuilder(StatementInterface::class)->getMock();
  2246. $statement->expects($this->once())
  2247. ->method('rowCount')
  2248. ->will($this->returnValue(0));
  2249. $connection->expects($this->once())->method('begin');
  2250. $connection->expects($this->once())->method('rollback');
  2251. $query->expects($this->once())
  2252. ->method('execute')
  2253. ->will($this->returnValue($statement));
  2254. $data = new Entity([
  2255. 'username' => 'superuser',
  2256. 'created' => new Time('2013-10-10 00:00'),
  2257. 'updated' => new Time('2013-10-10 00:00'),
  2258. ]);
  2259. $table->save($data);
  2260. }
  2261. /**
  2262. * Tests that only the properties marked as dirty are actually saved
  2263. * to the database
  2264. *
  2265. * @group save
  2266. * @return void
  2267. */
  2268. public function testSaveOnlyDirtyProperties()
  2269. {
  2270. $entity = new Entity([
  2271. 'username' => 'superuser',
  2272. 'password' => 'root',
  2273. 'created' => new Time('2013-10-10 00:00'),
  2274. 'updated' => new Time('2013-10-10 00:00'),
  2275. ]);
  2276. $entity->clean();
  2277. $entity->setDirty('username', true);
  2278. $entity->setDirty('created', true);
  2279. $entity->setDirty('updated', true);
  2280. $table = $this->getTableLocator()->get('users');
  2281. $this->assertSame($entity, $table->save($entity));
  2282. $this->assertEquals($entity->id, self::$nextUserId);
  2283. $row = $table->find('all')->where(['id' => self::$nextUserId])->first();
  2284. $entity->set('password', null);
  2285. $this->assertEquals($entity->toArray(), $row->toArray());
  2286. }
  2287. /**
  2288. * Tests that a recently saved entity is marked as clean
  2289. *
  2290. * @group save
  2291. * @return void
  2292. */
  2293. public function testASavedEntityIsClean()
  2294. {
  2295. $entity = new Entity([
  2296. 'username' => 'superuser',
  2297. 'password' => 'root',
  2298. 'created' => new Time('2013-10-10 00:00'),
  2299. 'updated' => new Time('2013-10-10 00:00'),
  2300. ]);
  2301. $table = $this->getTableLocator()->get('users');
  2302. $this->assertSame($entity, $table->save($entity));
  2303. $this->assertFalse($entity->isDirty('usermane'));
  2304. $this->assertFalse($entity->isDirty('password'));
  2305. $this->assertFalse($entity->isDirty('created'));
  2306. $this->assertFalse($entity->isDirty('updated'));
  2307. }
  2308. /**
  2309. * Tests that a recently saved entity is marked as not new
  2310. *
  2311. * @group save
  2312. * @return void
  2313. */
  2314. public function testASavedEntityIsNotNew()
  2315. {
  2316. $entity = new Entity([
  2317. 'username' => 'superuser',
  2318. 'password' => 'root',
  2319. 'created' => new Time('2013-10-10 00:00'),
  2320. 'updated' => new Time('2013-10-10 00:00'),
  2321. ]);
  2322. $table = $this->getTableLocator()->get('users');
  2323. $this->assertSame($entity, $table->save($entity));
  2324. $this->assertFalse($entity->isNew());
  2325. }
  2326. /**
  2327. * Tests that save can detect automatically if it needs to insert
  2328. * or update a row
  2329. *
  2330. * @group save
  2331. * @return void
  2332. */
  2333. public function testSaveUpdateAuto()
  2334. {
  2335. $entity = new Entity([
  2336. 'id' => 2,
  2337. 'username' => 'baggins',
  2338. ]);
  2339. $table = $this->getTableLocator()->get('users');
  2340. $original = $table->find('all')->where(['id' => 2])->first();
  2341. $this->assertSame($entity, $table->save($entity));
  2342. $row = $table->find('all')->where(['id' => 2])->first();
  2343. $this->assertSame('baggins', $row->username);
  2344. $this->assertEquals($original->password, $row->password);
  2345. $this->assertEquals($original->created, $row->created);
  2346. $this->assertEquals($original->updated, $row->updated);
  2347. $this->assertFalse($entity->isNew());
  2348. $this->assertFalse($entity->isDirty('id'));
  2349. $this->assertFalse($entity->isDirty('username'));
  2350. }
  2351. /**
  2352. * Tests that beforeFind gets the correct isNew() state for the entity
  2353. *
  2354. * @return void
  2355. */
  2356. public function testBeforeSaveGetsCorrectPersistance()
  2357. {
  2358. $entity = new Entity([
  2359. 'id' => 2,
  2360. 'username' => 'baggins',
  2361. ]);
  2362. $table = $this->getTableLocator()->get('users');
  2363. $called = false;
  2364. $listener = function (EventInterface $event, $entity) use (&$called) {
  2365. $this->assertFalse($entity->isNew());
  2366. $called = true;
  2367. };
  2368. $table->getEventManager()->on('Model.beforeSave', $listener);
  2369. $this->assertSame($entity, $table->save($entity));
  2370. $this->assertTrue($called);
  2371. }
  2372. /**
  2373. * Tests that marking an entity as already persisted will prevent the save
  2374. * method from trying to infer the entity's actual status.
  2375. *
  2376. * @group save
  2377. * @return void
  2378. */
  2379. public function testSaveUpdateWithHint()
  2380. {
  2381. /** @var \Cake\ORM\Table|\PHPUnit\Framework\MockObject\MockObject $table */
  2382. $table = $this->getMockBuilder(Table::class)
  2383. ->setMethods(['exists'])
  2384. ->setConstructorArgs([['table' => 'users', 'connection' => ConnectionManager::get('test')]])
  2385. ->getMock();
  2386. $entity = new Entity([
  2387. 'id' => 2,
  2388. 'username' => 'baggins',
  2389. ], ['markNew' => false]);
  2390. $this->assertFalse($entity->isNew());
  2391. $table->expects($this->never())->method('exists');
  2392. $this->assertSame($entity, $table->save($entity));
  2393. }
  2394. /**
  2395. * Tests that when updating the primary key is not passed to the list of
  2396. * attributes to change
  2397. *
  2398. * @group save
  2399. * @return void
  2400. */
  2401. public function testSaveUpdatePrimaryKeyNotModified()
  2402. {
  2403. /** @var \Cake\ORM\Table|\PHPUnit\Framework\MockObject\MockObject $table */
  2404. $table = $this->getMockBuilder(Table::class)
  2405. ->setMethods(['query'])
  2406. ->setConstructorArgs([['table' => 'users', 'connection' => $this->connection]])
  2407. ->getMock();
  2408. $query = $this->getMockBuilder('Cake\ORM\Query')
  2409. ->setMethods(['execute', 'addDefaultTypes', 'set'])
  2410. ->setConstructorArgs([$this->connection, $table])
  2411. ->getMock();
  2412. $table->expects($this->once())->method('query')
  2413. ->will($this->returnValue($query));
  2414. $statement = $this->getMockBuilder(StatementInterface::class)->getMock();
  2415. $statement->expects($this->once())
  2416. ->method('errorCode')
  2417. ->will($this->returnValue('00000'));
  2418. $query->expects($this->once())
  2419. ->method('execute')
  2420. ->will($this->returnValue($statement));
  2421. $query->expects($this->once())->method('set')
  2422. ->with(['username' => 'baggins'])
  2423. ->will($this->returnValue($query));
  2424. $entity = new Entity([
  2425. 'id' => 2,
  2426. 'username' => 'baggins',
  2427. ], ['markNew' => false]);
  2428. $this->assertSame($entity, $table->save($entity));
  2429. }
  2430. /**
  2431. * Tests that passing only the primary key to save will not execute any queries
  2432. * but still return success
  2433. *
  2434. * @group save
  2435. * @return void
  2436. */
  2437. public function testUpdateNoChange()
  2438. {
  2439. /** @var \Cake\ORM\Table|\PHPUnit\Framework\MockObject\MockObject $table */
  2440. $table = $this->getMockBuilder(Table::class)
  2441. ->setMethods(['query'])
  2442. ->setConstructorArgs([['table' => 'users', 'connection' => $this->connection]])
  2443. ->getMock();
  2444. $table->expects($this->never())->method('query');
  2445. $entity = new Entity([
  2446. 'id' => 2,
  2447. ], ['markNew' => false]);
  2448. $this->assertSame($entity, $table->save($entity));
  2449. }
  2450. /**
  2451. * Tests that passing only the primary key to save will not execute any queries
  2452. * but still return success
  2453. *
  2454. * @group save
  2455. * @group integration
  2456. * @return void
  2457. */
  2458. public function testUpdateDirtyNoActualChanges()
  2459. {
  2460. $table = $this->getTableLocator()->get('Articles');
  2461. $entity = $table->get(1);
  2462. $entity->setAccess('*', true);
  2463. $entity->set($entity->toArray());
  2464. $this->assertSame($entity, $table->save($entity));
  2465. }
  2466. /**
  2467. * Tests that failing to pass a primary key to save will result in exception
  2468. *
  2469. * @group save
  2470. * @return void
  2471. */
  2472. public function testUpdateNoPrimaryButOtherKeys()
  2473. {
  2474. $this->expectException(\InvalidArgumentException::class);
  2475. /** @var \Cake\ORM\Table|\PHPUnit\Framework\MockObject\MockObject $table */
  2476. $table = $this->getMockBuilder(Table::class)
  2477. ->setMethods(['query'])
  2478. ->setConstructorArgs([['table' => 'users', 'connection' => $this->connection]])
  2479. ->getMock();
  2480. $table->expects($this->never())->method('query');
  2481. $entity = new Entity([
  2482. 'username' => 'mariano',
  2483. ], ['markNew' => false]);
  2484. $this->assertSame($entity, $table->save($entity));
  2485. }
  2486. /**
  2487. * Test saveMany() with entities array
  2488. *
  2489. * @return void
  2490. */
  2491. public function testSaveManyArray()
  2492. {
  2493. $entities = [
  2494. new Entity(['name' => 'admad']),
  2495. new Entity(['name' => 'dakota']),
  2496. ];
  2497. $table = $this->getTableLocator()->get('authors');
  2498. $result = $table->saveMany($entities);
  2499. $this->assertSame($entities, $result);
  2500. $this->assertTrue(isset($result[0]->id));
  2501. foreach ($entities as $entity) {
  2502. $this->assertFalse($entity->isNew());
  2503. }
  2504. }
  2505. /**
  2506. * Test saveMany() with ResultSet instance
  2507. *
  2508. * @return void
  2509. */
  2510. public function testSaveManyResultSet()
  2511. {
  2512. $table = $this->getTableLocator()->get('authors');
  2513. $entities = $table->find()
  2514. ->order(['id' => 'ASC'])
  2515. ->all();
  2516. $entities->first()->name = 'admad';
  2517. $result = $table->saveMany($entities);
  2518. $this->assertSame($entities, $result);
  2519. $first = $table->find()
  2520. ->order(['id' => 'ASC'])
  2521. ->first();
  2522. $this->assertSame('admad', $first->name);
  2523. }
  2524. /**
  2525. * Test saveMany() with failed save
  2526. *
  2527. * @return void
  2528. */
  2529. public function testSaveManyFailed()
  2530. {
  2531. $table = $this->getTableLocator()->get('authors');
  2532. $entities = [
  2533. new Entity(['name' => 'mark']),
  2534. new Entity(['name' => 'jose']),
  2535. ];
  2536. $entities[1]->setErrors(['name' => ['message']]);
  2537. $result = $table->saveMany($entities);
  2538. $this->assertFalse($result);
  2539. foreach ($entities as $entity) {
  2540. $this->assertTrue($entity->isNew());
  2541. }
  2542. }
  2543. /**
  2544. * Test saveMany() with failed save due to an exception
  2545. *
  2546. * @return void
  2547. */
  2548. public function testSaveManyFailedWithException()
  2549. {
  2550. $table = $this->getTableLocator()
  2551. ->get('authors');
  2552. $entities = [
  2553. new Entity(['name' => 'mark']),
  2554. new Entity(['name' => 'jose']),
  2555. ];
  2556. $table->getEventManager()->on('Model.beforeSave', function (EventInterface $event, EntityInterface $entity) {
  2557. if ($entity->name === 'jose') {
  2558. throw new \Exception('Oh noes');
  2559. }
  2560. });
  2561. $this->expectException(\Exception::class);
  2562. try {
  2563. $table->saveMany($entities);
  2564. } finally {
  2565. foreach ($entities as $entity) {
  2566. $this->assertTrue($entity->isNew());
  2567. }
  2568. }
  2569. }
  2570. /**
  2571. * Test saveManyOrFail() with entities array
  2572. *
  2573. * @return void
  2574. */
  2575. public function testSaveManyOrFailArray()
  2576. {
  2577. $entities = [
  2578. new Entity(['name' => 'admad']),
  2579. new Entity(['name' => 'dakota']),
  2580. ];
  2581. $table = $this->getTableLocator()->get('authors');
  2582. $result = $table->saveManyOrFail($entities);
  2583. $this->assertSame($entities, $result);
  2584. $this->assertTrue(isset($result[0]->id));
  2585. foreach ($entities as $entity) {
  2586. $this->assertFalse($entity->isNew());
  2587. }
  2588. }
  2589. /**
  2590. * Test saveManyOrFail() with ResultSet instance
  2591. *
  2592. * @return void
  2593. */
  2594. public function testSaveManyOrFailResultSet()
  2595. {
  2596. $table = $this->getTableLocator()->get('authors');
  2597. $entities = $table->find()
  2598. ->order(['id' => 'ASC'])
  2599. ->all();
  2600. $entities->first()->name = 'admad';
  2601. $result = $table->saveManyOrFail($entities);
  2602. $this->assertSame($entities, $result);
  2603. $first = $table->find()
  2604. ->order(['id' => 'ASC'])
  2605. ->first();
  2606. $this->assertSame('admad', $first->name);
  2607. }
  2608. /**
  2609. * Test saveManyOrFail() with failed save
  2610. *
  2611. * @return void
  2612. */
  2613. public function testSaveManyOrFailFailed()
  2614. {
  2615. $table = $this->getTableLocator()->get('authors');
  2616. $entities = [
  2617. new Entity(['name' => 'mark']),
  2618. new Entity(['name' => 'jose']),
  2619. ];
  2620. $entities[1]->setErrors(['name' => ['message']]);
  2621. $this->expectException(PersistenceFailedException::class);
  2622. $table->saveManyOrFail($entities);
  2623. }
  2624. /**
  2625. * Test simple delete.
  2626. *
  2627. * @return void
  2628. */
  2629. public function testDelete()
  2630. {
  2631. $table = $this->getTableLocator()->get('users');
  2632. $conditions = [
  2633. 'limit' => 1,
  2634. 'conditions' => [
  2635. 'username' => 'nate',
  2636. ],
  2637. ];
  2638. $query = $table->find('all', $conditions);
  2639. $entity = $query->first();
  2640. $result = $table->delete($entity);
  2641. $this->assertTrue($result);
  2642. $query = $table->find('all', $conditions);
  2643. $results = $query->execute();
  2644. $this->assertCount(0, $results, 'Find should fail.');
  2645. }
  2646. /**
  2647. * Test delete with dependent records
  2648. *
  2649. * @return void
  2650. */
  2651. public function testDeleteDependent()
  2652. {
  2653. $table = $this->getTableLocator()->get('authors');
  2654. $table->hasOne('articles', [
  2655. 'foreignKey' => 'author_id',
  2656. 'dependent' => true,
  2657. ]);
  2658. $entity = $table->get(1);
  2659. $result = $table->delete($entity);
  2660. $articles = $table->getAssociation('articles')->getTarget();
  2661. $query = $articles->find('all', [
  2662. 'conditions' => [
  2663. 'author_id' => $entity->id,
  2664. ],
  2665. ]);
  2666. $this->assertNull($query->all()->first(), 'Should not find any rows.');
  2667. }
  2668. /**
  2669. * Test delete with dependent records
  2670. *
  2671. * @return void
  2672. */
  2673. public function testDeleteDependentHasMany()
  2674. {
  2675. $table = $this->getTableLocator()->get('authors');
  2676. $table->hasMany('articles', [
  2677. 'foreignKey' => 'author_id',
  2678. 'dependent' => true,
  2679. 'cascadeCallbacks' => true,
  2680. ]);
  2681. $entity = $table->get(1);
  2682. $result = $table->delete($entity);
  2683. $this->assertTrue($result);
  2684. }
  2685. /**
  2686. * Test delete with dependent = false does not cascade.
  2687. *
  2688. * @return void
  2689. */
  2690. public function testDeleteNoDependentNoCascade()
  2691. {
  2692. $table = $this->getTableLocator()->get('authors');
  2693. $table->hasMany('article', [
  2694. 'foreignKey' => 'author_id',
  2695. 'dependent' => false,
  2696. ]);
  2697. $query = $table->find('all')->where(['id' => 1]);
  2698. $entity = $query->first();
  2699. $result = $table->delete($entity);
  2700. $articles = $table->getAssociation('articles')->getTarget();
  2701. $query = $articles->find('all')->where(['author_id' => $entity->id]);
  2702. $this->assertCount(2, $query->execute(), 'Should find rows.');
  2703. }
  2704. /**
  2705. * Test delete with BelongsToMany
  2706. *
  2707. * @return void
  2708. */
  2709. public function testDeleteBelongsToMany()
  2710. {
  2711. $table = $this->getTableLocator()->get('articles');
  2712. $table->belongsToMany('tag', [
  2713. 'foreignKey' => 'article_id',
  2714. 'joinTable' => 'articles_tags',
  2715. ]);
  2716. $query = $table->find('all')->where(['id' => 1]);
  2717. $entity = $query->first();
  2718. $table->delete($entity);
  2719. $junction = $table->getAssociation('tags')->junction();
  2720. $query = $junction->find('all')->where(['article_id' => 1]);
  2721. $this->assertNull($query->all()->first(), 'Should not find any rows.');
  2722. }
  2723. /**
  2724. * Test delete with dependent records belonging to an aliased
  2725. * belongsToMany association.
  2726. *
  2727. * @return void
  2728. */
  2729. public function testDeleteDependentAliased()
  2730. {
  2731. $Authors = $this->getTableLocator()->get('authors');
  2732. $Authors->associations()->removeAll();
  2733. $Articles = $this->getTableLocator()->get('articles');
  2734. $Articles->associations()->removeAll();
  2735. $Authors->hasMany('AliasedArticles', [
  2736. 'className' => 'Articles',
  2737. 'dependent' => true,
  2738. 'cascadeCallbacks' => true,
  2739. ]);
  2740. $Articles->belongsToMany('Tags');
  2741. $author = $Authors->get(1);
  2742. $result = $Authors->delete($author);
  2743. $this->assertTrue($result);
  2744. }
  2745. /**
  2746. * Test that cascading associations are deleted first.
  2747. *
  2748. * @return void
  2749. */
  2750. public function testDeleteAssociationsCascadingCallbacksOrder()
  2751. {
  2752. $groups = $this->getTableLocator()->get('Groups');
  2753. $members = $this->getTableLocator()->get('Members');
  2754. $groupsMembers = $this->getTableLocator()->get('GroupsMembers');
  2755. $groups->belongsToMany('Members');
  2756. $groups->hasMany('GroupsMembers', [
  2757. 'dependent' => true,
  2758. 'cascadeCallbacks' => true,
  2759. ]);
  2760. $groupsMembers->belongsTo('Members');
  2761. $groupsMembers->addBehavior('CounterCache', [
  2762. 'Members' => ['group_count'],
  2763. ]);
  2764. $member = $members->get(1);
  2765. $this->assertEquals(2, $member->group_count);
  2766. $group = $groups->get(1);
  2767. $groups->delete($group);
  2768. $member = $members->get(1);
  2769. $this->assertEquals(1, $member->group_count);
  2770. }
  2771. /**
  2772. * Test delete callbacks
  2773. *
  2774. * @return void
  2775. */
  2776. public function testDeleteCallbacks()
  2777. {
  2778. $entity = new Entity(['id' => 1, 'name' => 'mark']);
  2779. $options = new \ArrayObject(['atomic' => true, 'checkRules' => false, '_primary' => true]);
  2780. $mock = $this->getMockBuilder('Cake\Event\EventManager')->getMock();
  2781. $mock->expects($this->at(0))
  2782. ->method('on');
  2783. $mock->expects($this->at(1))
  2784. ->method('dispatch');
  2785. $mock->expects($this->at(2))
  2786. ->method('dispatch')
  2787. ->with($this->callback(function (EventInterface $event) use ($entity, $options) {
  2788. return $event->getName() === 'Model.beforeDelete' &&
  2789. $event->getData() == ['entity' => $entity, 'options' => $options];
  2790. }));
  2791. $mock->expects($this->at(3))
  2792. ->method('dispatch')
  2793. ->with($this->callback(function (EventInterface $event) use ($entity, $options) {
  2794. return $event->getName() === 'Model.afterDelete' &&
  2795. $event->getData() == ['entity' => $entity, 'options' => $options];
  2796. }));
  2797. $mock->expects($this->at(4))
  2798. ->method('dispatch')
  2799. ->with($this->callback(function (EventInterface $event) use ($entity, $options) {
  2800. return $event->getName() === 'Model.afterDeleteCommit' &&
  2801. $event->getData() == ['entity' => $entity, 'options' => $options];
  2802. }));
  2803. $table = $this->getTableLocator()->get('users', ['eventManager' => $mock]);
  2804. $entity->setNew(false);
  2805. $table->delete($entity, ['checkRules' => false]);
  2806. }
  2807. /**
  2808. * Test afterDeleteCommit is also called for non-atomic delete
  2809. *
  2810. * @return void
  2811. */
  2812. public function testDeleteCallbacksNonAtomic()
  2813. {
  2814. $table = $this->getTableLocator()->get('users');
  2815. $data = $table->get(1);
  2816. $options = new \ArrayObject(['atomic' => false, 'checkRules' => false]);
  2817. $called = false;
  2818. $listener = function ($e, $entity, $options) use ($data, &$called) {
  2819. $this->assertSame($data, $entity);
  2820. $called = true;
  2821. };
  2822. $table->getEventManager()->on('Model.afterDelete', $listener);
  2823. $calledAfterCommit = false;
  2824. $listenerAfterCommit = function ($e, $entity, $options) use ($data, &$calledAfterCommit) {
  2825. $calledAfterCommit = true;
  2826. };
  2827. $table->getEventManager()->on('Model.afterDeleteCommit', $listenerAfterCommit);
  2828. $table->delete($data, ['atomic' => false]);
  2829. $this->assertTrue($called);
  2830. $this->assertTrue($calledAfterCommit);
  2831. }
  2832. /**
  2833. * Test that afterDeleteCommit is only triggered for primary table
  2834. *
  2835. * @return void
  2836. */
  2837. public function testAfterDeleteCommitTriggeredOnlyForPrimaryTable()
  2838. {
  2839. $table = $this->getTableLocator()->get('authors');
  2840. $table->hasOne('articles', [
  2841. 'foreignKey' => 'author_id',
  2842. 'dependent' => true,
  2843. ]);
  2844. $called = false;
  2845. $listener = function ($e, $entity, $options) use (&$called) {
  2846. $called = true;
  2847. };
  2848. $table->getEventManager()->on('Model.afterDeleteCommit', $listener);
  2849. $called2 = false;
  2850. $listener = function ($e, $entity, $options) use (&$called2) {
  2851. $called2 = true;
  2852. };
  2853. $table->articles->getEventManager()->on('Model.afterDeleteCommit', $listener);
  2854. $entity = $table->get(1);
  2855. $this->assertTrue($table->delete($entity));
  2856. $this->assertTrue($called);
  2857. $this->assertFalse($called2);
  2858. }
  2859. /**
  2860. * Test delete beforeDelete can abort the delete.
  2861. *
  2862. * @return void
  2863. */
  2864. public function testDeleteBeforeDeleteAbort()
  2865. {
  2866. $entity = new Entity(['id' => 1, 'name' => 'mark']);
  2867. $options = new \ArrayObject(['atomic' => true, 'cascade' => true]);
  2868. $mock = $this->getMockBuilder('Cake\Event\EventManager')->getMock();
  2869. $mock->expects($this->at(2))
  2870. ->method('dispatch')
  2871. ->will($this->returnCallback(function (EventInterface $event) {
  2872. $event->stopPropagation();
  2873. return $event;
  2874. }));
  2875. $table = $this->getTableLocator()->get('users', ['eventManager' => $mock]);
  2876. $entity->setNew(false);
  2877. $result = $table->delete($entity, ['checkRules' => false]);
  2878. $this->assertFalse($result);
  2879. }
  2880. /**
  2881. * Test delete beforeDelete return result
  2882. *
  2883. * @return void
  2884. */
  2885. public function testDeleteBeforeDeleteReturnResult()
  2886. {
  2887. $entity = new Entity(['id' => 1, 'name' => 'mark']);
  2888. $options = new \ArrayObject(['atomic' => true, 'cascade' => true]);
  2889. $mock = $this->getMockBuilder('Cake\Event\EventManager')->getMock();
  2890. $mock->expects($this->at(2))
  2891. ->method('dispatch')
  2892. ->will($this->returnCallback(function (EventInterface $event) {
  2893. $event->stopPropagation();
  2894. $event->setResult('got stopped');
  2895. return $event;
  2896. }));
  2897. $table = $this->getTableLocator()->get('users', ['eventManager' => $mock]);
  2898. $entity->setNew(false);
  2899. $result = $table->delete($entity, ['checkRules' => false]);
  2900. $this->assertTrue($result);
  2901. }
  2902. /**
  2903. * Test deleting new entities does nothing.
  2904. *
  2905. * @return void
  2906. */
  2907. public function testDeleteIsNew()
  2908. {
  2909. $entity = new Entity(['id' => 1, 'name' => 'mark']);
  2910. /** @var \Cake\ORM\Table|\PHPUnit\Framework\MockObject\MockObject $table */
  2911. $table = $this->getMockBuilder(Table::class)
  2912. ->setMethods(['query'])
  2913. ->setConstructorArgs([['connection' => $this->connection]])
  2914. ->getMock();
  2915. $table->expects($this->never())
  2916. ->method('query');
  2917. $entity->setNew(true);
  2918. $result = $table->delete($entity);
  2919. $this->assertFalse($result);
  2920. }
  2921. /**
  2922. * Test simple delete.
  2923. *
  2924. * @return void
  2925. */
  2926. public function testDeleteMany()
  2927. {
  2928. $table = $this->getTableLocator()->get('users');
  2929. $entities = $table->find()->limit(2)->all()->toArray();
  2930. $this->assertCount(2, $entities);
  2931. $result = $table->deleteMany($entities);
  2932. $this->assertSame($entities, $result);
  2933. $count = $table->find()->where(['id IN' => Hash::extract($entities, '{n}.id')])->count();
  2934. $this->assertSame(0, $count, 'Find should not return > 0.');
  2935. }
  2936. /**
  2937. * Test simple delete.
  2938. *
  2939. * @return void
  2940. */
  2941. public function testDeleteManyOrFail()
  2942. {
  2943. $table = $this->getTableLocator()->get('users');
  2944. $entities = $table->find()->limit(2)->all()->toArray();
  2945. $this->assertCount(2, $entities);
  2946. $result = $table->deleteManyOrFail($entities);
  2947. $this->assertSame($entities, $result);
  2948. $count = $table->find()->where(['id IN' => Hash::extract($entities, '{n}.id')])->count();
  2949. $this->assertSame(0, $count, 'Find should not return > 0.');
  2950. }
  2951. /**
  2952. * test hasField()
  2953. *
  2954. * @return void
  2955. */
  2956. public function testHasField()
  2957. {
  2958. $table = $this->getTableLocator()->get('articles');
  2959. $this->assertFalse($table->hasField('nope'), 'Should not be there.');
  2960. $this->assertTrue($table->hasField('title'), 'Should be there.');
  2961. $this->assertTrue($table->hasField('body'), 'Should be there.');
  2962. }
  2963. /**
  2964. * Tests that there exists a default validator
  2965. *
  2966. * @return void
  2967. */
  2968. public function testValidatorDefault()
  2969. {
  2970. $table = new Table();
  2971. $validator = $table->getValidator();
  2972. $this->assertSame($table, $validator->getProvider('table'));
  2973. $this->assertInstanceOf('Cake\Validation\Validator', $validator);
  2974. $default = $table->getValidator('default');
  2975. $this->assertSame($validator, $default);
  2976. }
  2977. /**
  2978. * Tests that there exists a validator defined in a behavior.
  2979. *
  2980. * @return void
  2981. */
  2982. public function testValidatorBehavior()
  2983. {
  2984. $table = new Table();
  2985. $table->addBehavior('Validation');
  2986. $validator = $table->getValidator('Behavior');
  2987. $set = $validator->field('name');
  2988. $this->assertArrayHasKey('behaviorRule', $set);
  2989. }
  2990. /**
  2991. * Tests that it is possible to define custom validator methods
  2992. *
  2993. * @return void
  2994. */
  2995. public function testValidationWithDefiner()
  2996. {
  2997. $table = $this->getMockBuilder(Table::class)
  2998. ->setMethods(['validationForOtherStuff'])
  2999. ->getMock();
  3000. $table->expects($this->once())->method('validationForOtherStuff')
  3001. ->will($this->returnArgument(0));
  3002. $other = $table->getValidator('forOtherStuff');
  3003. $this->assertInstanceOf('Cake\Validation\Validator', $other);
  3004. $this->assertNotSame($other, $table->getValidator());
  3005. $this->assertSame($table, $other->getProvider('table'));
  3006. }
  3007. /**
  3008. * Tests that a RuntimeException is thrown if the custom validator does not return an Validator instance
  3009. *
  3010. * @return void
  3011. */
  3012. public function testValidationWithBadDefiner()
  3013. {
  3014. $table = $this->getMockBuilder(Table::class)
  3015. ->setMethods(['validationBad'])
  3016. ->getMock();
  3017. $table->expects($this->once())
  3018. ->method('validationBad');
  3019. $this->expectException(\RuntimeException::class);
  3020. $this->expectExceptionMessage(sprintf(
  3021. 'The %s::validationBad() validation method must return an instance of Cake\Validation\Validator.',
  3022. get_class($table)
  3023. ));
  3024. $table->getValidator('bad');
  3025. }
  3026. /**
  3027. * Tests that a RuntimeException is thrown if the custom validator method does not exist.
  3028. *
  3029. * @return void
  3030. */
  3031. public function testValidatorWithMissingMethod()
  3032. {
  3033. $this->expectException(\RuntimeException::class);
  3034. $this->expectExceptionMessage('The Cake\ORM\Table::validationMissing() validation method does not exists.');
  3035. $table = new Table();
  3036. $table->getValidator('missing');
  3037. }
  3038. /**
  3039. * Tests that it is possible to set a custom validator under a name
  3040. *
  3041. * @return void
  3042. */
  3043. public function testValidatorSetter()
  3044. {
  3045. $table = new Table();
  3046. $validator = new \Cake\Validation\Validator();
  3047. $table->setValidator('other', $validator);
  3048. $this->assertSame($validator, $table->getValidator('other'));
  3049. $this->assertSame($table, $validator->getProvider('table'));
  3050. }
  3051. /**
  3052. * Tests hasValidator method.
  3053. *
  3054. * @return void
  3055. */
  3056. public function testHasValidator()
  3057. {
  3058. $table = new Table();
  3059. $this->assertTrue($table->hasValidator('default'));
  3060. $this->assertFalse($table->hasValidator('other'));
  3061. $validator = new \Cake\Validation\Validator();
  3062. $table->setValidator('other', $validator);
  3063. $this->assertTrue($table->hasValidator('other'));
  3064. }
  3065. /**
  3066. * Tests that the source of an existing Entity is the same as a new one
  3067. *
  3068. * @return void
  3069. */
  3070. public function testEntitySourceExistingAndNew()
  3071. {
  3072. $this->loadPlugins(['TestPlugin']);
  3073. $table = $this->getTableLocator()->get('TestPlugin.Authors');
  3074. $existingAuthor = $table->find()->first();
  3075. $newAuthor = $table->newEmptyEntity();
  3076. $this->assertSame('TestPlugin.Authors', $existingAuthor->getSource());
  3077. $this->assertSame('TestPlugin.Authors', $newAuthor->getSource());
  3078. }
  3079. /**
  3080. * Tests that calling an entity with an empty array will run validation.
  3081. *
  3082. * @return void
  3083. */
  3084. public function testNewEntityAndValidation()
  3085. {
  3086. $table = $this->getTableLocator()->get('Articles');
  3087. $table->getValidator()->requirePresence('title');
  3088. $entity = $table->newEntity([]);
  3089. $errors = $entity->getErrors();
  3090. $this->assertNotEmpty($errors['title']);
  3091. }
  3092. /**
  3093. * Tests that creating an entity will not run any validation.
  3094. *
  3095. * @return void
  3096. */
  3097. public function testCreateEntityAndValidation()
  3098. {
  3099. $table = $this->getTableLocator()->get('Articles');
  3100. $table->getValidator()->requirePresence('title');
  3101. $entity = $table->newEmptyEntity();
  3102. $this->assertEmpty($entity->getErrors());
  3103. }
  3104. /**
  3105. * Test magic findByXX method.
  3106. *
  3107. * @return void
  3108. */
  3109. public function testMagicFindDefaultToAll()
  3110. {
  3111. $table = $this->getTableLocator()->get('Users');
  3112. $result = $table->findByUsername('garrett');
  3113. $this->assertInstanceOf('Cake\ORM\Query', $result);
  3114. $expected = new QueryExpression(['Users.username' => 'garrett'], $this->usersTypeMap);
  3115. $this->assertEquals($expected, $result->clause('where'));
  3116. }
  3117. /**
  3118. * Test magic findByXX errors on missing arguments.
  3119. *
  3120. * @return void
  3121. */
  3122. public function testMagicFindError()
  3123. {
  3124. $this->expectException(\BadMethodCallException::class);
  3125. $this->expectExceptionMessage('Not enough arguments for magic finder. Got 0 required 1');
  3126. $table = $this->getTableLocator()->get('Users');
  3127. $table->findByUsername();
  3128. }
  3129. /**
  3130. * Test magic findByXX errors on missing arguments.
  3131. *
  3132. * @return void
  3133. */
  3134. public function testMagicFindErrorMissingField()
  3135. {
  3136. $this->expectException(\BadMethodCallException::class);
  3137. $this->expectExceptionMessage('Not enough arguments for magic finder. Got 1 required 2');
  3138. $table = $this->getTableLocator()->get('Users');
  3139. $table->findByUsernameAndId('garrett');
  3140. }
  3141. /**
  3142. * Test magic findByXX errors when there is a mix of or & and.
  3143. *
  3144. * @return void
  3145. */
  3146. public function testMagicFindErrorMixOfOperators()
  3147. {
  3148. $this->expectException(\BadMethodCallException::class);
  3149. $this->expectExceptionMessage('Cannot mix "and" & "or" in a magic finder. Use find() instead.');
  3150. $table = $this->getTableLocator()->get('Users');
  3151. $table->findByUsernameAndIdOrPassword('garrett', 1, 'sekret');
  3152. }
  3153. /**
  3154. * Test magic findByXX method.
  3155. *
  3156. * @return void
  3157. */
  3158. public function testMagicFindFirstAnd()
  3159. {
  3160. $table = $this->getTableLocator()->get('Users');
  3161. $result = $table->findByUsernameAndId('garrett', 4);
  3162. $this->assertInstanceOf('Cake\ORM\Query', $result);
  3163. $expected = new QueryExpression(['Users.username' => 'garrett', 'Users.id' => 4], $this->usersTypeMap);
  3164. $this->assertEquals($expected, $result->clause('where'));
  3165. }
  3166. /**
  3167. * Test magic findByXX method.
  3168. *
  3169. * @return void
  3170. */
  3171. public function testMagicFindFirstOr()
  3172. {
  3173. $table = $this->getTableLocator()->get('Users');
  3174. $result = $table->findByUsernameOrId('garrett', 4);
  3175. $this->assertInstanceOf('Cake\ORM\Query', $result);
  3176. $expected = new QueryExpression([], $this->usersTypeMap);
  3177. $expected->add(
  3178. [
  3179. 'OR' => [
  3180. 'Users.username' => 'garrett',
  3181. 'Users.id' => 4,
  3182. ]]
  3183. );
  3184. $this->assertEquals($expected, $result->clause('where'));
  3185. }
  3186. /**
  3187. * Test magic findAllByXX method.
  3188. *
  3189. * @return void
  3190. */
  3191. public function testMagicFindAll()
  3192. {
  3193. $table = $this->getTableLocator()->get('Articles');
  3194. $result = $table->findAllByAuthorId(1);
  3195. $this->assertInstanceOf('Cake\ORM\Query', $result);
  3196. $this->assertNull($result->clause('limit'));
  3197. $expected = new QueryExpression(['Articles.author_id' => 1], $this->articlesTypeMap);
  3198. $this->assertEquals($expected, $result->clause('where'));
  3199. }
  3200. /**
  3201. * Test magic findAllByXX method.
  3202. *
  3203. * @return void
  3204. */
  3205. public function testMagicFindAllAnd()
  3206. {
  3207. $table = $this->getTableLocator()->get('Users');
  3208. $result = $table->findAllByAuthorIdAndPublished(1, 'Y');
  3209. $this->assertInstanceOf('Cake\ORM\Query', $result);
  3210. $this->assertNull($result->clause('limit'));
  3211. $expected = new QueryExpression(
  3212. ['Users.author_id' => 1, 'Users.published' => 'Y'],
  3213. $this->usersTypeMap
  3214. );
  3215. $this->assertEquals($expected, $result->clause('where'));
  3216. }
  3217. /**
  3218. * Test magic findAllByXX method.
  3219. *
  3220. * @return void
  3221. */
  3222. public function testMagicFindAllOr()
  3223. {
  3224. $table = $this->getTableLocator()->get('Users');
  3225. $result = $table->findAllByAuthorIdOrPublished(1, 'Y');
  3226. $this->assertInstanceOf('Cake\ORM\Query', $result);
  3227. $this->assertNull($result->clause('limit'));
  3228. $expected = new QueryExpression();
  3229. $expected->getTypeMap()->setDefaults($this->usersTypeMap->toArray());
  3230. $expected->add(
  3231. ['or' => ['Users.author_id' => 1, 'Users.published' => 'Y']]
  3232. );
  3233. $this->assertEquals($expected, $result->clause('where'));
  3234. $this->assertNull($result->clause('order'));
  3235. }
  3236. /**
  3237. * Test the behavior method.
  3238. *
  3239. * @return void
  3240. */
  3241. public function testBehaviorIntrospection()
  3242. {
  3243. $table = $this->getTableLocator()->get('users');
  3244. $table->addBehavior('Timestamp');
  3245. $this->assertTrue($table->hasBehavior('Timestamp'), 'should be true on loaded behavior');
  3246. $this->assertFalse($table->hasBehavior('Tree'), 'should be false on unloaded behavior');
  3247. }
  3248. /**
  3249. * Tests saving belongsTo association
  3250. *
  3251. * @group save
  3252. * @return void
  3253. */
  3254. public function testSaveBelongsTo()
  3255. {
  3256. $entity = new Entity([
  3257. 'title' => 'A Title',
  3258. 'body' => 'A body',
  3259. ]);
  3260. $entity->author = new Entity([
  3261. 'name' => 'Jose',
  3262. ]);
  3263. $table = $this->getTableLocator()->get('articles');
  3264. $table->belongsTo('authors');
  3265. $this->assertSame($entity, $table->save($entity));
  3266. $this->assertFalse($entity->isNew());
  3267. $this->assertFalse($entity->author->isNew());
  3268. $this->assertEquals(5, $entity->author->id);
  3269. $this->assertEquals(5, $entity->get('author_id'));
  3270. }
  3271. /**
  3272. * Tests saving hasOne association
  3273. *
  3274. * @group save
  3275. * @return void
  3276. */
  3277. public function testSaveHasOne()
  3278. {
  3279. $entity = new Entity([
  3280. 'name' => 'Jose',
  3281. ]);
  3282. $entity->article = new Entity([
  3283. 'title' => 'A Title',
  3284. 'body' => 'A body',
  3285. ]);
  3286. $table = $this->getTableLocator()->get('authors');
  3287. $table->hasOne('articles');
  3288. $this->assertSame($entity, $table->save($entity));
  3289. $this->assertFalse($entity->isNew());
  3290. $this->assertFalse($entity->article->isNew());
  3291. $this->assertEquals(4, $entity->article->id);
  3292. $this->assertEquals(5, $entity->article->get('author_id'));
  3293. $this->assertFalse($entity->article->isDirty('author_id'));
  3294. }
  3295. /**
  3296. * Tests saving associations only saves associations
  3297. * if they are entities.
  3298. *
  3299. * @group save
  3300. * @return void
  3301. */
  3302. public function testSaveOnlySaveAssociatedEntities()
  3303. {
  3304. $entity = new Entity([
  3305. 'name' => 'Jose',
  3306. ]);
  3307. // Not an entity.
  3308. $entity->article = [
  3309. 'title' => 'A Title',
  3310. 'body' => 'A body',
  3311. ];
  3312. $table = $this->getTableLocator()->get('authors');
  3313. $table->hasOne('articles');
  3314. $table->save($entity);
  3315. $this->assertFalse($entity->isNew());
  3316. $this->assertIsArray($entity->article);
  3317. }
  3318. /**
  3319. * Tests saving multiple entities in a hasMany association
  3320. *
  3321. * @return void
  3322. */
  3323. public function testSaveHasMany()
  3324. {
  3325. $entity = new Entity([
  3326. 'name' => 'Jose',
  3327. ]);
  3328. $entity->articles = [
  3329. new Entity([
  3330. 'title' => 'A Title',
  3331. 'body' => 'A body',
  3332. ]),
  3333. new Entity([
  3334. 'title' => 'Another Title',
  3335. 'body' => 'Another body',
  3336. ]),
  3337. ];
  3338. $table = $this->getTableLocator()->get('authors');
  3339. $table->hasMany('articles');
  3340. $this->assertSame($entity, $table->save($entity));
  3341. $this->assertFalse($entity->isNew());
  3342. $this->assertFalse($entity->articles[0]->isNew());
  3343. $this->assertFalse($entity->articles[1]->isNew());
  3344. $this->assertEquals(4, $entity->articles[0]->id);
  3345. $this->assertEquals(5, $entity->articles[1]->id);
  3346. $this->assertEquals(5, $entity->articles[0]->author_id);
  3347. $this->assertEquals(5, $entity->articles[1]->author_id);
  3348. }
  3349. /**
  3350. * Tests overwriting hasMany associations in an integration scenario.
  3351. *
  3352. * @return void
  3353. */
  3354. public function testSaveHasManyOverwrite()
  3355. {
  3356. $table = $this->getTableLocator()->get('authors');
  3357. $table->hasMany('articles');
  3358. $entity = $table->get(3, ['contain' => ['articles']]);
  3359. $data = [
  3360. 'name' => 'big jose',
  3361. 'articles' => [
  3362. [
  3363. 'id' => 2,
  3364. 'title' => 'New title',
  3365. ],
  3366. ],
  3367. ];
  3368. $entity = $table->patchEntity($entity, $data, ['associated' => 'articles']);
  3369. $this->assertSame($entity, $table->save($entity));
  3370. $entity = $table->get(3, ['contain' => ['articles']]);
  3371. $this->assertSame('big jose', $entity->name, 'Author did not persist');
  3372. $this->assertSame('New title', $entity->articles[0]->title, 'Article did not persist');
  3373. }
  3374. /**
  3375. * Tests saving belongsToMany records
  3376. *
  3377. * @group save
  3378. * @return void
  3379. */
  3380. public function testSaveBelongsToMany()
  3381. {
  3382. $entity = new Entity([
  3383. 'title' => 'A Title',
  3384. 'body' => 'A body',
  3385. ]);
  3386. $entity->tags = [
  3387. new Entity([
  3388. 'name' => 'Something New',
  3389. ]),
  3390. new Entity([
  3391. 'name' => 'Another Something',
  3392. ]),
  3393. ];
  3394. $table = $this->getTableLocator()->get('Articles');
  3395. $table->belongsToMany('Tags');
  3396. $this->assertSame($entity, $table->save($entity));
  3397. $this->assertFalse($entity->isNew());
  3398. $this->assertFalse($entity->tags[0]->isNew());
  3399. $this->assertFalse($entity->tags[1]->isNew());
  3400. $this->assertEquals(4, $entity->tags[0]->id);
  3401. $this->assertEquals(5, $entity->tags[1]->id);
  3402. $this->assertEquals(4, $entity->tags[0]->_joinData->article_id);
  3403. $this->assertEquals(4, $entity->tags[1]->_joinData->article_id);
  3404. $this->assertEquals(4, $entity->tags[0]->_joinData->tag_id);
  3405. $this->assertEquals(5, $entity->tags[1]->_joinData->tag_id);
  3406. }
  3407. /**
  3408. * Tests saving belongsToMany records when record exists.
  3409. *
  3410. * @group save
  3411. * @return void
  3412. */
  3413. public function testSaveBelongsToManyJoinDataOnExistingRecord()
  3414. {
  3415. $tags = $this->getTableLocator()->get('Tags');
  3416. $table = $this->getTableLocator()->get('Articles');
  3417. $table->belongsToMany('Tags');
  3418. $entity = $table->find()->contain('Tags')->first();
  3419. // not associated to the article already.
  3420. $entity->tags[] = $tags->get(3);
  3421. $entity->setDirty('tags', true);
  3422. $this->assertSame($entity, $table->save($entity));
  3423. $this->assertFalse($entity->isNew());
  3424. $this->assertFalse($entity->tags[0]->isNew());
  3425. $this->assertFalse($entity->tags[1]->isNew());
  3426. $this->assertFalse($entity->tags[2]->isNew());
  3427. $this->assertNotEmpty($entity->tags[0]->_joinData);
  3428. $this->assertNotEmpty($entity->tags[1]->_joinData);
  3429. $this->assertNotEmpty($entity->tags[2]->_joinData);
  3430. }
  3431. /**
  3432. * Test that belongsToMany can be saved with _joinData data.
  3433. *
  3434. * @return void
  3435. */
  3436. public function testSaveBelongsToManyJoinData()
  3437. {
  3438. $articles = $this->getTableLocator()->get('Articles');
  3439. $article = $articles->get(1, ['contain' => ['tags']]);
  3440. $data = [
  3441. 'tags' => [
  3442. ['id' => 1, '_joinData' => ['highlighted' => 1]],
  3443. ['id' => 3],
  3444. ],
  3445. ];
  3446. $article = $articles->patchEntity($article, $data);
  3447. $result = $articles->save($article);
  3448. $this->assertSame($result, $article);
  3449. }
  3450. /**
  3451. * Test to check that association condition are used when fetching existing
  3452. * records to decide which records to unlink.
  3453. *
  3454. * @return void
  3455. */
  3456. public function testPolymorphicBelongsToManySave()
  3457. {
  3458. $articles = $this->getTableLocator()->get('Articles');
  3459. $articles->belongsToMany('Tags', [
  3460. 'through' => 'PolymorphicTagged',
  3461. 'foreignKey' => 'foreign_key',
  3462. 'conditions' => [
  3463. 'PolymorphicTagged.foreign_model' => 'Articles',
  3464. ],
  3465. 'sort' => ['PolymorphicTagged.position' => 'ASC'],
  3466. ]);
  3467. $articles->Tags->junction()->belongsTo('Tags');
  3468. $entity = $articles->get(1, ['contain' => ['Tags']]);
  3469. $data = [
  3470. 'id' => 1,
  3471. 'tags' => [
  3472. [
  3473. 'id' => 1,
  3474. '_joinData' => [
  3475. 'id' => 2,
  3476. 'foreign_model' => 'Articles',
  3477. 'position' => 2,
  3478. ],
  3479. ],
  3480. [
  3481. 'id' => 2,
  3482. '_joinData' => [
  3483. 'foreign_model' => 'Articles',
  3484. 'position' => 1,
  3485. ],
  3486. ],
  3487. ],
  3488. ];
  3489. $entity = $articles->patchEntity($entity, $data, ['associated' => ['Tags._joinData']]);
  3490. $entity = $articles->save($entity);
  3491. $expected = [
  3492. [
  3493. 'id' => 1,
  3494. 'tag_id' => 1,
  3495. 'foreign_key' => 1,
  3496. 'foreign_model' => 'Posts',
  3497. 'position' => 1,
  3498. ],
  3499. [
  3500. 'id' => 2,
  3501. 'tag_id' => 1,
  3502. 'foreign_key' => 1,
  3503. 'foreign_model' => 'Articles',
  3504. 'position' => 2,
  3505. ],
  3506. [
  3507. 'id' => 3,
  3508. 'tag_id' => 2,
  3509. 'foreign_key' => 1,
  3510. 'foreign_model' => 'Articles',
  3511. 'position' => 1,
  3512. ],
  3513. ];
  3514. $result = $this->getTableLocator()->get('PolymorphicTagged')
  3515. ->find('all', ['sort' => ['id' => 'DESC']])
  3516. ->enableHydration(false)
  3517. ->toArray();
  3518. $this->assertEquals($expected, $result);
  3519. }
  3520. /**
  3521. * Tests saving belongsToMany records can delete all links.
  3522. *
  3523. * @group save
  3524. * @return void
  3525. */
  3526. public function testSaveBelongsToManyDeleteAllLinks()
  3527. {
  3528. $table = $this->getTableLocator()->get('Articles');
  3529. $table->belongsToMany('Tags', [
  3530. 'saveStrategy' => 'replace',
  3531. ]);
  3532. $entity = $table->get(1, ['contain' => 'Tags']);
  3533. $this->assertCount(2, $entity->tags, 'Fixture data did not change.');
  3534. $entity->tags = [];
  3535. $result = $table->save($entity);
  3536. $this->assertSame($result, $entity);
  3537. $this->assertSame([], $entity->tags, 'No tags on the entity.');
  3538. $entity = $table->get(1, ['contain' => 'Tags']);
  3539. $this->assertSame([], $entity->tags, 'No tags in the db either.');
  3540. }
  3541. /**
  3542. * Tests saving belongsToMany records can delete some links.
  3543. *
  3544. * @group save
  3545. * @return void
  3546. */
  3547. public function testSaveBelongsToManyDeleteSomeLinks()
  3548. {
  3549. $table = $this->getTableLocator()->get('Articles');
  3550. $table->belongsToMany('Tags', [
  3551. 'saveStrategy' => 'replace',
  3552. ]);
  3553. $entity = $table->get(1, ['contain' => 'Tags']);
  3554. $this->assertCount(2, $entity->tags, 'Fixture data did not change.');
  3555. $tag = new Entity([
  3556. 'id' => 2,
  3557. ]);
  3558. $entity->tags = [$tag];
  3559. $result = $table->save($entity);
  3560. $this->assertSame($result, $entity);
  3561. $this->assertCount(1, $entity->tags, 'Only one tag left.');
  3562. $this->assertEquals($tag, $entity->tags[0]);
  3563. $entity = $table->get(1, ['contain' => 'Tags']);
  3564. $this->assertCount(1, $entity->tags, 'Only one tag in the db.');
  3565. $this->assertEquals($tag->id, $entity->tags[0]->id);
  3566. }
  3567. /**
  3568. * Test that belongsToMany ignores non-entity data.
  3569. *
  3570. * @return void
  3571. */
  3572. public function testSaveBelongsToManyIgnoreNonEntityData()
  3573. {
  3574. $articles = $this->getTableLocator()->get('Articles');
  3575. $article = $articles->get(1, ['contain' => ['Tags']]);
  3576. $article->tags = [
  3577. '_ids' => [2, 1],
  3578. ];
  3579. $result = $articles->save($article);
  3580. $this->assertSame($result, $article);
  3581. }
  3582. /**
  3583. * Test that a save call takes a SaveOptionBuilder object as well.
  3584. *
  3585. * @group save
  3586. * @return void
  3587. */
  3588. public function testSaveWithOptionBuilder()
  3589. {
  3590. $articles = new Table([
  3591. 'table' => 'articles',
  3592. 'connection' => $this->connection,
  3593. ]);
  3594. $articles->belongsTo('Authors');
  3595. $optionBuilder = new SaveOptionsBuilder($articles, [
  3596. 'associated' => [
  3597. 'Authors',
  3598. ],
  3599. ]);
  3600. $entity = $articles->newEntity([
  3601. 'title' => 'test save options',
  3602. 'author' => [
  3603. 'name' => 'author name',
  3604. ],
  3605. ]);
  3606. $articles->save($entity, $optionBuilder);
  3607. $this->assertFalse($entity->isNew());
  3608. $this->assertSame('test save options', $entity->title);
  3609. $this->assertNotEmpty($entity->id);
  3610. $this->assertNotEmpty($entity->author->id);
  3611. $this->assertSame('author name', $entity->author->name);
  3612. $entity = $articles->newEntity([
  3613. 'title' => 'test save options 2',
  3614. 'author' => [
  3615. 'name' => 'author name',
  3616. ],
  3617. ]);
  3618. $optionBuilder = new SaveOptionsBuilder($articles, [
  3619. 'associated' => [],
  3620. ]);
  3621. $articles->save($entity, $optionBuilder);
  3622. $this->assertFalse($entity->isNew());
  3623. $this->assertSame('test save options 2', $entity->title);
  3624. $this->assertNotEmpty($entity->id);
  3625. $this->assertEmpty($entity->author->id);
  3626. $this->assertTrue($entity->author->isNew());
  3627. }
  3628. /**
  3629. * Tests that saving a persisted and clean entity will is a no-op
  3630. *
  3631. * @group save
  3632. * @return void
  3633. */
  3634. public function testSaveCleanEntity()
  3635. {
  3636. $table = $this->getMockBuilder(Table::class)
  3637. ->setMethods(['_processSave'])
  3638. ->getMock();
  3639. $entity = new Entity(
  3640. ['id' => 'foo'],
  3641. ['markNew' => false, 'markClean' => true]
  3642. );
  3643. $table->expects($this->never())->method('_processSave');
  3644. $this->assertSame($entity, $table->save($entity));
  3645. }
  3646. /**
  3647. * Integration test to show how to append a new tag to an article
  3648. *
  3649. * @group save
  3650. * @return void
  3651. */
  3652. public function testBelongsToManyIntegration()
  3653. {
  3654. $table = $this->getTableLocator()->get('Articles');
  3655. $table->belongsToMany('Tags');
  3656. $article = $table->find('all')->where(['id' => 1])->contain(['Tags'])->first();
  3657. $tags = $article->tags;
  3658. $this->assertNotEmpty($tags);
  3659. $tags[] = new \TestApp\Model\Entity\Tag(['name' => 'Something New']);
  3660. $article->tags = $tags;
  3661. $this->assertSame($article, $table->save($article));
  3662. $tags = $article->tags;
  3663. $this->assertCount(3, $tags);
  3664. $this->assertFalse($tags[2]->isNew());
  3665. $this->assertEquals(4, $tags[2]->id);
  3666. $this->assertEquals(1, $tags[2]->_joinData->article_id);
  3667. $this->assertEquals(4, $tags[2]->_joinData->tag_id);
  3668. }
  3669. /**
  3670. * Tests that it is possible to do a deep save and control what associations get saved,
  3671. * while having control of the options passed to each level of the save
  3672. *
  3673. * @group save
  3674. * @return void
  3675. */
  3676. public function testSaveDeepAssociationOptions()
  3677. {
  3678. $articles = $this->getMockBuilder(Table::class)
  3679. ->setMethods(['_insert'])
  3680. ->setConstructorArgs([['table' => 'articles', 'connection' => $this->connection]])
  3681. ->getMock();
  3682. $authors = $this->getMockBuilder(Table::class)
  3683. ->setMethods(['_insert'])
  3684. ->setConstructorArgs([['table' => 'authors', 'connection' => $this->connection]])
  3685. ->getMock();
  3686. $supervisors = $this->getMockBuilder(Table::class)
  3687. ->setMethods(['_insert', 'validate'])
  3688. ->setConstructorArgs([[
  3689. 'table' => 'authors',
  3690. 'alias' => 'supervisors',
  3691. 'connection' => $this->connection,
  3692. ]])
  3693. ->getMock();
  3694. $tags = $this->getMockBuilder(Table::class)
  3695. ->setMethods(['_insert'])
  3696. ->setConstructorArgs([['table' => 'tags', 'connection' => $this->connection]])
  3697. ->getMock();
  3698. $articles->belongsTo('authors', ['targetTable' => $authors]);
  3699. $authors->hasOne('supervisors', ['targetTable' => $supervisors]);
  3700. $supervisors->belongsToMany('tags', ['targetTable' => $tags]);
  3701. $entity = new Entity([
  3702. 'title' => 'bar',
  3703. 'author' => new Entity([
  3704. 'name' => 'Juan',
  3705. 'supervisor' => new Entity(['name' => 'Marc']),
  3706. 'tags' => [
  3707. new Entity(['name' => 'foo']),
  3708. ],
  3709. ]),
  3710. ]);
  3711. $entity->setNew(true);
  3712. $entity->author->setNew(true);
  3713. $entity->author->supervisor->setNew(true);
  3714. $entity->author->tags[0]->setNew(true);
  3715. $articles->expects($this->once())
  3716. ->method('_insert')
  3717. ->with($entity, ['title' => 'bar'])
  3718. ->will($this->returnValue($entity));
  3719. $authors->expects($this->once())
  3720. ->method('_insert')
  3721. ->with($entity->author, ['name' => 'Juan'])
  3722. ->will($this->returnValue($entity->author));
  3723. $supervisors->expects($this->once())
  3724. ->method('_insert')
  3725. ->with($entity->author->supervisor, ['name' => 'Marc'])
  3726. ->will($this->returnValue($entity->author->supervisor));
  3727. $tags->expects($this->never())->method('_insert');
  3728. $this->assertSame($entity, $articles->save($entity, [
  3729. 'associated' => [
  3730. 'authors' => [],
  3731. 'authors.supervisors' => [
  3732. 'atomic' => false,
  3733. 'associated' => false,
  3734. ],
  3735. ],
  3736. ]));
  3737. }
  3738. /**
  3739. * @return void
  3740. */
  3741. public function testBelongsToFluentInterface()
  3742. {
  3743. /** @var \TestApp\Model\Table\ArticlesTable $articles */
  3744. $articles = $this->getMockBuilder(Table::class)
  3745. ->setMethods(['_insert'])
  3746. ->setConstructorArgs([['table' => 'articles', 'connection' => $this->connection]])
  3747. ->getMock();
  3748. $authors = $this->getMockBuilder(Table::class)
  3749. ->setMethods(['_insert'])
  3750. ->setConstructorArgs([['table' => 'authors', 'connection' => $this->connection]])
  3751. ->getMock();
  3752. try {
  3753. $articles->belongsTo('authors')
  3754. ->setForeignKey('author_id')
  3755. ->setName('Authors')
  3756. ->setTarget($authors)
  3757. ->setBindingKey('id')
  3758. ->setConditions([])
  3759. ->setFinder('list')
  3760. ->setProperty('authors')
  3761. ->setJoinType('inner');
  3762. } catch (\BadMethodCallException $e) {
  3763. $this->fail('Method chaining should be ok');
  3764. }
  3765. $this->assertSame('articles', $articles->getTable());
  3766. }
  3767. /**
  3768. * @return void
  3769. */
  3770. public function testHasOneFluentInterface()
  3771. {
  3772. /** @var \TestApp\Model\Table\AuthorsTable $authors */
  3773. $authors = $this->getMockBuilder(Table::class)
  3774. ->setMethods(['_insert'])
  3775. ->setConstructorArgs([['table' => 'authors', 'connection' => $this->connection]])
  3776. ->getMock();
  3777. try {
  3778. $authors->hasOne('articles')
  3779. ->setForeignKey('author_id')
  3780. ->setName('Articles')
  3781. ->setDependent(true)
  3782. ->setBindingKey('id')
  3783. ->setConditions([])
  3784. ->setCascadeCallbacks(true)
  3785. ->setFinder('list')
  3786. ->setStrategy('select')
  3787. ->setProperty('authors')
  3788. ->setJoinType('inner');
  3789. } catch (\BadMethodCallException $e) {
  3790. $this->fail('Method chaining should be ok');
  3791. }
  3792. $this->assertSame('authors', $authors->getTable());
  3793. }
  3794. /**
  3795. * @return void
  3796. */
  3797. public function testHasManyFluentInterface()
  3798. {
  3799. /** @var \TestApp\Model\Table\AuthorsTable $authors */
  3800. $authors = $this->getMockBuilder(Table::class)
  3801. ->setMethods(['_insert'])
  3802. ->setConstructorArgs([['table' => 'authors', 'connection' => $this->connection]])
  3803. ->getMock();
  3804. try {
  3805. $authors->hasMany('articles')
  3806. ->setForeignKey('author_id')
  3807. ->setName('Articles')
  3808. ->setDependent(true)
  3809. ->setSort(['created' => 'DESC'])
  3810. ->setBindingKey('id')
  3811. ->setConditions([])
  3812. ->setCascadeCallbacks(true)
  3813. ->setFinder('list')
  3814. ->setStrategy('select')
  3815. ->setSaveStrategy('replace')
  3816. ->setProperty('authors')
  3817. ->setJoinType('inner');
  3818. } catch (\BadMethodCallException $e) {
  3819. $this->fail('Method chaining should be ok');
  3820. }
  3821. $this->assertSame('authors', $authors->getTable());
  3822. }
  3823. /**
  3824. * @return void
  3825. */
  3826. public function testBelongsToManyFluentInterface()
  3827. {
  3828. /** @var \TestApp\Model\Table\AuthorsTable $authors */
  3829. $authors = $this->getMockBuilder(Table::class)
  3830. ->setMethods(['_insert'])
  3831. ->setConstructorArgs([['table' => 'authors', 'connection' => $this->connection]])
  3832. ->getMock();
  3833. try {
  3834. $authors->belongsToMany('articles')
  3835. ->setForeignKey('author_id')
  3836. ->setName('Articles')
  3837. ->setDependent(true)
  3838. ->setTargetForeignKey('article_id')
  3839. ->setBindingKey('id')
  3840. ->setConditions([])
  3841. ->setFinder('list')
  3842. ->setProperty('authors')
  3843. ->setSource($authors)
  3844. ->setStrategy('select')
  3845. ->setSaveStrategy('append')
  3846. ->setThrough('author_articles')
  3847. ->setJoinType('inner');
  3848. } catch (\BadMethodCallException $e) {
  3849. $this->fail('Method chaining should be ok');
  3850. }
  3851. $this->assertSame('authors', $authors->getTable());
  3852. }
  3853. /**
  3854. * Integration test for linking entities with belongsToMany
  3855. *
  3856. * @return void
  3857. */
  3858. public function testLinkBelongsToMany()
  3859. {
  3860. $table = $this->getTableLocator()->get('articles');
  3861. $table->belongsToMany('tags');
  3862. $tagsTable = $this->getTableLocator()->get('tags');
  3863. $source = ['source' => 'tags'];
  3864. $options = ['markNew' => false];
  3865. $article = new Entity([
  3866. 'id' => 1,
  3867. ], $options);
  3868. $newTag = new \TestApp\Model\Entity\Tag([
  3869. 'name' => 'Foo',
  3870. 'description' => 'Foo desc',
  3871. 'created' => null,
  3872. ], $source);
  3873. $tags[] = new \TestApp\Model\Entity\Tag([
  3874. 'id' => 3,
  3875. ], $options + $source);
  3876. $tags[] = $newTag;
  3877. $tagsTable->save($newTag);
  3878. $table->getAssociation('tags')->link($article, $tags);
  3879. $this->assertEquals($article->tags, $tags);
  3880. foreach ($tags as $tag) {
  3881. $this->assertFalse($tag->isNew());
  3882. }
  3883. $article = $table->find('all')->where(['id' => 1])->contain(['tags'])->first();
  3884. $this->assertEquals($article->tags[2]->id, $tags[0]->id);
  3885. $this->assertEquals($article->tags[3], $tags[1]);
  3886. }
  3887. /**
  3888. * Integration test for linking entities with HasMany
  3889. *
  3890. * @return void
  3891. */
  3892. public function testLinkHasMany()
  3893. {
  3894. $authors = $this->getTableLocator()->get('Authors');
  3895. $articles = $this->getTableLocator()->get('Articles');
  3896. $authors->hasMany('Articles', [
  3897. 'foreignKey' => 'author_id',
  3898. ]);
  3899. $author = $authors->newEntity(['name' => 'mylux']);
  3900. $author = $authors->save($author);
  3901. $newArticles = $articles->newEntities(
  3902. [
  3903. [
  3904. 'title' => 'New bakery next corner',
  3905. 'body' => 'They sell tastefull cakes',
  3906. ],
  3907. [
  3908. 'title' => 'Spicy cake recipe',
  3909. 'body' => 'chocolate and peppers',
  3910. ],
  3911. ]
  3912. );
  3913. $sizeArticles = count($newArticles);
  3914. $this->assertTrue($authors->Articles->link($author, $newArticles));
  3915. $this->assertCount($sizeArticles, $authors->Articles->findAllByAuthorId($author->id));
  3916. $this->assertCount($sizeArticles, $author->articles);
  3917. $this->assertFalse($author->isDirty('articles'));
  3918. }
  3919. /**
  3920. * Integration test for linking entities with HasMany combined with ReplaceSaveStrategy. It must append, not unlinking anything
  3921. *
  3922. * @return void
  3923. */
  3924. public function testLinkHasManyReplaceSaveStrategy()
  3925. {
  3926. $authors = $this->getTableLocator()->get('Authors');
  3927. $articles = $this->getTableLocator()->get('Articles');
  3928. $authors->hasMany('Articles', [
  3929. 'foreignKey' => 'author_id',
  3930. 'saveStrategy' => 'replace',
  3931. ]);
  3932. $author = $authors->newEntity(['name' => 'mylux']);
  3933. $author = $authors->save($author);
  3934. $newArticles = $articles->newEntities(
  3935. [
  3936. [
  3937. 'title' => 'New bakery next corner',
  3938. 'body' => 'They sell tastefull cakes',
  3939. ],
  3940. [
  3941. 'title' => 'Spicy cake recipe',
  3942. 'body' => 'chocolate and peppers',
  3943. ],
  3944. ]
  3945. );
  3946. $this->assertTrue($authors->Articles->link($author, $newArticles));
  3947. $sizeArticles = count($newArticles);
  3948. $newArticles = $articles->newEntities(
  3949. [
  3950. [
  3951. 'title' => 'Nothing but the cake',
  3952. 'body' => 'It is all that we need',
  3953. ],
  3954. ]
  3955. );
  3956. $this->assertTrue($authors->Articles->link($author, $newArticles));
  3957. $sizeArticles++;
  3958. $this->assertCount($sizeArticles, $authors->Articles->findAllByAuthorId($author->id));
  3959. $this->assertCount($sizeArticles, $author->articles);
  3960. $this->assertFalse($author->isDirty('articles'));
  3961. }
  3962. /**
  3963. * Integration test for linking entities with HasMany. The input contains already linked entities and they should not appeat duplicated
  3964. *
  3965. * @return void
  3966. */
  3967. public function testLinkHasManyExisting()
  3968. {
  3969. $authors = $this->getTableLocator()->get('Authors');
  3970. $articles = $this->getTableLocator()->get('Articles');
  3971. $authors->hasMany('Articles', [
  3972. 'foreignKey' => 'author_id',
  3973. 'saveStrategy' => 'replace',
  3974. ]);
  3975. $author = $authors->newEntity(['name' => 'mylux']);
  3976. $author = $authors->save($author);
  3977. $newArticles = $articles->newEntities(
  3978. [
  3979. [
  3980. 'title' => 'New bakery next corner',
  3981. 'body' => 'They sell tastefull cakes',
  3982. ],
  3983. [
  3984. 'title' => 'Spicy cake recipe',
  3985. 'body' => 'chocolate and peppers',
  3986. ],
  3987. ]
  3988. );
  3989. $this->assertTrue($authors->Articles->link($author, $newArticles));
  3990. $sizeArticles = count($newArticles);
  3991. $newArticles = array_merge(
  3992. $author->articles,
  3993. $articles->newEntities(
  3994. [
  3995. [
  3996. 'title' => 'Nothing but the cake',
  3997. 'body' => 'It is all that we need',
  3998. ],
  3999. ]
  4000. )
  4001. );
  4002. $this->assertTrue($authors->Articles->link($author, $newArticles));
  4003. $sizeArticles++;
  4004. $this->assertCount($sizeArticles, $authors->Articles->findAllByAuthorId($author->id));
  4005. $this->assertCount($sizeArticles, $author->articles);
  4006. $this->assertFalse($author->isDirty('articles'));
  4007. }
  4008. /**
  4009. * Integration test for unlinking entities with HasMany. The association property must be cleaned
  4010. *
  4011. * @return void
  4012. */
  4013. public function testUnlinkHasManyCleanProperty()
  4014. {
  4015. $authors = $this->getTableLocator()->get('Authors');
  4016. $articles = $this->getTableLocator()->get('Articles');
  4017. $authors->hasMany('Articles', [
  4018. 'foreignKey' => 'author_id',
  4019. 'saveStrategy' => 'replace',
  4020. ]);
  4021. $author = $authors->newEntity(['name' => 'mylux']);
  4022. $author = $authors->save($author);
  4023. $newArticles = $articles->newEntities(
  4024. [
  4025. [
  4026. 'title' => 'New bakery next corner',
  4027. 'body' => 'They sell tastefull cakes',
  4028. ],
  4029. [
  4030. 'title' => 'Spicy cake recipe',
  4031. 'body' => 'chocolate and peppers',
  4032. ],
  4033. [
  4034. 'title' => 'Creamy cake recipe',
  4035. 'body' => 'chocolate and cream',
  4036. ],
  4037. ]
  4038. );
  4039. $this->assertTrue($authors->Articles->link($author, $newArticles));
  4040. $sizeArticles = count($newArticles);
  4041. $articlesToUnlink = [ $author->articles[0], $author->articles[1] ];
  4042. $authors->Articles->unlink($author, $articlesToUnlink);
  4043. $this->assertCount($sizeArticles - count($articlesToUnlink), $authors->Articles->findAllByAuthorId($author->id));
  4044. $this->assertCount($sizeArticles - count($articlesToUnlink), $author->articles);
  4045. $this->assertFalse($author->isDirty('articles'));
  4046. }
  4047. /**
  4048. * Integration test for unlinking entities with HasMany. The association property must stay unchanged
  4049. *
  4050. * @return void
  4051. */
  4052. public function testUnlinkHasManyNotCleanProperty()
  4053. {
  4054. $authors = $this->getTableLocator()->get('Authors');
  4055. $articles = $this->getTableLocator()->get('Articles');
  4056. $authors->hasMany('Articles', [
  4057. 'foreignKey' => 'author_id',
  4058. 'saveStrategy' => 'replace',
  4059. ]);
  4060. $author = $authors->newEntity(['name' => 'mylux']);
  4061. $author = $authors->save($author);
  4062. $newArticles = $articles->newEntities(
  4063. [
  4064. [
  4065. 'title' => 'New bakery next corner',
  4066. 'body' => 'They sell tastefull cakes',
  4067. ],
  4068. [
  4069. 'title' => 'Spicy cake recipe',
  4070. 'body' => 'chocolate and peppers',
  4071. ],
  4072. [
  4073. 'title' => 'Creamy cake recipe',
  4074. 'body' => 'chocolate and cream',
  4075. ],
  4076. ]
  4077. );
  4078. $this->assertTrue($authors->Articles->link($author, $newArticles));
  4079. $sizeArticles = count($newArticles);
  4080. $articlesToUnlink = [ $author->articles[0], $author->articles[1] ];
  4081. $authors->Articles->unlink($author, $articlesToUnlink, ['cleanProperty' => false]);
  4082. $this->assertCount($sizeArticles - count($articlesToUnlink), $authors->Articles->findAllByAuthorId($author->id));
  4083. $this->assertCount($sizeArticles, $author->articles);
  4084. $this->assertFalse($author->isDirty('articles'));
  4085. }
  4086. /**
  4087. * Integration test for unlinking entities with HasMany.
  4088. * Checking that no error happens when the hasMany property is originally
  4089. * null
  4090. *
  4091. * @return void
  4092. */
  4093. public function testUnlinkHasManyEmpty()
  4094. {
  4095. $authors = $this->getTableLocator()->get('Authors');
  4096. $articles = $this->getTableLocator()->get('Articles');
  4097. $authors->hasMany('Articles');
  4098. $author = $authors->get(1);
  4099. $article = $authors->Articles->get(1);
  4100. $authors->Articles->unlink($author, [$article]);
  4101. $this->assertNotEmpty($authors);
  4102. }
  4103. /**
  4104. * Integration test for replacing entities which depend on their source entity with HasMany and failing transaction. False should be returned when
  4105. * unlinking fails while replacing even when cascadeCallbacks is enabled
  4106. *
  4107. * @return void
  4108. */
  4109. public function testReplaceHasManyOnErrorDependentCascadeCallbacks()
  4110. {
  4111. $articles = $this->getMockBuilder(Table::class)
  4112. ->setMethods(['delete'])
  4113. ->setConstructorArgs([[
  4114. 'connection' => $this->connection,
  4115. 'alias' => 'Articles',
  4116. 'table' => 'articles',
  4117. ]])
  4118. ->getMock();
  4119. $articles->method('delete')->willReturn(false);
  4120. $associations = new AssociationCollection();
  4121. $hasManyArticles = $this->getMockBuilder('Cake\ORM\Association\HasMany')
  4122. ->setMethods(['getTarget'])
  4123. ->setConstructorArgs([
  4124. 'articles',
  4125. [
  4126. 'target' => $articles,
  4127. 'foreignKey' => 'author_id',
  4128. 'dependent' => true,
  4129. 'cascadeCallbacks' => true,
  4130. ],
  4131. ])
  4132. ->getMock();
  4133. $hasManyArticles->method('getTarget')->willReturn($articles);
  4134. $associations->add('articles', $hasManyArticles);
  4135. $authors = new Table([
  4136. 'connection' => $this->connection,
  4137. 'alias' => 'Authors',
  4138. 'table' => 'authors',
  4139. 'associations' => $associations,
  4140. ]);
  4141. $authors->Articles->setSource($authors);
  4142. $author = $authors->newEntity(['name' => 'mylux']);
  4143. $author = $authors->save($author);
  4144. $newArticles = $articles->newEntities(
  4145. [
  4146. [
  4147. 'title' => 'New bakery next corner',
  4148. 'body' => 'They sell tastefull cakes',
  4149. ],
  4150. [
  4151. 'title' => 'Spicy cake recipe',
  4152. 'body' => 'chocolate and peppers',
  4153. ],
  4154. ]
  4155. );
  4156. $sizeArticles = count($newArticles);
  4157. $this->assertTrue($authors->Articles->link($author, $newArticles));
  4158. $this->assertEquals($authors->Articles->findAllByAuthorId($author->id)->count(), $sizeArticles);
  4159. $this->assertCount($sizeArticles, $author->articles);
  4160. $newArticles = array_merge(
  4161. $author->articles,
  4162. $articles->newEntities(
  4163. [
  4164. [
  4165. 'title' => 'Cheese cake recipe',
  4166. 'body' => 'The secrets of mixing salt and sugar',
  4167. ],
  4168. [
  4169. 'title' => 'Not another piece of cake',
  4170. 'body' => 'This is the best',
  4171. ],
  4172. ]
  4173. )
  4174. );
  4175. unset($newArticles[0]);
  4176. $this->assertFalse($authors->Articles->replace($author, $newArticles));
  4177. $this->assertCount($sizeArticles, $authors->Articles->findAllByAuthorId($author->id));
  4178. }
  4179. /**
  4180. * Integration test for replacing entities with HasMany and an empty target list. The transaction must be successful
  4181. *
  4182. * @return void
  4183. */
  4184. public function testReplaceHasManyEmptyList()
  4185. {
  4186. $authors = new Table([
  4187. 'connection' => $this->connection,
  4188. 'alias' => 'Authors',
  4189. 'table' => 'authors',
  4190. ]);
  4191. $authors->hasMany('Articles');
  4192. $author = $authors->newEntity(['name' => 'mylux']);
  4193. $author = $authors->save($author);
  4194. $newArticles = $authors->Articles->newEntities(
  4195. [
  4196. [
  4197. 'title' => 'New bakery next corner',
  4198. 'body' => 'They sell tastefull cakes',
  4199. ],
  4200. [
  4201. 'title' => 'Spicy cake recipe',
  4202. 'body' => 'chocolate and peppers',
  4203. ],
  4204. ]
  4205. );
  4206. $sizeArticles = count($newArticles);
  4207. $this->assertTrue($authors->Articles->link($author, $newArticles));
  4208. $this->assertEquals($authors->Articles->findAllByAuthorId($author->id)->count(), $sizeArticles);
  4209. $this->assertCount($sizeArticles, $author->articles);
  4210. $newArticles = [];
  4211. $this->assertTrue($authors->Articles->replace($author, $newArticles));
  4212. $this->assertCount(0, $authors->Articles->findAllByAuthorId($author->id));
  4213. }
  4214. /**
  4215. * Integration test for replacing entities with HasMany and no already persisted entities. The transaction must be successful.
  4216. * Replace operation should prevent considering 0 changed records an error when they are not found in the table
  4217. *
  4218. * @return void
  4219. */
  4220. public function testReplaceHasManyNoPersistedEntities()
  4221. {
  4222. $authors = new Table([
  4223. 'connection' => $this->connection,
  4224. 'alias' => 'Authors',
  4225. 'table' => 'authors',
  4226. ]);
  4227. $authors->hasMany('Articles');
  4228. $author = $authors->newEntity(['name' => 'mylux']);
  4229. $author = $authors->save($author);
  4230. $newArticles = $authors->Articles->newEntities(
  4231. [
  4232. [
  4233. 'title' => 'New bakery next corner',
  4234. 'body' => 'They sell tastefull cakes',
  4235. ],
  4236. [
  4237. 'title' => 'Spicy cake recipe',
  4238. 'body' => 'chocolate and peppers',
  4239. ],
  4240. ]
  4241. );
  4242. $authors->Articles->deleteAll(['1=1']);
  4243. $sizeArticles = count($newArticles);
  4244. $this->assertTrue($authors->Articles->link($author, $newArticles));
  4245. $this->assertEquals($authors->Articles->findAllByAuthorId($author->id)->count(), $sizeArticles);
  4246. $this->assertCount($sizeArticles, $author->articles);
  4247. $this->assertTrue($authors->Articles->replace($author, $newArticles));
  4248. $this->assertCount($sizeArticles, $authors->Articles->findAllByAuthorId($author->id));
  4249. }
  4250. /**
  4251. * Integration test for replacing entities with HasMany.
  4252. *
  4253. * @return void
  4254. */
  4255. public function testReplaceHasMany()
  4256. {
  4257. $authors = $this->getTableLocator()->get('Authors');
  4258. $articles = $this->getTableLocator()->get('Articles');
  4259. $authors->hasMany('Articles', [
  4260. 'foreignKey' => 'author_id',
  4261. ]);
  4262. $author = $authors->newEntity(['name' => 'mylux']);
  4263. $author = $authors->save($author);
  4264. $newArticles = $articles->newEntities(
  4265. [
  4266. [
  4267. 'title' => 'New bakery next corner',
  4268. 'body' => 'They sell tastefull cakes',
  4269. ],
  4270. [
  4271. 'title' => 'Spicy cake recipe',
  4272. 'body' => 'chocolate and peppers',
  4273. ],
  4274. ]
  4275. );
  4276. $sizeArticles = count($newArticles);
  4277. $this->assertTrue($authors->Articles->link($author, $newArticles));
  4278. $this->assertEquals($authors->Articles->findAllByAuthorId($author->id)->count(), $sizeArticles);
  4279. $this->assertCount($sizeArticles, $author->articles);
  4280. $newArticles = array_merge(
  4281. $author->articles,
  4282. $articles->newEntities(
  4283. [
  4284. [
  4285. 'title' => 'Cheese cake recipe',
  4286. 'body' => 'The secrets of mixing salt and sugar',
  4287. ],
  4288. [
  4289. 'title' => 'Not another piece of cake',
  4290. 'body' => 'This is the best',
  4291. ],
  4292. ]
  4293. )
  4294. );
  4295. unset($newArticles[0]);
  4296. $this->assertTrue($authors->Articles->replace($author, $newArticles));
  4297. $this->assertCount(count($newArticles), $author->articles);
  4298. $this->assertEquals((new Collection($newArticles))->extract('title'), (new Collection($author->articles))->extract('title'));
  4299. }
  4300. /**
  4301. * Integration test to show how to unlink a single record from a belongsToMany
  4302. *
  4303. * @return void
  4304. */
  4305. public function testUnlinkBelongsToMany()
  4306. {
  4307. $table = $this->getTableLocator()->get('articles');
  4308. $table->belongsToMany('tags');
  4309. $tagsTable = $this->getTableLocator()->get('tags');
  4310. $options = ['markNew' => false];
  4311. $article = $table->find('all')
  4312. ->where(['id' => 1])
  4313. ->contain(['tags'])->first();
  4314. $table->getAssociation('tags')->unlink($article, [$article->tags[0]]);
  4315. $this->assertCount(1, $article->tags);
  4316. $this->assertEquals(2, $article->tags[0]->get('id'));
  4317. $this->assertFalse($article->isDirty('tags'));
  4318. }
  4319. /**
  4320. * Integration test to show how to unlink multiple records from a belongsToMany
  4321. *
  4322. * @return void
  4323. */
  4324. public function testUnlinkBelongsToManyMultiple()
  4325. {
  4326. $table = $this->getTableLocator()->get('articles');
  4327. $table->belongsToMany('tags');
  4328. $tagsTable = $this->getTableLocator()->get('tags');
  4329. $options = ['markNew' => false];
  4330. $article = new Entity(['id' => 1], $options);
  4331. $tags[] = new \TestApp\Model\Entity\Tag(['id' => 1], $options);
  4332. $tags[] = new \TestApp\Model\Entity\Tag(['id' => 2], $options);
  4333. $table->getAssociation('tags')->unlink($article, $tags);
  4334. $left = $table->find('all')->where(['id' => 1])->contain(['tags'])->first();
  4335. $this->assertEmpty($left->tags);
  4336. }
  4337. /**
  4338. * Integration test to show how to unlink multiple records from a belongsToMany
  4339. * providing some of the joint
  4340. *
  4341. * @return void
  4342. */
  4343. public function testUnlinkBelongsToManyPassingJoint()
  4344. {
  4345. $table = $this->getTableLocator()->get('articles');
  4346. $table->belongsToMany('tags');
  4347. $tagsTable = $this->getTableLocator()->get('tags');
  4348. $options = ['markNew' => false];
  4349. $article = new Entity(['id' => 1], $options);
  4350. $tags[] = new \TestApp\Model\Entity\Tag(['id' => 1], $options);
  4351. $tags[] = new \TestApp\Model\Entity\Tag(['id' => 2], $options);
  4352. $tags[1]->_joinData = new Entity([
  4353. 'article_id' => 1,
  4354. 'tag_id' => 2,
  4355. ], $options);
  4356. $table->getAssociation('tags')->unlink($article, $tags);
  4357. $left = $table->find('all')->where(['id' => 1])->contain(['tags'])->first();
  4358. $this->assertEmpty($left->tags);
  4359. }
  4360. /**
  4361. * Integration test to show how to replace records from a belongsToMany
  4362. *
  4363. * @return void
  4364. */
  4365. public function testReplacelinksBelongsToMany()
  4366. {
  4367. $table = $this->getTableLocator()->get('Articles');
  4368. $table->belongsToMany('Tags');
  4369. $tagsTable = $this->getTableLocator()->get('Tags');
  4370. $options = ['markNew' => false];
  4371. $article = new Entity(['id' => 1], $options);
  4372. $tags[] = new \TestApp\Model\Entity\Tag(['id' => 2], $options);
  4373. $tags[] = new \TestApp\Model\Entity\Tag(['id' => 3], $options);
  4374. $tags[] = new \TestApp\Model\Entity\Tag(['name' => 'foo']);
  4375. $table->getAssociation('Tags')->replaceLinks($article, $tags);
  4376. $this->assertEquals(2, $article->tags[0]->id);
  4377. $this->assertEquals(3, $article->tags[1]->id);
  4378. $this->assertEquals(4, $article->tags[2]->id);
  4379. $article = $table->find('all')->where(['id' => 1])->contain(['Tags'])->first();
  4380. $this->assertCount(3, $article->tags);
  4381. $this->assertEquals(2, $article->tags[0]->id);
  4382. $this->assertEquals(3, $article->tags[1]->id);
  4383. $this->assertEquals(4, $article->tags[2]->id);
  4384. $this->assertSame('foo', $article->tags[2]->name);
  4385. }
  4386. /**
  4387. * Integration test to show how remove all links from a belongsToMany
  4388. *
  4389. * @return void
  4390. */
  4391. public function testReplacelinksBelongsToManyWithEmpty()
  4392. {
  4393. $table = $this->getTableLocator()->get('Articles');
  4394. $table->belongsToMany('Tags');
  4395. $tagsTable = $this->getTableLocator()->get('Tags');
  4396. $options = ['markNew' => false];
  4397. $article = new Entity(['id' => 1], $options);
  4398. $tags = [];
  4399. $table->getAssociation('Tags')->replaceLinks($article, $tags);
  4400. $this->assertSame($tags, $article->tags);
  4401. $article = $table->find('all')->where(['id' => 1])->contain(['Tags'])->first();
  4402. $this->assertEmpty($article->tags);
  4403. }
  4404. /**
  4405. * Integration test to show how to replace records from a belongsToMany
  4406. * passing the joint property along in the target entity
  4407. *
  4408. * @return void
  4409. */
  4410. public function testReplacelinksBelongsToManyWithJoint()
  4411. {
  4412. $table = $this->getTableLocator()->get('Articles');
  4413. $table->belongsToMany('Tags');
  4414. $tagsTable = $this->getTableLocator()->get('Tags');
  4415. $options = ['markNew' => false];
  4416. $article = new Entity(['id' => 1], $options);
  4417. $tags[] = new \TestApp\Model\Entity\Tag([
  4418. 'id' => 2,
  4419. '_joinData' => new Entity([
  4420. 'article_id' => 1,
  4421. 'tag_id' => 2,
  4422. ]),
  4423. ], $options);
  4424. $tags[] = new \TestApp\Model\Entity\Tag(['id' => 3], $options);
  4425. $table->getAssociation('Tags')->replaceLinks($article, $tags);
  4426. $this->assertSame($tags, $article->tags);
  4427. $article = $table->find('all')->where(['id' => 1])->contain(['Tags'])->first();
  4428. $this->assertCount(2, $article->tags);
  4429. $this->assertEquals(2, $article->tags[0]->id);
  4430. $this->assertEquals(3, $article->tags[1]->id);
  4431. }
  4432. /**
  4433. * Tests that options are being passed through to the internal table method calls.
  4434. *
  4435. * @return void
  4436. */
  4437. public function testOptionsBeingPassedToImplicitBelongsToManyDeletesUsingSaveReplace()
  4438. {
  4439. $articles = $this->getTableLocator()->get('Articles');
  4440. $tags = $articles->belongsToMany('Tags');
  4441. $tags->setSaveStrategy(BelongsToMany::SAVE_REPLACE)
  4442. ->setDependent(true)
  4443. ->setCascadeCallbacks(true);
  4444. $actualOptions = null;
  4445. $tags->junction()->getEventManager()->on(
  4446. 'Model.beforeDelete',
  4447. function (EventInterface $event, EntityInterface $entity, ArrayObject $options) use (&$actualOptions) {
  4448. $actualOptions = $options->getArrayCopy();
  4449. }
  4450. );
  4451. $article = $articles->get(1);
  4452. $article->tags = [];
  4453. $article->setDirty('tags', true);
  4454. $result = $articles->save($article, ['foo' => 'bar']);
  4455. $this->assertNotEmpty($result);
  4456. $expected = [
  4457. '_primary' => false,
  4458. 'foo' => 'bar',
  4459. 'atomic' => true,
  4460. 'checkRules' => true,
  4461. 'checkExisting' => true,
  4462. ];
  4463. $this->assertEquals($expected, $actualOptions);
  4464. }
  4465. /**
  4466. * Tests that options are being passed through to the internal table method calls.
  4467. *
  4468. * @return void
  4469. */
  4470. public function testOptionsBeingPassedToInternalSaveCallsUsingBelongsToManyLink()
  4471. {
  4472. $articles = $this->getTableLocator()->get('Articles');
  4473. $tags = $articles->belongsToMany('Tags');
  4474. $actualOptions = null;
  4475. $tags->junction()->getEventManager()->on(
  4476. 'Model.beforeSave',
  4477. function (EventInterface $event, EntityInterface $entity, ArrayObject $options) use (&$actualOptions) {
  4478. $actualOptions = $options->getArrayCopy();
  4479. }
  4480. );
  4481. $article = $articles->get(1);
  4482. $result = $tags->link($article, [$tags->getTarget()->get(2)], ['foo' => 'bar']);
  4483. $this->assertTrue($result);
  4484. $expected = [
  4485. '_primary' => true,
  4486. 'foo' => 'bar',
  4487. 'atomic' => true,
  4488. 'checkRules' => true,
  4489. 'checkExisting' => true,
  4490. 'associated' => [
  4491. 'articles' => [],
  4492. 'tags' => [],
  4493. ],
  4494. ];
  4495. $this->assertEquals($expected, $actualOptions);
  4496. }
  4497. /**
  4498. * Tests that options are being passed through to the internal table method calls.
  4499. *
  4500. * @return void
  4501. */
  4502. public function testOptionsBeingPassedToInternalSaveCallsUsingBelongsToManyUnlink()
  4503. {
  4504. $articles = $this->getTableLocator()->get('Articles');
  4505. $tags = $articles->belongsToMany('Tags');
  4506. $actualOptions = null;
  4507. $tags->junction()->getEventManager()->on(
  4508. 'Model.beforeDelete',
  4509. function (EventInterface $event, EntityInterface $entity, ArrayObject $options) use (&$actualOptions) {
  4510. $actualOptions = $options->getArrayCopy();
  4511. }
  4512. );
  4513. $article = $articles->get(1);
  4514. $tags->unlink($article, [$tags->getTarget()->get(2)], ['foo' => 'bar']);
  4515. $expected = [
  4516. '_primary' => true,
  4517. 'foo' => 'bar',
  4518. 'atomic' => true,
  4519. 'checkRules' => true,
  4520. 'cleanProperty' => true,
  4521. ];
  4522. $this->assertEquals($expected, $actualOptions);
  4523. }
  4524. /**
  4525. * Tests that options are being passed through to the internal table method calls.
  4526. *
  4527. * @return void
  4528. */
  4529. public function testOptionsBeingPassedToInternalSaveAndDeleteCallsUsingBelongsToManyReplaceLinks()
  4530. {
  4531. $articles = $this->getTableLocator()->get('Articles');
  4532. $tags = $articles->belongsToMany('Tags');
  4533. $actualSaveOptions = null;
  4534. $actualDeleteOptions = null;
  4535. $tags->junction()->getEventManager()->on(
  4536. 'Model.beforeSave',
  4537. function (EventInterface $event, EntityInterface $entity, ArrayObject $options) use (&$actualSaveOptions) {
  4538. $actualSaveOptions = $options->getArrayCopy();
  4539. }
  4540. );
  4541. $tags->junction()->getEventManager()->on(
  4542. 'Model.beforeDelete',
  4543. function (EventInterface $event, EntityInterface $entity, ArrayObject $options) use (&$actualDeleteOptions) {
  4544. $actualDeleteOptions = $options->getArrayCopy();
  4545. }
  4546. );
  4547. $article = $articles->get(1);
  4548. $result = $tags->replaceLinks(
  4549. $article,
  4550. [
  4551. $tags->getTarget()->newEntity(['name' => 'new']),
  4552. $tags->getTarget()->get(2),
  4553. ],
  4554. ['foo' => 'bar']
  4555. );
  4556. $this->assertTrue($result);
  4557. $expected = [
  4558. '_primary' => true,
  4559. 'foo' => 'bar',
  4560. 'atomic' => true,
  4561. 'checkRules' => true,
  4562. 'checkExisting' => true,
  4563. 'associated' => [],
  4564. ];
  4565. $this->assertEquals($expected, $actualSaveOptions);
  4566. $expected = [
  4567. '_primary' => true,
  4568. 'foo' => 'bar',
  4569. 'atomic' => true,
  4570. 'checkRules' => true,
  4571. ];
  4572. $this->assertEquals($expected, $actualDeleteOptions);
  4573. }
  4574. /**
  4575. * Tests that options are being passed through to the internal table method calls.
  4576. *
  4577. * @return void
  4578. */
  4579. public function testOptionsBeingPassedToImplicitHasManyDeletesUsingSaveReplace()
  4580. {
  4581. $authors = $this->getTableLocator()->get('Authors');
  4582. $articles = $authors->hasMany('Articles');
  4583. $articles->setSaveStrategy(HasMany::SAVE_REPLACE)
  4584. ->setDependent(true)
  4585. ->setCascadeCallbacks(true);
  4586. $actualOptions = null;
  4587. $articles->getTarget()->getEventManager()->on(
  4588. 'Model.beforeDelete',
  4589. function (EventInterface $event, EntityInterface $entity, ArrayObject $options) use (&$actualOptions) {
  4590. $actualOptions = $options->getArrayCopy();
  4591. }
  4592. );
  4593. $author = $authors->get(1);
  4594. $author->articles = [];
  4595. $author->setDirty('articles', true);
  4596. $result = $authors->save($author, ['foo' => 'bar']);
  4597. $this->assertNotEmpty($result);
  4598. $expected = [
  4599. '_primary' => false,
  4600. 'foo' => 'bar',
  4601. 'atomic' => true,
  4602. 'checkRules' => true,
  4603. 'checkExisting' => true,
  4604. '_sourceTable' => $authors,
  4605. ];
  4606. $this->assertEquals($expected, $actualOptions);
  4607. }
  4608. /**
  4609. * Tests that options are being passed through to the internal table method calls.
  4610. *
  4611. * @return void
  4612. */
  4613. public function testOptionsBeingPassedToInternalSaveCallsUsingHasManyLink()
  4614. {
  4615. $authors = $this->getTableLocator()->get('Authors');
  4616. $articles = $authors->hasMany('Articles');
  4617. $actualOptions = null;
  4618. $articles->getTarget()->getEventManager()->on(
  4619. 'Model.beforeSave',
  4620. function (EventInterface $event, EntityInterface $entity, ArrayObject $options) use (&$actualOptions) {
  4621. $actualOptions = $options->getArrayCopy();
  4622. }
  4623. );
  4624. $author = $authors->get(1);
  4625. $author->articles = [];
  4626. $author->setDirty('articles', true);
  4627. $result = $articles->link($author, [$articles->getTarget()->get(2)], ['foo' => 'bar']);
  4628. $this->assertTrue($result);
  4629. $expected = [
  4630. '_primary' => true,
  4631. 'foo' => 'bar',
  4632. 'atomic' => true,
  4633. 'checkRules' => true,
  4634. 'checkExisting' => true,
  4635. '_sourceTable' => $authors,
  4636. 'associated' => [
  4637. 'authors' => [],
  4638. 'tags' => [],
  4639. 'articlestags' => [],
  4640. ],
  4641. ];
  4642. $this->assertEquals($expected, $actualOptions);
  4643. }
  4644. /**
  4645. * Tests that options are being passed through to the internal table method calls.
  4646. *
  4647. * @return void
  4648. */
  4649. public function testOptionsBeingPassedToInternalSaveCallsUsingHasManyUnlink()
  4650. {
  4651. $authors = $this->getTableLocator()->get('Authors');
  4652. $articles = $authors->hasMany('Articles');
  4653. $articles->setDependent(true);
  4654. $articles->setCascadeCallbacks(true);
  4655. $actualOptions = null;
  4656. $articles->getTarget()->getEventManager()->on(
  4657. 'Model.beforeDelete',
  4658. function (EventInterface $event, EntityInterface $entity, ArrayObject $options) use (&$actualOptions) {
  4659. $actualOptions = $options->getArrayCopy();
  4660. }
  4661. );
  4662. $author = $authors->get(1);
  4663. $author->articles = [];
  4664. $author->setDirty('articles', true);
  4665. $articles->unlink($author, [$articles->getTarget()->get(1)], ['foo' => 'bar']);
  4666. $expected = [
  4667. '_primary' => true,
  4668. 'foo' => 'bar',
  4669. 'atomic' => true,
  4670. 'checkRules' => true,
  4671. 'cleanProperty' => true,
  4672. ];
  4673. $this->assertEquals($expected, $actualOptions);
  4674. }
  4675. /**
  4676. * Tests that options are being passed through to the internal table method calls.
  4677. *
  4678. * @return void
  4679. */
  4680. public function testOptionsBeingPassedToInternalSaveAndDeleteCallsUsingHasManyReplace()
  4681. {
  4682. $authors = $this->getTableLocator()->get('Authors');
  4683. $articles = $authors->hasMany('Articles');
  4684. $articles->setDependent(true);
  4685. $articles->setCascadeCallbacks(true);
  4686. $actualSaveOptions = null;
  4687. $actualDeleteOptions = null;
  4688. $articles->getTarget()->getEventManager()->on(
  4689. 'Model.beforeSave',
  4690. function (EventInterface $event, EntityInterface $entity, ArrayObject $options) use (&$actualSaveOptions) {
  4691. $actualSaveOptions = $options->getArrayCopy();
  4692. }
  4693. );
  4694. $articles->getTarget()->getEventManager()->on(
  4695. 'Model.beforeDelete',
  4696. function (EventInterface $event, EntityInterface $entity, ArrayObject $options) use (&$actualDeleteOptions) {
  4697. $actualDeleteOptions = $options->getArrayCopy();
  4698. }
  4699. );
  4700. $author = $authors->get(1);
  4701. $result = $articles->replace(
  4702. $author,
  4703. [
  4704. $articles->getTarget()->newEntity(['title' => 'new', 'body' => 'new']),
  4705. $articles->getTarget()->get(1),
  4706. ],
  4707. ['foo' => 'bar']
  4708. );
  4709. $this->assertTrue($result);
  4710. $expected = [
  4711. '_primary' => true,
  4712. 'foo' => 'bar',
  4713. 'atomic' => true,
  4714. 'checkRules' => true,
  4715. 'checkExisting' => true,
  4716. '_sourceTable' => $authors,
  4717. 'associated' => [
  4718. 'authors' => [],
  4719. 'tags' => [],
  4720. 'articlestags' => [],
  4721. ],
  4722. ];
  4723. $this->assertEquals($expected, $actualSaveOptions);
  4724. $expected = [
  4725. '_primary' => true,
  4726. 'foo' => 'bar',
  4727. 'atomic' => true,
  4728. 'checkRules' => true,
  4729. '_sourceTable' => $authors,
  4730. ];
  4731. $this->assertEquals($expected, $actualDeleteOptions);
  4732. }
  4733. /**
  4734. * Tests backwards compatibility of the the `$options` argument, formerly `$cleanProperty`.
  4735. *
  4736. * @return void
  4737. */
  4738. public function testBackwardsCompatibilityForBelongsToManyUnlinkCleanPropertyOption()
  4739. {
  4740. $articles = $this->getTableLocator()->get('Articles');
  4741. $tags = $articles->belongsToMany('Tags');
  4742. $actualOptions = null;
  4743. $tags->junction()->getEventManager()->on(
  4744. 'Model.beforeDelete',
  4745. function (EventInterface $event, EntityInterface $entity, ArrayObject $options) use (&$actualOptions) {
  4746. $actualOptions = $options->getArrayCopy();
  4747. }
  4748. );
  4749. $article = $articles->get(1);
  4750. $tags->unlink($article, [$tags->getTarget()->get(1)], false);
  4751. $this->assertArrayHasKey('cleanProperty', $actualOptions);
  4752. $this->assertFalse($actualOptions['cleanProperty']);
  4753. $actualOptions = null;
  4754. $tags->unlink($article, [$tags->getTarget()->get(2)]);
  4755. $this->assertArrayHasKey('cleanProperty', $actualOptions);
  4756. $this->assertTrue($actualOptions['cleanProperty']);
  4757. }
  4758. /**
  4759. * Tests backwards compatibility of the the `$options` argument, formerly `$cleanProperty`.
  4760. *
  4761. * @return void
  4762. */
  4763. public function testBackwardsCompatibilityForHasManyUnlinkCleanPropertyOption()
  4764. {
  4765. $authors = $this->getTableLocator()->get('Authors');
  4766. $articles = $authors->hasMany('Articles');
  4767. $articles->setDependent(true);
  4768. $articles->setCascadeCallbacks(true);
  4769. $actualOptions = null;
  4770. $articles->getTarget()->getEventManager()->on(
  4771. 'Model.beforeDelete',
  4772. function (EventInterface $event, EntityInterface $entity, ArrayObject $options) use (&$actualOptions) {
  4773. $actualOptions = $options->getArrayCopy();
  4774. }
  4775. );
  4776. $author = $authors->get(1);
  4777. $author->articles = [];
  4778. $author->setDirty('articles', true);
  4779. $articles->unlink($author, [$articles->getTarget()->get(1)], false);
  4780. $this->assertArrayHasKey('cleanProperty', $actualOptions);
  4781. $this->assertFalse($actualOptions['cleanProperty']);
  4782. $actualOptions = null;
  4783. $articles->unlink($author, [$articles->getTarget()->get(3)]);
  4784. $this->assertArrayHasKey('cleanProperty', $actualOptions);
  4785. $this->assertTrue($actualOptions['cleanProperty']);
  4786. }
  4787. /**
  4788. * Tests that it is possible to call find with no arguments
  4789. *
  4790. * @return void
  4791. */
  4792. public function testSimplifiedFind()
  4793. {
  4794. $table = $this->getMockBuilder(Table::class)
  4795. ->setMethods(['callFinder'])
  4796. ->setConstructorArgs([[
  4797. 'connection' => $this->connection,
  4798. 'schema' => ['id' => ['type' => 'integer']],
  4799. ]])
  4800. ->getMock();
  4801. $query = (new Query($this->connection, $table))->select();
  4802. $table->expects($this->once())->method('callFinder')
  4803. ->with('all', $query, []);
  4804. $table->find();
  4805. }
  4806. public function providerForTestGet()
  4807. {
  4808. return [
  4809. [ ['fields' => ['id']] ],
  4810. [ ['fields' => ['id'], 'cache' => false] ],
  4811. ];
  4812. }
  4813. /**
  4814. * Test that get() will use the primary key for searching and return the first
  4815. * entity found
  4816. *
  4817. * @dataProvider providerForTestGet
  4818. * @param array $options
  4819. * @return void
  4820. */
  4821. public function testGet($options)
  4822. {
  4823. $table = $this->getMockBuilder(Table::class)
  4824. ->setMethods(['callFinder', 'query'])
  4825. ->setConstructorArgs([[
  4826. 'connection' => $this->connection,
  4827. 'schema' => [
  4828. 'id' => ['type' => 'integer'],
  4829. 'bar' => ['type' => 'integer'],
  4830. '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['bar']]],
  4831. ],
  4832. ]])
  4833. ->getMock();
  4834. $query = $this->getMockBuilder('Cake\ORM\Query')
  4835. ->setMethods(['addDefaultTypes', 'firstOrFail', 'where', 'cache'])
  4836. ->setConstructorArgs([$this->connection, $table])
  4837. ->getMock();
  4838. $entity = new Entity();
  4839. $table->expects($this->once())->method('query')
  4840. ->will($this->returnValue($query));
  4841. $table->expects($this->once())->method('callFinder')
  4842. ->with('all', $query, ['fields' => ['id']])
  4843. ->will($this->returnValue($query));
  4844. $query->expects($this->once())->method('where')
  4845. ->with([$table->getAlias() . '.bar' => 10])
  4846. ->will($this->returnSelf());
  4847. $query->expects($this->never())->method('cache');
  4848. $query->expects($this->once())->method('firstOrFail')
  4849. ->will($this->returnValue($entity));
  4850. $result = $table->get(10, $options);
  4851. $this->assertSame($entity, $result);
  4852. }
  4853. public function providerForTestGetWithCustomFinder()
  4854. {
  4855. return [
  4856. [ ['fields' => ['id'], 'finder' => 'custom'] ],
  4857. ];
  4858. }
  4859. /**
  4860. * Test that get() will call a custom finder.
  4861. *
  4862. * @dataProvider providerForTestGetWithCustomFinder
  4863. * @param array $options
  4864. * @return void
  4865. */
  4866. public function testGetWithCustomFinder($options)
  4867. {
  4868. $table = $this->getMockBuilder(Table::class)
  4869. ->setMethods(['callFinder', 'query'])
  4870. ->setConstructorArgs([[
  4871. 'connection' => $this->connection,
  4872. 'schema' => [
  4873. 'id' => ['type' => 'integer'],
  4874. 'bar' => ['type' => 'integer'],
  4875. '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['bar']]],
  4876. ],
  4877. ]])
  4878. ->getMock();
  4879. $query = $this->getMockBuilder('Cake\ORM\Query')
  4880. ->setMethods(['addDefaultTypes', 'firstOrFail', 'where', 'cache'])
  4881. ->setConstructorArgs([$this->connection, $table])
  4882. ->getMock();
  4883. $entity = new Entity();
  4884. $table->expects($this->once())->method('query')
  4885. ->will($this->returnValue($query));
  4886. $table->expects($this->once())->method('callFinder')
  4887. ->with('custom', $query, ['fields' => ['id']])
  4888. ->will($this->returnValue($query));
  4889. $query->expects($this->once())->method('where')
  4890. ->with([$table->getAlias() . '.bar' => 10])
  4891. ->will($this->returnSelf());
  4892. $query->expects($this->never())->method('cache');
  4893. $query->expects($this->once())->method('firstOrFail')
  4894. ->will($this->returnValue($entity));
  4895. $result = $table->get(10, $options);
  4896. $this->assertSame($entity, $result);
  4897. }
  4898. public function providerForTestGetWithCache()
  4899. {
  4900. return [
  4901. [
  4902. ['fields' => ['id'], 'cache' => 'default'],
  4903. 'get:test.table_name[10]', 'default',
  4904. ],
  4905. [
  4906. ['fields' => ['id'], 'cache' => 'default', 'key' => 'custom_key'],
  4907. 'custom_key', 'default',
  4908. ],
  4909. ];
  4910. }
  4911. /**
  4912. * Test that get() will use the cache.
  4913. *
  4914. * @dataProvider providerForTestGetWithCache
  4915. * @param array $options
  4916. * @param string $cacheKey
  4917. * @param string $cacheConfig
  4918. * @return void
  4919. */
  4920. public function testGetWithCache($options, $cacheKey, $cacheConfig)
  4921. {
  4922. $table = $this->getMockBuilder(Table::class)
  4923. ->setMethods(['callFinder', 'query'])
  4924. ->setConstructorArgs([[
  4925. 'connection' => $this->connection,
  4926. 'schema' => [
  4927. 'id' => ['type' => 'integer'],
  4928. 'bar' => ['type' => 'integer'],
  4929. '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['bar']]],
  4930. ],
  4931. ]])
  4932. ->getMock();
  4933. $table->setTable('table_name');
  4934. $query = $this->getMockBuilder('Cake\ORM\Query')
  4935. ->setMethods(['addDefaultTypes', 'firstOrFail', 'where', 'cache'])
  4936. ->setConstructorArgs([$this->connection, $table])
  4937. ->getMock();
  4938. $entity = new Entity();
  4939. $table->expects($this->once())->method('query')
  4940. ->will($this->returnValue($query));
  4941. $table->expects($this->once())->method('callFinder')
  4942. ->with('all', $query, ['fields' => ['id']])
  4943. ->will($this->returnValue($query));
  4944. $query->expects($this->once())->method('where')
  4945. ->with([$table->getAlias() . '.bar' => 10])
  4946. ->will($this->returnSelf());
  4947. $query->expects($this->once())->method('cache')
  4948. ->with($cacheKey, $cacheConfig)
  4949. ->will($this->returnSelf());
  4950. $query->expects($this->once())->method('firstOrFail')
  4951. ->will($this->returnValue($entity));
  4952. $result = $table->get(10, $options);
  4953. $this->assertSame($entity, $result);
  4954. }
  4955. /**
  4956. * Tests that get() will throw an exception if the record was not found
  4957. *
  4958. * @return void
  4959. */
  4960. public function testGetNotFoundException()
  4961. {
  4962. $this->expectException(\Cake\Datasource\Exception\RecordNotFoundException::class);
  4963. $this->expectExceptionMessage('Record not found in table "articles"');
  4964. $table = new Table([
  4965. 'name' => 'Articles',
  4966. 'connection' => $this->connection,
  4967. 'table' => 'articles',
  4968. ]);
  4969. $table->get(10);
  4970. }
  4971. /**
  4972. * Test that an exception is raised when there are not enough keys.
  4973. *
  4974. * @return void
  4975. */
  4976. public function testGetExceptionOnNoData()
  4977. {
  4978. $this->expectException(\Cake\Datasource\Exception\InvalidPrimaryKeyException::class);
  4979. $this->expectExceptionMessage('Record not found in table "articles" with primary key [NULL]');
  4980. $table = new Table([
  4981. 'name' => 'Articles',
  4982. 'connection' => $this->connection,
  4983. 'table' => 'articles',
  4984. ]);
  4985. $table->get(null);
  4986. }
  4987. /**
  4988. * Test that an exception is raised when there are too many keys.
  4989. *
  4990. * @return void
  4991. */
  4992. public function testGetExceptionOnTooMuchData()
  4993. {
  4994. $this->expectException(\Cake\Datasource\Exception\InvalidPrimaryKeyException::class);
  4995. $this->expectExceptionMessage('Record not found in table "articles" with primary key [1, \'two\']');
  4996. $table = new Table([
  4997. 'name' => 'Articles',
  4998. 'connection' => $this->connection,
  4999. 'table' => 'articles',
  5000. ]);
  5001. $table->get([1, 'two']);
  5002. }
  5003. /**
  5004. * Tests that patchEntity delegates the task to the marshaller and passed
  5005. * all associations
  5006. *
  5007. * @return void
  5008. */
  5009. public function testPatchEntityMarshallerUsage()
  5010. {
  5011. $table = $this->getMockBuilder(Table::class)
  5012. ->setMethods(['marshaller'])
  5013. ->getMock();
  5014. $marshaller = $this->getMockBuilder('Cake\ORM\Marshaller')
  5015. ->setConstructorArgs([$table])
  5016. ->getMock();
  5017. $table->belongsTo('users');
  5018. $table->hasMany('articles');
  5019. $table->expects($this->once())->method('marshaller')
  5020. ->will($this->returnValue($marshaller));
  5021. $entity = new Entity();
  5022. $data = ['foo' => 'bar'];
  5023. $marshaller->expects($this->once())
  5024. ->method('merge')
  5025. ->with($entity, $data, ['associated' => ['users', 'articles']])
  5026. ->will($this->returnValue($entity));
  5027. $table->patchEntity($entity, $data);
  5028. }
  5029. /**
  5030. * Tests patchEntity in a simple scenario. The tests for Marshaller cover
  5031. * patch scenarios in more depth.
  5032. *
  5033. * @return void
  5034. */
  5035. public function testPatchEntity()
  5036. {
  5037. $table = $this->getTableLocator()->get('Articles');
  5038. $entity = new Entity(['title' => 'old title'], ['markNew' => false]);
  5039. $data = ['title' => 'new title'];
  5040. $entity = $table->patchEntity($entity, $data);
  5041. $this->assertSame($data['title'], $entity->title);
  5042. $this->assertFalse($entity->isNew(), 'entity should not be new.');
  5043. }
  5044. /**
  5045. * Tests that patchEntities delegates the task to the marshaller and passed
  5046. * all associations
  5047. *
  5048. * @return void
  5049. */
  5050. public function testPatchEntitiesMarshallerUsage()
  5051. {
  5052. $table = $this->getMockBuilder(Table::class)
  5053. ->setMethods(['marshaller'])
  5054. ->getMock();
  5055. $marshaller = $this->getMockBuilder('Cake\ORM\Marshaller')
  5056. ->setConstructorArgs([$table])
  5057. ->getMock();
  5058. $table->belongsTo('users');
  5059. $table->hasMany('articles');
  5060. $table->expects($this->once())->method('marshaller')
  5061. ->will($this->returnValue($marshaller));
  5062. $entities = [new Entity()];
  5063. $data = [['foo' => 'bar']];
  5064. $marshaller->expects($this->once())
  5065. ->method('mergeMany')
  5066. ->with($entities, $data, ['associated' => ['users', 'articles']])
  5067. ->will($this->returnValue($entities));
  5068. $table->patchEntities($entities, $data);
  5069. }
  5070. /**
  5071. * Tests patchEntities in a simple scenario. The tests for Marshaller cover
  5072. * patch scenarios in more depth.
  5073. *
  5074. * @return void
  5075. */
  5076. public function testPatchEntities()
  5077. {
  5078. $table = $this->getTableLocator()->get('Articles');
  5079. $entities = $table->find()->limit(2)->toArray();
  5080. $data = [
  5081. ['id' => $entities[0]->id, 'title' => 'new title'],
  5082. ['id' => $entities[1]->id, 'title' => 'new title2'],
  5083. ];
  5084. $entities = $table->patchEntities($entities, $data);
  5085. foreach ($entities as $i => $entity) {
  5086. $this->assertFalse($entity->isNew(), 'entities should not be new.');
  5087. $this->assertSame($data[$i]['title'], $entity->title);
  5088. }
  5089. }
  5090. /**
  5091. * Tests __debugInfo
  5092. *
  5093. * @return void
  5094. */
  5095. public function testDebugInfo()
  5096. {
  5097. $articles = $this->getTableLocator()->get('articles');
  5098. $articles->addBehavior('Timestamp');
  5099. $result = $articles->__debugInfo();
  5100. $expected = [
  5101. 'registryAlias' => 'articles',
  5102. 'table' => 'articles',
  5103. 'alias' => 'articles',
  5104. 'entityClass' => 'TestApp\Model\Entity\Article',
  5105. 'associations' => ['authors', 'tags', 'articlestags'],
  5106. 'behaviors' => ['Timestamp'],
  5107. 'defaultConnection' => 'default',
  5108. 'connectionName' => 'test',
  5109. ];
  5110. $this->assertEquals($expected, $result);
  5111. $articles = $this->getTableLocator()->get('Foo.Articles');
  5112. $result = $articles->__debugInfo();
  5113. $expected = [
  5114. 'registryAlias' => 'Foo.Articles',
  5115. 'table' => 'articles',
  5116. 'alias' => 'Articles',
  5117. 'entityClass' => 'Cake\ORM\Entity',
  5118. 'associations' => [],
  5119. 'behaviors' => [],
  5120. 'defaultConnection' => 'default',
  5121. 'connectionName' => 'test',
  5122. ];
  5123. $this->assertEquals($expected, $result);
  5124. }
  5125. /**
  5126. * Test that findOrCreate creates a new entity, and then finds that entity.
  5127. *
  5128. * @return void
  5129. */
  5130. public function testFindOrCreateNewEntity()
  5131. {
  5132. $articles = $this->getTableLocator()->get('Articles');
  5133. $callbackExecuted = false;
  5134. $firstArticle = $articles->findOrCreate(['title' => 'Not there'], function ($article) use (&$callbackExecuted) {
  5135. $this->assertInstanceOf(EntityInterface::class, $article);
  5136. $article->body = 'New body';
  5137. $callbackExecuted = true;
  5138. });
  5139. $this->assertTrue($callbackExecuted);
  5140. $this->assertFalse($firstArticle->isNew());
  5141. $this->assertNotNull($firstArticle->id);
  5142. $this->assertSame('Not there', $firstArticle->title);
  5143. $this->assertSame('New body', $firstArticle->body);
  5144. $secondArticle = $articles->findOrCreate(['title' => 'Not there'], function ($article) {
  5145. $this->fail('Should not be called for existing entities.');
  5146. });
  5147. $this->assertFalse($secondArticle->isNew());
  5148. $this->assertNotNull($secondArticle->id);
  5149. $this->assertSame('Not there', $secondArticle->title);
  5150. $this->assertEquals($firstArticle->id, $secondArticle->id);
  5151. }
  5152. /**
  5153. * Test that findOrCreate finds fixture data.
  5154. *
  5155. * @return void
  5156. */
  5157. public function testFindOrCreateExistingEntity()
  5158. {
  5159. $articles = $this->getTableLocator()->get('Articles');
  5160. $article = $articles->findOrCreate(['title' => 'First Article'], function ($article) {
  5161. $this->fail('Should not be called for existing entities.');
  5162. });
  5163. $this->assertFalse($article->isNew());
  5164. $this->assertNotNull($article->id);
  5165. $this->assertSame('First Article', $article->title);
  5166. }
  5167. /**
  5168. * Test that findOrCreate uses the search conditions as defaults for new entity.
  5169. *
  5170. * @return void
  5171. */
  5172. public function testFindOrCreateDefaults()
  5173. {
  5174. $articles = $this->getTableLocator()->get('Articles');
  5175. $callbackExecuted = false;
  5176. $article = $articles->findOrCreate(
  5177. ['author_id' => 2, 'title' => 'First Article'],
  5178. function ($article) use (&$callbackExecuted) {
  5179. $this->assertInstanceOf('Cake\Datasource\EntityInterface', $article);
  5180. $article->set(['published' => 'N', 'body' => 'New body']);
  5181. $callbackExecuted = true;
  5182. }
  5183. );
  5184. $this->assertTrue($callbackExecuted);
  5185. $this->assertFalse($article->isNew());
  5186. $this->assertNotNull($article->id);
  5187. $this->assertSame('First Article', $article->title);
  5188. $this->assertSame('New body', $article->body);
  5189. $this->assertSame('N', $article->published);
  5190. $this->assertEquals(2, $article->author_id);
  5191. $query = $articles->find()->where(['author_id' => 2, 'title' => 'First Article']);
  5192. $article = $articles->findOrCreate($query);
  5193. $this->assertSame('First Article', $article->title);
  5194. $this->assertEquals(2, $article->author_id);
  5195. $this->assertFalse($article->isNew());
  5196. }
  5197. /**
  5198. * Test that findOrCreate adds new entity without using a callback.
  5199. *
  5200. * @return void
  5201. */
  5202. public function testFindOrCreateNoCallable()
  5203. {
  5204. $articles = $this->getTableLocator()->get('Articles');
  5205. $article = $articles->findOrCreate(['title' => 'Just Something New']);
  5206. $this->assertFalse($article->isNew());
  5207. $this->assertNotNull($article->id);
  5208. $this->assertSame('Just Something New', $article->title);
  5209. }
  5210. /**
  5211. * Test that findOrCreate executes search conditions as a callable.
  5212. *
  5213. * @return void
  5214. */
  5215. public function testFindOrCreateSearchCallable()
  5216. {
  5217. $articles = $this->getTableLocator()->get('Articles');
  5218. $calledOne = false;
  5219. $calledTwo = false;
  5220. $article = $articles->findOrCreate(function ($query) use (&$calledOne) {
  5221. $this->assertInstanceOf('Cake\ORM\Query', $query);
  5222. $query->where(['title' => 'Something Else']);
  5223. $calledOne = true;
  5224. }, function ($article) use (&$calledTwo) {
  5225. $this->assertInstanceOf('Cake\Datasource\EntityInterface', $article);
  5226. $article->title = 'Set Defaults Here';
  5227. $calledTwo = true;
  5228. });
  5229. $this->assertTrue($calledOne);
  5230. $this->assertTrue($calledTwo);
  5231. $this->assertFalse($article->isNew());
  5232. $this->assertNotNull($article->id);
  5233. $this->assertSame('Set Defaults Here', $article->title);
  5234. }
  5235. /**
  5236. * Test that findOrCreate options disable defaults.
  5237. *
  5238. * @return void
  5239. */
  5240. public function testFindOrCreateNoDefaults()
  5241. {
  5242. $articles = $this->getTableLocator()->get('Articles');
  5243. $article = $articles->findOrCreate(['title' => 'A New Article', 'published' => 'Y'], function ($article) {
  5244. $this->assertInstanceOf('Cake\Datasource\EntityInterface', $article);
  5245. $article->title = 'A Different Title';
  5246. }, ['defaults' => false]);
  5247. $this->assertFalse($article->isNew());
  5248. $this->assertNotNull($article->id);
  5249. $this->assertSame('A Different Title', $article->title);
  5250. $this->assertNull($article->published, 'Expected Null since defaults are disabled.');
  5251. }
  5252. /**
  5253. * Test that findOrCreate executes callable inside transaction.
  5254. *
  5255. * @return void
  5256. */
  5257. public function testFindOrCreateTransactions()
  5258. {
  5259. $articles = $this->getTableLocator()->get('Articles');
  5260. $articles->getEventManager()->on('Model.afterSaveCommit', function (EventInterface $event, EntityInterface $entity, ArrayObject $options) {
  5261. $entity->afterSaveCommit = true;
  5262. });
  5263. $article = $articles->findOrCreate(function ($query) {
  5264. $this->assertInstanceOf('Cake\ORM\Query', $query);
  5265. $query->where(['title' => 'Find Something New']);
  5266. $this->assertTrue($this->connection->inTransaction());
  5267. }, function ($article) {
  5268. $this->assertInstanceOf('Cake\Datasource\EntityInterface', $article);
  5269. $article->title = 'Success';
  5270. $this->assertTrue($this->connection->inTransaction());
  5271. });
  5272. $this->assertFalse($article->isNew());
  5273. $this->assertNotNull($article->id);
  5274. $this->assertSame('Success', $article->title);
  5275. $this->assertTrue($article->afterSaveCommit);
  5276. }
  5277. /**
  5278. * Test that findOrCreate executes callable without transaction.
  5279. *
  5280. * @return void
  5281. */
  5282. public function testFindOrCreateNoTransaction()
  5283. {
  5284. $articles = $this->getTableLocator()->get('Articles');
  5285. $article = $articles->findOrCreate(function (Query $query) {
  5286. $this->assertInstanceOf(Query::class, $query);
  5287. $query->where(['title' => 'Find Something New']);
  5288. $this->assertFalse($this->connection->inTransaction());
  5289. }, function ($article) {
  5290. $this->assertInstanceOf(EntityInterface::class, $article);
  5291. $this->assertFalse($this->connection->inTransaction());
  5292. $article->title = 'Success';
  5293. }, ['atomic' => false]);
  5294. $this->assertFalse($article->isNew());
  5295. $this->assertNotNull($article->id);
  5296. $this->assertSame('Success', $article->title);
  5297. }
  5298. /**
  5299. * Test that findOrCreate throws a PersistenceFailedException when it cannot save
  5300. * an entity created from $search
  5301. *
  5302. * @return void
  5303. */
  5304. public function testFindOrCreateWithInvalidEntity()
  5305. {
  5306. $this->expectException(PersistenceFailedException::class);
  5307. $this->expectExceptionMessage(
  5308. 'Entity findOrCreate failure. ' .
  5309. 'Found the following errors (title._empty: "This field cannot be left empty").'
  5310. );
  5311. $articles = $this->getTableLocator()->get('Articles');
  5312. $validator = new Validator();
  5313. $validator->notEmptyString('title');
  5314. $articles->setValidator('default', $validator);
  5315. $articles->findOrCreate(['title' => '']);
  5316. }
  5317. /**
  5318. * Test that findOrCreate allows patching of all $search keys
  5319. *
  5320. * @return void
  5321. */
  5322. public function testFindOrCreateAccessibleFields()
  5323. {
  5324. $articles = $this->getTableLocator()->get('Articles');
  5325. $articles->setEntityClass(ProtectedEntity::class);
  5326. $validator = new Validator();
  5327. $validator->notBlank('title');
  5328. $articles->setValidator('default', $validator);
  5329. $article = $articles->findOrCreate(['title' => 'test']);
  5330. $this->assertInstanceOf(ProtectedEntity::class, $article);
  5331. $this->assertSame('test', $article->title);
  5332. }
  5333. /**
  5334. * Test that findOrCreate cannot accidentally bypass required validation.
  5335. *
  5336. * @return void
  5337. */
  5338. public function testFindOrCreatePartialValidation()
  5339. {
  5340. $articles = $this->getTableLocator()->get('Articles');
  5341. $articles->setEntityClass(ProtectedEntity::class);
  5342. $validator = new Validator();
  5343. $validator->notBlank('title')->requirePresence('title', 'create');
  5344. $validator->notBlank('body')->requirePresence('body', 'create');
  5345. $articles->setValidator('default', $validator);
  5346. $this->expectException(PersistenceFailedException::class);
  5347. $this->expectExceptionMessage(
  5348. 'Entity findOrCreate failure. ' .
  5349. 'Found the following errors (title._required: "This field is required").'
  5350. );
  5351. $articles->findOrCreate(['body' => 'test']);
  5352. }
  5353. /**
  5354. * Test that creating a table fires the initialize event.
  5355. *
  5356. * @return void
  5357. */
  5358. public function testInitializeEvent()
  5359. {
  5360. $count = 0;
  5361. $cb = function (EventInterface $event) use (&$count) {
  5362. $count++;
  5363. };
  5364. EventManager::instance()->on('Model.initialize', $cb);
  5365. $articles = $this->getTableLocator()->get('Articles');
  5366. $this->assertEquals(1, $count, 'Callback should be called');
  5367. EventManager::instance()->off('Model.initialize', $cb);
  5368. }
  5369. /**
  5370. * Tests the hasFinder method
  5371. *
  5372. * @return void
  5373. */
  5374. public function testHasFinder()
  5375. {
  5376. $table = $this->getTableLocator()->get('articles');
  5377. $table->addBehavior('Sluggable');
  5378. $this->assertTrue($table->hasFinder('list'));
  5379. $this->assertTrue($table->hasFinder('noSlug'));
  5380. $this->assertFalse($table->hasFinder('noFind'));
  5381. }
  5382. /**
  5383. * Tests that calling validator() trigger the buildValidator event
  5384. *
  5385. * @return void
  5386. */
  5387. public function testBuildValidatorEvent()
  5388. {
  5389. $count = 0;
  5390. $cb = function (EventInterface $event) use (&$count) {
  5391. $count++;
  5392. };
  5393. EventManager::instance()->on('Model.buildValidator', $cb);
  5394. $articles = $this->getTableLocator()->get('Articles');
  5395. $articles->getValidator();
  5396. $this->assertEquals(1, $count, 'Callback should be called');
  5397. $articles->getValidator();
  5398. $this->assertEquals(1, $count, 'Callback should be called only once');
  5399. }
  5400. /**
  5401. * Tests the validateUnique method with different combinations
  5402. *
  5403. * @return void
  5404. */
  5405. public function testValidateUnique()
  5406. {
  5407. $table = $this->getTableLocator()->get('Users');
  5408. $validator = new Validator();
  5409. $validator->add('username', 'unique', ['rule' => 'validateUnique', 'provider' => 'table']);
  5410. $validator->setProvider('table', $table);
  5411. $data = ['username' => ['larry', 'notthere']];
  5412. $this->assertNotEmpty($validator->validate($data));
  5413. $data = ['username' => 'larry'];
  5414. $this->assertNotEmpty($validator->validate($data));
  5415. $data = ['username' => 'jose'];
  5416. $this->assertEmpty($validator->validate($data));
  5417. $data = ['username' => 'larry', 'id' => 3];
  5418. $this->assertEmpty($validator->validate($data, false));
  5419. $data = ['username' => 'larry', 'id' => 3];
  5420. $this->assertNotEmpty($validator->validate($data));
  5421. $data = ['username' => 'larry'];
  5422. $this->assertNotEmpty($validator->validate($data, false));
  5423. $validator->add('username', 'unique', [
  5424. 'rule' => 'validateUnique', 'provider' => 'table',
  5425. ]);
  5426. $data = ['username' => 'larry'];
  5427. $this->assertNotEmpty($validator->validate($data, false));
  5428. }
  5429. /**
  5430. * Tests the validateUnique method with scope
  5431. *
  5432. * @return void
  5433. */
  5434. public function testValidateUniqueScope()
  5435. {
  5436. $table = $this->getTableLocator()->get('Users');
  5437. $validator = new Validator();
  5438. $validator->add('username', 'unique', [
  5439. 'rule' => ['validateUnique', ['derp' => 'erp', 'scope' => 'id']],
  5440. 'provider' => 'table',
  5441. ]);
  5442. $validator->setProvider('table', $table);
  5443. $data = ['username' => 'larry', 'id' => 3];
  5444. $this->assertNotEmpty($validator->validate($data));
  5445. $data = ['username' => 'larry', 'id' => 1];
  5446. $this->assertEmpty($validator->validate($data));
  5447. $data = ['username' => 'jose'];
  5448. $this->assertEmpty($validator->validate($data));
  5449. }
  5450. /**
  5451. * Tests the validateUnique method with options
  5452. *
  5453. * @return void
  5454. */
  5455. public function testValidateUniqueMultipleNulls()
  5456. {
  5457. $entity = new Entity([
  5458. 'id' => 9,
  5459. 'site_id' => 1,
  5460. 'author_id' => null,
  5461. 'title' => 'Null title',
  5462. ]);
  5463. $table = $this->getTableLocator()->get('SiteArticles');
  5464. $table->save($entity);
  5465. $validator = new Validator();
  5466. $validator->add('site_id', 'unique', [
  5467. 'rule' => [
  5468. 'validateUnique',
  5469. [
  5470. 'allowMultipleNulls' => false,
  5471. 'scope' => ['author_id'],
  5472. ],
  5473. ],
  5474. 'provider' => 'table',
  5475. 'message' => 'Must be unique.',
  5476. ]);
  5477. $validator->setProvider('table', $table);
  5478. $data = ['site_id' => 1, 'author_id' => null, 'title' => 'Null dupe'];
  5479. $expected = ['site_id' => ['unique' => 'Must be unique.']];
  5480. $this->assertEquals($expected, $validator->validate($data));
  5481. }
  5482. /**
  5483. * Tests that the callbacks receive the expected types of arguments.
  5484. *
  5485. * @return void
  5486. */
  5487. public function testCallbackArgumentTypes()
  5488. {
  5489. $table = $this->getTableLocator()->get('articles');
  5490. $table->belongsTo('authors');
  5491. $eventManager = $table->getEventManager();
  5492. $associationBeforeFindCount = 0;
  5493. $table->getAssociation('authors')->getTarget()->getEventManager()->on(
  5494. 'Model.beforeFind',
  5495. function (EventInterface $event, Query $query, ArrayObject $options, $primary) use (&$associationBeforeFindCount) {
  5496. $this->assertIsBool($primary);
  5497. $associationBeforeFindCount++;
  5498. }
  5499. );
  5500. $beforeFindCount = 0;
  5501. $eventManager->on(
  5502. 'Model.beforeFind',
  5503. function (EventInterface $event, Query $query, ArrayObject $options, $primary) use (&$beforeFindCount) {
  5504. $this->assertIsBool($primary);
  5505. $beforeFindCount++;
  5506. }
  5507. );
  5508. $table->find()->contain('authors')->first();
  5509. $this->assertEquals(1, $associationBeforeFindCount);
  5510. $this->assertEquals(1, $beforeFindCount);
  5511. $buildValidatorCount = 0;
  5512. $eventManager->on(
  5513. 'Model.buildValidator',
  5514. $callback = function (EventInterface $event, Validator $validator, $name) use (&$buildValidatorCount) {
  5515. $this->assertIsString($name);
  5516. $buildValidatorCount++;
  5517. }
  5518. );
  5519. $table->getValidator();
  5520. $this->assertEquals(1, $buildValidatorCount);
  5521. $buildRulesCount =
  5522. $beforeRulesCount =
  5523. $afterRulesCount =
  5524. $beforeSaveCount =
  5525. $afterSaveCount = 0;
  5526. $eventManager->on(
  5527. 'Model.buildRules',
  5528. function (EventInterface $event, RulesChecker $rules) use (&$buildRulesCount) {
  5529. $buildRulesCount++;
  5530. }
  5531. );
  5532. $eventManager->on(
  5533. 'Model.beforeRules',
  5534. function (EventInterface $event, EntityInterface $entity, ArrayObject $options, $operation) use (&$beforeRulesCount) {
  5535. $this->assertIsString($operation);
  5536. $beforeRulesCount++;
  5537. }
  5538. );
  5539. $eventManager->on(
  5540. 'Model.afterRules',
  5541. function (EventInterface $event, EntityInterface $entity, ArrayObject $options, $result, $operation) use (&$afterRulesCount) {
  5542. $this->assertIsBool($result);
  5543. $this->assertIsString($operation);
  5544. $afterRulesCount++;
  5545. }
  5546. );
  5547. $eventManager->on(
  5548. 'Model.beforeSave',
  5549. function (EventInterface $event, EntityInterface $entity, ArrayObject $options) use (&$beforeSaveCount) {
  5550. $beforeSaveCount++;
  5551. }
  5552. );
  5553. $eventManager->on(
  5554. 'Model.afterSave',
  5555. $afterSaveCallback = function (EventInterface $event, EntityInterface $entity, ArrayObject $options) use (&$afterSaveCount) {
  5556. $afterSaveCount++;
  5557. }
  5558. );
  5559. $entity = new Entity(['title' => 'Title']);
  5560. $this->assertNotFalse($table->save($entity));
  5561. $this->assertEquals(1, $buildRulesCount);
  5562. $this->assertEquals(1, $beforeRulesCount);
  5563. $this->assertEquals(1, $afterRulesCount);
  5564. $this->assertEquals(1, $beforeSaveCount);
  5565. $this->assertEquals(1, $afterSaveCount);
  5566. $beforeDeleteCount =
  5567. $afterDeleteCount = 0;
  5568. $eventManager->on(
  5569. 'Model.beforeDelete',
  5570. function (EventInterface $event, EntityInterface $entity, ArrayObject $options) use (&$beforeDeleteCount) {
  5571. $beforeDeleteCount++;
  5572. }
  5573. );
  5574. $eventManager->on(
  5575. 'Model.afterDelete',
  5576. function (EventInterface $event, EntityInterface $entity, ArrayObject $options) use (&$afterDeleteCount) {
  5577. $afterDeleteCount++;
  5578. }
  5579. );
  5580. $this->assertTrue($table->delete($entity, ['checkRules' => false]));
  5581. $this->assertEquals(1, $beforeDeleteCount);
  5582. $this->assertEquals(1, $afterDeleteCount);
  5583. }
  5584. /**
  5585. * Tests that calling newEmptyEntity() on a table sets the right source alias.
  5586. *
  5587. * @return void
  5588. */
  5589. public function testSetEntitySource()
  5590. {
  5591. $table = $this->getTableLocator()->get('Articles');
  5592. $this->assertSame('Articles', $table->newEmptyEntity()->getSource());
  5593. $this->loadPlugins(['TestPlugin']);
  5594. $table = $this->getTableLocator()->get('TestPlugin.Comments');
  5595. $this->assertSame('TestPlugin.Comments', $table->newEmptyEntity()->getSource());
  5596. }
  5597. /**
  5598. * Tests that passing a coned entity that was marked as new to save() will
  5599. * actually save it as a new entity
  5600. *
  5601. * @group save
  5602. * @return void
  5603. */
  5604. public function testSaveWithClonedEntity()
  5605. {
  5606. $table = $this->getTableLocator()->get('Articles');
  5607. $article = $table->get(1);
  5608. $cloned = clone $article;
  5609. $cloned->unset('id');
  5610. $cloned->setNew(true);
  5611. $this->assertSame($cloned, $table->save($cloned));
  5612. $this->assertEquals(
  5613. $article->extract(['title', 'author_id']),
  5614. $cloned->extract(['title', 'author_id'])
  5615. );
  5616. $this->assertEquals(4, $cloned->id);
  5617. }
  5618. /**
  5619. * Tests that the _ids notation can be used for HasMany
  5620. *
  5621. * @return void
  5622. */
  5623. public function testSaveHasManyWithIds()
  5624. {
  5625. $data = [
  5626. 'username' => 'lux',
  5627. 'password' => 'passphrase',
  5628. 'comments' => [
  5629. '_ids' => [1, 2],
  5630. ],
  5631. ];
  5632. $userTable = $this->getTableLocator()->get('Users');
  5633. $userTable->hasMany('Comments');
  5634. $savedUser = $userTable->save($userTable->newEntity($data, ['associated' => ['Comments']]));
  5635. $retrievedUser = $userTable->find('all')->where(['id' => $savedUser->id])->contain(['Comments'])->first();
  5636. $this->assertEquals($savedUser->comments[0]->user_id, $retrievedUser->comments[0]->user_id);
  5637. $this->assertEquals($savedUser->comments[1]->user_id, $retrievedUser->comments[1]->user_id);
  5638. }
  5639. /**
  5640. * Tests that on second save, entities for the has many relation are not marked
  5641. * as dirty unnecessarily. This helps avoid wasteful database statements and makes
  5642. * for a cleaner transaction log
  5643. *
  5644. * @return void
  5645. */
  5646. public function testSaveHasManyNoWasteSave()
  5647. {
  5648. $data = [
  5649. 'username' => 'lux',
  5650. 'password' => 'passphrase',
  5651. 'comments' => [
  5652. '_ids' => [1, 2],
  5653. ],
  5654. ];
  5655. $userTable = $this->getTableLocator()->get('Users');
  5656. $userTable->hasMany('Comments');
  5657. $savedUser = $userTable->save($userTable->newEntity($data, ['associated' => ['Comments']]));
  5658. $counter = 0;
  5659. $userTable->Comments
  5660. ->getEventManager()
  5661. ->on('Model.afterSave', function (EventInterface $event, $entity) use (&$counter) {
  5662. if ($entity->isDirty()) {
  5663. $counter++;
  5664. }
  5665. });
  5666. $savedUser->comments[] = $userTable->Comments->get(5);
  5667. $this->assertCount(3, $savedUser->comments);
  5668. $savedUser->setDirty('comments', true);
  5669. $userTable->save($savedUser);
  5670. $this->assertEquals(1, $counter);
  5671. }
  5672. /**
  5673. * Tests that on second save, entities for the belongsToMany relation are not marked
  5674. * as dirty unnecessarily. This helps avoid wasteful database statements and makes
  5675. * for a cleaner transaction log
  5676. *
  5677. * @return void
  5678. */
  5679. public function testSaveBelongsToManyNoWasteSave()
  5680. {
  5681. $data = [
  5682. 'title' => 'foo',
  5683. 'body' => 'bar',
  5684. 'tags' => [
  5685. '_ids' => [1, 2],
  5686. ],
  5687. ];
  5688. $table = $this->getTableLocator()->get('Articles');
  5689. $table->belongsToMany('Tags');
  5690. $article = $table->save($table->newEntity($data, ['associated' => ['Tags']]));
  5691. $counter = 0;
  5692. $table->Tags->junction()
  5693. ->getEventManager()
  5694. ->on('Model.afterSave', function (EventInterface $event, $entity) use (&$counter) {
  5695. if ($entity->isDirty()) {
  5696. $counter++;
  5697. }
  5698. });
  5699. $article->tags[] = $table->Tags->get(3);
  5700. $this->assertCount(3, $article->tags);
  5701. $article->setDirty('tags', true);
  5702. $table->save($article);
  5703. $this->assertEquals(1, $counter);
  5704. }
  5705. /**
  5706. * Tests that after saving then entity contains the right primary
  5707. * key casted to the right type
  5708. *
  5709. * @group save
  5710. * @return void
  5711. */
  5712. public function testSaveCorrectPrimaryKeyType()
  5713. {
  5714. $entity = new Entity([
  5715. 'username' => 'superuser',
  5716. 'created' => new Time('2013-10-10 00:00'),
  5717. 'updated' => new Time('2013-10-10 00:00'),
  5718. ], ['markNew' => true]);
  5719. $table = $this->getTableLocator()->get('Users');
  5720. $this->assertSame($entity, $table->save($entity));
  5721. $this->assertSame(self::$nextUserId, $entity->id);
  5722. }
  5723. /**
  5724. * Tests entity clean()
  5725. *
  5726. * @return void
  5727. */
  5728. public function testEntityClean()
  5729. {
  5730. $table = $this->getTableLocator()->get('Articles');
  5731. $validator = $table->getValidator()->requirePresence('body');
  5732. $entity = $table->newEntity(['title' => 'mark']);
  5733. $entity->setDirty('title', true);
  5734. $entity->setInvalidField('title', 'albert');
  5735. $this->assertNotEmpty($entity->getErrors());
  5736. $this->assertTrue($entity->isDirty());
  5737. $this->assertEquals(['title' => 'albert'], $entity->getInvalid());
  5738. $entity->title = 'alex';
  5739. $this->assertSame($entity->getOriginal('title'), 'mark');
  5740. $entity->clean();
  5741. $this->assertEmpty($entity->getErrors());
  5742. $this->assertFalse($entity->isDirty());
  5743. $this->assertEquals([], $entity->getInvalid());
  5744. $this->assertSame($entity->getOriginal('title'), 'alex');
  5745. }
  5746. /**
  5747. * Tests the loadInto() method
  5748. *
  5749. * @return void
  5750. */
  5751. public function testLoadIntoEntity()
  5752. {
  5753. $table = $this->getTableLocator()->get('Authors');
  5754. $table->hasMany('SiteArticles');
  5755. $articles = $table->hasMany('Articles');
  5756. $articles->belongsToMany('Tags');
  5757. $entity = $table->get(1);
  5758. $result = $table->loadInto($entity, ['SiteArticles', 'Articles.Tags']);
  5759. $this->assertSame($entity, $result);
  5760. $expected = $table->get(1, ['contain' => ['SiteArticles', 'Articles.Tags']]);
  5761. $this->assertEquals($expected, $result);
  5762. }
  5763. /**
  5764. * Tests that it is possible to pass conditions and fields to loadInto()
  5765. *
  5766. * @return void
  5767. */
  5768. public function testLoadIntoWithConditions()
  5769. {
  5770. $table = $this->getTableLocator()->get('Authors');
  5771. $table->hasMany('SiteArticles');
  5772. $articles = $table->hasMany('Articles');
  5773. $articles->belongsToMany('Tags');
  5774. $entity = $table->get(1);
  5775. $options = [
  5776. 'SiteArticles' => ['fields' => ['title', 'author_id']],
  5777. 'Articles.Tags' => function ($q) {
  5778. return $q->where(['Tags.name' => 'tag2']);
  5779. },
  5780. ];
  5781. $result = $table->loadInto($entity, $options);
  5782. $this->assertSame($entity, $result);
  5783. $expected = $table->get(1, ['contain' => $options]);
  5784. $this->assertEquals($expected, $result);
  5785. }
  5786. /**
  5787. * Tests loadInto() with a belongsTo association
  5788. *
  5789. * @return void
  5790. */
  5791. public function testLoadBelongsTo()
  5792. {
  5793. $table = $this->getTableLocator()->get('Articles');
  5794. $table->belongsTo('Authors');
  5795. $entity = $table->get(2);
  5796. $result = $table->loadInto($entity, ['Authors']);
  5797. $this->assertSame($entity, $result);
  5798. $expected = $table->get(2, ['contain' => ['Authors']]);
  5799. $this->assertEquals($expected, $entity);
  5800. }
  5801. /**
  5802. * Tests that it is possible to post-load associations for many entities at
  5803. * the same time
  5804. *
  5805. * @return void
  5806. */
  5807. public function testLoadIntoMany()
  5808. {
  5809. $table = $this->getTableLocator()->get('Authors');
  5810. $table->hasMany('SiteArticles');
  5811. $articles = $table->hasMany('Articles');
  5812. $articles->belongsToMany('Tags');
  5813. $entities = $table->find()->compile();
  5814. $contain = ['SiteArticles', 'Articles.Tags'];
  5815. $result = $table->loadInto($entities, $contain);
  5816. foreach ($entities as $k => $v) {
  5817. $this->assertSame($v, $result[$k]);
  5818. }
  5819. $expected = $table->find()->contain($contain)->toList();
  5820. $this->assertEquals($expected, $result);
  5821. }
  5822. /**
  5823. * Tests that saveOrFail triggers an exception on not successful save
  5824. *
  5825. * @return void
  5826. */
  5827. public function testSaveOrFail()
  5828. {
  5829. $this->expectException(\Cake\ORM\Exception\PersistenceFailedException::class);
  5830. $this->expectExceptionMessage('Entity save failure.');
  5831. $entity = new Entity([
  5832. 'foo' => 'bar',
  5833. ]);
  5834. $table = $this->getTableLocator()->get('users');
  5835. $table->saveOrFail($entity);
  5836. }
  5837. /**
  5838. * Tests that saveOrFail displays useful messages on output, especially in tests for CLI.
  5839. *
  5840. * @return void
  5841. */
  5842. public function testSaveOrFailErrorDisplay()
  5843. {
  5844. $this->expectException(\Cake\ORM\Exception\PersistenceFailedException::class);
  5845. $this->expectExceptionMessage('Entity save failure. Found the following errors (field.0: "Some message", multiple.one: "One", multiple.two: "Two")');
  5846. $entity = new Entity([
  5847. 'foo' => 'bar',
  5848. ]);
  5849. $entity->setError('field', 'Some message');
  5850. $entity->setError('multiple', ['one' => 'One', 'two' => 'Two']);
  5851. $table = $this->getTableLocator()->get('users');
  5852. $table->saveOrFail($entity);
  5853. }
  5854. /**
  5855. * Tests that saveOrFail with nested errors
  5856. *
  5857. * @return void
  5858. */
  5859. public function testSaveOrFailNestedError()
  5860. {
  5861. $this->expectException(\Cake\ORM\Exception\PersistenceFailedException::class);
  5862. $this->expectExceptionMessage('Entity save failure. Found the following errors (articles.0.title.0: "Bad value")');
  5863. $entity = new Entity([
  5864. 'username' => 'bad',
  5865. 'articles' => [
  5866. new Entity(['title' => 'not an entity']),
  5867. ],
  5868. ]);
  5869. $entity->articles[0]->setError('title', 'Bad value');
  5870. $table = $this->getTableLocator()->get('Users');
  5871. $table->hasMany('Articles');
  5872. $table->saveOrFail($entity);
  5873. }
  5874. /**
  5875. * Tests that saveOrFail returns the right entity
  5876. *
  5877. * @return void
  5878. */
  5879. public function testSaveOrFailGetEntity()
  5880. {
  5881. $entity = new Entity([
  5882. 'foo' => 'bar',
  5883. ]);
  5884. $table = $this->getTableLocator()->get('users');
  5885. try {
  5886. $table->saveOrFail($entity);
  5887. } catch (\Cake\ORM\Exception\PersistenceFailedException $e) {
  5888. $this->assertSame($entity, $e->getEntity());
  5889. }
  5890. }
  5891. /**
  5892. * Tests that deleteOrFail triggers an exception on not successful delete
  5893. *
  5894. * @return void
  5895. */
  5896. public function testDeleteOrFail()
  5897. {
  5898. $this->expectException(\Cake\ORM\Exception\PersistenceFailedException::class);
  5899. $this->expectExceptionMessage('Entity delete failure.');
  5900. $entity = new Entity([
  5901. 'id' => 999,
  5902. ]);
  5903. $table = $this->getTableLocator()->get('users');
  5904. $result = $table->deleteOrFail($entity);
  5905. }
  5906. /**
  5907. * Tests that deleteOrFail returns the right entity
  5908. *
  5909. * @return void
  5910. */
  5911. public function testDeleteOrFailGetEntity()
  5912. {
  5913. $entity = new Entity([
  5914. 'id' => 999,
  5915. ]);
  5916. $table = $this->getTableLocator()->get('users');
  5917. try {
  5918. $table->deleteOrFail($entity);
  5919. } catch (\Cake\ORM\Exception\PersistenceFailedException $e) {
  5920. $this->assertSame($entity, $e->getEntity());
  5921. }
  5922. }
  5923. /**
  5924. * Test getting the save options builder.
  5925. *
  5926. * @return void
  5927. */
  5928. public function getSaveOptionsBuilder()
  5929. {
  5930. $table = $this->getTableLocator()->get('Authors');
  5931. $result = $table->getSaveOptionsBuilder();
  5932. $this->assertInstanceOf('Cake\ORM\SaveOptionsBuilder', $result);
  5933. }
  5934. /**
  5935. * Helper method to skip tests when connection is SQLServer.
  5936. *
  5937. * @return void
  5938. */
  5939. public function skipIfSqlServer()
  5940. {
  5941. $this->skipIf(
  5942. $this->connection->getDriver() instanceof \Cake\Database\Driver\Sqlserver,
  5943. 'SQLServer does not support the requirements of this test.'
  5944. );
  5945. }
  5946. }