TableTest.php 209 KB

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