TableTest.php 224 KB

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