api de gestion de ticket, basé sur php-crud-api. Le but est de décorrélé les outils de gestion des données, afin
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

api.php 421KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066906790689069907090719072907390749075907690779078907990809081908290839084908590869087908890899090909190929093909490959096909790989099910091019102910391049105910691079108910991109111911291139114911591169117911891199120912191229123912491259126912791289129913091319132913391349135913691379138913991409141914291439144914591469147914891499150915191529153915491559156915791589159916091619162916391649165916691679168916991709171917291739174917591769177917891799180918191829183918491859186918791889189919091919192919391949195919691979198919992009201920292039204920592069207920892099210921192129213921492159216921792189219922092219222922392249225922692279228922992309231923292339234923592369237923892399240924192429243924492459246924792489249925092519252925392549255925692579258925992609261926292639264926592669267926892699270927192729273927492759276927792789279928092819282928392849285928692879288928992909291929292939294929592969297929892999300930193029303930493059306930793089309931093119312931393149315931693179318931993209321932293239324932593269327932893299330933193329333933493359336933793389339934093419342934393449345934693479348934993509351935293539354935593569357935893599360936193629363936493659366936793689369937093719372937393749375937693779378937993809381938293839384938593869387938893899390939193929393939493959396939793989399940094019402940394049405940694079408940994109411941294139414941594169417941894199420942194229423942494259426942794289429943094319432943394349435943694379438943994409441944294439444944594469447944894499450945194529453945494559456945794589459946094619462946394649465946694679468946994709471947294739474947594769477947894799480948194829483948494859486948794889489949094919492949394949495949694979498949995009501950295039504950595069507950895099510951195129513951495159516951795189519952095219522952395249525952695279528952995309531953295339534953595369537953895399540954195429543954495459546954795489549955095519552955395549555955695579558955995609561956295639564956595669567956895699570957195729573957495759576957795789579958095819582958395849585958695879588958995909591959295939594959595969597959895999600960196029603960496059606960796089609961096119612961396149615961696179618961996209621962296239624962596269627962896299630963196329633963496359636963796389639964096419642964396449645964696479648964996509651965296539654965596569657965896599660966196629663966496659666966796689669967096719672967396749675967696779678967996809681968296839684968596869687968896899690969196929693969496959696969796989699970097019702970397049705970697079708970997109711971297139714971597169717971897199720972197229723972497259726972797289729973097319732973397349735973697379738973997409741974297439744974597469747974897499750975197529753975497559756975797589759976097619762976397649765976697679768976997709771977297739774977597769777977897799780978197829783978497859786978797889789979097919792979397949795979697979798979998009801980298039804980598069807980898099810981198129813981498159816981798189819982098219822982398249825982698279828982998309831983298339834983598369837983898399840984198429843984498459846984798489849985098519852985398549855985698579858985998609861986298639864986598669867986898699870987198729873987498759876987798789879988098819882988398849885988698879888988998909891989298939894989598969897989898999900990199029903990499059906990799089909991099119912991399149915991699179918991999209921992299239924992599269927992899299930993199329933993499359936993799389939994099419942994399449945994699479948994999509951995299539954995599569957995899599960996199629963996499659966996799689969997099719972997399749975997699779978997999809981998299839984998599869987998899899990999199929993999499959996999799989999100001000110002100031000410005100061000710008100091001010011100121001310014100151001610017100181001910020100211002210023100241002510026100271002810029100301003110032100331003410035100361003710038100391004010041100421004310044100451004610047100481004910050100511005210053100541005510056100571005810059100601006110062100631006410065100661006710068100691007010071100721007310074100751007610077100781007910080100811008210083100841008510086100871008810089100901009110092100931009410095100961009710098100991010010101101021010310104101051010610107101081010910110101111011210113101141011510116101171011810119101201012110122101231012410125101261012710128101291013010131101321013310134101351013610137101381013910140101411014210143101441014510146101471014810149101501015110152101531015410155101561015710158101591016010161101621016310164101651016610167101681016910170101711017210173101741017510176101771017810179101801018110182101831018410185101861018710188101891019010191101921019310194101951019610197101981019910200102011020210203102041020510206102071020810209102101021110212102131021410215102161021710218102191022010221102221022310224102251022610227102281022910230102311023210233102341023510236102371023810239102401024110242102431024410245102461024710248102491025010251102521025310254102551025610257102581025910260102611026210263102641026510266102671026810269102701027110272102731027410275102761027710278102791028010281102821028310284102851028610287102881028910290102911029210293102941029510296102971029810299103001030110302103031030410305103061030710308103091031010311103121031310314103151031610317103181031910320103211032210323103241032510326103271032810329103301033110332103331033410335103361033710338103391034010341103421034310344103451034610347103481034910350103511035210353103541035510356103571035810359103601036110362103631036410365103661036710368103691037010371103721037310374103751037610377103781037910380103811038210383103841038510386103871038810389103901039110392103931039410395103961039710398103991040010401104021040310404104051040610407104081040910410104111041210413104141041510416104171041810419104201042110422104231042410425104261042710428104291043010431104321043310434104351043610437104381043910440104411044210443104441044510446104471044810449104501045110452104531045410455104561045710458104591046010461104621046310464104651046610467104681046910470104711047210473104741047510476104771047810479104801048110482104831048410485104861048710488104891049010491104921049310494104951049610497104981049910500105011050210503105041050510506105071050810509105101051110512105131051410515105161051710518105191052010521105221052310524105251052610527105281052910530105311053210533105341053510536105371053810539105401054110542105431054410545105461054710548105491055010551105521055310554105551055610557105581055910560105611056210563105641056510566105671056810569105701057110572105731057410575105761057710578105791058010581105821058310584105851058610587105881058910590105911059210593105941059510596105971059810599106001060110602106031060410605106061060710608106091061010611106121061310614106151061610617106181061910620106211062210623106241062510626106271062810629106301063110632106331063410635106361063710638106391064010641106421064310644106451064610647106481064910650106511065210653106541065510656106571065810659106601066110662106631066410665106661066710668106691067010671106721067310674106751067610677106781067910680106811068210683106841068510686106871068810689106901069110692106931069410695106961069710698106991070010701107021070310704107051070610707107081070910710107111071210713107141071510716107171071810719107201072110722107231072410725107261072710728107291073010731107321073310734107351073610737107381073910740107411074210743107441074510746107471074810749107501075110752107531075410755107561075710758107591076010761107621076310764107651076610767107681076910770107711077210773107741077510776107771077810779107801078110782107831078410785107861078710788107891079010791107921079310794107951079610797107981079910800108011080210803108041080510806108071080810809108101081110812108131081410815108161081710818108191082010821108221082310824108251082610827108281082910830108311083210833108341083510836108371083810839108401084110842108431084410845108461084710848108491085010851108521085310854108551085610857108581085910860108611086210863108641086510866108671086810869108701087110872108731087410875108761087710878108791088010881108821088310884108851088610887108881088910890108911089210893108941089510896108971089810899109001090110902109031090410905109061090710908109091091010911109121091310914109151091610917109181091910920109211092210923109241092510926109271092810929109301093110932109331093410935109361093710938109391094010941109421094310944109451094610947109481094910950109511095210953109541095510956109571095810959109601096110962109631096410965109661096710968109691097010971109721097310974109751097610977109781097910980109811098210983109841098510986109871098810989109901099110992109931099410995109961099710998109991100011001110021100311004110051100611007110081100911010110111101211013110141101511016110171101811019110201102111022110231102411025110261102711028110291103011031110321103311034110351103611037110381103911040110411104211043110441104511046110471104811049110501105111052110531105411055110561105711058110591106011061110621106311064110651106611067110681106911070110711107211073110741107511076110771107811079110801108111082110831108411085110861108711088110891109011091110921109311094110951109611097110981109911100111011110211103111041110511106111071110811109111101111111112111131111411115111161111711118111191112011121111221112311124111251112611127111281112911130111311113211133111341113511136111371113811139111401114111142111431114411145111461114711148111491115011151111521115311154111551115611157111581115911160111611116211163111641116511166111671116811169111701117111172111731117411175111761117711178111791118011181111821118311184111851118611187111881118911190111911119211193111941119511196111971119811199112001120111202112031120411205112061120711208112091121011211112121121311214112151121611217112181121911220112211122211223112241122511226112271122811229112301123111232112331123411235112361123711238112391124011241112421124311244112451124611247112481124911250112511125211253112541125511256112571125811259112601126111262112631126411265112661126711268112691127011271112721127311274112751127611277112781127911280112811128211283112841128511286112871128811289112901129111292112931129411295112961129711298112991130011301113021130311304113051130611307113081130911310113111131211313113141131511316113171131811319113201132111322113231132411325113261132711328
  1. <?php
  2. /**
  3. * PHP-CRUD-API v2 License: MIT
  4. * Maurits van der Schee: maurits@vdschee.nl
  5. * https://github.com/mevdschee/php-crud-api
  6. *
  7. * Dependencies:
  8. * - vendor/psr/*: PHP-FIG
  9. * https://github.com/php-fig
  10. * - vendor/nyholm/*: Tobias Nyholm
  11. * https://github.com/Nyholm
  12. **/
  13. // file: vendor/psr/http-factory/src/RequestFactoryInterface.php
  14. namespace Psr\Http\Message {
  15. interface RequestFactoryInterface
  16. {
  17. /**
  18. * Create a new request.
  19. *
  20. * @param string $method The HTTP method associated with the request.
  21. * @param UriInterface|string $uri The URI associated with the request. If
  22. * the value is a string, the factory MUST create a UriInterface
  23. * instance based on it.
  24. *
  25. * @return RequestInterface
  26. */
  27. public function createRequest(string $method, $uri): RequestInterface;
  28. }
  29. }
  30. // file: vendor/psr/http-factory/src/ResponseFactoryInterface.php
  31. namespace Psr\Http\Message {
  32. interface ResponseFactoryInterface
  33. {
  34. /**
  35. * Create a new response.
  36. *
  37. * @param int $code HTTP status code; defaults to 200
  38. * @param string $reasonPhrase Reason phrase to associate with status code
  39. * in generated response; if none is provided implementations MAY use
  40. * the defaults as suggested in the HTTP specification.
  41. *
  42. * @return ResponseInterface
  43. */
  44. public function createResponse(int $code = 200, string $reasonPhrase = ''): ResponseInterface;
  45. }
  46. }
  47. // file: vendor/psr/http-factory/src/ServerRequestFactoryInterface.php
  48. namespace Psr\Http\Message {
  49. interface ServerRequestFactoryInterface
  50. {
  51. /**
  52. * Create a new server request.
  53. *
  54. * Note that server-params are taken precisely as given - no parsing/processing
  55. * of the given values is performed, and, in particular, no attempt is made to
  56. * determine the HTTP method or URI, which must be provided explicitly.
  57. *
  58. * @param string $method The HTTP method associated with the request.
  59. * @param UriInterface|string $uri The URI associated with the request. If
  60. * the value is a string, the factory MUST create a UriInterface
  61. * instance based on it.
  62. * @param array $serverParams Array of SAPI parameters with which to seed
  63. * the generated request instance.
  64. *
  65. * @return ServerRequestInterface
  66. */
  67. public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface;
  68. }
  69. }
  70. // file: vendor/psr/http-factory/src/StreamFactoryInterface.php
  71. namespace Psr\Http\Message {
  72. interface StreamFactoryInterface
  73. {
  74. /**
  75. * Create a new stream from a string.
  76. *
  77. * The stream SHOULD be created with a temporary resource.
  78. *
  79. * @param string $content String content with which to populate the stream.
  80. *
  81. * @return StreamInterface
  82. */
  83. public function createStream(string $content = ''): StreamInterface;
  84. /**
  85. * Create a stream from an existing file.
  86. *
  87. * The file MUST be opened using the given mode, which may be any mode
  88. * supported by the `fopen` function.
  89. *
  90. * The `$filename` MAY be any string supported by `fopen()`.
  91. *
  92. * @param string $filename Filename or stream URI to use as basis of stream.
  93. * @param string $mode Mode with which to open the underlying filename/stream.
  94. *
  95. * @return StreamInterface
  96. * @throws \RuntimeException If the file cannot be opened.
  97. * @throws \InvalidArgumentException If the mode is invalid.
  98. */
  99. public function createStreamFromFile(string $filename, string $mode = 'r'): StreamInterface;
  100. /**
  101. * Create a new stream from an existing resource.
  102. *
  103. * The stream MUST be readable and may be writable.
  104. *
  105. * @param resource $resource PHP resource to use as basis of stream.
  106. *
  107. * @return StreamInterface
  108. */
  109. public function createStreamFromResource($resource): StreamInterface;
  110. }
  111. }
  112. // file: vendor/psr/http-factory/src/UploadedFileFactoryInterface.php
  113. namespace Psr\Http\Message {
  114. interface UploadedFileFactoryInterface
  115. {
  116. /**
  117. * Create a new uploaded file.
  118. *
  119. * If a size is not provided it will be determined by checking the size of
  120. * the file.
  121. *
  122. * @see http://php.net/manual/features.file-upload.post-method.php
  123. * @see http://php.net/manual/features.file-upload.errors.php
  124. *
  125. * @param StreamInterface $stream Underlying stream representing the
  126. * uploaded file content.
  127. * @param int $size in bytes
  128. * @param int $error PHP file upload error
  129. * @param string $clientFilename Filename as provided by the client, if any.
  130. * @param string $clientMediaType Media type as provided by the client, if any.
  131. *
  132. * @return UploadedFileInterface
  133. *
  134. * @throws \InvalidArgumentException If the file resource is not readable.
  135. */
  136. public function createUploadedFile(
  137. StreamInterface $stream,
  138. int $size = null,
  139. int $error = \UPLOAD_ERR_OK,
  140. string $clientFilename = null,
  141. string $clientMediaType = null
  142. ): UploadedFileInterface;
  143. }
  144. }
  145. // file: vendor/psr/http-factory/src/UriFactoryInterface.php
  146. namespace Psr\Http\Message {
  147. interface UriFactoryInterface
  148. {
  149. /**
  150. * Create a new URI.
  151. *
  152. * @param string $uri
  153. *
  154. * @return UriInterface
  155. *
  156. * @throws \InvalidArgumentException If the given URI cannot be parsed.
  157. */
  158. public function createUri(string $uri = ''): UriInterface;
  159. }
  160. }
  161. // file: vendor/psr/http-message/src/MessageInterface.php
  162. namespace Psr\Http\Message {
  163. /**
  164. * HTTP messages consist of requests from a client to a server and responses
  165. * from a server to a client. This interface defines the methods common to
  166. * each.
  167. *
  168. * Messages are considered immutable; all methods that might change state MUST
  169. * be implemented such that they retain the internal state of the current
  170. * message and return an instance that contains the changed state.
  171. *
  172. * @link http://www.ietf.org/rfc/rfc7230.txt
  173. * @link http://www.ietf.org/rfc/rfc7231.txt
  174. */
  175. interface MessageInterface
  176. {
  177. /**
  178. * Retrieves the HTTP protocol version as a string.
  179. *
  180. * The string MUST contain only the HTTP version number (e.g., "1.1", "1.0").
  181. *
  182. * @return string HTTP protocol version.
  183. */
  184. public function getProtocolVersion();
  185. /**
  186. * Return an instance with the specified HTTP protocol version.
  187. *
  188. * The version string MUST contain only the HTTP version number (e.g.,
  189. * "1.1", "1.0").
  190. *
  191. * This method MUST be implemented in such a way as to retain the
  192. * immutability of the message, and MUST return an instance that has the
  193. * new protocol version.
  194. *
  195. * @param string $version HTTP protocol version
  196. * @return static
  197. */
  198. public function withProtocolVersion($version);
  199. /**
  200. * Retrieves all message header values.
  201. *
  202. * The keys represent the header name as it will be sent over the wire, and
  203. * each value is an array of strings associated with the header.
  204. *
  205. * // Represent the headers as a string
  206. * foreach ($message->getHeaders() as $name => $values) {
  207. * echo $name . ": " . implode(", ", $values);
  208. * }
  209. *
  210. * // Emit headers iteratively:
  211. * foreach ($message->getHeaders() as $name => $values) {
  212. * foreach ($values as $value) {
  213. * header(sprintf('%s: %s', $name, $value), false);
  214. * }
  215. * }
  216. *
  217. * While header names are not case-sensitive, getHeaders() will preserve the
  218. * exact case in which headers were originally specified.
  219. *
  220. * @return string[][] Returns an associative array of the message's headers. Each
  221. * key MUST be a header name, and each value MUST be an array of strings
  222. * for that header.
  223. */
  224. public function getHeaders();
  225. /**
  226. * Checks if a header exists by the given case-insensitive name.
  227. *
  228. * @param string $name Case-insensitive header field name.
  229. * @return bool Returns true if any header names match the given header
  230. * name using a case-insensitive string comparison. Returns false if
  231. * no matching header name is found in the message.
  232. */
  233. public function hasHeader($name);
  234. /**
  235. * Retrieves a message header value by the given case-insensitive name.
  236. *
  237. * This method returns an array of all the header values of the given
  238. * case-insensitive header name.
  239. *
  240. * If the header does not appear in the message, this method MUST return an
  241. * empty array.
  242. *
  243. * @param string $name Case-insensitive header field name.
  244. * @return string[] An array of string values as provided for the given
  245. * header. If the header does not appear in the message, this method MUST
  246. * return an empty array.
  247. */
  248. public function getHeader($name);
  249. /**
  250. * Retrieves a comma-separated string of the values for a single header.
  251. *
  252. * This method returns all of the header values of the given
  253. * case-insensitive header name as a string concatenated together using
  254. * a comma.
  255. *
  256. * NOTE: Not all header values may be appropriately represented using
  257. * comma concatenation. For such headers, use getHeader() instead
  258. * and supply your own delimiter when concatenating.
  259. *
  260. * If the header does not appear in the message, this method MUST return
  261. * an empty string.
  262. *
  263. * @param string $name Case-insensitive header field name.
  264. * @return string A string of values as provided for the given header
  265. * concatenated together using a comma. If the header does not appear in
  266. * the message, this method MUST return an empty string.
  267. */
  268. public function getHeaderLine($name);
  269. /**
  270. * Return an instance with the provided value replacing the specified header.
  271. *
  272. * While header names are case-insensitive, the casing of the header will
  273. * be preserved by this function, and returned from getHeaders().
  274. *
  275. * This method MUST be implemented in such a way as to retain the
  276. * immutability of the message, and MUST return an instance that has the
  277. * new and/or updated header and value.
  278. *
  279. * @param string $name Case-insensitive header field name.
  280. * @param string|string[] $value Header value(s).
  281. * @return static
  282. * @throws \InvalidArgumentException for invalid header names or values.
  283. */
  284. public function withHeader($name, $value);
  285. /**
  286. * Return an instance with the specified header appended with the given value.
  287. *
  288. * Existing values for the specified header will be maintained. The new
  289. * value(s) will be appended to the existing list. If the header did not
  290. * exist previously, it will be added.
  291. *
  292. * This method MUST be implemented in such a way as to retain the
  293. * immutability of the message, and MUST return an instance that has the
  294. * new header and/or value.
  295. *
  296. * @param string $name Case-insensitive header field name to add.
  297. * @param string|string[] $value Header value(s).
  298. * @return static
  299. * @throws \InvalidArgumentException for invalid header names or values.
  300. */
  301. public function withAddedHeader($name, $value);
  302. /**
  303. * Return an instance without the specified header.
  304. *
  305. * Header resolution MUST be done without case-sensitivity.
  306. *
  307. * This method MUST be implemented in such a way as to retain the
  308. * immutability of the message, and MUST return an instance that removes
  309. * the named header.
  310. *
  311. * @param string $name Case-insensitive header field name to remove.
  312. * @return static
  313. */
  314. public function withoutHeader($name);
  315. /**
  316. * Gets the body of the message.
  317. *
  318. * @return StreamInterface Returns the body as a stream.
  319. */
  320. public function getBody();
  321. /**
  322. * Return an instance with the specified message body.
  323. *
  324. * The body MUST be a StreamInterface object.
  325. *
  326. * This method MUST be implemented in such a way as to retain the
  327. * immutability of the message, and MUST return a new instance that has the
  328. * new body stream.
  329. *
  330. * @param StreamInterface $body Body.
  331. * @return static
  332. * @throws \InvalidArgumentException When the body is not valid.
  333. */
  334. public function withBody(StreamInterface $body);
  335. }
  336. }
  337. // file: vendor/psr/http-message/src/RequestInterface.php
  338. namespace Psr\Http\Message {
  339. /**
  340. * Representation of an outgoing, client-side request.
  341. *
  342. * Per the HTTP specification, this interface includes properties for
  343. * each of the following:
  344. *
  345. * - Protocol version
  346. * - HTTP method
  347. * - URI
  348. * - Headers
  349. * - Message body
  350. *
  351. * During construction, implementations MUST attempt to set the Host header from
  352. * a provided URI if no Host header is provided.
  353. *
  354. * Requests are considered immutable; all methods that might change state MUST
  355. * be implemented such that they retain the internal state of the current
  356. * message and return an instance that contains the changed state.
  357. */
  358. interface RequestInterface extends MessageInterface
  359. {
  360. /**
  361. * Retrieves the message's request target.
  362. *
  363. * Retrieves the message's request-target either as it will appear (for
  364. * clients), as it appeared at request (for servers), or as it was
  365. * specified for the instance (see withRequestTarget()).
  366. *
  367. * In most cases, this will be the origin-form of the composed URI,
  368. * unless a value was provided to the concrete implementation (see
  369. * withRequestTarget() below).
  370. *
  371. * If no URI is available, and no request-target has been specifically
  372. * provided, this method MUST return the string "/".
  373. *
  374. * @return string
  375. */
  376. public function getRequestTarget();
  377. /**
  378. * Return an instance with the specific request-target.
  379. *
  380. * If the request needs a non-origin-form request-target — e.g., for
  381. * specifying an absolute-form, authority-form, or asterisk-form —
  382. * this method may be used to create an instance with the specified
  383. * request-target, verbatim.
  384. *
  385. * This method MUST be implemented in such a way as to retain the
  386. * immutability of the message, and MUST return an instance that has the
  387. * changed request target.
  388. *
  389. * @link http://tools.ietf.org/html/rfc7230#section-5.3 (for the various
  390. * request-target forms allowed in request messages)
  391. * @param mixed $requestTarget
  392. * @return static
  393. */
  394. public function withRequestTarget($requestTarget);
  395. /**
  396. * Retrieves the HTTP method of the request.
  397. *
  398. * @return string Returns the request method.
  399. */
  400. public function getMethod();
  401. /**
  402. * Return an instance with the provided HTTP method.
  403. *
  404. * While HTTP method names are typically all uppercase characters, HTTP
  405. * method names are case-sensitive and thus implementations SHOULD NOT
  406. * modify the given string.
  407. *
  408. * This method MUST be implemented in such a way as to retain the
  409. * immutability of the message, and MUST return an instance that has the
  410. * changed request method.
  411. *
  412. * @param string $method Case-sensitive method.
  413. * @return static
  414. * @throws \InvalidArgumentException for invalid HTTP methods.
  415. */
  416. public function withMethod($method);
  417. /**
  418. * Retrieves the URI instance.
  419. *
  420. * This method MUST return a UriInterface instance.
  421. *
  422. * @link http://tools.ietf.org/html/rfc3986#section-4.3
  423. * @return UriInterface Returns a UriInterface instance
  424. * representing the URI of the request.
  425. */
  426. public function getUri();
  427. /**
  428. * Returns an instance with the provided URI.
  429. *
  430. * This method MUST update the Host header of the returned request by
  431. * default if the URI contains a host component. If the URI does not
  432. * contain a host component, any pre-existing Host header MUST be carried
  433. * over to the returned request.
  434. *
  435. * You can opt-in to preserving the original state of the Host header by
  436. * setting `$preserveHost` to `true`. When `$preserveHost` is set to
  437. * `true`, this method interacts with the Host header in the following ways:
  438. *
  439. * - If the Host header is missing or empty, and the new URI contains
  440. * a host component, this method MUST update the Host header in the returned
  441. * request.
  442. * - If the Host header is missing or empty, and the new URI does not contain a
  443. * host component, this method MUST NOT update the Host header in the returned
  444. * request.
  445. * - If a Host header is present and non-empty, this method MUST NOT update
  446. * the Host header in the returned request.
  447. *
  448. * This method MUST be implemented in such a way as to retain the
  449. * immutability of the message, and MUST return an instance that has the
  450. * new UriInterface instance.
  451. *
  452. * @link http://tools.ietf.org/html/rfc3986#section-4.3
  453. * @param UriInterface $uri New request URI to use.
  454. * @param bool $preserveHost Preserve the original state of the Host header.
  455. * @return static
  456. */
  457. public function withUri(UriInterface $uri, $preserveHost = false);
  458. }
  459. }
  460. // file: vendor/psr/http-message/src/ResponseInterface.php
  461. namespace Psr\Http\Message {
  462. /**
  463. * Representation of an outgoing, server-side response.
  464. *
  465. * Per the HTTP specification, this interface includes properties for
  466. * each of the following:
  467. *
  468. * - Protocol version
  469. * - Status code and reason phrase
  470. * - Headers
  471. * - Message body
  472. *
  473. * Responses are considered immutable; all methods that might change state MUST
  474. * be implemented such that they retain the internal state of the current
  475. * message and return an instance that contains the changed state.
  476. */
  477. interface ResponseInterface extends MessageInterface
  478. {
  479. /**
  480. * Gets the response status code.
  481. *
  482. * The status code is a 3-digit integer result code of the server's attempt
  483. * to understand and satisfy the request.
  484. *
  485. * @return int Status code.
  486. */
  487. public function getStatusCode();
  488. /**
  489. * Return an instance with the specified status code and, optionally, reason phrase.
  490. *
  491. * If no reason phrase is specified, implementations MAY choose to default
  492. * to the RFC 7231 or IANA recommended reason phrase for the response's
  493. * status code.
  494. *
  495. * This method MUST be implemented in such a way as to retain the
  496. * immutability of the message, and MUST return an instance that has the
  497. * updated status and reason phrase.
  498. *
  499. * @link http://tools.ietf.org/html/rfc7231#section-6
  500. * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
  501. * @param int $code The 3-digit integer result code to set.
  502. * @param string $reasonPhrase The reason phrase to use with the
  503. * provided status code; if none is provided, implementations MAY
  504. * use the defaults as suggested in the HTTP specification.
  505. * @return static
  506. * @throws \InvalidArgumentException For invalid status code arguments.
  507. */
  508. public function withStatus($code, $reasonPhrase = '');
  509. /**
  510. * Gets the response reason phrase associated with the status code.
  511. *
  512. * Because a reason phrase is not a required element in a response
  513. * status line, the reason phrase value MAY be null. Implementations MAY
  514. * choose to return the default RFC 7231 recommended reason phrase (or those
  515. * listed in the IANA HTTP Status Code Registry) for the response's
  516. * status code.
  517. *
  518. * @link http://tools.ietf.org/html/rfc7231#section-6
  519. * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
  520. * @return string Reason phrase; must return an empty string if none present.
  521. */
  522. public function getReasonPhrase();
  523. }
  524. }
  525. // file: vendor/psr/http-message/src/ServerRequestInterface.php
  526. namespace Psr\Http\Message {
  527. /**
  528. * Representation of an incoming, server-side HTTP request.
  529. *
  530. * Per the HTTP specification, this interface includes properties for
  531. * each of the following:
  532. *
  533. * - Protocol version
  534. * - HTTP method
  535. * - URI
  536. * - Headers
  537. * - Message body
  538. *
  539. * Additionally, it encapsulates all data as it has arrived to the
  540. * application from the CGI and/or PHP environment, including:
  541. *
  542. * - The values represented in $_SERVER.
  543. * - Any cookies provided (generally via $_COOKIE)
  544. * - Query string arguments (generally via $_GET, or as parsed via parse_str())
  545. * - Upload files, if any (as represented by $_FILES)
  546. * - Deserialized body parameters (generally from $_POST)
  547. *
  548. * $_SERVER values MUST be treated as immutable, as they represent application
  549. * state at the time of request; as such, no methods are provided to allow
  550. * modification of those values. The other values provide such methods, as they
  551. * can be restored from $_SERVER or the request body, and may need treatment
  552. * during the application (e.g., body parameters may be deserialized based on
  553. * content type).
  554. *
  555. * Additionally, this interface recognizes the utility of introspecting a
  556. * request to derive and match additional parameters (e.g., via URI path
  557. * matching, decrypting cookie values, deserializing non-form-encoded body
  558. * content, matching authorization headers to users, etc). These parameters
  559. * are stored in an "attributes" property.
  560. *
  561. * Requests are considered immutable; all methods that might change state MUST
  562. * be implemented such that they retain the internal state of the current
  563. * message and return an instance that contains the changed state.
  564. */
  565. interface ServerRequestInterface extends RequestInterface
  566. {
  567. /**
  568. * Retrieve server parameters.
  569. *
  570. * Retrieves data related to the incoming request environment,
  571. * typically derived from PHP's $_SERVER superglobal. The data IS NOT
  572. * REQUIRED to originate from $_SERVER.
  573. *
  574. * @return array
  575. */
  576. public function getServerParams();
  577. /**
  578. * Retrieve cookies.
  579. *
  580. * Retrieves cookies sent by the client to the server.
  581. *
  582. * The data MUST be compatible with the structure of the $_COOKIE
  583. * superglobal.
  584. *
  585. * @return array
  586. */
  587. public function getCookieParams();
  588. /**
  589. * Return an instance with the specified cookies.
  590. *
  591. * The data IS NOT REQUIRED to come from the $_COOKIE superglobal, but MUST
  592. * be compatible with the structure of $_COOKIE. Typically, this data will
  593. * be injected at instantiation.
  594. *
  595. * This method MUST NOT update the related Cookie header of the request
  596. * instance, nor related values in the server params.
  597. *
  598. * This method MUST be implemented in such a way as to retain the
  599. * immutability of the message, and MUST return an instance that has the
  600. * updated cookie values.
  601. *
  602. * @param array $cookies Array of key/value pairs representing cookies.
  603. * @return static
  604. */
  605. public function withCookieParams(array $cookies);
  606. /**
  607. * Retrieve query string arguments.
  608. *
  609. * Retrieves the deserialized query string arguments, if any.
  610. *
  611. * Note: the query params might not be in sync with the URI or server
  612. * params. If you need to ensure you are only getting the original
  613. * values, you may need to parse the query string from `getUri()->getQuery()`
  614. * or from the `QUERY_STRING` server param.
  615. *
  616. * @return array
  617. */
  618. public function getQueryParams();
  619. /**
  620. * Return an instance with the specified query string arguments.
  621. *
  622. * These values SHOULD remain immutable over the course of the incoming
  623. * request. They MAY be injected during instantiation, such as from PHP's
  624. * $_GET superglobal, or MAY be derived from some other value such as the
  625. * URI. In cases where the arguments are parsed from the URI, the data
  626. * MUST be compatible with what PHP's parse_str() would return for
  627. * purposes of how duplicate query parameters are handled, and how nested
  628. * sets are handled.
  629. *
  630. * Setting query string arguments MUST NOT change the URI stored by the
  631. * request, nor the values in the server params.
  632. *
  633. * This method MUST be implemented in such a way as to retain the
  634. * immutability of the message, and MUST return an instance that has the
  635. * updated query string arguments.
  636. *
  637. * @param array $query Array of query string arguments, typically from
  638. * $_GET.
  639. * @return static
  640. */
  641. public function withQueryParams(array $query);
  642. /**
  643. * Retrieve normalized file upload data.
  644. *
  645. * This method returns upload metadata in a normalized tree, with each leaf
  646. * an instance of Psr\Http\Message\UploadedFileInterface.
  647. *
  648. * These values MAY be prepared from $_FILES or the message body during
  649. * instantiation, or MAY be injected via withUploadedFiles().
  650. *
  651. * @return array An array tree of UploadedFileInterface instances; an empty
  652. * array MUST be returned if no data is present.
  653. */
  654. public function getUploadedFiles();
  655. /**
  656. * Create a new instance with the specified uploaded files.
  657. *
  658. * This method MUST be implemented in such a way as to retain the
  659. * immutability of the message, and MUST return an instance that has the
  660. * updated body parameters.
  661. *
  662. * @param array $uploadedFiles An array tree of UploadedFileInterface instances.
  663. * @return static
  664. * @throws \InvalidArgumentException if an invalid structure is provided.
  665. */
  666. public function withUploadedFiles(array $uploadedFiles);
  667. /**
  668. * Retrieve any parameters provided in the request body.
  669. *
  670. * If the request Content-Type is either application/x-www-form-urlencoded
  671. * or multipart/form-data, and the request method is POST, this method MUST
  672. * return the contents of $_POST.
  673. *
  674. * Otherwise, this method may return any results of deserializing
  675. * the request body content; as parsing returns structured content, the
  676. * potential types MUST be arrays or objects only. A null value indicates
  677. * the absence of body content.
  678. *
  679. * @return null|array|object The deserialized body parameters, if any.
  680. * These will typically be an array or object.
  681. */
  682. public function getParsedBody();
  683. /**
  684. * Return an instance with the specified body parameters.
  685. *
  686. * These MAY be injected during instantiation.
  687. *
  688. * If the request Content-Type is either application/x-www-form-urlencoded
  689. * or multipart/form-data, and the request method is POST, use this method
  690. * ONLY to inject the contents of $_POST.
  691. *
  692. * The data IS NOT REQUIRED to come from $_POST, but MUST be the results of
  693. * deserializing the request body content. Deserialization/parsing returns
  694. * structured data, and, as such, this method ONLY accepts arrays or objects,
  695. * or a null value if nothing was available to parse.
  696. *
  697. * As an example, if content negotiation determines that the request data
  698. * is a JSON payload, this method could be used to create a request
  699. * instance with the deserialized parameters.
  700. *
  701. * This method MUST be implemented in such a way as to retain the
  702. * immutability of the message, and MUST return an instance that has the
  703. * updated body parameters.
  704. *
  705. * @param null|array|object $data The deserialized body data. This will
  706. * typically be in an array or object.
  707. * @return static
  708. * @throws \InvalidArgumentException if an unsupported argument type is
  709. * provided.
  710. */
  711. public function withParsedBody($data);
  712. /**
  713. * Retrieve attributes derived from the request.
  714. *
  715. * The request "attributes" may be used to allow injection of any
  716. * parameters derived from the request: e.g., the results of path
  717. * match operations; the results of decrypting cookies; the results of
  718. * deserializing non-form-encoded message bodies; etc. Attributes
  719. * will be application and request specific, and CAN be mutable.
  720. *
  721. * @return array Attributes derived from the request.
  722. */
  723. public function getAttributes();
  724. /**
  725. * Retrieve a single derived request attribute.
  726. *
  727. * Retrieves a single derived request attribute as described in
  728. * getAttributes(). If the attribute has not been previously set, returns
  729. * the default value as provided.
  730. *
  731. * This method obviates the need for a hasAttribute() method, as it allows
  732. * specifying a default value to return if the attribute is not found.
  733. *
  734. * @see getAttributes()
  735. * @param string $name The attribute name.
  736. * @param mixed $default Default value to return if the attribute does not exist.
  737. * @return mixed
  738. */
  739. public function getAttribute($name, $default = null);
  740. /**
  741. * Return an instance with the specified derived request attribute.
  742. *
  743. * This method allows setting a single derived request attribute as
  744. * described in getAttributes().
  745. *
  746. * This method MUST be implemented in such a way as to retain the
  747. * immutability of the message, and MUST return an instance that has the
  748. * updated attribute.
  749. *
  750. * @see getAttributes()
  751. * @param string $name The attribute name.
  752. * @param mixed $value The value of the attribute.
  753. * @return static
  754. */
  755. public function withAttribute($name, $value);
  756. /**
  757. * Return an instance that removes the specified derived request attribute.
  758. *
  759. * This method allows removing a single derived request attribute as
  760. * described in getAttributes().
  761. *
  762. * This method MUST be implemented in such a way as to retain the
  763. * immutability of the message, and MUST return an instance that removes
  764. * the attribute.
  765. *
  766. * @see getAttributes()
  767. * @param string $name The attribute name.
  768. * @return static
  769. */
  770. public function withoutAttribute($name);
  771. }
  772. }
  773. // file: vendor/psr/http-message/src/StreamInterface.php
  774. namespace Psr\Http\Message {
  775. /**
  776. * Describes a data stream.
  777. *
  778. * Typically, an instance will wrap a PHP stream; this interface provides
  779. * a wrapper around the most common operations, including serialization of
  780. * the entire stream to a string.
  781. */
  782. interface StreamInterface
  783. {
  784. /**
  785. * Reads all data from the stream into a string, from the beginning to end.
  786. *
  787. * This method MUST attempt to seek to the beginning of the stream before
  788. * reading data and read the stream until the end is reached.
  789. *
  790. * Warning: This could attempt to load a large amount of data into memory.
  791. *
  792. * This method MUST NOT raise an exception in order to conform with PHP's
  793. * string casting operations.
  794. *
  795. * @see http://php.net/manual/en/language.oop5.magic.php#object.tostring
  796. * @return string
  797. */
  798. public function __toString();
  799. /**
  800. * Closes the stream and any underlying resources.
  801. *
  802. * @return void
  803. */
  804. public function close();
  805. /**
  806. * Separates any underlying resources from the stream.
  807. *
  808. * After the stream has been detached, the stream is in an unusable state.
  809. *
  810. * @return resource|null Underlying PHP stream, if any
  811. */
  812. public function detach();
  813. /**
  814. * Get the size of the stream if known.
  815. *
  816. * @return int|null Returns the size in bytes if known, or null if unknown.
  817. */
  818. public function getSize();
  819. /**
  820. * Returns the current position of the file read/write pointer
  821. *
  822. * @return int Position of the file pointer
  823. * @throws \RuntimeException on error.
  824. */
  825. public function tell();
  826. /**
  827. * Returns true if the stream is at the end of the stream.
  828. *
  829. * @return bool
  830. */
  831. public function eof();
  832. /**
  833. * Returns whether or not the stream is seekable.
  834. *
  835. * @return bool
  836. */
  837. public function isSeekable();
  838. /**
  839. * Seek to a position in the stream.
  840. *
  841. * @link http://www.php.net/manual/en/function.fseek.php
  842. * @param int $offset Stream offset
  843. * @param int $whence Specifies how the cursor position will be calculated
  844. * based on the seek offset. Valid values are identical to the built-in
  845. * PHP $whence values for `fseek()`. SEEK_SET: Set position equal to
  846. * offset bytes SEEK_CUR: Set position to current location plus offset
  847. * SEEK_END: Set position to end-of-stream plus offset.
  848. * @throws \RuntimeException on failure.
  849. */
  850. public function seek($offset, $whence = SEEK_SET);
  851. /**
  852. * Seek to the beginning of the stream.
  853. *
  854. * If the stream is not seekable, this method will raise an exception;
  855. * otherwise, it will perform a seek(0).
  856. *
  857. * @see seek()
  858. * @link http://www.php.net/manual/en/function.fseek.php
  859. * @throws \RuntimeException on failure.
  860. */
  861. public function rewind();
  862. /**
  863. * Returns whether or not the stream is writable.
  864. *
  865. * @return bool
  866. */
  867. public function isWritable();
  868. /**
  869. * Write data to the stream.
  870. *
  871. * @param string $string The string that is to be written.
  872. * @return int Returns the number of bytes written to the stream.
  873. * @throws \RuntimeException on failure.
  874. */
  875. public function write($string);
  876. /**
  877. * Returns whether or not the stream is readable.
  878. *
  879. * @return bool
  880. */
  881. public function isReadable();
  882. /**
  883. * Read data from the stream.
  884. *
  885. * @param int $length Read up to $length bytes from the object and return
  886. * them. Fewer than $length bytes may be returned if underlying stream
  887. * call returns fewer bytes.
  888. * @return string Returns the data read from the stream, or an empty string
  889. * if no bytes are available.
  890. * @throws \RuntimeException if an error occurs.
  891. */
  892. public function read($length);
  893. /**
  894. * Returns the remaining contents in a string
  895. *
  896. * @return string
  897. * @throws \RuntimeException if unable to read or an error occurs while
  898. * reading.
  899. */
  900. public function getContents();
  901. /**
  902. * Get stream metadata as an associative array or retrieve a specific key.
  903. *
  904. * The keys returned are identical to the keys returned from PHP's
  905. * stream_get_meta_data() function.
  906. *
  907. * @link http://php.net/manual/en/function.stream-get-meta-data.php
  908. * @param string $key Specific metadata to retrieve.
  909. * @return array|mixed|null Returns an associative array if no key is
  910. * provided. Returns a specific key value if a key is provided and the
  911. * value is found, or null if the key is not found.
  912. */
  913. public function getMetadata($key = null);
  914. }
  915. }
  916. // file: vendor/psr/http-message/src/UploadedFileInterface.php
  917. namespace Psr\Http\Message {
  918. /**
  919. * Value object representing a file uploaded through an HTTP request.
  920. *
  921. * Instances of this interface are considered immutable; all methods that
  922. * might change state MUST be implemented such that they retain the internal
  923. * state of the current instance and return an instance that contains the
  924. * changed state.
  925. */
  926. interface UploadedFileInterface
  927. {
  928. /**
  929. * Retrieve a stream representing the uploaded file.
  930. *
  931. * This method MUST return a StreamInterface instance, representing the
  932. * uploaded file. The purpose of this method is to allow utilizing native PHP
  933. * stream functionality to manipulate the file upload, such as
  934. * stream_copy_to_stream() (though the result will need to be decorated in a
  935. * native PHP stream wrapper to work with such functions).
  936. *
  937. * If the moveTo() method has been called previously, this method MUST raise
  938. * an exception.
  939. *
  940. * @return StreamInterface Stream representation of the uploaded file.
  941. * @throws \RuntimeException in cases when no stream is available or can be
  942. * created.
  943. */
  944. public function getStream();
  945. /**
  946. * Move the uploaded file to a new location.
  947. *
  948. * Use this method as an alternative to move_uploaded_file(). This method is
  949. * guaranteed to work in both SAPI and non-SAPI environments.
  950. * Implementations must determine which environment they are in, and use the
  951. * appropriate method (move_uploaded_file(), rename(), or a stream
  952. * operation) to perform the operation.
  953. *
  954. * $targetPath may be an absolute path, or a relative path. If it is a
  955. * relative path, resolution should be the same as used by PHP's rename()
  956. * function.
  957. *
  958. * The original file or stream MUST be removed on completion.
  959. *
  960. * If this method is called more than once, any subsequent calls MUST raise
  961. * an exception.
  962. *
  963. * When used in an SAPI environment where $_FILES is populated, when writing
  964. * files via moveTo(), is_uploaded_file() and move_uploaded_file() SHOULD be
  965. * used to ensure permissions and upload status are verified correctly.
  966. *
  967. * If you wish to move to a stream, use getStream(), as SAPI operations
  968. * cannot guarantee writing to stream destinations.
  969. *
  970. * @see http://php.net/is_uploaded_file
  971. * @see http://php.net/move_uploaded_file
  972. * @param string $targetPath Path to which to move the uploaded file.
  973. * @throws \InvalidArgumentException if the $targetPath specified is invalid.
  974. * @throws \RuntimeException on any error during the move operation, or on
  975. * the second or subsequent call to the method.
  976. */
  977. public function moveTo($targetPath);
  978. /**
  979. * Retrieve the file size.
  980. *
  981. * Implementations SHOULD return the value stored in the "size" key of
  982. * the file in the $_FILES array if available, as PHP calculates this based
  983. * on the actual size transmitted.
  984. *
  985. * @return int|null The file size in bytes or null if unknown.
  986. */
  987. public function getSize();
  988. /**
  989. * Retrieve the error associated with the uploaded file.
  990. *
  991. * The return value MUST be one of PHP's UPLOAD_ERR_XXX constants.
  992. *
  993. * If the file was uploaded successfully, this method MUST return
  994. * UPLOAD_ERR_OK.
  995. *
  996. * Implementations SHOULD return the value stored in the "error" key of
  997. * the file in the $_FILES array.
  998. *
  999. * @see http://php.net/manual/en/features.file-upload.errors.php
  1000. * @return int One of PHP's UPLOAD_ERR_XXX constants.
  1001. */
  1002. public function getError();
  1003. /**
  1004. * Retrieve the filename sent by the client.
  1005. *
  1006. * Do not trust the value returned by this method. A client could send
  1007. * a malicious filename with the intention to corrupt or hack your
  1008. * application.
  1009. *
  1010. * Implementations SHOULD return the value stored in the "name" key of
  1011. * the file in the $_FILES array.
  1012. *
  1013. * @return string|null The filename sent by the client or null if none
  1014. * was provided.
  1015. */
  1016. public function getClientFilename();
  1017. /**
  1018. * Retrieve the media type sent by the client.
  1019. *
  1020. * Do not trust the value returned by this method. A client could send
  1021. * a malicious media type with the intention to corrupt or hack your
  1022. * application.
  1023. *
  1024. * Implementations SHOULD return the value stored in the "type" key of
  1025. * the file in the $_FILES array.
  1026. *
  1027. * @return string|null The media type sent by the client or null if none
  1028. * was provided.
  1029. */
  1030. public function getClientMediaType();
  1031. }
  1032. }
  1033. // file: vendor/psr/http-message/src/UriInterface.php
  1034. namespace Psr\Http\Message {
  1035. /**
  1036. * Value object representing a URI.
  1037. *
  1038. * This interface is meant to represent URIs according to RFC 3986 and to
  1039. * provide methods for most common operations. Additional functionality for
  1040. * working with URIs can be provided on top of the interface or externally.
  1041. * Its primary use is for HTTP requests, but may also be used in other
  1042. * contexts.
  1043. *
  1044. * Instances of this interface are considered immutable; all methods that
  1045. * might change state MUST be implemented such that they retain the internal
  1046. * state of the current instance and return an instance that contains the
  1047. * changed state.
  1048. *
  1049. * Typically the Host header will be also be present in the request message.
  1050. * For server-side requests, the scheme will typically be discoverable in the
  1051. * server parameters.
  1052. *
  1053. * @link http://tools.ietf.org/html/rfc3986 (the URI specification)
  1054. */
  1055. interface UriInterface
  1056. {
  1057. /**
  1058. * Retrieve the scheme component of the URI.
  1059. *
  1060. * If no scheme is present, this method MUST return an empty string.
  1061. *
  1062. * The value returned MUST be normalized to lowercase, per RFC 3986
  1063. * Section 3.1.
  1064. *
  1065. * The trailing ":" character is not part of the scheme and MUST NOT be
  1066. * added.
  1067. *
  1068. * @see https://tools.ietf.org/html/rfc3986#section-3.1
  1069. * @return string The URI scheme.
  1070. */
  1071. public function getScheme();
  1072. /**
  1073. * Retrieve the authority component of the URI.
  1074. *
  1075. * If no authority information is present, this method MUST return an empty
  1076. * string.
  1077. *
  1078. * The authority syntax of the URI is:
  1079. *
  1080. * <pre>
  1081. * [user-info@]host[:port]
  1082. * </pre>
  1083. *
  1084. * If the port component is not set or is the standard port for the current
  1085. * scheme, it SHOULD NOT be included.
  1086. *
  1087. * @see https://tools.ietf.org/html/rfc3986#section-3.2
  1088. * @return string The URI authority, in "[user-info@]host[:port]" format.
  1089. */
  1090. public function getAuthority();
  1091. /**
  1092. * Retrieve the user information component of the URI.
  1093. *
  1094. * If no user information is present, this method MUST return an empty
  1095. * string.
  1096. *
  1097. * If a user is present in the URI, this will return that value;
  1098. * additionally, if the password is also present, it will be appended to the
  1099. * user value, with a colon (":") separating the values.
  1100. *
  1101. * The trailing "@" character is not part of the user information and MUST
  1102. * NOT be added.
  1103. *
  1104. * @return string The URI user information, in "username[:password]" format.
  1105. */
  1106. public function getUserInfo();
  1107. /**
  1108. * Retrieve the host component of the URI.
  1109. *
  1110. * If no host is present, this method MUST return an empty string.
  1111. *
  1112. * The value returned MUST be normalized to lowercase, per RFC 3986
  1113. * Section 3.2.2.
  1114. *
  1115. * @see http://tools.ietf.org/html/rfc3986#section-3.2.2
  1116. * @return string The URI host.
  1117. */
  1118. public function getHost();
  1119. /**
  1120. * Retrieve the port component of the URI.
  1121. *
  1122. * If a port is present, and it is non-standard for the current scheme,
  1123. * this method MUST return it as an integer. If the port is the standard port
  1124. * used with the current scheme, this method SHOULD return null.
  1125. *
  1126. * If no port is present, and no scheme is present, this method MUST return
  1127. * a null value.
  1128. *
  1129. * If no port is present, but a scheme is present, this method MAY return
  1130. * the standard port for that scheme, but SHOULD return null.
  1131. *
  1132. * @return null|int The URI port.
  1133. */
  1134. public function getPort();
  1135. /**
  1136. * Retrieve the path component of the URI.
  1137. *
  1138. * The path can either be empty or absolute (starting with a slash) or
  1139. * rootless (not starting with a slash). Implementations MUST support all
  1140. * three syntaxes.
  1141. *
  1142. * Normally, the empty path "" and absolute path "/" are considered equal as
  1143. * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically
  1144. * do this normalization because in contexts with a trimmed base path, e.g.
  1145. * the front controller, this difference becomes significant. It's the task
  1146. * of the user to handle both "" and "/".
  1147. *
  1148. * The value returned MUST be percent-encoded, but MUST NOT double-encode
  1149. * any characters. To determine what characters to encode, please refer to
  1150. * RFC 3986, Sections 2 and 3.3.
  1151. *
  1152. * As an example, if the value should include a slash ("/") not intended as
  1153. * delimiter between path segments, that value MUST be passed in encoded
  1154. * form (e.g., "%2F") to the instance.
  1155. *
  1156. * @see https://tools.ietf.org/html/rfc3986#section-2
  1157. * @see https://tools.ietf.org/html/rfc3986#section-3.3
  1158. * @return string The URI path.
  1159. */
  1160. public function getPath();
  1161. /**
  1162. * Retrieve the query string of the URI.
  1163. *
  1164. * If no query string is present, this method MUST return an empty string.
  1165. *
  1166. * The leading "?" character is not part of the query and MUST NOT be
  1167. * added.
  1168. *
  1169. * The value returned MUST be percent-encoded, but MUST NOT double-encode
  1170. * any characters. To determine what characters to encode, please refer to
  1171. * RFC 3986, Sections 2 and 3.4.
  1172. *
  1173. * As an example, if a value in a key/value pair of the query string should
  1174. * include an ampersand ("&") not intended as a delimiter between values,
  1175. * that value MUST be passed in encoded form (e.g., "%26") to the instance.
  1176. *
  1177. * @see https://tools.ietf.org/html/rfc3986#section-2
  1178. * @see https://tools.ietf.org/html/rfc3986#section-3.4
  1179. * @return string The URI query string.
  1180. */
  1181. public function getQuery();
  1182. /**
  1183. * Retrieve the fragment component of the URI.
  1184. *
  1185. * If no fragment is present, this method MUST return an empty string.
  1186. *
  1187. * The leading "#" character is not part of the fragment and MUST NOT be
  1188. * added.
  1189. *
  1190. * The value returned MUST be percent-encoded, but MUST NOT double-encode
  1191. * any characters. To determine what characters to encode, please refer to
  1192. * RFC 3986, Sections 2 and 3.5.
  1193. *
  1194. * @see https://tools.ietf.org/html/rfc3986#section-2
  1195. * @see https://tools.ietf.org/html/rfc3986#section-3.5
  1196. * @return string The URI fragment.
  1197. */
  1198. public function getFragment();
  1199. /**
  1200. * Return an instance with the specified scheme.
  1201. *
  1202. * This method MUST retain the state of the current instance, and return
  1203. * an instance that contains the specified scheme.
  1204. *
  1205. * Implementations MUST support the schemes "http" and "https" case
  1206. * insensitively, and MAY accommodate other schemes if required.
  1207. *
  1208. * An empty scheme is equivalent to removing the scheme.
  1209. *
  1210. * @param string $scheme The scheme to use with the new instance.
  1211. * @return static A new instance with the specified scheme.
  1212. * @throws \InvalidArgumentException for invalid or unsupported schemes.
  1213. */
  1214. public function withScheme($scheme);
  1215. /**
  1216. * Return an instance with the specified user information.
  1217. *
  1218. * This method MUST retain the state of the current instance, and return
  1219. * an instance that contains the specified user information.
  1220. *
  1221. * Password is optional, but the user information MUST include the
  1222. * user; an empty string for the user is equivalent to removing user
  1223. * information.
  1224. *
  1225. * @param string $user The user name to use for authority.
  1226. * @param null|string $password The password associated with $user.
  1227. * @return static A new instance with the specified user information.
  1228. */
  1229. public function withUserInfo($user, $password = null);
  1230. /**
  1231. * Return an instance with the specified host.
  1232. *
  1233. * This method MUST retain the state of the current instance, and return
  1234. * an instance that contains the specified host.
  1235. *
  1236. * An empty host value is equivalent to removing the host.
  1237. *
  1238. * @param string $host The hostname to use with the new instance.
  1239. * @return static A new instance with the specified host.
  1240. * @throws \InvalidArgumentException for invalid hostnames.
  1241. */
  1242. public function withHost($host);
  1243. /**
  1244. * Return an instance with the specified port.
  1245. *
  1246. * This method MUST retain the state of the current instance, and return
  1247. * an instance that contains the specified port.
  1248. *
  1249. * Implementations MUST raise an exception for ports outside the
  1250. * established TCP and UDP port ranges.
  1251. *
  1252. * A null value provided for the port is equivalent to removing the port
  1253. * information.
  1254. *
  1255. * @param null|int $port The port to use with the new instance; a null value
  1256. * removes the port information.
  1257. * @return static A new instance with the specified port.
  1258. * @throws \InvalidArgumentException for invalid ports.
  1259. */
  1260. public function withPort($port);
  1261. /**
  1262. * Return an instance with the specified path.
  1263. *
  1264. * This method MUST retain the state of the current instance, and return
  1265. * an instance that contains the specified path.
  1266. *
  1267. * The path can either be empty or absolute (starting with a slash) or
  1268. * rootless (not starting with a slash). Implementations MUST support all
  1269. * three syntaxes.
  1270. *
  1271. * If the path is intended to be domain-relative rather than path relative then
  1272. * it must begin with a slash ("/"). Paths not starting with a slash ("/")
  1273. * are assumed to be relative to some base path known to the application or
  1274. * consumer.
  1275. *
  1276. * Users can provide both encoded and decoded path characters.
  1277. * Implementations ensure the correct encoding as outlined in getPath().
  1278. *
  1279. * @param string $path The path to use with the new instance.
  1280. * @return static A new instance with the specified path.
  1281. * @throws \InvalidArgumentException for invalid paths.
  1282. */
  1283. public function withPath($path);
  1284. /**
  1285. * Return an instance with the specified query string.
  1286. *
  1287. * This method MUST retain the state of the current instance, and return
  1288. * an instance that contains the specified query string.
  1289. *
  1290. * Users can provide both encoded and decoded query characters.
  1291. * Implementations ensure the correct encoding as outlined in getQuery().
  1292. *
  1293. * An empty query string value is equivalent to removing the query string.
  1294. *
  1295. * @param string $query The query string to use with the new instance.
  1296. * @return static A new instance with the specified query string.
  1297. * @throws \InvalidArgumentException for invalid query strings.
  1298. */
  1299. public function withQuery($query);
  1300. /**
  1301. * Return an instance with the specified URI fragment.
  1302. *
  1303. * This method MUST retain the state of the current instance, and return
  1304. * an instance that contains the specified URI fragment.
  1305. *
  1306. * Users can provide both encoded and decoded fragment characters.
  1307. * Implementations ensure the correct encoding as outlined in getFragment().
  1308. *
  1309. * An empty fragment value is equivalent to removing the fragment.
  1310. *
  1311. * @param string $fragment The fragment to use with the new instance.
  1312. * @return static A new instance with the specified fragment.
  1313. */
  1314. public function withFragment($fragment);
  1315. /**
  1316. * Return the string representation as a URI reference.
  1317. *
  1318. * Depending on which components of the URI are present, the resulting
  1319. * string is either a full URI or relative reference according to RFC 3986,
  1320. * Section 4.1. The method concatenates the various components of the URI,
  1321. * using the appropriate delimiters:
  1322. *
  1323. * - If a scheme is present, it MUST be suffixed by ":".
  1324. * - If an authority is present, it MUST be prefixed by "//".
  1325. * - The path can be concatenated without delimiters. But there are two
  1326. * cases where the path has to be adjusted to make the URI reference
  1327. * valid as PHP does not allow to throw an exception in __toString():
  1328. * - If the path is rootless and an authority is present, the path MUST
  1329. * be prefixed by "/".
  1330. * - If the path is starting with more than one "/" and no authority is
  1331. * present, the starting slashes MUST be reduced to one.
  1332. * - If a query is present, it MUST be prefixed by "?".
  1333. * - If a fragment is present, it MUST be prefixed by "#".
  1334. *
  1335. * @see http://tools.ietf.org/html/rfc3986#section-4.1
  1336. * @return string
  1337. */
  1338. public function __toString();
  1339. }
  1340. }
  1341. // file: vendor/psr/http-server-handler/src/RequestHandlerInterface.php
  1342. namespace Psr\Http\Server {
  1343. use Psr\Http\Message\ResponseInterface;
  1344. use Psr\Http\Message\ServerRequestInterface;
  1345. /**
  1346. * Handles a server request and produces a response.
  1347. *
  1348. * An HTTP request handler process an HTTP request in order to produce an
  1349. * HTTP response.
  1350. */
  1351. interface RequestHandlerInterface
  1352. {
  1353. /**
  1354. * Handles a request and produces a response.
  1355. *
  1356. * May call other collaborating code to generate the response.
  1357. */
  1358. public function handle(ServerRequestInterface $request): ResponseInterface;
  1359. }
  1360. }
  1361. // file: vendor/psr/http-server-middleware/src/MiddlewareInterface.php
  1362. namespace Psr\Http\Server {
  1363. use Psr\Http\Message\ResponseInterface;
  1364. use Psr\Http\Message\ServerRequestInterface;
  1365. /**
  1366. * Participant in processing a server request and response.
  1367. *
  1368. * An HTTP middleware component participates in processing an HTTP message:
  1369. * by acting on the request, generating the response, or forwarding the
  1370. * request to a subsequent middleware and possibly acting on its response.
  1371. */
  1372. interface MiddlewareInterface
  1373. {
  1374. /**
  1375. * Process an incoming server request.
  1376. *
  1377. * Processes an incoming server request in order to produce a response.
  1378. * If unable to produce the response itself, it may delegate to the provided
  1379. * request handler to do so.
  1380. */
  1381. public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface;
  1382. }
  1383. }
  1384. // file: vendor/nyholm/psr7/src/Factory/Psr17Factory.php
  1385. namespace Nyholm\Psr7\Factory {
  1386. use Nyholm\Psr7\{Request, Response, ServerRequest, Stream, UploadedFile, Uri};
  1387. use Psr\Http\Message\{RequestFactoryInterface, RequestInterface, ResponseFactoryInterface, ResponseInterface, ServerRequestFactoryInterface, ServerRequestInterface, StreamFactoryInterface, StreamInterface, UploadedFileFactoryInterface, UploadedFileInterface, UriFactoryInterface, UriInterface};
  1388. /**
  1389. * @author Tobias Nyholm <tobias.nyholm@gmail.com>
  1390. * @author Martijn van der Ven <martijn@vanderven.se>
  1391. */
  1392. final class Psr17Factory implements RequestFactoryInterface, ResponseFactoryInterface, ServerRequestFactoryInterface, StreamFactoryInterface, UploadedFileFactoryInterface, UriFactoryInterface
  1393. {
  1394. public function createRequest(string $method, $uri): RequestInterface
  1395. {
  1396. return new Request($method, $uri);
  1397. }
  1398. public function createResponse(int $code = 200, string $reasonPhrase = ''): ResponseInterface
  1399. {
  1400. if (2 > \func_num_args()) {
  1401. // This will make the Response class to use a custom reasonPhrase
  1402. $reasonPhrase = null;
  1403. }
  1404. return new Response($code, [], null, '1.1', $reasonPhrase);
  1405. }
  1406. public function createStream(string $content = ''): StreamInterface
  1407. {
  1408. return Stream::create($content);
  1409. }
  1410. public function createStreamFromFile(string $filename, string $mode = 'r'): StreamInterface
  1411. {
  1412. $resource = @\fopen($filename, $mode);
  1413. if (false === $resource) {
  1414. if ('' === $mode || false === \in_array($mode[0], ['r', 'w', 'a', 'x', 'c'])) {
  1415. throw new \InvalidArgumentException('The mode ' . $mode . ' is invalid.');
  1416. }
  1417. throw new \RuntimeException('The file ' . $filename . ' cannot be opened.');
  1418. }
  1419. return Stream::create($resource);
  1420. }
  1421. public function createStreamFromResource($resource): StreamInterface
  1422. {
  1423. return Stream::create($resource);
  1424. }
  1425. public function createUploadedFile(StreamInterface $stream, int $size = null, int $error = \UPLOAD_ERR_OK, string $clientFilename = null, string $clientMediaType = null): UploadedFileInterface
  1426. {
  1427. if (null === $size) {
  1428. $size = $stream->getSize();
  1429. }
  1430. return new UploadedFile($stream, $size, $error, $clientFilename, $clientMediaType);
  1431. }
  1432. public function createUri(string $uri = ''): UriInterface
  1433. {
  1434. return new Uri($uri);
  1435. }
  1436. public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface
  1437. {
  1438. return new ServerRequest($method, $uri, [], null, '1.1', $serverParams);
  1439. }
  1440. }
  1441. }
  1442. // file: vendor/nyholm/psr7/src/MessageTrait.php
  1443. namespace Nyholm\Psr7 {
  1444. use Psr\Http\Message\StreamInterface;
  1445. /**
  1446. * Trait implementing functionality common to requests and responses.
  1447. *
  1448. * @author Michael Dowling and contributors to guzzlehttp/psr7
  1449. * @author Tobias Nyholm <tobias.nyholm@gmail.com>
  1450. * @author Martijn van der Ven <martijn@vanderven.se>
  1451. *
  1452. * @internal should not be used outside of Nyholm/Psr7 as it does not fall under our BC promise
  1453. */
  1454. trait MessageTrait
  1455. {
  1456. /** @var array Map of all registered headers, as original name => array of values */
  1457. private $headers = [];
  1458. /** @var array Map of lowercase header name => original name at registration */
  1459. private $headerNames = [];
  1460. /** @var string */
  1461. private $protocol = '1.1';
  1462. /** @var StreamInterface|null */
  1463. private $stream;
  1464. public function getProtocolVersion(): string
  1465. {
  1466. return $this->protocol;
  1467. }
  1468. public function withProtocolVersion($version): self
  1469. {
  1470. if ($this->protocol === $version) {
  1471. return $this;
  1472. }
  1473. $new = clone $this;
  1474. $new->protocol = $version;
  1475. return $new;
  1476. }
  1477. public function getHeaders(): array
  1478. {
  1479. return $this->headers;
  1480. }
  1481. public function hasHeader($header): bool
  1482. {
  1483. return isset($this->headerNames[\strtolower($header)]);
  1484. }
  1485. public function getHeader($header): array
  1486. {
  1487. $header = \strtolower($header);
  1488. if (!isset($this->headerNames[$header])) {
  1489. return [];
  1490. }
  1491. $header = $this->headerNames[$header];
  1492. return $this->headers[$header];
  1493. }
  1494. public function getHeaderLine($header): string
  1495. {
  1496. return \implode(', ', $this->getHeader($header));
  1497. }
  1498. public function withHeader($header, $value): self
  1499. {
  1500. $value = $this->validateAndTrimHeader($header, $value);
  1501. $normalized = \strtolower($header);
  1502. $new = clone $this;
  1503. if (isset($new->headerNames[$normalized])) {
  1504. unset($new->headers[$new->headerNames[$normalized]]);
  1505. }
  1506. $new->headerNames[$normalized] = $header;
  1507. $new->headers[$header] = $value;
  1508. return $new;
  1509. }
  1510. public function withAddedHeader($header, $value): self
  1511. {
  1512. if (!\is_string($header) || '' === $header) {
  1513. throw new \InvalidArgumentException('Header name must be an RFC 7230 compatible string.');
  1514. }
  1515. $new = clone $this;
  1516. $new->setHeaders([$header => $value]);
  1517. return $new;
  1518. }
  1519. public function withoutHeader($header): self
  1520. {
  1521. $normalized = \strtolower($header);
  1522. if (!isset($this->headerNames[$normalized])) {
  1523. return $this;
  1524. }
  1525. $header = $this->headerNames[$normalized];
  1526. $new = clone $this;
  1527. unset($new->headers[$header], $new->headerNames[$normalized]);
  1528. return $new;
  1529. }
  1530. public function getBody(): StreamInterface
  1531. {
  1532. if (null === $this->stream) {
  1533. $this->stream = Stream::create('');
  1534. }
  1535. return $this->stream;
  1536. }
  1537. public function withBody(StreamInterface $body): self
  1538. {
  1539. if ($body === $this->stream) {
  1540. return $this;
  1541. }
  1542. $new = clone $this;
  1543. $new->stream = $body;
  1544. return $new;
  1545. }
  1546. private function setHeaders(array $headers) /*:void*/
  1547. {
  1548. foreach ($headers as $header => $value) {
  1549. $value = $this->validateAndTrimHeader($header, $value);
  1550. $normalized = \strtolower($header);
  1551. if (isset($this->headerNames[$normalized])) {
  1552. $header = $this->headerNames[$normalized];
  1553. $this->headers[$header] = \array_merge($this->headers[$header], $value);
  1554. } else {
  1555. $this->headerNames[$normalized] = $header;
  1556. $this->headers[$header] = $value;
  1557. }
  1558. }
  1559. }
  1560. /**
  1561. * Make sure the header complies with RFC 7230.
  1562. *
  1563. * Header names must be a non-empty string consisting of token characters.
  1564. *
  1565. * Header values must be strings consisting of visible characters with all optional
  1566. * leading and trailing whitespace stripped. This method will always strip such
  1567. * optional whitespace. Note that the method does not allow folding whitespace within
  1568. * the values as this was deprecated for almost all instances by the RFC.
  1569. *
  1570. * header-field = field-name ":" OWS field-value OWS
  1571. * field-name = 1*( "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." / "^"
  1572. * / "_" / "`" / "|" / "~" / %x30-39 / ( %x41-5A / %x61-7A ) )
  1573. * OWS = *( SP / HTAB )
  1574. * field-value = *( ( %x21-7E / %x80-FF ) [ 1*( SP / HTAB ) ( %x21-7E / %x80-FF ) ] )
  1575. *
  1576. * @see https://tools.ietf.org/html/rfc7230#section-3.2.4
  1577. */
  1578. private function validateAndTrimHeader($header, $values): array
  1579. {
  1580. if (!\is_string($header) || 1 !== \preg_match("@^[!#$%&'*+.^_`|~0-9A-Za-z-]+$@", $header)) {
  1581. throw new \InvalidArgumentException('Header name must be an RFC 7230 compatible string.');
  1582. }
  1583. if (!\is_array($values)) {
  1584. // This is simple, just one value.
  1585. if ((!\is_numeric($values) && !\is_string($values)) || 1 !== \preg_match("@^[ \t\x21-\x7E\x80-\xFF]*$@", (string) $values)) {
  1586. throw new \InvalidArgumentException('Header values must be RFC 7230 compatible strings.');
  1587. }
  1588. return [\trim((string) $values, " \t")];
  1589. }
  1590. if (empty($values)) {
  1591. throw new \InvalidArgumentException('Header values must be a string or an array of strings, empty array given.');
  1592. }
  1593. // Assert Non empty array
  1594. $returnValues = [];
  1595. foreach ($values as $v) {
  1596. if ((!\is_numeric($v) && !\is_string($v)) || 1 !== \preg_match("@^[ \t\x21-\x7E\x80-\xFF]*$@", (string) $v)) {
  1597. throw new \InvalidArgumentException('Header values must be RFC 7230 compatible strings.');
  1598. }
  1599. $returnValues[] = \trim((string) $v, " \t");
  1600. }
  1601. return $returnValues;
  1602. }
  1603. }
  1604. }
  1605. // file: vendor/nyholm/psr7/src/Request.php
  1606. namespace Nyholm\Psr7 {
  1607. use Psr\Http\Message\{RequestInterface, StreamInterface, UriInterface};
  1608. /**
  1609. * @author Tobias Nyholm <tobias.nyholm@gmail.com>
  1610. * @author Martijn van der Ven <martijn@vanderven.se>
  1611. */
  1612. final class Request implements RequestInterface
  1613. {
  1614. use MessageTrait;
  1615. use RequestTrait;
  1616. /**
  1617. * @param string $method HTTP method
  1618. * @param string|UriInterface $uri URI
  1619. * @param array $headers Request headers
  1620. * @param string|resource|StreamInterface|null $body Request body
  1621. * @param string $version Protocol version
  1622. */
  1623. public function __construct(string $method, $uri, array $headers = [], $body = null, string $version = '1.1')
  1624. {
  1625. if (!($uri instanceof UriInterface)) {
  1626. $uri = new Uri($uri);
  1627. }
  1628. $this->method = $method;
  1629. $this->uri = $uri;
  1630. $this->setHeaders($headers);
  1631. $this->protocol = $version;
  1632. if (!$this->hasHeader('Host')) {
  1633. $this->updateHostFromUri();
  1634. }
  1635. // If we got no body, defer initialization of the stream until Request::getBody()
  1636. if ('' !== $body && null !== $body) {
  1637. $this->stream = Stream::create($body);
  1638. }
  1639. }
  1640. }
  1641. }
  1642. // file: vendor/nyholm/psr7/src/RequestTrait.php
  1643. namespace Nyholm\Psr7 {
  1644. use Psr\Http\Message\UriInterface;
  1645. /**
  1646. * @author Michael Dowling and contributors to guzzlehttp/psr7
  1647. * @author Tobias Nyholm <tobias.nyholm@gmail.com>
  1648. * @author Martijn van der Ven <martijn@vanderven.se>
  1649. *
  1650. * @internal should not be used outside of Nyholm/Psr7 as it does not fall under our BC promise
  1651. */
  1652. trait RequestTrait
  1653. {
  1654. /** @var string */
  1655. private $method;
  1656. /** @var string|null */
  1657. private $requestTarget;
  1658. /** @var UriInterface|null */
  1659. private $uri;
  1660. public function getRequestTarget(): string
  1661. {
  1662. if (null !== $this->requestTarget) {
  1663. return $this->requestTarget;
  1664. }
  1665. if ('' === $target = $this->uri->getPath()) {
  1666. $target = '/';
  1667. }
  1668. if ('' !== $this->uri->getQuery()) {
  1669. $target .= '?' . $this->uri->getQuery();
  1670. }
  1671. return $target;
  1672. }
  1673. public function withRequestTarget($requestTarget): self
  1674. {
  1675. if (\preg_match('#\s#', $requestTarget)) {
  1676. throw new \InvalidArgumentException('Invalid request target provided; cannot contain whitespace');
  1677. }
  1678. $new = clone $this;
  1679. $new->requestTarget = $requestTarget;
  1680. return $new;
  1681. }
  1682. public function getMethod(): string
  1683. {
  1684. return $this->method;
  1685. }
  1686. public function withMethod($method): self
  1687. {
  1688. if (!\is_string($method)) {
  1689. throw new \InvalidArgumentException('Method must be a string');
  1690. }
  1691. $new = clone $this;
  1692. $new->method = $method;
  1693. return $new;
  1694. }
  1695. public function getUri(): UriInterface
  1696. {
  1697. return $this->uri;
  1698. }
  1699. public function withUri(UriInterface $uri, $preserveHost = false): self
  1700. {
  1701. if ($uri === $this->uri) {
  1702. return $this;
  1703. }
  1704. $new = clone $this;
  1705. $new->uri = $uri;
  1706. if (!$preserveHost || !$this->hasHeader('Host')) {
  1707. $new->updateHostFromUri();
  1708. }
  1709. return $new;
  1710. }
  1711. private function updateHostFromUri() /*:void*/
  1712. {
  1713. if ('' === $host = $this->uri->getHost()) {
  1714. return;
  1715. }
  1716. if (null !== ($port = $this->uri->getPort())) {
  1717. $host .= ':' . $port;
  1718. }
  1719. if (isset($this->headerNames['host'])) {
  1720. $header = $this->headerNames['host'];
  1721. } else {
  1722. $this->headerNames['host'] = $header = 'Host';
  1723. }
  1724. // Ensure Host is the first header.
  1725. // See: http://tools.ietf.org/html/rfc7230#section-5.4
  1726. $this->headers = [$header => [$host]] + $this->headers;
  1727. }
  1728. }
  1729. }
  1730. // file: vendor/nyholm/psr7/src/Response.php
  1731. namespace Nyholm\Psr7 {
  1732. use Psr\Http\Message\{ResponseInterface, StreamInterface};
  1733. /**
  1734. * @author Michael Dowling and contributors to guzzlehttp/psr7
  1735. * @author Tobias Nyholm <tobias.nyholm@gmail.com>
  1736. * @author Martijn van der Ven <martijn@vanderven.se>
  1737. */
  1738. final class Response implements ResponseInterface
  1739. {
  1740. use MessageTrait;
  1741. /** @var array Map of standard HTTP status code/reason phrases */
  1742. /*private*/ const PHRASES = [
  1743. 100 => 'Continue', 101 => 'Switching Protocols', 102 => 'Processing',
  1744. 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content', 207 => 'Multi-status', 208 => 'Already Reported',
  1745. 300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 306 => 'Switch Proxy', 307 => 'Temporary Redirect',
  1746. 400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Time-out', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Large', 415 => 'Unsupported Media Type', 416 => 'Requested range not satisfiable', 417 => 'Expectation Failed', 418 => 'I\'m a teapot', 422 => 'Unprocessable Entity', 423 => 'Locked', 424 => 'Failed Dependency', 425 => 'Unordered Collection', 426 => 'Upgrade Required', 428 => 'Precondition Required', 429 => 'Too Many Requests', 431 => 'Request Header Fields Too Large', 451 => 'Unavailable For Legal Reasons',
  1747. 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Time-out', 505 => 'HTTP Version not supported', 506 => 'Variant Also Negotiates', 507 => 'Insufficient Storage', 508 => 'Loop Detected', 511 => 'Network Authentication Required',
  1748. ];
  1749. /** @var string */
  1750. private $reasonPhrase = '';
  1751. /** @var int */
  1752. private $statusCode;
  1753. /**
  1754. * @param int $status Status code
  1755. * @param array $headers Response headers
  1756. * @param string|resource|StreamInterface|null $body Response body
  1757. * @param string $version Protocol version
  1758. * @param string|null $reason Reason phrase (when empty a default will be used based on the status code)
  1759. */
  1760. public function __construct(int $status = 200, array $headers = [], $body = null, string $version = '1.1', string $reason = null)
  1761. {
  1762. // If we got no body, defer initialization of the stream until Response::getBody()
  1763. if ('' !== $body && null !== $body) {
  1764. $this->stream = Stream::create($body);
  1765. }
  1766. $this->statusCode = $status;
  1767. $this->setHeaders($headers);
  1768. if (null === $reason && isset(self::PHRASES[$this->statusCode])) {
  1769. $this->reasonPhrase = self::PHRASES[$status];
  1770. } else {
  1771. $this->reasonPhrase = $reason ?? '';
  1772. }
  1773. $this->protocol = $version;
  1774. }
  1775. public function getStatusCode(): int
  1776. {
  1777. return $this->statusCode;
  1778. }
  1779. public function getReasonPhrase(): string
  1780. {
  1781. return $this->reasonPhrase;
  1782. }
  1783. public function withStatus($code, $reasonPhrase = ''): self
  1784. {
  1785. if (!\is_int($code) && !\is_string($code)) {
  1786. throw new \InvalidArgumentException('Status code has to be an integer');
  1787. }
  1788. $code = (int) $code;
  1789. if ($code < 100 || $code > 599) {
  1790. throw new \InvalidArgumentException('Status code has to be an integer between 100 and 599');
  1791. }
  1792. $new = clone $this;
  1793. $new->statusCode = $code;
  1794. if ((null === $reasonPhrase || '' === $reasonPhrase) && isset(self::PHRASES[$new->statusCode])) {
  1795. $reasonPhrase = self::PHRASES[$new->statusCode];
  1796. }
  1797. $new->reasonPhrase = $reasonPhrase;
  1798. return $new;
  1799. }
  1800. }
  1801. }
  1802. // file: vendor/nyholm/psr7/src/ServerRequest.php
  1803. namespace Nyholm\Psr7 {
  1804. use Psr\Http\Message\{ServerRequestInterface, StreamInterface, UploadedFileInterface, UriInterface};
  1805. /**
  1806. * @author Michael Dowling and contributors to guzzlehttp/psr7
  1807. * @author Tobias Nyholm <tobias.nyholm@gmail.com>
  1808. * @author Martijn van der Ven <martijn@vanderven.se>
  1809. */
  1810. final class ServerRequest implements ServerRequestInterface
  1811. {
  1812. use MessageTrait;
  1813. use RequestTrait;
  1814. /** @var array */
  1815. private $attributes = [];
  1816. /** @var array */
  1817. private $cookieParams = [];
  1818. /** @var array|object|null */
  1819. private $parsedBody;
  1820. /** @var array */
  1821. private $queryParams = [];
  1822. /** @var array */
  1823. private $serverParams;
  1824. /** @var UploadedFileInterface[] */
  1825. private $uploadedFiles = [];
  1826. /**
  1827. * @param string $method HTTP method
  1828. * @param string|UriInterface $uri URI
  1829. * @param array $headers Request headers
  1830. * @param string|resource|StreamInterface|null $body Request body
  1831. * @param string $version Protocol version
  1832. * @param array $serverParams Typically the $_SERVER superglobal
  1833. */
  1834. public function __construct(string $method, $uri, array $headers = [], $body = null, string $version = '1.1', array $serverParams = [])
  1835. {
  1836. $this->serverParams = $serverParams;
  1837. if (!($uri instanceof UriInterface)) {
  1838. $uri = new Uri($uri);
  1839. }
  1840. $this->method = $method;
  1841. $this->uri = $uri;
  1842. $this->setHeaders($headers);
  1843. $this->protocol = $version;
  1844. if (!$this->hasHeader('Host')) {
  1845. $this->updateHostFromUri();
  1846. }
  1847. // If we got no body, defer initialization of the stream until ServerRequest::getBody()
  1848. if ('' !== $body && null !== $body) {
  1849. $this->stream = Stream::create($body);
  1850. }
  1851. }
  1852. public function getServerParams(): array
  1853. {
  1854. return $this->serverParams;
  1855. }
  1856. public function getUploadedFiles(): array
  1857. {
  1858. return $this->uploadedFiles;
  1859. }
  1860. public function withUploadedFiles(array $uploadedFiles)
  1861. {
  1862. $new = clone $this;
  1863. $new->uploadedFiles = $uploadedFiles;
  1864. return $new;
  1865. }
  1866. public function getCookieParams(): array
  1867. {
  1868. return $this->cookieParams;
  1869. }
  1870. public function withCookieParams(array $cookies)
  1871. {
  1872. $new = clone $this;
  1873. $new->cookieParams = $cookies;
  1874. return $new;
  1875. }
  1876. public function getQueryParams(): array
  1877. {
  1878. return $this->queryParams;
  1879. }
  1880. public function withQueryParams(array $query)
  1881. {
  1882. $new = clone $this;
  1883. $new->queryParams = $query;
  1884. return $new;
  1885. }
  1886. public function getParsedBody()
  1887. {
  1888. return $this->parsedBody;
  1889. }
  1890. public function withParsedBody($data)
  1891. {
  1892. if (!\is_array($data) && !\is_object($data) && null !== $data) {
  1893. throw new \InvalidArgumentException('First parameter to withParsedBody MUST be object, array or null');
  1894. }
  1895. $new = clone $this;
  1896. $new->parsedBody = $data;
  1897. return $new;
  1898. }
  1899. public function getAttributes(): array
  1900. {
  1901. return $this->attributes;
  1902. }
  1903. public function getAttribute($attribute, $default = null)
  1904. {
  1905. if (false === \array_key_exists($attribute, $this->attributes)) {
  1906. return $default;
  1907. }
  1908. return $this->attributes[$attribute];
  1909. }
  1910. public function withAttribute($attribute, $value): self
  1911. {
  1912. $new = clone $this;
  1913. $new->attributes[$attribute] = $value;
  1914. return $new;
  1915. }
  1916. public function withoutAttribute($attribute): self
  1917. {
  1918. if (false === \array_key_exists($attribute, $this->attributes)) {
  1919. return $this;
  1920. }
  1921. $new = clone $this;
  1922. unset($new->attributes[$attribute]);
  1923. return $new;
  1924. }
  1925. }
  1926. }
  1927. // file: vendor/nyholm/psr7/src/Stream.php
  1928. namespace Nyholm\Psr7 {
  1929. use Psr\Http\Message\StreamInterface;
  1930. /**
  1931. * @author Michael Dowling and contributors to guzzlehttp/psr7
  1932. * @author Tobias Nyholm <tobias.nyholm@gmail.com>
  1933. * @author Martijn van der Ven <martijn@vanderven.se>
  1934. */
  1935. final class Stream implements StreamInterface
  1936. {
  1937. /** @var resource|null A resource reference */
  1938. private $stream;
  1939. /** @var bool */
  1940. private $seekable;
  1941. /** @var bool */
  1942. private $readable;
  1943. /** @var bool */
  1944. private $writable;
  1945. /** @var array|mixed|void|null */
  1946. private $uri;
  1947. /** @var int|null */
  1948. private $size;
  1949. /** @var array Hash of readable and writable stream types */
  1950. /*private*/ const READ_WRITE_HASH = [
  1951. 'read' => [
  1952. 'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true,
  1953. 'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true,
  1954. 'c+b' => true, 'rt' => true, 'w+t' => true, 'r+t' => true,
  1955. 'x+t' => true, 'c+t' => true, 'a+' => true,
  1956. ],
  1957. 'write' => [
  1958. 'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true,
  1959. 'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true,
  1960. 'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true,
  1961. 'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true,
  1962. ],
  1963. ];
  1964. private function __construct()
  1965. {
  1966. }
  1967. /**
  1968. * Creates a new PSR-7 stream.
  1969. *
  1970. * @param string|resource|StreamInterface $body
  1971. *
  1972. * @return StreamInterface
  1973. *
  1974. * @throws \InvalidArgumentException
  1975. */
  1976. public static function create($body = ''): StreamInterface
  1977. {
  1978. if ($body instanceof StreamInterface) {
  1979. return $body;
  1980. }
  1981. if (\is_string($body)) {
  1982. $resource = \fopen('php://temp', 'rw+');
  1983. \fwrite($resource, $body);
  1984. $body = $resource;
  1985. }
  1986. if (\is_resource($body)) {
  1987. $new = new self();
  1988. $new->stream = $body;
  1989. $meta = \stream_get_meta_data($new->stream);
  1990. $new->seekable = $meta['seekable'] && 0 === \fseek($new->stream, 0, \SEEK_CUR);
  1991. $new->readable = isset(self::READ_WRITE_HASH['read'][$meta['mode']]);
  1992. $new->writable = isset(self::READ_WRITE_HASH['write'][$meta['mode']]);
  1993. $new->uri = $new->getMetadata('uri');
  1994. return $new;
  1995. }
  1996. throw new \InvalidArgumentException('First argument to Stream::create() must be a string, resource or StreamInterface.');
  1997. }
  1998. /**
  1999. * Closes the stream when the destructed.
  2000. */
  2001. public function __destruct()
  2002. {
  2003. $this->close();
  2004. }
  2005. public function __toString(): string
  2006. {
  2007. try {
  2008. if ($this->isSeekable()) {
  2009. $this->seek(0);
  2010. }
  2011. return $this->getContents();
  2012. } catch (\Exception $e) {
  2013. return '';
  2014. }
  2015. }
  2016. public function close() /*:void*/
  2017. {
  2018. if (isset($this->stream)) {
  2019. if (\is_resource($this->stream)) {
  2020. \fclose($this->stream);
  2021. }
  2022. $this->detach();
  2023. }
  2024. }
  2025. public function detach()
  2026. {
  2027. if (!isset($this->stream)) {
  2028. return null;
  2029. }
  2030. $result = $this->stream;
  2031. unset($this->stream);
  2032. $this->size = $this->uri = null;
  2033. $this->readable = $this->writable = $this->seekable = false;
  2034. return $result;
  2035. }
  2036. public function getSize() /*:?int*/
  2037. {
  2038. if (null !== $this->size) {
  2039. return $this->size;
  2040. }
  2041. if (!isset($this->stream)) {
  2042. return null;
  2043. }
  2044. // Clear the stat cache if the stream has a URI
  2045. if ($this->uri) {
  2046. \clearstatcache(true, $this->uri);
  2047. }
  2048. $stats = \fstat($this->stream);
  2049. if (isset($stats['size'])) {
  2050. $this->size = $stats['size'];
  2051. return $this->size;
  2052. }
  2053. return null;
  2054. }
  2055. public function tell(): int
  2056. {
  2057. if (false === $result = \ftell($this->stream)) {
  2058. throw new \RuntimeException('Unable to determine stream position');
  2059. }
  2060. return $result;
  2061. }
  2062. public function eof(): bool
  2063. {
  2064. return !$this->stream || \feof($this->stream);
  2065. }
  2066. public function isSeekable(): bool
  2067. {
  2068. return $this->seekable;
  2069. }
  2070. public function seek($offset, $whence = \SEEK_SET) /*:void*/
  2071. {
  2072. if (!$this->seekable) {
  2073. throw new \RuntimeException('Stream is not seekable');
  2074. }
  2075. if (-1 === \fseek($this->stream, $offset, $whence)) {
  2076. throw new \RuntimeException('Unable to seek to stream position ' . $offset . ' with whence ' . \var_export($whence, true));
  2077. }
  2078. }
  2079. public function rewind() /*:void*/
  2080. {
  2081. $this->seek(0);
  2082. }
  2083. public function isWritable(): bool
  2084. {
  2085. return $this->writable;
  2086. }
  2087. public function write($string): int
  2088. {
  2089. if (!$this->writable) {
  2090. throw new \RuntimeException('Cannot write to a non-writable stream');
  2091. }
  2092. // We can't know the size after writing anything
  2093. $this->size = null;
  2094. if (false === $result = \fwrite($this->stream, $string)) {
  2095. throw new \RuntimeException('Unable to write to stream');
  2096. }
  2097. return $result;
  2098. }
  2099. public function isReadable(): bool
  2100. {
  2101. return $this->readable;
  2102. }
  2103. public function read($length): string
  2104. {
  2105. if (!$this->readable) {
  2106. throw new \RuntimeException('Cannot read from non-readable stream');
  2107. }
  2108. return \fread($this->stream, $length);
  2109. }
  2110. public function getContents(): string
  2111. {
  2112. if (!isset($this->stream)) {
  2113. throw new \RuntimeException('Unable to read stream contents');
  2114. }
  2115. if (false === $contents = \stream_get_contents($this->stream)) {
  2116. throw new \RuntimeException('Unable to read stream contents');
  2117. }
  2118. return $contents;
  2119. }
  2120. public function getMetadata($key = null)
  2121. {
  2122. if (!isset($this->stream)) {
  2123. return $key ? null : [];
  2124. }
  2125. $meta = \stream_get_meta_data($this->stream);
  2126. if (null === $key) {
  2127. return $meta;
  2128. }
  2129. return $meta[$key] ?? null;
  2130. }
  2131. }
  2132. }
  2133. // file: vendor/nyholm/psr7/src/UploadedFile.php
  2134. namespace Nyholm\Psr7 {
  2135. use Psr\Http\Message\{StreamInterface, UploadedFileInterface};
  2136. /**
  2137. * @author Michael Dowling and contributors to guzzlehttp/psr7
  2138. * @author Tobias Nyholm <tobias.nyholm@gmail.com>
  2139. * @author Martijn van der Ven <martijn@vanderven.se>
  2140. */
  2141. final class UploadedFile implements UploadedFileInterface
  2142. {
  2143. /** @var array */
  2144. /*private*/ const ERRORS = [
  2145. \UPLOAD_ERR_OK => 1,
  2146. \UPLOAD_ERR_INI_SIZE => 1,
  2147. \UPLOAD_ERR_FORM_SIZE => 1,
  2148. \UPLOAD_ERR_PARTIAL => 1,
  2149. \UPLOAD_ERR_NO_FILE => 1,
  2150. \UPLOAD_ERR_NO_TMP_DIR => 1,
  2151. \UPLOAD_ERR_CANT_WRITE => 1,
  2152. \UPLOAD_ERR_EXTENSION => 1,
  2153. ];
  2154. /** @var string */
  2155. private $clientFilename;
  2156. /** @var string */
  2157. private $clientMediaType;
  2158. /** @var int */
  2159. private $error;
  2160. /** @var string|null */
  2161. private $file;
  2162. /** @var bool */
  2163. private $moved = false;
  2164. /** @var int */
  2165. private $size;
  2166. /** @var StreamInterface|null */
  2167. private $stream;
  2168. /**
  2169. * @param StreamInterface|string|resource $streamOrFile
  2170. * @param int $size
  2171. * @param int $errorStatus
  2172. * @param string|null $clientFilename
  2173. * @param string|null $clientMediaType
  2174. */
  2175. public function __construct($streamOrFile, $size, $errorStatus, $clientFilename = null, $clientMediaType = null)
  2176. {
  2177. if (false === \is_int($errorStatus) || !isset(self::ERRORS[$errorStatus])) {
  2178. throw new \InvalidArgumentException('Upload file error status must be an integer value and one of the "UPLOAD_ERR_*" constants.');
  2179. }
  2180. if (false === \is_int($size)) {
  2181. throw new \InvalidArgumentException('Upload file size must be an integer');
  2182. }
  2183. if (null !== $clientFilename && !\is_string($clientFilename)) {
  2184. throw new \InvalidArgumentException('Upload file client filename must be a string or null');
  2185. }
  2186. if (null !== $clientMediaType && !\is_string($clientMediaType)) {
  2187. throw new \InvalidArgumentException('Upload file client media type must be a string or null');
  2188. }
  2189. $this->error = $errorStatus;
  2190. $this->size = $size;
  2191. $this->clientFilename = $clientFilename;
  2192. $this->clientMediaType = $clientMediaType;
  2193. if (\UPLOAD_ERR_OK === $this->error) {
  2194. // Depending on the value set file or stream variable.
  2195. if (\is_string($streamOrFile)) {
  2196. $this->file = $streamOrFile;
  2197. } elseif (\is_resource($streamOrFile)) {
  2198. $this->stream = Stream::create($streamOrFile);
  2199. } elseif ($streamOrFile instanceof StreamInterface) {
  2200. $this->stream = $streamOrFile;
  2201. } else {
  2202. throw new \InvalidArgumentException('Invalid stream or file provided for UploadedFile');
  2203. }
  2204. }
  2205. }
  2206. /**
  2207. * @throws \RuntimeException if is moved or not ok
  2208. */
  2209. private function validateActive() /*:void*/
  2210. {
  2211. if (\UPLOAD_ERR_OK !== $this->error) {
  2212. throw new \RuntimeException('Cannot retrieve stream due to upload error');
  2213. }
  2214. if ($this->moved) {
  2215. throw new \RuntimeException('Cannot retrieve stream after it has already been moved');
  2216. }
  2217. }
  2218. public function getStream(): StreamInterface
  2219. {
  2220. $this->validateActive();
  2221. if ($this->stream instanceof StreamInterface) {
  2222. return $this->stream;
  2223. }
  2224. $resource = \fopen($this->file, 'r');
  2225. return Stream::create($resource);
  2226. }
  2227. public function moveTo($targetPath) /*:void*/
  2228. {
  2229. $this->validateActive();
  2230. if (!\is_string($targetPath) || '' === $targetPath) {
  2231. throw new \InvalidArgumentException('Invalid path provided for move operation; must be a non-empty string');
  2232. }
  2233. if (null !== $this->file) {
  2234. $this->moved = 'cli' === \PHP_SAPI ? \rename($this->file, $targetPath) : \move_uploaded_file($this->file, $targetPath);
  2235. } else {
  2236. $stream = $this->getStream();
  2237. if ($stream->isSeekable()) {
  2238. $stream->rewind();
  2239. }
  2240. // Copy the contents of a stream into another stream until end-of-file.
  2241. $dest = Stream::create(\fopen($targetPath, 'w'));
  2242. while (!$stream->eof()) {
  2243. if (!$dest->write($stream->read(1048576))) {
  2244. break;
  2245. }
  2246. }
  2247. $this->moved = true;
  2248. }
  2249. if (false === $this->moved) {
  2250. throw new \RuntimeException(\sprintf('Uploaded file could not be moved to %s', $targetPath));
  2251. }
  2252. }
  2253. public function getSize(): int
  2254. {
  2255. return $this->size;
  2256. }
  2257. public function getError(): int
  2258. {
  2259. return $this->error;
  2260. }
  2261. public function getClientFilename() /*:?string*/
  2262. {
  2263. return $this->clientFilename;
  2264. }
  2265. public function getClientMediaType() /*:?string*/
  2266. {
  2267. return $this->clientMediaType;
  2268. }
  2269. }
  2270. }
  2271. // file: vendor/nyholm/psr7/src/Uri.php
  2272. namespace Nyholm\Psr7 {
  2273. use Psr\Http\Message\UriInterface;
  2274. /**
  2275. * PSR-7 URI implementation.
  2276. *
  2277. * @author Michael Dowling
  2278. * @author Tobias Schultze
  2279. * @author Matthew Weier O'Phinney
  2280. * @author Tobias Nyholm <tobias.nyholm@gmail.com>
  2281. * @author Martijn van der Ven <martijn@vanderven.se>
  2282. */
  2283. final class Uri implements UriInterface
  2284. {
  2285. /*private*/ const SCHEMES = ['http' => 80, 'https' => 443];
  2286. /*private*/ const CHAR_UNRESERVED = 'a-zA-Z0-9_\-\.~';
  2287. /*private*/ const CHAR_SUB_DELIMS = '!\$&\'\(\)\*\+,;=';
  2288. /** @var string Uri scheme. */
  2289. private $scheme = '';
  2290. /** @var string Uri user info. */
  2291. private $userInfo = '';
  2292. /** @var string Uri host. */
  2293. private $host = '';
  2294. /** @var int|null Uri port. */
  2295. private $port;
  2296. /** @var string Uri path. */
  2297. private $path = '';
  2298. /** @var string Uri query string. */
  2299. private $query = '';
  2300. /** @var string Uri fragment. */
  2301. private $fragment = '';
  2302. public function __construct(string $uri = '')
  2303. {
  2304. if ('' !== $uri) {
  2305. if (false === $parts = \parse_url($uri)) {
  2306. throw new \InvalidArgumentException("Unable to parse URI: $uri");
  2307. }
  2308. // Apply parse_url parts to a URI.
  2309. $this->scheme = isset($parts['scheme']) ? \strtolower($parts['scheme']) : '';
  2310. $this->userInfo = $parts['user'] ?? '';
  2311. $this->host = isset($parts['host']) ? \strtolower($parts['host']) : '';
  2312. $this->port = isset($parts['port']) ? $this->filterPort($parts['port']) : null;
  2313. $this->path = isset($parts['path']) ? $this->filterPath($parts['path']) : '';
  2314. $this->query = isset($parts['query']) ? $this->filterQueryAndFragment($parts['query']) : '';
  2315. $this->fragment = isset($parts['fragment']) ? $this->filterQueryAndFragment($parts['fragment']) : '';
  2316. if (isset($parts['pass'])) {
  2317. $this->userInfo .= ':' . $parts['pass'];
  2318. }
  2319. }
  2320. }
  2321. public function __toString(): string
  2322. {
  2323. return self::createUriString($this->scheme, $this->getAuthority(), $this->path, $this->query, $this->fragment);
  2324. }
  2325. public function getScheme(): string
  2326. {
  2327. return $this->scheme;
  2328. }
  2329. public function getAuthority(): string
  2330. {
  2331. if ('' === $this->host) {
  2332. return '';
  2333. }
  2334. $authority = $this->host;
  2335. if ('' !== $this->userInfo) {
  2336. $authority = $this->userInfo . '@' . $authority;
  2337. }
  2338. if (null !== $this->port) {
  2339. $authority .= ':' . $this->port;
  2340. }
  2341. return $authority;
  2342. }
  2343. public function getUserInfo(): string
  2344. {
  2345. return $this->userInfo;
  2346. }
  2347. public function getHost(): string
  2348. {
  2349. return $this->host;
  2350. }
  2351. public function getPort() /*:?int*/
  2352. {
  2353. return $this->port;
  2354. }
  2355. public function getPath(): string
  2356. {
  2357. return $this->path;
  2358. }
  2359. public function getQuery(): string
  2360. {
  2361. return $this->query;
  2362. }
  2363. public function getFragment(): string
  2364. {
  2365. return $this->fragment;
  2366. }
  2367. public function withScheme($scheme): self
  2368. {
  2369. if (!\is_string($scheme)) {
  2370. throw new \InvalidArgumentException('Scheme must be a string');
  2371. }
  2372. if ($this->scheme === $scheme = \strtolower($scheme)) {
  2373. return $this;
  2374. }
  2375. $new = clone $this;
  2376. $new->scheme = $scheme;
  2377. $new->port = $new->filterPort($new->port);
  2378. return $new;
  2379. }
  2380. public function withUserInfo($user, $password = null): self
  2381. {
  2382. $info = $user;
  2383. if (null !== $password && '' !== $password) {
  2384. $info .= ':' . $password;
  2385. }
  2386. if ($this->userInfo === $info) {
  2387. return $this;
  2388. }
  2389. $new = clone $this;
  2390. $new->userInfo = $info;
  2391. return $new;
  2392. }
  2393. public function withHost($host): self
  2394. {
  2395. if (!\is_string($host)) {
  2396. throw new \InvalidArgumentException('Host must be a string');
  2397. }
  2398. if ($this->host === $host = \strtolower($host)) {
  2399. return $this;
  2400. }
  2401. $new = clone $this;
  2402. $new->host = $host;
  2403. return $new;
  2404. }
  2405. public function withPort($port): self
  2406. {
  2407. if ($this->port === $port = $this->filterPort($port)) {
  2408. return $this;
  2409. }
  2410. $new = clone $this;
  2411. $new->port = $port;
  2412. return $new;
  2413. }
  2414. public function withPath($path): self
  2415. {
  2416. if ($this->path === $path = $this->filterPath($path)) {
  2417. return $this;
  2418. }
  2419. $new = clone $this;
  2420. $new->path = $path;
  2421. return $new;
  2422. }
  2423. public function withQuery($query): self
  2424. {
  2425. if ($this->query === $query = $this->filterQueryAndFragment($query)) {
  2426. return $this;
  2427. }
  2428. $new = clone $this;
  2429. $new->query = $query;
  2430. return $new;
  2431. }
  2432. public function withFragment($fragment): self
  2433. {
  2434. if ($this->fragment === $fragment = $this->filterQueryAndFragment($fragment)) {
  2435. return $this;
  2436. }
  2437. $new = clone $this;
  2438. $new->fragment = $fragment;
  2439. return $new;
  2440. }
  2441. /**
  2442. * Create a URI string from its various parts.
  2443. */
  2444. private static function createUriString(string $scheme, string $authority, string $path, string $query, string $fragment): string
  2445. {
  2446. $uri = '';
  2447. if ('' !== $scheme) {
  2448. $uri .= $scheme . ':';
  2449. }
  2450. if ('' !== $authority) {
  2451. $uri .= '//' . $authority;
  2452. }
  2453. if ('' !== $path) {
  2454. if ('/' !== $path[0]) {
  2455. if ('' !== $authority) {
  2456. // If the path is rootless and an authority is present, the path MUST be prefixed by "/"
  2457. $path = '/' . $path;
  2458. }
  2459. } elseif (isset($path[1]) && '/' === $path[1]) {
  2460. if ('' === $authority) {
  2461. // If the path is starting with more than one "/" and no authority is present, the
  2462. // starting slashes MUST be reduced to one.
  2463. $path = '/' . \ltrim($path, '/');
  2464. }
  2465. }
  2466. $uri .= $path;
  2467. }
  2468. if ('' !== $query) {
  2469. $uri .= '?' . $query;
  2470. }
  2471. if ('' !== $fragment) {
  2472. $uri .= '#' . $fragment;
  2473. }
  2474. return $uri;
  2475. }
  2476. /**
  2477. * Is a given port non-standard for the current scheme?
  2478. */
  2479. private static function isNonStandardPort(string $scheme, int $port): bool
  2480. {
  2481. return !isset(self::SCHEMES[$scheme]) || $port !== self::SCHEMES[$scheme];
  2482. }
  2483. private function filterPort($port) /*:?int*/
  2484. {
  2485. if (null === $port) {
  2486. return null;
  2487. }
  2488. $port = (int) $port;
  2489. if (0 > $port || 0xffff < $port) {
  2490. throw new \InvalidArgumentException(\sprintf('Invalid port: %d. Must be between 0 and 65535', $port));
  2491. }
  2492. return self::isNonStandardPort($this->scheme, $port) ? $port : null;
  2493. }
  2494. private function filterPath($path): string
  2495. {
  2496. if (!\is_string($path)) {
  2497. throw new \InvalidArgumentException('Path must be a string');
  2498. }
  2499. return \preg_replace_callback('/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:@\/]++|%(?![A-Fa-f0-9]{2}))/', [__CLASS__, 'rawurlencodeMatchZero'], $path);
  2500. }
  2501. private function filterQueryAndFragment($str): string
  2502. {
  2503. if (!\is_string($str)) {
  2504. throw new \InvalidArgumentException('Query and fragment must be a string');
  2505. }
  2506. return \preg_replace_callback('/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/', [__CLASS__, 'rawurlencodeMatchZero'], $str);
  2507. }
  2508. private static function rawurlencodeMatchZero(array $match): string
  2509. {
  2510. return \rawurlencode($match[0]);
  2511. }
  2512. }
  2513. }
  2514. // file: vendor/nyholm/psr7-server/src/ServerRequestCreator.php
  2515. namespace Nyholm\Psr7Server {
  2516. use Psr\Http\Message\ServerRequestFactoryInterface;
  2517. use Psr\Http\Message\ServerRequestInterface;
  2518. use Psr\Http\Message\StreamFactoryInterface;
  2519. use Psr\Http\Message\StreamInterface;
  2520. use Psr\Http\Message\UploadedFileFactoryInterface;
  2521. use Psr\Http\Message\UploadedFileInterface;
  2522. use Psr\Http\Message\UriFactoryInterface;
  2523. use Psr\Http\Message\UriInterface;
  2524. /**
  2525. * @author Tobias Nyholm <tobias.nyholm@gmail.com>
  2526. * @author Martijn van der Ven <martijn@vanderven.se>
  2527. */
  2528. final class ServerRequestCreator implements ServerRequestCreatorInterface
  2529. {
  2530. private $serverRequestFactory;
  2531. private $uriFactory;
  2532. private $uploadedFileFactory;
  2533. private $streamFactory;
  2534. public function __construct(
  2535. ServerRequestFactoryInterface $serverRequestFactory,
  2536. UriFactoryInterface $uriFactory,
  2537. UploadedFileFactoryInterface $uploadedFileFactory,
  2538. StreamFactoryInterface $streamFactory
  2539. ) {
  2540. $this->serverRequestFactory = $serverRequestFactory;
  2541. $this->uriFactory = $uriFactory;
  2542. $this->uploadedFileFactory = $uploadedFileFactory;
  2543. $this->streamFactory = $streamFactory;
  2544. }
  2545. /**
  2546. * {@inheritdoc}
  2547. */
  2548. public function fromGlobals(): ServerRequestInterface
  2549. {
  2550. $server = $_SERVER;
  2551. if (false === isset($server['REQUEST_METHOD'])) {
  2552. $server['REQUEST_METHOD'] = 'GET';
  2553. }
  2554. $headers = \function_exists('getallheaders') ? getallheaders() : static::getHeadersFromServer($_SERVER);
  2555. return $this->fromArrays($server, $headers, $_COOKIE, $_GET, $_POST, $_FILES, \fopen('php://input', 'r') ?: null);
  2556. }
  2557. /**
  2558. * {@inheritdoc}
  2559. */
  2560. public function fromArrays(array $server, array $headers = [], array $cookie = [], array $get = [], array $post = [], array $files = [], $body = null): ServerRequestInterface
  2561. {
  2562. $method = $this->getMethodFromEnv($server);
  2563. $uri = $this->getUriFromEnvWithHTTP($server);
  2564. $protocol = isset($server['SERVER_PROTOCOL']) ? \str_replace('HTTP/', '', $server['SERVER_PROTOCOL']) : '1.1';
  2565. $serverRequest = $this->serverRequestFactory->createServerRequest($method, $uri, $server);
  2566. foreach ($headers as $name => $value) {
  2567. $serverRequest = $serverRequest->withAddedHeader($name, $value);
  2568. }
  2569. $serverRequest = $serverRequest
  2570. ->withProtocolVersion($protocol)
  2571. ->withCookieParams($cookie)
  2572. ->withQueryParams($get)
  2573. ->withParsedBody($post)
  2574. ->withUploadedFiles($this->normalizeFiles($files));
  2575. if (null === $body) {
  2576. return $serverRequest;
  2577. }
  2578. if (\is_resource($body)) {
  2579. $body = $this->streamFactory->createStreamFromResource($body);
  2580. } elseif (\is_string($body)) {
  2581. $body = $this->streamFactory->createStream($body);
  2582. } elseif (!$body instanceof StreamInterface) {
  2583. throw new \InvalidArgumentException('The $body parameter to ServerRequestCreator::fromArrays must be string, resource or StreamInterface');
  2584. }
  2585. return $serverRequest->withBody($body);
  2586. }
  2587. /**
  2588. * Implementation from Zend\Diactoros\marshalHeadersFromSapi().
  2589. */
  2590. public static function getHeadersFromServer(array $server): array
  2591. {
  2592. $headers = [];
  2593. foreach ($server as $key => $value) {
  2594. // Apache prefixes environment variables with REDIRECT_
  2595. // if they are added by rewrite rules
  2596. if (0 === \strpos($key, 'REDIRECT_')) {
  2597. $key = \substr($key, 9);
  2598. // We will not overwrite existing variables with the
  2599. // prefixed versions, though
  2600. if (\array_key_exists($key, $server)) {
  2601. continue;
  2602. }
  2603. }
  2604. if ($value && 0 === \strpos($key, 'HTTP_')) {
  2605. $name = \strtr(\strtolower(\substr($key, 5)), '_', '-');
  2606. $headers[$name] = $value;
  2607. continue;
  2608. }
  2609. if ($value && 0 === \strpos($key, 'CONTENT_')) {
  2610. $name = 'content-'.\strtolower(\substr($key, 8));
  2611. $headers[$name] = $value;
  2612. continue;
  2613. }
  2614. }
  2615. return $headers;
  2616. }
  2617. private function getMethodFromEnv(array $environment): string
  2618. {
  2619. if (false === isset($environment['REQUEST_METHOD'])) {
  2620. throw new \InvalidArgumentException('Cannot determine HTTP method');
  2621. }
  2622. return $environment['REQUEST_METHOD'];
  2623. }
  2624. private function getUriFromEnvWithHTTP(array $environment): UriInterface
  2625. {
  2626. $uri = $this->createUriFromArray($environment);
  2627. if (empty($uri->getScheme())) {
  2628. $uri = $uri->withScheme('http');
  2629. }
  2630. return $uri;
  2631. }
  2632. /**
  2633. * Return an UploadedFile instance array.
  2634. *
  2635. * @param array $files A array which respect $_FILES structure
  2636. *
  2637. * @return UploadedFileInterface[]
  2638. *
  2639. * @throws \InvalidArgumentException for unrecognized values
  2640. */
  2641. private function normalizeFiles(array $files): array
  2642. {
  2643. $normalized = [];
  2644. foreach ($files as $key => $value) {
  2645. if ($value instanceof UploadedFileInterface) {
  2646. $normalized[$key] = $value;
  2647. } elseif (\is_array($value) && isset($value['tmp_name'])) {
  2648. $normalized[$key] = $this->createUploadedFileFromSpec($value);
  2649. } elseif (\is_array($value)) {
  2650. $normalized[$key] = $this->normalizeFiles($value);
  2651. } else {
  2652. throw new \InvalidArgumentException('Invalid value in files specification');
  2653. }
  2654. }
  2655. return $normalized;
  2656. }
  2657. /**
  2658. * Create and return an UploadedFile instance from a $_FILES specification.
  2659. *
  2660. * If the specification represents an array of values, this method will
  2661. * delegate to normalizeNestedFileSpec() and return that return value.
  2662. *
  2663. * @param array $value $_FILES struct
  2664. *
  2665. * @return array|UploadedFileInterface
  2666. */
  2667. private function createUploadedFileFromSpec(array $value)
  2668. {
  2669. if (\is_array($value['tmp_name'])) {
  2670. return $this->normalizeNestedFileSpec($value);
  2671. }
  2672. try {
  2673. $stream = $this->streamFactory->createStreamFromFile($value['tmp_name']);
  2674. } catch (\RuntimeException $e) {
  2675. $stream = $this->streamFactory->createStream();
  2676. }
  2677. return $this->uploadedFileFactory->createUploadedFile(
  2678. $stream,
  2679. (int) $value['size'],
  2680. (int) $value['error'],
  2681. $value['name'],
  2682. $value['type']
  2683. );
  2684. }
  2685. /**
  2686. * Normalize an array of file specifications.
  2687. *
  2688. * Loops through all nested files and returns a normalized array of
  2689. * UploadedFileInterface instances.
  2690. *
  2691. * @param array $files
  2692. *
  2693. * @return UploadedFileInterface[]
  2694. */
  2695. private function normalizeNestedFileSpec(array $files = []): array
  2696. {
  2697. $normalizedFiles = [];
  2698. foreach (\array_keys($files['tmp_name']) as $key) {
  2699. $spec = [
  2700. 'tmp_name' => $files['tmp_name'][$key],
  2701. 'size' => $files['size'][$key],
  2702. 'error' => $files['error'][$key],
  2703. 'name' => $files['name'][$key],
  2704. 'type' => $files['type'][$key],
  2705. ];
  2706. $normalizedFiles[$key] = $this->createUploadedFileFromSpec($spec);
  2707. }
  2708. return $normalizedFiles;
  2709. }
  2710. /**
  2711. * Create a new uri from server variable.
  2712. *
  2713. * @param array $server typically $_SERVER or similar structure
  2714. */
  2715. private function createUriFromArray(array $server): UriInterface
  2716. {
  2717. $uri = $this->uriFactory->createUri('');
  2718. if (isset($server['HTTP_X_FORWARDED_PROTO'])) {
  2719. $uri = $uri->withScheme($server['HTTP_X_FORWARDED_PROTO']);
  2720. } else {
  2721. if (isset($server['REQUEST_SCHEME'])) {
  2722. $uri = $uri->withScheme($server['REQUEST_SCHEME']);
  2723. } elseif (isset($server['HTTPS'])) {
  2724. $uri = $uri->withScheme('on' === $server['HTTPS'] ? 'https' : 'http');
  2725. }
  2726. if (isset($server['SERVER_PORT'])) {
  2727. $uri = $uri->withPort($server['SERVER_PORT']);
  2728. }
  2729. }
  2730. if (isset($server['HTTP_HOST'])) {
  2731. if (1 === \preg_match('/^(.+)\:(\d+)$/', $server['HTTP_HOST'], $matches)) {
  2732. $uri = $uri->withHost($matches[1])->withPort($matches[2]);
  2733. } else {
  2734. $uri = $uri->withHost($server['HTTP_HOST']);
  2735. }
  2736. } elseif (isset($server['SERVER_NAME'])) {
  2737. $uri = $uri->withHost($server['SERVER_NAME']);
  2738. }
  2739. if (isset($server['REQUEST_URI'])) {
  2740. $uri = $uri->withPath(\current(\explode('?', $server['REQUEST_URI'])));
  2741. }
  2742. if (isset($server['QUERY_STRING'])) {
  2743. $uri = $uri->withQuery($server['QUERY_STRING']);
  2744. }
  2745. return $uri;
  2746. }
  2747. }
  2748. }
  2749. // file: vendor/nyholm/psr7-server/src/ServerRequestCreatorInterface.php
  2750. namespace Nyholm\Psr7Server {
  2751. use Psr\Http\Message\ServerRequestInterface;
  2752. use Psr\Http\Message\StreamInterface;
  2753. /**
  2754. * @author Tobias Nyholm <tobias.nyholm@gmail.com>
  2755. * @author Martijn van der Ven <martijn@vanderven.se>
  2756. */
  2757. interface ServerRequestCreatorInterface
  2758. {
  2759. /**
  2760. * Create a new server request from the current environment variables.
  2761. * Defaults to a GET request to minimise the risk of an \InvalidArgumentException.
  2762. * Includes the current request headers as supplied by the server through `getallheaders()`.
  2763. * If `getallheaders()` is unavailable on the current server it will fallback to its own `getHeadersFromServer()` method.
  2764. * Defaults to php://input for the request body.
  2765. *
  2766. * @throws \InvalidArgumentException if no valid method or URI can be determined
  2767. */
  2768. public function fromGlobals(): ServerRequestInterface;
  2769. /**
  2770. * Create a new server request from a set of arrays.
  2771. *
  2772. * @param array $server typically $_SERVER or similar structure
  2773. * @param array $headers typically the output of getallheaders() or similar structure
  2774. * @param array $cookie typically $_COOKIE or similar structure
  2775. * @param array $get typically $_GET or similar structure
  2776. * @param array $post typically $_POST or similar structure
  2777. * @param array $files typically $_FILES or similar structure
  2778. * @param StreamInterface|resource|string|null $body Typically stdIn
  2779. *
  2780. * @throws \InvalidArgumentException if no valid method or URI can be determined
  2781. */
  2782. public function fromArrays(
  2783. array $server,
  2784. array $headers = [],
  2785. array $cookie = [],
  2786. array $get = [],
  2787. array $post = [],
  2788. array $files = [],
  2789. $body = null
  2790. ): ServerRequestInterface;
  2791. /**
  2792. * Get parsed headers from ($_SERVER) array.
  2793. *
  2794. * @param array $server typically $_SERVER or similar structure
  2795. *
  2796. * @return array
  2797. */
  2798. public static function getHeadersFromServer(array $server): array;
  2799. }
  2800. }
  2801. // file: src/Tqdev/PhpCrudApi/Cache/Cache.php
  2802. namespace Tqdev\PhpCrudApi\Cache {
  2803. interface Cache
  2804. {
  2805. public function set(string $key, string $value, int $ttl = 0): bool;
  2806. public function get(string $key): string;
  2807. public function clear(): bool;
  2808. }
  2809. }
  2810. // file: src/Tqdev/PhpCrudApi/Cache/CacheFactory.php
  2811. namespace Tqdev\PhpCrudApi\Cache {
  2812. class CacheFactory
  2813. {
  2814. public static function create(string $type, string $prefix, string $config): Cache
  2815. {
  2816. switch ($type) {
  2817. case 'TempFile':
  2818. $cache = new TempFileCache($prefix, $config);
  2819. break;
  2820. case 'Redis':
  2821. $cache = new RedisCache($prefix, $config);
  2822. break;
  2823. case 'Memcache':
  2824. $cache = new MemcacheCache($prefix, $config);
  2825. break;
  2826. case 'Memcached':
  2827. $cache = new MemcachedCache($prefix, $config);
  2828. break;
  2829. default:
  2830. $cache = new NoCache();
  2831. }
  2832. return $cache;
  2833. }
  2834. }
  2835. }
  2836. // file: src/Tqdev/PhpCrudApi/Cache/MemcacheCache.php
  2837. namespace Tqdev\PhpCrudApi\Cache {
  2838. class MemcacheCache implements Cache
  2839. {
  2840. protected $prefix;
  2841. protected $memcache;
  2842. public function __construct(string $prefix, string $config)
  2843. {
  2844. $this->prefix = $prefix;
  2845. if ($config == '') {
  2846. $address = 'localhost';
  2847. $port = 11211;
  2848. } elseif (strpos($config, ':') === false) {
  2849. $address = $config;
  2850. $port = 11211;
  2851. } else {
  2852. list($address, $port) = explode(':', $config);
  2853. }
  2854. $this->memcache = $this->create();
  2855. $this->memcache->addServer($address, $port);
  2856. }
  2857. protected function create() /*: \Memcache*/
  2858. {
  2859. return new \Memcache();
  2860. }
  2861. public function set(string $key, string $value, int $ttl = 0): bool
  2862. {
  2863. return $this->memcache->set($this->prefix . $key, $value, 0, $ttl);
  2864. }
  2865. public function get(string $key): string
  2866. {
  2867. return $this->memcache->get($this->prefix . $key) ?: '';
  2868. }
  2869. public function clear(): bool
  2870. {
  2871. return $this->memcache->flush();
  2872. }
  2873. }
  2874. }
  2875. // file: src/Tqdev/PhpCrudApi/Cache/MemcachedCache.php
  2876. namespace Tqdev\PhpCrudApi\Cache {
  2877. class MemcachedCache extends MemcacheCache
  2878. {
  2879. protected function create() /*: \Memcached*/
  2880. {
  2881. return new \Memcached();
  2882. }
  2883. public function set(string $key, string $value, int $ttl = 0): bool
  2884. {
  2885. return $this->memcache->set($this->prefix . $key, $value, $ttl);
  2886. }
  2887. }
  2888. }
  2889. // file: src/Tqdev/PhpCrudApi/Cache/NoCache.php
  2890. namespace Tqdev\PhpCrudApi\Cache {
  2891. class NoCache implements Cache
  2892. {
  2893. public function __construct()
  2894. {
  2895. }
  2896. public function set(string $key, string $value, int $ttl = 0): bool
  2897. {
  2898. return true;
  2899. }
  2900. public function get(string $key): string
  2901. {
  2902. return '';
  2903. }
  2904. public function clear(): bool
  2905. {
  2906. return true;
  2907. }
  2908. }
  2909. }
  2910. // file: src/Tqdev/PhpCrudApi/Cache/RedisCache.php
  2911. namespace Tqdev\PhpCrudApi\Cache {
  2912. class RedisCache implements Cache
  2913. {
  2914. protected $prefix;
  2915. protected $redis;
  2916. public function __construct(string $prefix, string $config)
  2917. {
  2918. $this->prefix = $prefix;
  2919. if ($config == '') {
  2920. $config = '127.0.0.1';
  2921. }
  2922. $params = explode(':', $config, 6);
  2923. if (isset($params[3])) {
  2924. $params[3] = null;
  2925. }
  2926. $this->redis = new \Redis();
  2927. call_user_func_array(array($this->redis, 'pconnect'), $params);
  2928. }
  2929. public function set(string $key, string $value, int $ttl = 0): bool
  2930. {
  2931. return $this->redis->set($this->prefix . $key, $value, $ttl);
  2932. }
  2933. public function get(string $key): string
  2934. {
  2935. return $this->redis->get($this->prefix . $key) ?: '';
  2936. }
  2937. public function clear(): bool
  2938. {
  2939. return $this->redis->flushDb();
  2940. }
  2941. }
  2942. }
  2943. // file: src/Tqdev/PhpCrudApi/Cache/TempFileCache.php
  2944. namespace Tqdev\PhpCrudApi\Cache {
  2945. class TempFileCache implements Cache
  2946. {
  2947. const SUFFIX = 'cache';
  2948. private $path;
  2949. private $segments;
  2950. public function __construct(string $prefix, string $config)
  2951. {
  2952. $this->segments = [];
  2953. $s = DIRECTORY_SEPARATOR;
  2954. $ps = PATH_SEPARATOR;
  2955. if ($config == '') {
  2956. $this->path = sys_get_temp_dir() . $s . $prefix . self::SUFFIX;
  2957. } elseif (strpos($config, $ps) === false) {
  2958. $this->path = $config;
  2959. } else {
  2960. list($path, $segments) = explode($ps, $config);
  2961. $this->path = $path;
  2962. $this->segments = explode(',', $segments);
  2963. }
  2964. if (file_exists($this->path) && is_dir($this->path)) {
  2965. $this->clean($this->path, array_filter($this->segments), strlen(md5('')), false);
  2966. }
  2967. }
  2968. private function getFileName(string $key): string
  2969. {
  2970. $s = DIRECTORY_SEPARATOR;
  2971. $md5 = md5($key);
  2972. $filename = rtrim($this->path, $s) . $s;
  2973. $i = 0;
  2974. foreach ($this->segments as $segment) {
  2975. $filename .= substr($md5, $i, $segment) . $s;
  2976. $i += $segment;
  2977. }
  2978. $filename .= substr($md5, $i);
  2979. return $filename;
  2980. }
  2981. public function set(string $key, string $value, int $ttl = 0): bool
  2982. {
  2983. $filename = $this->getFileName($key);
  2984. $dirname = dirname($filename);
  2985. if (!file_exists($dirname)) {
  2986. if (!mkdir($dirname, 0755, true)) {
  2987. return false;
  2988. }
  2989. }
  2990. $string = $ttl . '|' . $value;
  2991. return $this->filePutContents($filename, $string) !== false;
  2992. }
  2993. private function filePutContents($filename, $string)
  2994. {
  2995. return file_put_contents($filename, $string, LOCK_EX);
  2996. }
  2997. private function fileGetContents($filename)
  2998. {
  2999. $file = fopen($filename, 'rb');
  3000. if ($file === false) {
  3001. return false;
  3002. }
  3003. $lock = flock($file, LOCK_SH);
  3004. if (!$lock) {
  3005. fclose($file);
  3006. return false;
  3007. }
  3008. $string = '';
  3009. while (!feof($file)) {
  3010. $string .= fread($file, 8192);
  3011. }
  3012. flock($file, LOCK_UN);
  3013. fclose($file);
  3014. return $string;
  3015. }
  3016. private function getString($filename): string
  3017. {
  3018. $data = $this->fileGetContents($filename);
  3019. if ($data === false) {
  3020. return '';
  3021. }
  3022. if (strpos($data, '|') === false) {
  3023. return '';
  3024. }
  3025. list($ttl, $string) = explode('|', $data, 2);
  3026. if ($ttl > 0 && time() - filemtime($filename) > $ttl) {
  3027. return '';
  3028. }
  3029. return $string;
  3030. }
  3031. public function get(string $key): string
  3032. {
  3033. $filename = $this->getFileName($key);
  3034. if (!file_exists($filename)) {
  3035. return '';
  3036. }
  3037. $string = $this->getString($filename);
  3038. if ($string == null) {
  3039. return '';
  3040. }
  3041. return $string;
  3042. }
  3043. private function clean(string $path, array $segments, int $len, bool $all) /*: void*/
  3044. {
  3045. $entries = scandir($path);
  3046. foreach ($entries as $entry) {
  3047. if ($entry === '.' || $entry === '..') {
  3048. continue;
  3049. }
  3050. $filename = $path . DIRECTORY_SEPARATOR . $entry;
  3051. if (count($segments) == 0) {
  3052. if (strlen($entry) != $len) {
  3053. continue;
  3054. }
  3055. if (file_exists($filename) && is_file($filename)) {
  3056. if ($all || $this->getString($filename) == null) {
  3057. @unlink($filename);
  3058. }
  3059. }
  3060. } else {
  3061. if (strlen($entry) != $segments[0]) {
  3062. continue;
  3063. }
  3064. if (file_exists($filename) && is_dir($filename)) {
  3065. $this->clean($filename, array_slice($segments, 1), $len - $segments[0], $all);
  3066. @rmdir($filename);
  3067. }
  3068. }
  3069. }
  3070. }
  3071. public function clear(): bool
  3072. {
  3073. if (!file_exists($this->path) || !is_dir($this->path)) {
  3074. return false;
  3075. }
  3076. $this->clean($this->path, array_filter($this->segments), strlen(md5('')), true);
  3077. return true;
  3078. }
  3079. }
  3080. }
  3081. // file: src/Tqdev/PhpCrudApi/Column/Reflection/ReflectedColumn.php
  3082. namespace Tqdev\PhpCrudApi\Column\Reflection {
  3083. use Tqdev\PhpCrudApi\Database\GenericReflection;
  3084. class ReflectedColumn implements \JsonSerializable
  3085. {
  3086. const DEFAULT_LENGTH = 255;
  3087. const DEFAULT_PRECISION = 19;
  3088. const DEFAULT_SCALE = 4;
  3089. private $name;
  3090. private $type;
  3091. private $length;
  3092. private $precision;
  3093. private $scale;
  3094. private $nullable;
  3095. private $pk;
  3096. private $fk;
  3097. public function __construct(string $name, string $type, int $length, int $precision, int $scale, bool $nullable, bool $pk, string $fk)
  3098. {
  3099. $this->name = $name;
  3100. $this->type = $type;
  3101. $this->length = $length;
  3102. $this->precision = $precision;
  3103. $this->scale = $scale;
  3104. $this->nullable = $nullable;
  3105. $this->pk = $pk;
  3106. $this->fk = $fk;
  3107. $this->sanitize();
  3108. }
  3109. private static function parseColumnType(string $columnType, int &$length, int &$precision, int &$scale) /*: void*/
  3110. {
  3111. if (!$columnType) {
  3112. return;
  3113. }
  3114. $pos = strpos($columnType, '(');
  3115. if ($pos) {
  3116. $dataSize = rtrim(substr($columnType, $pos + 1), ')');
  3117. if ($length) {
  3118. $length = (int) $dataSize;
  3119. } else {
  3120. $pos = strpos($dataSize, ',');
  3121. if ($pos) {
  3122. $precision = (int) substr($dataSize, 0, $pos);
  3123. $scale = (int) substr($dataSize, $pos + 1);
  3124. } else {
  3125. $precision = (int) $dataSize;
  3126. $scale = 0;
  3127. }
  3128. }
  3129. }
  3130. }
  3131. private static function getDataSize(int $length, int $precision, int $scale): string
  3132. {
  3133. $dataSize = '';
  3134. if ($length) {
  3135. $dataSize = $length;
  3136. } elseif ($precision) {
  3137. if ($scale) {
  3138. $dataSize = $precision . ',' . $scale;
  3139. } else {
  3140. $dataSize = $precision;
  3141. }
  3142. }
  3143. return $dataSize;
  3144. }
  3145. public static function fromReflection(GenericReflection $reflection, array $columnResult): ReflectedColumn
  3146. {
  3147. $name = $columnResult['COLUMN_NAME'];
  3148. $dataType = $columnResult['DATA_TYPE'];
  3149. $length = (int) $columnResult['CHARACTER_MAXIMUM_LENGTH'];
  3150. $precision = (int) $columnResult['NUMERIC_PRECISION'];
  3151. $scale = (int) $columnResult['NUMERIC_SCALE'];
  3152. $columnType = $columnResult['COLUMN_TYPE'];
  3153. self::parseColumnType($columnType, $length, $precision, $scale);
  3154. $dataSize = self::getDataSize($length, $precision, $scale);
  3155. $type = $reflection->toJdbcType($dataType, $dataSize);
  3156. $nullable = in_array(strtoupper($columnResult['IS_NULLABLE']), ['TRUE', 'YES', 'T', 'Y', '1']);
  3157. $pk = false;
  3158. $fk = '';
  3159. return new ReflectedColumn($name, $type, $length, $precision, $scale, $nullable, $pk, $fk);
  3160. }
  3161. public static function fromJson(/* object */$json): ReflectedColumn
  3162. {
  3163. $name = $json->name;
  3164. $type = $json->type;
  3165. $length = isset($json->length) ? (int) $json->length : 0;
  3166. $precision = isset($json->precision) ? (int) $json->precision : 0;
  3167. $scale = isset($json->scale) ? (int) $json->scale : 0;
  3168. $nullable = isset($json->nullable) ? (bool) $json->nullable : false;
  3169. $pk = isset($json->pk) ? (bool) $json->pk : false;
  3170. $fk = isset($json->fk) ? $json->fk : '';
  3171. return new ReflectedColumn($name, $type, $length, $precision, $scale, $nullable, $pk, $fk);
  3172. }
  3173. private function sanitize()
  3174. {
  3175. $this->length = $this->hasLength() ? $this->getLength() : 0;
  3176. $this->precision = $this->hasPrecision() ? $this->getPrecision() : 0;
  3177. $this->scale = $this->hasScale() ? $this->getScale() : 0;
  3178. }
  3179. public function getName(): string
  3180. {
  3181. return $this->name;
  3182. }
  3183. public function getNullable(): bool
  3184. {
  3185. return $this->nullable;
  3186. }
  3187. public function getType(): string
  3188. {
  3189. return $this->type;
  3190. }
  3191. public function getLength(): int
  3192. {
  3193. return $this->length ?: self::DEFAULT_LENGTH;
  3194. }
  3195. public function getPrecision(): int
  3196. {
  3197. return $this->precision ?: self::DEFAULT_PRECISION;
  3198. }
  3199. public function getScale(): int
  3200. {
  3201. return $this->scale ?: self::DEFAULT_SCALE;
  3202. }
  3203. public function hasLength(): bool
  3204. {
  3205. return in_array($this->type, ['varchar', 'varbinary']);
  3206. }
  3207. public function hasPrecision(): bool
  3208. {
  3209. return $this->type == 'decimal';
  3210. }
  3211. public function hasScale(): bool
  3212. {
  3213. return $this->type == 'decimal';
  3214. }
  3215. public function isBinary(): bool
  3216. {
  3217. return in_array($this->type, ['blob', 'varbinary']);
  3218. }
  3219. public function isBoolean(): bool
  3220. {
  3221. return $this->type == 'boolean';
  3222. }
  3223. public function isGeometry(): bool
  3224. {
  3225. return $this->type == 'geometry';
  3226. }
  3227. public function isInteger(): bool
  3228. {
  3229. return in_array($this->type, ['integer', 'bigint', 'smallint', 'tinyint']);
  3230. }
  3231. public function setPk($value) /*: void*/
  3232. {
  3233. $this->pk = $value;
  3234. }
  3235. public function getPk(): bool
  3236. {
  3237. return $this->pk;
  3238. }
  3239. public function setFk($value) /*: void*/
  3240. {
  3241. $this->fk = $value;
  3242. }
  3243. public function getFk(): string
  3244. {
  3245. return $this->fk;
  3246. }
  3247. public function serialize()
  3248. {
  3249. return [
  3250. 'name' => $this->name,
  3251. 'type' => $this->type,
  3252. 'length' => $this->length,
  3253. 'precision' => $this->precision,
  3254. 'scale' => $this->scale,
  3255. 'nullable' => $this->nullable,
  3256. 'pk' => $this->pk,
  3257. 'fk' => $this->fk,
  3258. ];
  3259. }
  3260. public function jsonSerialize()
  3261. {
  3262. return array_filter($this->serialize());
  3263. }
  3264. }
  3265. }
  3266. // file: src/Tqdev/PhpCrudApi/Column/Reflection/ReflectedDatabase.php
  3267. namespace Tqdev\PhpCrudApi\Column\Reflection {
  3268. use Tqdev\PhpCrudApi\Database\GenericReflection;
  3269. class ReflectedDatabase implements \JsonSerializable
  3270. {
  3271. private $tableTypes;
  3272. public function __construct(array $tableTypes)
  3273. {
  3274. $this->tableTypes = $tableTypes;
  3275. }
  3276. public static function fromReflection(GenericReflection $reflection): ReflectedDatabase
  3277. {
  3278. $tableTypes = [];
  3279. foreach ($reflection->getTables() as $table) {
  3280. $tableName = $table['TABLE_NAME'];
  3281. $tableType = $table['TABLE_TYPE'];
  3282. if (in_array($tableName, $reflection->getIgnoredTables())) {
  3283. continue;
  3284. }
  3285. $tableTypes[$tableName] = $tableType;
  3286. }
  3287. return new ReflectedDatabase($tableTypes);
  3288. }
  3289. public static function fromJson(/* object */$json): ReflectedDatabase
  3290. {
  3291. $tableTypes = (array) $json->tables;
  3292. return new ReflectedDatabase($tableTypes);
  3293. }
  3294. public function hasTable(string $tableName): bool
  3295. {
  3296. return isset($this->tableTypes[$tableName]);
  3297. }
  3298. public function getType(string $tableName): string
  3299. {
  3300. return isset($this->tableTypes[$tableName]) ? $this->tableTypes[$tableName] : '';
  3301. }
  3302. public function getTableNames(): array
  3303. {
  3304. return array_keys($this->tableTypes);
  3305. }
  3306. public function removeTable(string $tableName): bool
  3307. {
  3308. if (!isset($this->tableTypes[$tableName])) {
  3309. return false;
  3310. }
  3311. unset($this->tableTypes[$tableName]);
  3312. return true;
  3313. }
  3314. public function serialize()
  3315. {
  3316. return [
  3317. 'tables' => $this->tableTypes,
  3318. ];
  3319. }
  3320. public function jsonSerialize()
  3321. {
  3322. return $this->serialize();
  3323. }
  3324. }
  3325. }
  3326. // file: src/Tqdev/PhpCrudApi/Column/Reflection/ReflectedTable.php
  3327. namespace Tqdev\PhpCrudApi\Column\Reflection {
  3328. use Tqdev\PhpCrudApi\Database\GenericReflection;
  3329. class ReflectedTable implements \JsonSerializable
  3330. {
  3331. private $name;
  3332. private $type;
  3333. private $columns;
  3334. private $pk;
  3335. private $fks;
  3336. public function __construct(string $name, string $type, array $columns)
  3337. {
  3338. $this->name = $name;
  3339. $this->type = $type;
  3340. // set columns
  3341. $this->columns = [];
  3342. foreach ($columns as $column) {
  3343. $columnName = $column->getName();
  3344. $this->columns[$columnName] = $column;
  3345. }
  3346. // set primary key
  3347. $this->pk = null;
  3348. foreach ($columns as $column) {
  3349. if ($column->getPk() == true) {
  3350. $this->pk = $column;
  3351. }
  3352. }
  3353. // set foreign keys
  3354. $this->fks = [];
  3355. foreach ($columns as $column) {
  3356. $columnName = $column->getName();
  3357. $referencedTableName = $column->getFk();
  3358. if ($referencedTableName != '') {
  3359. $this->fks[$columnName] = $referencedTableName;
  3360. }
  3361. }
  3362. }
  3363. public static function fromReflection(GenericReflection $reflection, string $name, string $type): ReflectedTable
  3364. {
  3365. // set columns
  3366. $columns = [];
  3367. foreach ($reflection->getTableColumns($name, $type) as $tableColumn) {
  3368. $column = ReflectedColumn::fromReflection($reflection, $tableColumn);
  3369. $columns[$column->getName()] = $column;
  3370. }
  3371. // set primary key
  3372. $columnName = false;
  3373. if ($type == 'view') {
  3374. $columnName = 'id';
  3375. } else {
  3376. $columnNames = $reflection->getTablePrimaryKeys($name);
  3377. if (count($columnNames) == 1) {
  3378. $columnName = $columnNames[0];
  3379. }
  3380. }
  3381. if ($columnName && isset($columns[$columnName])) {
  3382. $pk = $columns[$columnName];
  3383. $pk->setPk(true);
  3384. }
  3385. // set foreign keys
  3386. if ($type == 'view') {
  3387. $tables = $reflection->getTables();
  3388. foreach ($columns as $columnName => $column) {
  3389. if (substr($columnName, -3) == '_id') {
  3390. foreach ($tables as $table) {
  3391. $tableName = $table['TABLE_NAME'];
  3392. $suffix = $tableName . '_id';
  3393. if (substr($columnName, -1 * strlen($suffix)) == $suffix) {
  3394. $column->setFk($tableName);
  3395. }
  3396. }
  3397. }
  3398. }
  3399. } else {
  3400. $fks = $reflection->getTableForeignKeys($name);
  3401. foreach ($fks as $columnName => $table) {
  3402. $columns[$columnName]->setFk($table);
  3403. }
  3404. }
  3405. return new ReflectedTable($name, $type, array_values($columns));
  3406. }
  3407. public static function fromJson( /* object */$json): ReflectedTable
  3408. {
  3409. $name = $json->name;
  3410. $type = isset($json->type) ? $json->type : 'table';
  3411. $columns = [];
  3412. if (isset($json->columns) && is_array($json->columns)) {
  3413. foreach ($json->columns as $column) {
  3414. $columns[] = ReflectedColumn::fromJson($column);
  3415. }
  3416. }
  3417. return new ReflectedTable($name, $type, $columns);
  3418. }
  3419. public function hasColumn(string $columnName): bool
  3420. {
  3421. return isset($this->columns[$columnName]);
  3422. }
  3423. public function hasPk(): bool
  3424. {
  3425. return $this->pk != null;
  3426. }
  3427. public function getPk() /*: ?ReflectedColumn */
  3428. {
  3429. return $this->pk;
  3430. }
  3431. public function getName(): string
  3432. {
  3433. return $this->name;
  3434. }
  3435. public function getType(): string
  3436. {
  3437. return $this->type;
  3438. }
  3439. public function getColumnNames(): array
  3440. {
  3441. return array_keys($this->columns);
  3442. }
  3443. public function getColumn($columnName): ReflectedColumn
  3444. {
  3445. return $this->columns[$columnName];
  3446. }
  3447. public function getFksTo(string $tableName): array
  3448. {
  3449. $columns = array();
  3450. foreach ($this->fks as $columnName => $referencedTableName) {
  3451. if ($tableName == $referencedTableName) {
  3452. $columns[] = $this->columns[$columnName];
  3453. }
  3454. }
  3455. return $columns;
  3456. }
  3457. public function removeColumn(string $columnName): bool
  3458. {
  3459. if (!isset($this->columns[$columnName])) {
  3460. return false;
  3461. }
  3462. unset($this->columns[$columnName]);
  3463. return true;
  3464. }
  3465. public function serialize()
  3466. {
  3467. return [
  3468. 'name' => $this->name,
  3469. 'type' => $this->type,
  3470. 'columns' => array_values($this->columns),
  3471. ];
  3472. }
  3473. public function jsonSerialize()
  3474. {
  3475. return $this->serialize();
  3476. }
  3477. }
  3478. }
  3479. // file: src/Tqdev/PhpCrudApi/Column/DefinitionService.php
  3480. namespace Tqdev\PhpCrudApi\Column {
  3481. use Tqdev\PhpCrudApi\Column\Reflection\ReflectedColumn;
  3482. use Tqdev\PhpCrudApi\Column\Reflection\ReflectedTable;
  3483. use Tqdev\PhpCrudApi\Database\GenericDB;
  3484. class DefinitionService
  3485. {
  3486. private $db;
  3487. private $reflection;
  3488. public function __construct(GenericDB $db, ReflectionService $reflection)
  3489. {
  3490. $this->db = $db;
  3491. $this->reflection = $reflection;
  3492. }
  3493. public function updateTable(string $tableName, /* object */ $changes): bool
  3494. {
  3495. $table = $this->reflection->getTable($tableName);
  3496. $newTable = ReflectedTable::fromJson((object) array_merge((array) $table->jsonSerialize(), (array) $changes));
  3497. if ($table->getName() != $newTable->getName()) {
  3498. if (!$this->db->definition()->renameTable($table->getName(), $newTable->getName())) {
  3499. return false;
  3500. }
  3501. }
  3502. return true;
  3503. }
  3504. public function updateColumn(string $tableName, string $columnName, /* object */ $changes): bool
  3505. {
  3506. $table = $this->reflection->getTable($tableName);
  3507. $column = $table->getColumn($columnName);
  3508. // remove constraints on other column
  3509. $newColumn = ReflectedColumn::fromJson((object) array_merge((array) $column->jsonSerialize(), (array) $changes));
  3510. if ($newColumn->getPk() != $column->getPk() && $table->hasPk()) {
  3511. $oldColumn = $table->getPk();
  3512. if ($oldColumn->getName() != $columnName) {
  3513. $oldColumn->setPk(false);
  3514. if (!$this->db->definition()->removeColumnPrimaryKey($table->getName(), $oldColumn->getName(), $oldColumn)) {
  3515. return false;
  3516. }
  3517. }
  3518. }
  3519. // remove constraints
  3520. $newColumn = ReflectedColumn::fromJson((object) array_merge((array) $column->jsonSerialize(), ['pk' => false, 'fk' => false]));
  3521. if ($newColumn->getPk() != $column->getPk() && !$newColumn->getPk()) {
  3522. if (!$this->db->definition()->removeColumnPrimaryKey($table->getName(), $column->getName(), $newColumn)) {
  3523. return false;
  3524. }
  3525. }
  3526. if ($newColumn->getFk() != $column->getFk() && !$newColumn->getFk()) {
  3527. if (!$this->db->definition()->removeColumnForeignKey($table->getName(), $column->getName(), $newColumn)) {
  3528. return false;
  3529. }
  3530. }
  3531. // name and type
  3532. $newColumn = ReflectedColumn::fromJson((object) array_merge((array) $column->jsonSerialize(), (array) $changes));
  3533. $newColumn->setPk(false);
  3534. $newColumn->setFk('');
  3535. if ($newColumn->getName() != $column->getName()) {
  3536. if (!$this->db->definition()->renameColumn($table->getName(), $column->getName(), $newColumn)) {
  3537. return false;
  3538. }
  3539. }
  3540. if (
  3541. $newColumn->getType() != $column->getType() ||
  3542. $newColumn->getLength() != $column->getLength() ||
  3543. $newColumn->getPrecision() != $column->getPrecision() ||
  3544. $newColumn->getScale() != $column->getScale()
  3545. ) {
  3546. if (!$this->db->definition()->retypeColumn($table->getName(), $newColumn->getName(), $newColumn)) {
  3547. return false;
  3548. }
  3549. }
  3550. if ($newColumn->getNullable() != $column->getNullable()) {
  3551. if (!$this->db->definition()->setColumnNullable($table->getName(), $newColumn->getName(), $newColumn)) {
  3552. return false;
  3553. }
  3554. }
  3555. // add constraints
  3556. $newColumn = ReflectedColumn::fromJson((object) array_merge((array) $column->jsonSerialize(), (array) $changes));
  3557. if ($newColumn->getFk()) {
  3558. if (!$this->db->definition()->addColumnForeignKey($table->getName(), $newColumn->getName(), $newColumn)) {
  3559. return false;
  3560. }
  3561. }
  3562. if ($newColumn->getPk()) {
  3563. if (!$this->db->definition()->addColumnPrimaryKey($table->getName(), $newColumn->getName(), $newColumn)) {
  3564. return false;
  3565. }
  3566. }
  3567. return true;
  3568. }
  3569. public function addTable(/* object */$definition)
  3570. {
  3571. $newTable = ReflectedTable::fromJson($definition);
  3572. if (!$this->db->definition()->addTable($newTable)) {
  3573. return false;
  3574. }
  3575. return true;
  3576. }
  3577. public function addColumn(string $tableName, /* object */ $definition)
  3578. {
  3579. $newColumn = ReflectedColumn::fromJson($definition);
  3580. if (!$this->db->definition()->addColumn($tableName, $newColumn)) {
  3581. return false;
  3582. }
  3583. if ($newColumn->getFk()) {
  3584. if (!$this->db->definition()->addColumnForeignKey($tableName, $newColumn->getName(), $newColumn)) {
  3585. return false;
  3586. }
  3587. }
  3588. if ($newColumn->getPk()) {
  3589. if (!$this->db->definition()->addColumnPrimaryKey($tableName, $newColumn->getName(), $newColumn)) {
  3590. return false;
  3591. }
  3592. }
  3593. return true;
  3594. }
  3595. public function removeTable(string $tableName)
  3596. {
  3597. if (!$this->db->definition()->removeTable($tableName)) {
  3598. return false;
  3599. }
  3600. return true;
  3601. }
  3602. public function removeColumn(string $tableName, string $columnName)
  3603. {
  3604. $table = $this->reflection->getTable($tableName);
  3605. $newColumn = $table->getColumn($columnName);
  3606. if ($newColumn->getPk()) {
  3607. $newColumn->setPk(false);
  3608. if (!$this->db->definition()->removeColumnPrimaryKey($table->getName(), $newColumn->getName(), $newColumn)) {
  3609. return false;
  3610. }
  3611. }
  3612. if ($newColumn->getFk()) {
  3613. $newColumn->setFk("");
  3614. if (!$this->db->definition()->removeColumnForeignKey($tableName, $columnName, $newColumn)) {
  3615. return false;
  3616. }
  3617. }
  3618. if (!$this->db->definition()->removeColumn($tableName, $columnName)) {
  3619. return false;
  3620. }
  3621. return true;
  3622. }
  3623. }
  3624. }
  3625. // file: src/Tqdev/PhpCrudApi/Column/ReflectionService.php
  3626. namespace Tqdev\PhpCrudApi\Column {
  3627. use Tqdev\PhpCrudApi\Cache\Cache;
  3628. use Tqdev\PhpCrudApi\Column\Reflection\ReflectedDatabase;
  3629. use Tqdev\PhpCrudApi\Column\Reflection\ReflectedTable;
  3630. use Tqdev\PhpCrudApi\Database\GenericDB;
  3631. class ReflectionService
  3632. {
  3633. private $db;
  3634. private $cache;
  3635. private $ttl;
  3636. private $database;
  3637. private $tables;
  3638. public function __construct(GenericDB $db, Cache $cache, int $ttl)
  3639. {
  3640. $this->db = $db;
  3641. $this->cache = $cache;
  3642. $this->ttl = $ttl;
  3643. $this->database = null;
  3644. $this->tables = [];
  3645. }
  3646. private function database(): ReflectedDatabase
  3647. {
  3648. if ($this->database) {
  3649. return $this->database;
  3650. }
  3651. $this->database = $this->loadDatabase(true);
  3652. return $this->database;
  3653. }
  3654. private function loadDatabase(bool $useCache): ReflectedDatabase
  3655. {
  3656. $key = sprintf('%s-ReflectedDatabase', $this->db->getCacheKey());
  3657. $data = $useCache ? $this->cache->get($key) : '';
  3658. if ($data != '') {
  3659. $database = ReflectedDatabase::fromJson(json_decode(gzuncompress($data)));
  3660. } else {
  3661. $database = ReflectedDatabase::fromReflection($this->db->reflection());
  3662. $data = gzcompress(json_encode($database, JSON_UNESCAPED_UNICODE));
  3663. $this->cache->set($key, $data, $this->ttl);
  3664. }
  3665. return $database;
  3666. }
  3667. private function loadTable(string $tableName, bool $useCache): ReflectedTable
  3668. {
  3669. $key = sprintf('%s-ReflectedTable(%s)', $this->db->getCacheKey(), $tableName);
  3670. $data = $useCache ? $this->cache->get($key) : '';
  3671. if ($data != '') {
  3672. $table = ReflectedTable::fromJson(json_decode(gzuncompress($data)));
  3673. } else {
  3674. $tableType = $this->database()->getType($tableName);
  3675. $table = ReflectedTable::fromReflection($this->db->reflection(), $tableName, $tableType);
  3676. $data = gzcompress(json_encode($table, JSON_UNESCAPED_UNICODE));
  3677. $this->cache->set($key, $data, $this->ttl);
  3678. }
  3679. return $table;
  3680. }
  3681. public function refreshTables()
  3682. {
  3683. $this->database = $this->loadDatabase(false);
  3684. }
  3685. public function refreshTable(string $tableName)
  3686. {
  3687. $this->tables[$tableName] = $this->loadTable($tableName, false);
  3688. }
  3689. public function hasTable(string $tableName): bool
  3690. {
  3691. return $this->database()->hasTable($tableName);
  3692. }
  3693. public function getType(string $tableName): string
  3694. {
  3695. return $this->database()->getType($tableName);
  3696. }
  3697. public function getTable(string $tableName): ReflectedTable
  3698. {
  3699. if (!isset($this->tables[$tableName])) {
  3700. $this->tables[$tableName] = $this->loadTable($tableName, true);
  3701. }
  3702. return $this->tables[$tableName];
  3703. }
  3704. public function getTableNames(): array
  3705. {
  3706. return $this->database()->getTableNames();
  3707. }
  3708. public function removeTable(string $tableName): bool
  3709. {
  3710. unset($this->tables[$tableName]);
  3711. return $this->database()->removeTable($tableName);
  3712. }
  3713. }
  3714. }
  3715. // file: src/Tqdev/PhpCrudApi/Controller/CacheController.php
  3716. namespace Tqdev\PhpCrudApi\Controller {
  3717. use Psr\Http\Message\ResponseInterface;
  3718. use Psr\Http\Message\ServerRequestInterface;
  3719. use Tqdev\PhpCrudApi\Cache\Cache;
  3720. use Tqdev\PhpCrudApi\Middleware\Router\Router;
  3721. class CacheController
  3722. {
  3723. private $cache;
  3724. private $responder;
  3725. public function __construct(Router $router, Responder $responder, Cache $cache)
  3726. {
  3727. $router->register('GET', '/cache/clear', array($this, 'clear'));
  3728. $this->cache = $cache;
  3729. $this->responder = $responder;
  3730. }
  3731. public function clear(ServerRequestInterface $request): ResponseInterface
  3732. {
  3733. return $this->responder->success($this->cache->clear());
  3734. }
  3735. }
  3736. }
  3737. // file: src/Tqdev/PhpCrudApi/Controller/ColumnController.php
  3738. namespace Tqdev\PhpCrudApi\Controller {
  3739. use Psr\Http\Message\ResponseInterface;
  3740. use Psr\Http\Message\ServerRequestInterface;
  3741. use Tqdev\PhpCrudApi\Column\DefinitionService;
  3742. use Tqdev\PhpCrudApi\Column\ReflectionService;
  3743. use Tqdev\PhpCrudApi\Middleware\Router\Router;
  3744. use Tqdev\PhpCrudApi\Record\ErrorCode;
  3745. use Tqdev\PhpCrudApi\RequestUtils;
  3746. class ColumnController
  3747. {
  3748. private $responder;
  3749. private $reflection;
  3750. private $definition;
  3751. public function __construct(Router $router, Responder $responder, ReflectionService $reflection, DefinitionService $definition)
  3752. {
  3753. $router->register('GET', '/columns', array($this, 'getDatabase'));
  3754. $router->register('GET', '/columns/*', array($this, 'getTable'));
  3755. $router->register('GET', '/columns/*/*', array($this, 'getColumn'));
  3756. $router->register('PUT', '/columns/*', array($this, 'updateTable'));
  3757. $router->register('PUT', '/columns/*/*', array($this, 'updateColumn'));
  3758. $router->register('POST', '/columns', array($this, 'addTable'));
  3759. $router->register('POST', '/columns/*', array($this, 'addColumn'));
  3760. $router->register('DELETE', '/columns/*', array($this, 'removeTable'));
  3761. $router->register('DELETE', '/columns/*/*', array($this, 'removeColumn'));
  3762. $this->responder = $responder;
  3763. $this->reflection = $reflection;
  3764. $this->definition = $definition;
  3765. }
  3766. public function getDatabase(ServerRequestInterface $request): ResponseInterface
  3767. {
  3768. $tables = [];
  3769. foreach ($this->reflection->getTableNames() as $table) {
  3770. $tables[] = $this->reflection->getTable($table);
  3771. }
  3772. $database = ['tables' => $tables];
  3773. return $this->responder->success($database);
  3774. }
  3775. public function getTable(ServerRequestInterface $request): ResponseInterface
  3776. {
  3777. $tableName = RequestUtils::getPathSegment($request, 2);
  3778. if (!$this->reflection->hasTable($tableName)) {
  3779. return $this->responder->error(ErrorCode::TABLE_NOT_FOUND, $tableName);
  3780. }
  3781. $table = $this->reflection->getTable($tableName);
  3782. return $this->responder->success($table);
  3783. }
  3784. public function getColumn(ServerRequestInterface $request): ResponseInterface
  3785. {
  3786. $tableName = RequestUtils::getPathSegment($request, 2);
  3787. $columnName = RequestUtils::getPathSegment($request, 3);
  3788. if (!$this->reflection->hasTable($tableName)) {
  3789. return $this->responder->error(ErrorCode::TABLE_NOT_FOUND, $tableName);
  3790. }
  3791. $table = $this->reflection->getTable($tableName);
  3792. if (!$table->hasColumn($columnName)) {
  3793. return $this->responder->error(ErrorCode::COLUMN_NOT_FOUND, $columnName);
  3794. }
  3795. $column = $table->getColumn($columnName);
  3796. return $this->responder->success($column);
  3797. }
  3798. public function updateTable(ServerRequestInterface $request): ResponseInterface
  3799. {
  3800. $tableName = RequestUtils::getPathSegment($request, 2);
  3801. if (!$this->reflection->hasTable($tableName)) {
  3802. return $this->responder->error(ErrorCode::TABLE_NOT_FOUND, $tableName);
  3803. }
  3804. $success = $this->definition->updateTable($tableName, $request->getParsedBody());
  3805. if ($success) {
  3806. $this->reflection->refreshTables();
  3807. }
  3808. return $this->responder->success($success);
  3809. }
  3810. public function updateColumn(ServerRequestInterface $request): ResponseInterface
  3811. {
  3812. $tableName = RequestUtils::getPathSegment($request, 2);
  3813. $columnName = RequestUtils::getPathSegment($request, 3);
  3814. if (!$this->reflection->hasTable($tableName)) {
  3815. return $this->responder->error(ErrorCode::TABLE_NOT_FOUND, $tableName);
  3816. }
  3817. $table = $this->reflection->getTable($tableName);
  3818. if (!$table->hasColumn($columnName)) {
  3819. return $this->responder->error(ErrorCode::COLUMN_NOT_FOUND, $columnName);
  3820. }
  3821. $success = $this->definition->updateColumn($tableName, $columnName, $request->getParsedBody());
  3822. if ($success) {
  3823. $this->reflection->refreshTable($tableName);
  3824. }
  3825. return $this->responder->success($success);
  3826. }
  3827. public function addTable(ServerRequestInterface $request): ResponseInterface
  3828. {
  3829. $tableName = $request->getParsedBody()->name;
  3830. if ($this->reflection->hasTable($tableName)) {
  3831. return $this->responder->error(ErrorCode::TABLE_ALREADY_EXISTS, $tableName);
  3832. }
  3833. $success = $this->definition->addTable($request->getParsedBody());
  3834. if ($success) {
  3835. $this->reflection->refreshTables();
  3836. }
  3837. return $this->responder->success($success);
  3838. }
  3839. public function addColumn(ServerRequestInterface $request): ResponseInterface
  3840. {
  3841. $tableName = RequestUtils::getPathSegment($request, 2);
  3842. if (!$this->reflection->hasTable($tableName)) {
  3843. return $this->responder->error(ErrorCode::TABLE_NOT_FOUND, $tableName);
  3844. }
  3845. $columnName = $request->getParsedBody()->name;
  3846. $table = $this->reflection->getTable($tableName);
  3847. if ($table->hasColumn($columnName)) {
  3848. return $this->responder->error(ErrorCode::COLUMN_ALREADY_EXISTS, $columnName);
  3849. }
  3850. $success = $this->definition->addColumn($tableName, $request->getParsedBody());
  3851. if ($success) {
  3852. $this->reflection->refreshTable($tableName);
  3853. }
  3854. return $this->responder->success($success);
  3855. }
  3856. public function removeTable(ServerRequestInterface $request): ResponseInterface
  3857. {
  3858. $tableName = RequestUtils::getPathSegment($request, 2);
  3859. if (!$this->reflection->hasTable($tableName)) {
  3860. return $this->responder->error(ErrorCode::TABLE_NOT_FOUND, $tableName);
  3861. }
  3862. $success = $this->definition->removeTable($tableName);
  3863. if ($success) {
  3864. $this->reflection->refreshTables();
  3865. }
  3866. return $this->responder->success($success);
  3867. }
  3868. public function removeColumn(ServerRequestInterface $request): ResponseInterface
  3869. {
  3870. $tableName = RequestUtils::getPathSegment($request, 2);
  3871. $columnName = RequestUtils::getPathSegment($request, 3);
  3872. if (!$this->reflection->hasTable($tableName)) {
  3873. return $this->responder->error(ErrorCode::TABLE_NOT_FOUND, $tableName);
  3874. }
  3875. $table = $this->reflection->getTable($tableName);
  3876. if (!$table->hasColumn($columnName)) {
  3877. return $this->responder->error(ErrorCode::COLUMN_NOT_FOUND, $columnName);
  3878. }
  3879. $success = $this->definition->removeColumn($tableName, $columnName);
  3880. if ($success) {
  3881. $this->reflection->refreshTable($tableName);
  3882. }
  3883. return $this->responder->success($success);
  3884. }
  3885. }
  3886. }
  3887. // file: src/Tqdev/PhpCrudApi/Controller/GeoJsonController.php
  3888. namespace Tqdev\PhpCrudApi\Controller {
  3889. use Psr\Http\Message\ResponseInterface;
  3890. use Psr\Http\Message\ServerRequestInterface;
  3891. use Tqdev\PhpCrudApi\GeoJson\GeoJsonService;
  3892. use Tqdev\PhpCrudApi\Middleware\Router\Router;
  3893. use Tqdev\PhpCrudApi\Record\ErrorCode;
  3894. use Tqdev\PhpCrudApi\RequestUtils;
  3895. class GeoJsonController
  3896. {
  3897. private $service;
  3898. private $responder;
  3899. public function __construct(Router $router, Responder $responder, GeoJsonService $service)
  3900. {
  3901. $router->register('GET', '/geojson/*', array($this, '_list'));
  3902. $router->register('GET', '/geojson/*/*', array($this, 'read'));
  3903. $this->service = $service;
  3904. $this->responder = $responder;
  3905. }
  3906. public function _list(ServerRequestInterface $request): ResponseInterface
  3907. {
  3908. $table = RequestUtils::getPathSegment($request, 2);
  3909. $params = RequestUtils::getParams($request);
  3910. if (!$this->service->hasTable($table)) {
  3911. return $this->responder->error(ErrorCode::TABLE_NOT_FOUND, $table);
  3912. }
  3913. return $this->responder->success($this->service->_list($table, $params));
  3914. }
  3915. public function read(ServerRequestInterface $request): ResponseInterface
  3916. {
  3917. $table = RequestUtils::getPathSegment($request, 2);
  3918. if (!$this->service->hasTable($table)) {
  3919. return $this->responder->error(ErrorCode::TABLE_NOT_FOUND, $table);
  3920. }
  3921. if ($this->service->getType($table) != 'table') {
  3922. return $this->responder->error(ErrorCode::OPERATION_NOT_SUPPORTED, __FUNCTION__);
  3923. }
  3924. $id = RequestUtils::getPathSegment($request, 3);
  3925. $params = RequestUtils::getParams($request);
  3926. if (strpos($id, ',') !== false) {
  3927. $ids = explode(',', $id);
  3928. $result = (object) array('type' => 'FeatureCollection', 'features' => array());
  3929. for ($i = 0; $i < count($ids); $i++) {
  3930. array_push($result->features, $this->service->read($table, $ids[$i], $params));
  3931. }
  3932. return $this->responder->success($result);
  3933. } else {
  3934. $response = $this->service->read($table, $id, $params);
  3935. if ($response === null) {
  3936. return $this->responder->error(ErrorCode::RECORD_NOT_FOUND, $id);
  3937. }
  3938. return $this->responder->success($response);
  3939. }
  3940. }
  3941. }
  3942. }
  3943. // file: src/Tqdev/PhpCrudApi/Controller/JsonResponder.php
  3944. namespace Tqdev\PhpCrudApi\Controller {
  3945. use Psr\Http\Message\ResponseInterface;
  3946. use Tqdev\PhpCrudApi\Record\Document\ErrorDocument;
  3947. use Tqdev\PhpCrudApi\Record\ErrorCode;
  3948. use Tqdev\PhpCrudApi\ResponseFactory;
  3949. class JsonResponder implements Responder
  3950. {
  3951. public function error(int $error, string $argument, $details = null): ResponseInterface
  3952. {
  3953. $errorCode = new ErrorCode($error);
  3954. $status = $errorCode->getStatus();
  3955. $document = new ErrorDocument($errorCode, $argument, $details);
  3956. return ResponseFactory::fromObject($status, $document);
  3957. }
  3958. public function success($result): ResponseInterface
  3959. {
  3960. return ResponseFactory::fromObject(ResponseFactory::OK, $result);
  3961. }
  3962. }
  3963. }
  3964. // file: src/Tqdev/PhpCrudApi/Controller/OpenApiController.php
  3965. namespace Tqdev\PhpCrudApi\Controller {
  3966. use Psr\Http\Message\ResponseInterface;
  3967. use Psr\Http\Message\ServerRequestInterface;
  3968. use Tqdev\PhpCrudApi\Middleware\Router\Router;
  3969. use Tqdev\PhpCrudApi\OpenApi\OpenApiService;
  3970. class OpenApiController
  3971. {
  3972. private $openApi;
  3973. private $responder;
  3974. public function __construct(Router $router, Responder $responder, OpenApiService $openApi)
  3975. {
  3976. $router->register('GET', '/openapi', array($this, 'openapi'));
  3977. $this->openApi = $openApi;
  3978. $this->responder = $responder;
  3979. }
  3980. public function openapi(ServerRequestInterface $request): ResponseInterface
  3981. {
  3982. return $this->responder->success($this->openApi->get());
  3983. }
  3984. }
  3985. }
  3986. // file: src/Tqdev/PhpCrudApi/Controller/RecordController.php
  3987. namespace Tqdev\PhpCrudApi\Controller {
  3988. use Psr\Http\Message\ResponseInterface;
  3989. use Psr\Http\Message\ServerRequestInterface;
  3990. use Tqdev\PhpCrudApi\Middleware\Router\Router;
  3991. use Tqdev\PhpCrudApi\Record\ErrorCode;
  3992. use Tqdev\PhpCrudApi\Record\RecordService;
  3993. use Tqdev\PhpCrudApi\RequestUtils;
  3994. class RecordController
  3995. {
  3996. private $service;
  3997. private $responder;
  3998. public function __construct(Router $router, Responder $responder, RecordService $service)
  3999. {
  4000. $router->register('GET', '/records/*', array($this, '_list'));
  4001. $router->register('POST', '/records/*', array($this, 'create'));
  4002. $router->register('GET', '/records/*/*', array($this, 'read'));
  4003. $router->register('PUT', '/records/*/*', array($this, 'update'));
  4004. $router->register('DELETE', '/records/*/*', array($this, 'delete'));
  4005. $router->register('PATCH', '/records/*/*', array($this, 'increment'));
  4006. $this->service = $service;
  4007. $this->responder = $responder;
  4008. }
  4009. public function _list(ServerRequestInterface $request): ResponseInterface
  4010. {
  4011. $table = RequestUtils::getPathSegment($request, 2);
  4012. $params = RequestUtils::getParams($request);
  4013. if (!$this->service->hasTable($table)) {
  4014. return $this->responder->error(ErrorCode::TABLE_NOT_FOUND, $table);
  4015. }
  4016. return $this->responder->success($this->service->_list($table, $params));
  4017. }
  4018. public function read(ServerRequestInterface $request): ResponseInterface
  4019. {
  4020. $table = RequestUtils::getPathSegment($request, 2);
  4021. if (!$this->service->hasTable($table)) {
  4022. return $this->responder->error(ErrorCode::TABLE_NOT_FOUND, $table);
  4023. }
  4024. $id = RequestUtils::getPathSegment($request, 3);
  4025. $params = RequestUtils::getParams($request);
  4026. if (strpos($id, ',') !== false) {
  4027. $ids = explode(',', $id);
  4028. $result = [];
  4029. for ($i = 0; $i < count($ids); $i++) {
  4030. array_push($result, $this->service->read($table, $ids[$i], $params));
  4031. }
  4032. return $this->responder->success($result);
  4033. } else {
  4034. $response = $this->service->read($table, $id, $params);
  4035. if ($response === null) {
  4036. return $this->responder->error(ErrorCode::RECORD_NOT_FOUND, $id);
  4037. }
  4038. return $this->responder->success($response);
  4039. }
  4040. }
  4041. public function create(ServerRequestInterface $request): ResponseInterface
  4042. {
  4043. $table = RequestUtils::getPathSegment($request, 2);
  4044. if (!$this->service->hasTable($table)) {
  4045. return $this->responder->error(ErrorCode::TABLE_NOT_FOUND, $table);
  4046. }
  4047. if ($this->service->getType($table) != 'table') {
  4048. return $this->responder->error(ErrorCode::OPERATION_NOT_SUPPORTED, __FUNCTION__);
  4049. }
  4050. $record = $request->getParsedBody();
  4051. if ($record === null) {
  4052. return $this->responder->error(ErrorCode::HTTP_MESSAGE_NOT_READABLE, '');
  4053. }
  4054. $params = RequestUtils::getParams($request);
  4055. if (is_array($record)) {
  4056. $result = array();
  4057. foreach ($record as $r) {
  4058. $result[] = $this->service->create($table, $r, $params);
  4059. }
  4060. return $this->responder->success($result);
  4061. } else {
  4062. return $this->responder->success($this->service->create($table, $record, $params));
  4063. }
  4064. }
  4065. public function update(ServerRequestInterface $request): ResponseInterface
  4066. {
  4067. $table = RequestUtils::getPathSegment($request, 2);
  4068. if (!$this->service->hasTable($table)) {
  4069. return $this->responder->error(ErrorCode::TABLE_NOT_FOUND, $table);
  4070. }
  4071. if ($this->service->getType($table) != 'table') {
  4072. return $this->responder->error(ErrorCode::OPERATION_NOT_SUPPORTED, __FUNCTION__);
  4073. }
  4074. $id = RequestUtils::getPathSegment($request, 3);
  4075. $params = RequestUtils::getParams($request);
  4076. $record = $request->getParsedBody();
  4077. if ($record === null) {
  4078. return $this->responder->error(ErrorCode::HTTP_MESSAGE_NOT_READABLE, '');
  4079. }
  4080. $ids = explode(',', $id);
  4081. if (is_array($record)) {
  4082. if (count($ids) != count($record)) {
  4083. return $this->responder->error(ErrorCode::ARGUMENT_COUNT_MISMATCH, $id);
  4084. }
  4085. $result = array();
  4086. for ($i = 0; $i < count($ids); $i++) {
  4087. $result[] = $this->service->update($table, $ids[$i], $record[$i], $params);
  4088. }
  4089. return $this->responder->success($result);
  4090. } else {
  4091. if (count($ids) != 1) {
  4092. return $this->responder->error(ErrorCode::ARGUMENT_COUNT_MISMATCH, $id);
  4093. }
  4094. return $this->responder->success($this->service->update($table, $id, $record, $params));
  4095. }
  4096. }
  4097. public function delete(ServerRequestInterface $request): ResponseInterface
  4098. {
  4099. $table = RequestUtils::getPathSegment($request, 2);
  4100. if (!$this->service->hasTable($table)) {
  4101. return $this->responder->error(ErrorCode::TABLE_NOT_FOUND, $table);
  4102. }
  4103. if ($this->service->getType($table) != 'table') {
  4104. return $this->responder->error(ErrorCode::OPERATION_NOT_SUPPORTED, __FUNCTION__);
  4105. }
  4106. $id = RequestUtils::getPathSegment($request, 3);
  4107. $params = RequestUtils::getParams($request);
  4108. $ids = explode(',', $id);
  4109. if (count($ids) > 1) {
  4110. $result = array();
  4111. for ($i = 0; $i < count($ids); $i++) {
  4112. $result[] = $this->service->delete($table, $ids[$i], $params);
  4113. }
  4114. return $this->responder->success($result);
  4115. } else {
  4116. return $this->responder->success($this->service->delete($table, $id, $params));
  4117. }
  4118. }
  4119. public function increment(ServerRequestInterface $request): ResponseInterface
  4120. {
  4121. $table = RequestUtils::getPathSegment($request, 2);
  4122. if (!$this->service->hasTable($table)) {
  4123. return $this->responder->error(ErrorCode::TABLE_NOT_FOUND, $table);
  4124. }
  4125. if ($this->service->getType($table) != 'table') {
  4126. return $this->responder->error(ErrorCode::OPERATION_NOT_SUPPORTED, __FUNCTION__);
  4127. }
  4128. $id = RequestUtils::getPathSegment($request, 3);
  4129. $record = $request->getParsedBody();
  4130. if ($record === null) {
  4131. return $this->responder->error(ErrorCode::HTTP_MESSAGE_NOT_READABLE, '');
  4132. }
  4133. $params = RequestUtils::getParams($request);
  4134. $ids = explode(',', $id);
  4135. if (is_array($record)) {
  4136. if (count($ids) != count($record)) {
  4137. return $this->responder->error(ErrorCode::ARGUMENT_COUNT_MISMATCH, $id);
  4138. }
  4139. $result = array();
  4140. for ($i = 0; $i < count($ids); $i++) {
  4141. $result[] = $this->service->increment($table, $ids[$i], $record[$i], $params);
  4142. }
  4143. return $this->responder->success($result);
  4144. } else {
  4145. if (count($ids) != 1) {
  4146. return $this->responder->error(ErrorCode::ARGUMENT_COUNT_MISMATCH, $id);
  4147. }
  4148. return $this->responder->success($this->service->increment($table, $id, $record, $params));
  4149. }
  4150. }
  4151. }
  4152. }
  4153. // file: src/Tqdev/PhpCrudApi/Controller/Responder.php
  4154. namespace Tqdev\PhpCrudApi\Controller {
  4155. use Psr\Http\Message\ResponseInterface;
  4156. interface Responder
  4157. {
  4158. public function error(int $error, string $argument, $details = null): ResponseInterface;
  4159. public function success($result): ResponseInterface;
  4160. }
  4161. }
  4162. // file: src/Tqdev/PhpCrudApi/Database/ColumnConverter.php
  4163. namespace Tqdev\PhpCrudApi\Database {
  4164. use Tqdev\PhpCrudApi\Column\Reflection\ReflectedColumn;
  4165. class ColumnConverter
  4166. {
  4167. private $driver;
  4168. public function __construct(string $driver)
  4169. {
  4170. $this->driver = $driver;
  4171. }
  4172. public function convertColumnValue(ReflectedColumn $column): string
  4173. {
  4174. if ($column->isBoolean()) {
  4175. switch ($this->driver) {
  4176. case 'mysql':
  4177. return "IFNULL(IF(?,TRUE,FALSE),NULL)";
  4178. case 'pgsql':
  4179. return "?";
  4180. case 'sqlsrv':
  4181. return "?";
  4182. }
  4183. }
  4184. if ($column->isBinary()) {
  4185. switch ($this->driver) {
  4186. case 'mysql':
  4187. return "FROM_BASE64(?)";
  4188. case 'pgsql':
  4189. return "decode(?, 'base64')";
  4190. case 'sqlsrv':
  4191. return "CONVERT(XML, ?).value('.','varbinary(max)')";
  4192. }
  4193. }
  4194. if ($column->isGeometry()) {
  4195. switch ($this->driver) {
  4196. case 'mysql':
  4197. case 'pgsql':
  4198. return "ST_GeomFromText(?)";
  4199. case 'sqlsrv':
  4200. return "geometry::STGeomFromText(?,0)";
  4201. }
  4202. }
  4203. return '?';
  4204. }
  4205. public function convertColumnName(ReflectedColumn $column, $value): string
  4206. {
  4207. if ($column->isBinary()) {
  4208. switch ($this->driver) {
  4209. case 'mysql':
  4210. return "TO_BASE64($value) as $value";
  4211. case 'pgsql':
  4212. return "encode($value::bytea, 'base64') as $value";
  4213. case 'sqlsrv':
  4214. return "CASE WHEN $value IS NULL THEN NULL ELSE (SELECT CAST($value as varbinary(max)) FOR XML PATH(''), BINARY BASE64) END as $value";
  4215. }
  4216. }
  4217. if ($column->isGeometry()) {
  4218. switch ($this->driver) {
  4219. case 'mysql':
  4220. case 'pgsql':
  4221. return "ST_AsText($value) as $value";
  4222. case 'sqlsrv':
  4223. return "REPLACE($value.STAsText(),' (','(') as $value";
  4224. }
  4225. }
  4226. return $value;
  4227. }
  4228. }
  4229. }
  4230. // file: src/Tqdev/PhpCrudApi/Database/ColumnsBuilder.php
  4231. namespace Tqdev\PhpCrudApi\Database {
  4232. use Tqdev\PhpCrudApi\Column\Reflection\ReflectedColumn;
  4233. use Tqdev\PhpCrudApi\Column\Reflection\ReflectedTable;
  4234. class ColumnsBuilder
  4235. {
  4236. private $driver;
  4237. private $converter;
  4238. public function __construct(string $driver)
  4239. {
  4240. $this->driver = $driver;
  4241. $this->converter = new ColumnConverter($driver);
  4242. }
  4243. public function getOffsetLimit(int $offset, int $limit): string
  4244. {
  4245. if ($limit < 0 || $offset < 0) {
  4246. return '';
  4247. }
  4248. switch ($this->driver) {
  4249. case 'mysql':
  4250. return " LIMIT $offset, $limit";
  4251. case 'pgsql':
  4252. return " LIMIT $limit OFFSET $offset";
  4253. case 'sqlsrv':
  4254. return " OFFSET $offset ROWS FETCH NEXT $limit ROWS ONLY";
  4255. case 'sqlite':
  4256. return " LIMIT $limit OFFSET $offset";
  4257. }
  4258. }
  4259. private function quoteColumnName(ReflectedColumn $column): string
  4260. {
  4261. return '"' . $column->getName() . '"';
  4262. }
  4263. public function getOrderBy(ReflectedTable $table, array $columnOrdering): string
  4264. {
  4265. if (count($columnOrdering) == 0) {
  4266. return '';
  4267. }
  4268. $results = array();
  4269. foreach ($columnOrdering as $i => list($columnName, $ordering)) {
  4270. $column = $table->getColumn($columnName);
  4271. $quotedColumnName = $this->quoteColumnName($column);
  4272. $results[] = $quotedColumnName . ' ' . $ordering;
  4273. }
  4274. return ' ORDER BY ' . implode(',', $results);
  4275. }
  4276. public function getSelect(ReflectedTable $table, array $columnNames): string
  4277. {
  4278. $results = array();
  4279. foreach ($columnNames as $columnName) {
  4280. $column = $table->getColumn($columnName);
  4281. $quotedColumnName = $this->quoteColumnName($column);
  4282. $quotedColumnName = $this->converter->convertColumnName($column, $quotedColumnName);
  4283. $results[] = $quotedColumnName;
  4284. }
  4285. return implode(',', $results);
  4286. }
  4287. public function getInsert(ReflectedTable $table, array $columnValues): string
  4288. {
  4289. $columns = array();
  4290. $values = array();
  4291. foreach ($columnValues as $columnName => $columnValue) {
  4292. $column = $table->getColumn($columnName);
  4293. $quotedColumnName = $this->quoteColumnName($column);
  4294. $columns[] = $quotedColumnName;
  4295. $columnValue = $this->converter->convertColumnValue($column);
  4296. $values[] = $columnValue;
  4297. }
  4298. $columnsSql = '(' . implode(',', $columns) . ')';
  4299. $valuesSql = '(' . implode(',', $values) . ')';
  4300. $outputColumn = $this->quoteColumnName($table->getPk());
  4301. switch ($this->driver) {
  4302. case 'mysql':
  4303. return "$columnsSql VALUES $valuesSql";
  4304. case 'pgsql':
  4305. return "$columnsSql VALUES $valuesSql RETURNING $outputColumn";
  4306. case 'sqlsrv':
  4307. return "$columnsSql OUTPUT INSERTED.$outputColumn VALUES $valuesSql";
  4308. case 'sqlite':
  4309. return "$columnsSql VALUES $valuesSql";
  4310. }
  4311. }
  4312. public function getUpdate(ReflectedTable $table, array $columnValues): string
  4313. {
  4314. $results = array();
  4315. foreach ($columnValues as $columnName => $columnValue) {
  4316. $column = $table->getColumn($columnName);
  4317. $quotedColumnName = $this->quoteColumnName($column);
  4318. $columnValue = $this->converter->convertColumnValue($column);
  4319. $results[] = $quotedColumnName . '=' . $columnValue;
  4320. }
  4321. return implode(',', $results);
  4322. }
  4323. public function getIncrement(ReflectedTable $table, array $columnValues): string
  4324. {
  4325. $results = array();
  4326. foreach ($columnValues as $columnName => $columnValue) {
  4327. if (!is_numeric($columnValue)) {
  4328. continue;
  4329. }
  4330. $column = $table->getColumn($columnName);
  4331. $quotedColumnName = $this->quoteColumnName($column);
  4332. $columnValue = $this->converter->convertColumnValue($column);
  4333. $results[] = $quotedColumnName . '=' . $quotedColumnName . '+' . $columnValue;
  4334. }
  4335. return implode(',', $results);
  4336. }
  4337. }
  4338. }
  4339. // file: src/Tqdev/PhpCrudApi/Database/ConditionsBuilder.php
  4340. namespace Tqdev\PhpCrudApi\Database {
  4341. use Tqdev\PhpCrudApi\Column\Reflection\ReflectedColumn;
  4342. use Tqdev\PhpCrudApi\Record\Condition\AndCondition;
  4343. use Tqdev\PhpCrudApi\Record\Condition\ColumnCondition;
  4344. use Tqdev\PhpCrudApi\Record\Condition\Condition;
  4345. use Tqdev\PhpCrudApi\Record\Condition\NoCondition;
  4346. use Tqdev\PhpCrudApi\Record\Condition\NotCondition;
  4347. use Tqdev\PhpCrudApi\Record\Condition\OrCondition;
  4348. use Tqdev\PhpCrudApi\Record\Condition\SpatialCondition;
  4349. class ConditionsBuilder
  4350. {
  4351. private $driver;
  4352. public function __construct(string $driver)
  4353. {
  4354. $this->driver = $driver;
  4355. }
  4356. private function getConditionSql(Condition $condition, array &$arguments): string
  4357. {
  4358. if ($condition instanceof AndCondition) {
  4359. return $this->getAndConditionSql($condition, $arguments);
  4360. }
  4361. if ($condition instanceof OrCondition) {
  4362. return $this->getOrConditionSql($condition, $arguments);
  4363. }
  4364. if ($condition instanceof NotCondition) {
  4365. return $this->getNotConditionSql($condition, $arguments);
  4366. }
  4367. if ($condition instanceof SpatialCondition) {
  4368. return $this->getSpatialConditionSql($condition, $arguments);
  4369. }
  4370. if ($condition instanceof ColumnCondition) {
  4371. return $this->getColumnConditionSql($condition, $arguments);
  4372. }
  4373. throw new \Exception('Unknown Condition: ' . get_class($condition));
  4374. }
  4375. private function getAndConditionSql(AndCondition $and, array &$arguments): string
  4376. {
  4377. $parts = [];
  4378. foreach ($and->getConditions() as $condition) {
  4379. $parts[] = $this->getConditionSql($condition, $arguments);
  4380. }
  4381. return '(' . implode(' AND ', $parts) . ')';
  4382. }
  4383. private function getOrConditionSql(OrCondition $or, array &$arguments): string
  4384. {
  4385. $parts = [];
  4386. foreach ($or->getConditions() as $condition) {
  4387. $parts[] = $this->getConditionSql($condition, $arguments);
  4388. }
  4389. return '(' . implode(' OR ', $parts) . ')';
  4390. }
  4391. private function getNotConditionSql(NotCondition $not, array &$arguments): string
  4392. {
  4393. $condition = $not->getCondition();
  4394. return '(NOT ' . $this->getConditionSql($condition, $arguments) . ')';
  4395. }
  4396. private function quoteColumnName(ReflectedColumn $column): string
  4397. {
  4398. return '"' . $column->getName() . '"';
  4399. }
  4400. private function escapeLikeValue(string $value): string
  4401. {
  4402. return addcslashes($value, '%_');
  4403. }
  4404. private function getColumnConditionSql(ColumnCondition $condition, array &$arguments): string
  4405. {
  4406. $column = $this->quoteColumnName($condition->getColumn());
  4407. $operator = $condition->getOperator();
  4408. $value = $condition->getValue();
  4409. switch ($operator) {
  4410. case 'cs':
  4411. $sql = "$column LIKE ?";
  4412. $arguments[] = '%' . $this->escapeLikeValue($value) . '%';
  4413. break;
  4414. case 'sw':
  4415. $sql = "$column LIKE ?";
  4416. $arguments[] = $this->escapeLikeValue($value) . '%';
  4417. break;
  4418. case 'ew':
  4419. $sql = "$column LIKE ?";
  4420. $arguments[] = '%' . $this->escapeLikeValue($value);
  4421. break;
  4422. case 'eq':
  4423. $sql = "$column = ?";
  4424. $arguments[] = $value;
  4425. break;
  4426. case 'lt':
  4427. $sql = "$column < ?";
  4428. $arguments[] = $value;
  4429. break;
  4430. case 'le':
  4431. $sql = "$column <= ?";
  4432. $arguments[] = $value;
  4433. break;
  4434. case 'ge':
  4435. $sql = "$column >= ?";
  4436. $arguments[] = $value;
  4437. break;
  4438. case 'gt':
  4439. $sql = "$column > ?";
  4440. $arguments[] = $value;
  4441. break;
  4442. case 'bt':
  4443. $parts = explode(',', $value, 2);
  4444. $count = count($parts);
  4445. if ($count == 2) {
  4446. $sql = "($column >= ? AND $column <= ?)";
  4447. $arguments[] = $parts[0];
  4448. $arguments[] = $parts[1];
  4449. } else {
  4450. $sql = "FALSE";
  4451. }
  4452. break;
  4453. case 'in':
  4454. $parts = explode(',', $value);
  4455. $count = count($parts);
  4456. if ($count > 0) {
  4457. $qmarks = implode(',', str_split(str_repeat('?', $count)));
  4458. $sql = "$column IN ($qmarks)";
  4459. for ($i = 0; $i < $count; $i++) {
  4460. $arguments[] = $parts[$i];
  4461. }
  4462. } else {
  4463. $sql = "FALSE";
  4464. }
  4465. break;
  4466. case 'is':
  4467. $sql = "$column IS NULL";
  4468. break;
  4469. }
  4470. return $sql;
  4471. }
  4472. private function getSpatialFunctionName(string $operator): string
  4473. {
  4474. switch ($operator) {
  4475. case 'co':
  4476. return 'ST_Contains';
  4477. case 'cr':
  4478. return 'ST_Crosses';
  4479. case 'di':
  4480. return 'ST_Disjoint';
  4481. case 'eq':
  4482. return 'ST_Equals';
  4483. case 'in':
  4484. return 'ST_Intersects';
  4485. case 'ov':
  4486. return 'ST_Overlaps';
  4487. case 'to':
  4488. return 'ST_Touches';
  4489. case 'wi':
  4490. return 'ST_Within';
  4491. case 'ic':
  4492. return 'ST_IsClosed';
  4493. case 'is':
  4494. return 'ST_IsSimple';
  4495. case 'iv':
  4496. return 'ST_IsValid';
  4497. }
  4498. }
  4499. private function hasSpatialArgument(string $operator): bool
  4500. {
  4501. return in_array($operator, ['ic', 'is', 'iv']) ? false : true;
  4502. }
  4503. private function getSpatialFunctionCall(string $functionName, string $column, bool $hasArgument): string
  4504. {
  4505. switch ($this->driver) {
  4506. case 'mysql':
  4507. case 'pgsql':
  4508. $argument = $hasArgument ? 'ST_GeomFromText(?)' : '';
  4509. return "$functionName($column, $argument)=TRUE";
  4510. case 'sqlsrv':
  4511. $functionName = str_replace('ST_', 'ST', $functionName);
  4512. $argument = $hasArgument ? 'geometry::STGeomFromText(?,0)' : '';
  4513. return "$column.$functionName($argument)=1";
  4514. case 'sqlite':
  4515. $argument = $hasArgument ? '?' : '0';
  4516. return "$functionName($column, $argument)=1";
  4517. }
  4518. }
  4519. private function getSpatialConditionSql(ColumnCondition $condition, array &$arguments): string
  4520. {
  4521. $column = $this->quoteColumnName($condition->getColumn());
  4522. $operator = $condition->getOperator();
  4523. $value = $condition->getValue();
  4524. $functionName = $this->getSpatialFunctionName($operator);
  4525. $hasArgument = $this->hasSpatialArgument($operator);
  4526. $sql = $this->getSpatialFunctionCall($functionName, $column, $hasArgument);
  4527. if ($hasArgument) {
  4528. $arguments[] = $value;
  4529. }
  4530. return $sql;
  4531. }
  4532. public function getWhereClause(Condition $condition, array &$arguments): string
  4533. {
  4534. if ($condition instanceof NoCondition) {
  4535. return '';
  4536. }
  4537. return ' WHERE ' . $this->getConditionSql($condition, $arguments);
  4538. }
  4539. }
  4540. }
  4541. // file: src/Tqdev/PhpCrudApi/Database/DataConverter.php
  4542. namespace Tqdev\PhpCrudApi\Database {
  4543. use Tqdev\PhpCrudApi\Column\Reflection\ReflectedColumn;
  4544. use Tqdev\PhpCrudApi\Column\Reflection\ReflectedTable;
  4545. class DataConverter
  4546. {
  4547. private $driver;
  4548. public function __construct(string $driver)
  4549. {
  4550. $this->driver = $driver;
  4551. }
  4552. private function convertRecordValue($conversion, $value)
  4553. {
  4554. $args = explode('|', $conversion);
  4555. $type = array_shift($args);
  4556. switch ($type) {
  4557. case 'boolean':
  4558. return $value ? true : false;
  4559. case 'integer':
  4560. return (int) $value;
  4561. case 'float':
  4562. return (float) $value;
  4563. case 'decimal':
  4564. return number_format($value, $args[0], '.', '');
  4565. }
  4566. return $value;
  4567. }
  4568. private function getRecordValueConversion(ReflectedColumn $column): string
  4569. {
  4570. if (in_array($this->driver, ['mysql', 'sqlsrv', 'sqlite']) && $column->isBoolean()) {
  4571. return 'boolean';
  4572. }
  4573. if (in_array($this->driver, ['sqlsrv', 'sqlite']) && in_array($column->getType(), ['integer', 'bigint'])) {
  4574. return 'integer';
  4575. }
  4576. if (in_array($this->driver, ['sqlite', 'pgsql']) && in_array($column->getType(), ['float', 'double'])) {
  4577. return 'float';
  4578. }
  4579. if (in_array($this->driver, ['sqlite']) && in_array($column->getType(), ['decimal'])) {
  4580. return 'decimal|' . $column->getScale();
  4581. }
  4582. return 'none';
  4583. }
  4584. public function convertRecords(ReflectedTable $table, array $columnNames, array &$records) /*: void*/
  4585. {
  4586. foreach ($columnNames as $columnName) {
  4587. $column = $table->getColumn($columnName);
  4588. $conversion = $this->getRecordValueConversion($column);
  4589. if ($conversion != 'none') {
  4590. foreach ($records as $i => $record) {
  4591. $value = $records[$i][$columnName];
  4592. if ($value === null) {
  4593. continue;
  4594. }
  4595. $records[$i][$columnName] = $this->convertRecordValue($conversion, $value);
  4596. }
  4597. }
  4598. }
  4599. }
  4600. private function convertInputValue($conversion, $value)
  4601. {
  4602. switch ($conversion) {
  4603. case 'boolean':
  4604. return $value ? 1 : 0;
  4605. case 'base64url_to_base64':
  4606. return str_pad(strtr($value, '-_', '+/'), ceil(strlen($value) / 4) * 4, '=', STR_PAD_RIGHT);
  4607. }
  4608. return $value;
  4609. }
  4610. private function getInputValueConversion(ReflectedColumn $column): string
  4611. {
  4612. if ($column->isBoolean()) {
  4613. return 'boolean';
  4614. }
  4615. if ($column->isBinary()) {
  4616. return 'base64url_to_base64';
  4617. }
  4618. return 'none';
  4619. }
  4620. public function convertColumnValues(ReflectedTable $table, array &$columnValues) /*: void*/
  4621. {
  4622. $columnNames = array_keys($columnValues);
  4623. foreach ($columnNames as $columnName) {
  4624. $column = $table->getColumn($columnName);
  4625. $conversion = $this->getInputValueConversion($column);
  4626. if ($conversion != 'none') {
  4627. $value = $columnValues[$columnName];
  4628. if ($value !== null) {
  4629. $columnValues[$columnName] = $this->convertInputValue($conversion, $value);
  4630. }
  4631. }
  4632. }
  4633. }
  4634. }
  4635. }
  4636. // file: src/Tqdev/PhpCrudApi/Database/GenericDB.php
  4637. namespace Tqdev\PhpCrudApi\Database {
  4638. use Tqdev\PhpCrudApi\Column\Reflection\ReflectedTable;
  4639. use Tqdev\PhpCrudApi\Middleware\Communication\VariableStore;
  4640. use Tqdev\PhpCrudApi\Record\Condition\ColumnCondition;
  4641. use Tqdev\PhpCrudApi\Record\Condition\Condition;
  4642. class GenericDB
  4643. {
  4644. private $driver;
  4645. private $address;
  4646. private $port;
  4647. private $database;
  4648. private $tables;
  4649. private $username;
  4650. private $password;
  4651. private $pdo;
  4652. private $reflection;
  4653. private $definition;
  4654. private $conditions;
  4655. private $columns;
  4656. private $converter;
  4657. private function getDsn(): string
  4658. {
  4659. switch ($this->driver) {
  4660. case 'mysql':
  4661. return "$this->driver:host=$this->address;port=$this->port;dbname=$this->database;charset=utf8mb4";
  4662. case 'pgsql':
  4663. return "$this->driver:host=$this->address port=$this->port dbname=$this->database options='--client_encoding=UTF8'";
  4664. case 'sqlsrv':
  4665. return "$this->driver:Server=$this->address,$this->port;Database=$this->database";
  4666. case 'sqlite':
  4667. return "$this->driver:$this->address";
  4668. }
  4669. }
  4670. private function getCommands(): array
  4671. {
  4672. switch ($this->driver) {
  4673. case 'mysql':
  4674. return [
  4675. 'SET SESSION sql_warnings=1;',
  4676. 'SET NAMES utf8mb4;',
  4677. 'SET SESSION sql_mode = "ANSI,TRADITIONAL";',
  4678. ];
  4679. case 'pgsql':
  4680. return [
  4681. "SET NAMES 'UTF8';",
  4682. ];
  4683. case 'sqlsrv':
  4684. return [];
  4685. case 'sqlite':
  4686. return [
  4687. 'PRAGMA foreign_keys = on;',
  4688. ];
  4689. }
  4690. }
  4691. private function getOptions(): array
  4692. {
  4693. $options = array(
  4694. \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
  4695. \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
  4696. );
  4697. switch ($this->driver) {
  4698. case 'mysql':
  4699. return $options + [
  4700. \PDO::ATTR_EMULATE_PREPARES => false,
  4701. \PDO::MYSQL_ATTR_FOUND_ROWS => true,
  4702. \PDO::ATTR_PERSISTENT => true,
  4703. ];
  4704. case 'pgsql':
  4705. return $options + [
  4706. \PDO::ATTR_EMULATE_PREPARES => false,
  4707. \PDO::ATTR_PERSISTENT => true,
  4708. ];
  4709. case 'sqlsrv':
  4710. return $options + [
  4711. \PDO::SQLSRV_ATTR_DIRECT_QUERY => false,
  4712. \PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE => true,
  4713. ];
  4714. case 'sqlite':
  4715. return $options + [];
  4716. }
  4717. }
  4718. private function initPdo(): bool
  4719. {
  4720. if ($this->pdo) {
  4721. $result = $this->pdo->reconstruct($this->getDsn(), $this->username, $this->password, $this->getOptions());
  4722. } else {
  4723. $this->pdo = new LazyPdo($this->getDsn(), $this->username, $this->password, $this->getOptions());
  4724. $result = true;
  4725. }
  4726. $commands = $this->getCommands();
  4727. foreach ($commands as $command) {
  4728. $this->pdo->addInitCommand($command);
  4729. }
  4730. $this->reflection = new GenericReflection($this->pdo, $this->driver, $this->database, $this->tables);
  4731. $this->definition = new GenericDefinition($this->pdo, $this->driver, $this->database, $this->tables);
  4732. $this->conditions = new ConditionsBuilder($this->driver);
  4733. $this->columns = new ColumnsBuilder($this->driver);
  4734. $this->converter = new DataConverter($this->driver);
  4735. return $result;
  4736. }
  4737. public function __construct(string $driver, string $address, int $port, string $database, array $tables, string $username, string $password)
  4738. {
  4739. $this->driver = $driver;
  4740. $this->address = $address;
  4741. $this->port = $port;
  4742. $this->database = $database;
  4743. $this->tables = $tables;
  4744. $this->username = $username;
  4745. $this->password = $password;
  4746. $this->initPdo();
  4747. }
  4748. public function reconstruct(string $driver, string $address, int $port, string $database, array $tables, string $username, string $password): bool
  4749. {
  4750. if ($driver) {
  4751. $this->driver = $driver;
  4752. }
  4753. if ($address) {
  4754. $this->address = $address;
  4755. }
  4756. if ($port) {
  4757. $this->port = $port;
  4758. }
  4759. if ($database) {
  4760. $this->database = $database;
  4761. }
  4762. if ($tables) {
  4763. $this->tables = $tables;
  4764. }
  4765. if ($username) {
  4766. $this->username = $username;
  4767. }
  4768. if ($password) {
  4769. $this->password = $password;
  4770. }
  4771. return $this->initPdo();
  4772. }
  4773. public function pdo(): LazyPdo
  4774. {
  4775. return $this->pdo;
  4776. }
  4777. public function reflection(): GenericReflection
  4778. {
  4779. return $this->reflection;
  4780. }
  4781. public function definition(): GenericDefinition
  4782. {
  4783. return $this->definition;
  4784. }
  4785. private function addMiddlewareConditions(string $tableName, Condition $condition): Condition
  4786. {
  4787. $condition1 = VariableStore::get("authorization.conditions.$tableName");
  4788. if ($condition1) {
  4789. $condition = $condition->_and($condition1);
  4790. }
  4791. $condition2 = VariableStore::get("multiTenancy.conditions.$tableName");
  4792. if ($condition2) {
  4793. $condition = $condition->_and($condition2);
  4794. }
  4795. return $condition;
  4796. }
  4797. public function createSingle(ReflectedTable $table, array $columnValues) /*: ?String*/
  4798. {
  4799. $this->converter->convertColumnValues($table, $columnValues);
  4800. $insertColumns = $this->columns->getInsert($table, $columnValues);
  4801. $tableName = $table->getName();
  4802. $pkName = $table->getPk()->getName();
  4803. $parameters = array_values($columnValues);
  4804. $sql = 'INSERT INTO "' . $tableName . '" ' . $insertColumns;
  4805. $stmt = $this->query($sql, $parameters);
  4806. // return primary key value if specified in the input
  4807. if (isset($columnValues[$pkName])) {
  4808. return $columnValues[$pkName];
  4809. }
  4810. // work around missing "returning" or "output" in mysql
  4811. switch ($this->driver) {
  4812. case 'mysql':
  4813. $stmt = $this->query('SELECT LAST_INSERT_ID()', []);
  4814. break;
  4815. case 'sqlite':
  4816. $stmt = $this->query('SELECT LAST_INSERT_ROWID()', []);
  4817. break;
  4818. }
  4819. $pkValue = $stmt->fetchColumn(0);
  4820. if ($this->driver == 'sqlsrv' && $table->getPk()->getType() == 'bigint') {
  4821. return (int) $pkValue;
  4822. }
  4823. if ($this->driver == 'sqlite' && in_array($table->getPk()->getType(), ['integer', 'bigint'])) {
  4824. return (int) $pkValue;
  4825. }
  4826. return $pkValue;
  4827. }
  4828. public function selectSingle(ReflectedTable $table, array $columnNames, string $id) /*: ?array*/
  4829. {
  4830. $selectColumns = $this->columns->getSelect($table, $columnNames);
  4831. $tableName = $table->getName();
  4832. $condition = new ColumnCondition($table->getPk(), 'eq', $id);
  4833. $condition = $this->addMiddlewareConditions($tableName, $condition);
  4834. $parameters = array();
  4835. $whereClause = $this->conditions->getWhereClause($condition, $parameters);
  4836. $sql = 'SELECT ' . $selectColumns . ' FROM "' . $tableName . '" ' . $whereClause;
  4837. $stmt = $this->query($sql, $parameters);
  4838. $record = $stmt->fetch() ?: null;
  4839. if ($record === null) {
  4840. return null;
  4841. }
  4842. $records = array($record);
  4843. $this->converter->convertRecords($table, $columnNames, $records);
  4844. return $records[0];
  4845. }
  4846. public function selectMultiple(ReflectedTable $table, array $columnNames, array $ids): array
  4847. {
  4848. if (count($ids) == 0) {
  4849. return [];
  4850. }
  4851. $selectColumns = $this->columns->getSelect($table, $columnNames);
  4852. $tableName = $table->getName();
  4853. $condition = new ColumnCondition($table->getPk(), 'in', implode(',', $ids));
  4854. $condition = $this->addMiddlewareConditions($tableName, $condition);
  4855. $parameters = array();
  4856. $whereClause = $this->conditions->getWhereClause($condition, $parameters);
  4857. $sql = 'SELECT ' . $selectColumns . ' FROM "' . $tableName . '" ' . $whereClause;
  4858. $stmt = $this->query($sql, $parameters);
  4859. $records = $stmt->fetchAll();
  4860. $this->converter->convertRecords($table, $columnNames, $records);
  4861. return $records;
  4862. }
  4863. public function selectCount(ReflectedTable $table, Condition $condition): int
  4864. {
  4865. $tableName = $table->getName();
  4866. $condition = $this->addMiddlewareConditions($tableName, $condition);
  4867. $parameters = array();
  4868. $whereClause = $this->conditions->getWhereClause($condition, $parameters);
  4869. $sql = 'SELECT COUNT(*) FROM "' . $tableName . '"' . $whereClause;
  4870. $stmt = $this->query($sql, $parameters);
  4871. return $stmt->fetchColumn(0);
  4872. }
  4873. public function selectAll(ReflectedTable $table, array $columnNames, Condition $condition, array $columnOrdering, int $offset, int $limit): array
  4874. {
  4875. if ($limit == 0) {
  4876. return array();
  4877. }
  4878. $selectColumns = $this->columns->getSelect($table, $columnNames);
  4879. $tableName = $table->getName();
  4880. $condition = $this->addMiddlewareConditions($tableName, $condition);
  4881. $parameters = array();
  4882. $whereClause = $this->conditions->getWhereClause($condition, $parameters);
  4883. $orderBy = $this->columns->getOrderBy($table, $columnOrdering);
  4884. $offsetLimit = $this->columns->getOffsetLimit($offset, $limit);
  4885. $sql = 'SELECT ' . $selectColumns . ' FROM "' . $tableName . '"' . $whereClause . $orderBy . $offsetLimit;
  4886. $stmt = $this->query($sql, $parameters);
  4887. $records = $stmt->fetchAll();
  4888. $this->converter->convertRecords($table, $columnNames, $records);
  4889. return $records;
  4890. }
  4891. public function updateSingle(ReflectedTable $table, array $columnValues, string $id)
  4892. {
  4893. if (count($columnValues) == 0) {
  4894. return 0;
  4895. }
  4896. $this->converter->convertColumnValues($table, $columnValues);
  4897. $updateColumns = $this->columns->getUpdate($table, $columnValues);
  4898. $tableName = $table->getName();
  4899. $condition = new ColumnCondition($table->getPk(), 'eq', $id);
  4900. $condition = $this->addMiddlewareConditions($tableName, $condition);
  4901. $parameters = array_values($columnValues);
  4902. $whereClause = $this->conditions->getWhereClause($condition, $parameters);
  4903. $sql = 'UPDATE "' . $tableName . '" SET ' . $updateColumns . $whereClause;
  4904. $stmt = $this->query($sql, $parameters);
  4905. return $stmt->rowCount();
  4906. }
  4907. public function deleteSingle(ReflectedTable $table, string $id)
  4908. {
  4909. $tableName = $table->getName();
  4910. $condition = new ColumnCondition($table->getPk(), 'eq', $id);
  4911. $condition = $this->addMiddlewareConditions($tableName, $condition);
  4912. $parameters = array();
  4913. $whereClause = $this->conditions->getWhereClause($condition, $parameters);
  4914. $sql = 'DELETE FROM "' . $tableName . '" ' . $whereClause;
  4915. $stmt = $this->query($sql, $parameters);
  4916. return $stmt->rowCount();
  4917. }
  4918. public function incrementSingle(ReflectedTable $table, array $columnValues, string $id)
  4919. {
  4920. if (count($columnValues) == 0) {
  4921. return 0;
  4922. }
  4923. $this->converter->convertColumnValues($table, $columnValues);
  4924. $updateColumns = $this->columns->getIncrement($table, $columnValues);
  4925. $tableName = $table->getName();
  4926. $condition = new ColumnCondition($table->getPk(), 'eq', $id);
  4927. $condition = $this->addMiddlewareConditions($tableName, $condition);
  4928. $parameters = array_values($columnValues);
  4929. $whereClause = $this->conditions->getWhereClause($condition, $parameters);
  4930. $sql = 'UPDATE "' . $tableName . '" SET ' . $updateColumns . $whereClause;
  4931. $stmt = $this->query($sql, $parameters);
  4932. return $stmt->rowCount();
  4933. }
  4934. private function query(string $sql, array $parameters): \PDOStatement
  4935. {
  4936. $stmt = $this->pdo->prepare($sql);
  4937. //echo "- $sql -- " . json_encode($parameters, JSON_UNESCAPED_UNICODE) . "\n";
  4938. $stmt->execute($parameters);
  4939. return $stmt;
  4940. }
  4941. public function getCacheKey(): string
  4942. {
  4943. return md5(json_encode([
  4944. $this->driver,
  4945. $this->address,
  4946. $this->port,
  4947. $this->database,
  4948. $this->tables,
  4949. $this->username
  4950. ]));
  4951. }
  4952. }
  4953. }
  4954. // file: src/Tqdev/PhpCrudApi/Database/GenericDefinition.php
  4955. namespace Tqdev\PhpCrudApi\Database {
  4956. use Tqdev\PhpCrudApi\Column\Reflection\ReflectedColumn;
  4957. use Tqdev\PhpCrudApi\Column\Reflection\ReflectedTable;
  4958. use Tqdev\PhpCrudApi\Database\LazyPdo;
  4959. class GenericDefinition
  4960. {
  4961. private $pdo;
  4962. private $driver;
  4963. private $database;
  4964. private $typeConverter;
  4965. private $reflection;
  4966. public function __construct(LazyPdo $pdo, string $driver, string $database, array $tables)
  4967. {
  4968. $this->pdo = $pdo;
  4969. $this->driver = $driver;
  4970. $this->database = $database;
  4971. $this->typeConverter = new TypeConverter($driver);
  4972. $this->reflection = new GenericReflection($pdo, $driver, $database, $tables);
  4973. }
  4974. private function quote(string $identifier): string
  4975. {
  4976. return '"' . str_replace('"', '', $identifier) . '"';
  4977. }
  4978. public function getColumnType(ReflectedColumn $column, bool $update): string
  4979. {
  4980. if ($this->driver == 'pgsql' && !$update && $column->getPk() && $this->canAutoIncrement($column)) {
  4981. return 'serial';
  4982. }
  4983. $type = $this->typeConverter->fromJdbc($column->getType());
  4984. if ($column->hasPrecision() && $column->hasScale()) {
  4985. $size = '(' . $column->getPrecision() . ',' . $column->getScale() . ')';
  4986. } elseif ($column->hasPrecision()) {
  4987. $size = '(' . $column->getPrecision() . ')';
  4988. } elseif ($column->hasLength()) {
  4989. $size = '(' . $column->getLength() . ')';
  4990. } else {
  4991. $size = '';
  4992. }
  4993. $null = $this->getColumnNullType($column, $update);
  4994. $auto = $this->getColumnAutoIncrement($column, $update);
  4995. return $type . $size . $null . $auto;
  4996. }
  4997. private function getPrimaryKey(string $tableName): string
  4998. {
  4999. $pks = $this->reflection->getTablePrimaryKeys($tableName);
  5000. if (count($pks) == 1) {
  5001. return $pks[0];
  5002. }
  5003. return "";
  5004. }
  5005. private function canAutoIncrement(ReflectedColumn $column): bool
  5006. {
  5007. return in_array($column->getType(), ['integer', 'bigint']);
  5008. }
  5009. private function getColumnAutoIncrement(ReflectedColumn $column, bool $update): string
  5010. {
  5011. if (!$this->canAutoIncrement($column)) {
  5012. return '';
  5013. }
  5014. switch ($this->driver) {
  5015. case 'mysql':
  5016. return $column->getPk() ? ' AUTO_INCREMENT' : '';
  5017. case 'pgsql':
  5018. case 'sqlsrv':
  5019. return $column->getPk() ? ' IDENTITY(1,1)' : '';
  5020. case 'sqlite':
  5021. return $column->getPk() ? ' AUTOINCREMENT' : '';
  5022. }
  5023. }
  5024. private function getColumnNullType(ReflectedColumn $column, bool $update): string
  5025. {
  5026. if ($this->driver == 'pgsql' && $update) {
  5027. return '';
  5028. }
  5029. return $column->getNullable() ? ' NULL' : ' NOT NULL';
  5030. }
  5031. private function getTableRenameSQL(string $tableName, string $newTableName): string
  5032. {
  5033. $p1 = $this->quote($tableName);
  5034. $p2 = $this->quote($newTableName);
  5035. switch ($this->driver) {
  5036. case 'mysql':
  5037. return "RENAME TABLE $p1 TO $p2";
  5038. case 'pgsql':
  5039. return "ALTER TABLE $p1 RENAME TO $p2";
  5040. case 'sqlsrv':
  5041. return "EXEC sp_rename $p1, $p2";
  5042. case 'sqlite':
  5043. return "ALTER TABLE $p1 RENAME TO $p2";
  5044. }
  5045. }
  5046. private function getColumnRenameSQL(string $tableName, string $columnName, ReflectedColumn $newColumn): string
  5047. {
  5048. $p1 = $this->quote($tableName);
  5049. $p2 = $this->quote($columnName);
  5050. $p3 = $this->quote($newColumn->getName());
  5051. switch ($this->driver) {
  5052. case 'mysql':
  5053. $p4 = $this->getColumnType($newColumn, true);
  5054. return "ALTER TABLE $p1 CHANGE $p2 $p3 $p4";
  5055. case 'pgsql':
  5056. return "ALTER TABLE $p1 RENAME COLUMN $p2 TO $p3";
  5057. case 'sqlsrv':
  5058. $p4 = $this->quote($tableName . '.' . $columnName);
  5059. return "EXEC sp_rename $p4, $p3, 'COLUMN'";
  5060. case 'sqlite':
  5061. return "ALTER TABLE $p1 RENAME COLUMN $p2 TO $p3";
  5062. }
  5063. }
  5064. private function getColumnRetypeSQL(string $tableName, string $columnName, ReflectedColumn $newColumn): string
  5065. {
  5066. $p1 = $this->quote($tableName);
  5067. $p2 = $this->quote($columnName);
  5068. $p3 = $this->quote($newColumn->getName());
  5069. $p4 = $this->getColumnType($newColumn, true);
  5070. switch ($this->driver) {
  5071. case 'mysql':
  5072. return "ALTER TABLE $p1 CHANGE $p2 $p3 $p4";
  5073. case 'pgsql':
  5074. return "ALTER TABLE $p1 ALTER COLUMN $p3 TYPE $p4";
  5075. case 'sqlsrv':
  5076. return "ALTER TABLE $p1 ALTER COLUMN $p3 $p4";
  5077. }
  5078. }
  5079. private function getSetColumnNullableSQL(string $tableName, string $columnName, ReflectedColumn $newColumn): string
  5080. {
  5081. $p1 = $this->quote($tableName);
  5082. $p2 = $this->quote($columnName);
  5083. $p3 = $this->quote($newColumn->getName());
  5084. $p4 = $this->getColumnType($newColumn, true);
  5085. switch ($this->driver) {
  5086. case 'mysql':
  5087. return "ALTER TABLE $p1 CHANGE $p2 $p3 $p4";
  5088. case 'pgsql':
  5089. $p5 = $newColumn->getNullable() ? 'DROP NOT NULL' : 'SET NOT NULL';
  5090. return "ALTER TABLE $p1 ALTER COLUMN $p2 $p5";
  5091. case 'sqlsrv':
  5092. return "ALTER TABLE $p1 ALTER COLUMN $p2 $p4";
  5093. }
  5094. }
  5095. private function getSetColumnPkConstraintSQL(string $tableName, string $columnName, ReflectedColumn $newColumn): string
  5096. {
  5097. $p1 = $this->quote($tableName);
  5098. $p2 = $this->quote($columnName);
  5099. $p3 = $this->quote($tableName . '_pkey');
  5100. switch ($this->driver) {
  5101. case 'mysql':
  5102. $p4 = $newColumn->getPk() ? "ADD PRIMARY KEY ($p2)" : 'DROP PRIMARY KEY';
  5103. return "ALTER TABLE $p1 $p4";
  5104. case 'pgsql':
  5105. case 'sqlsrv':
  5106. $p4 = $newColumn->getPk() ? "ADD CONSTRAINT $p3 PRIMARY KEY ($p2)" : "DROP CONSTRAINT $p3";
  5107. return "ALTER TABLE $p1 $p4";
  5108. }
  5109. }
  5110. private function getSetColumnPkSequenceSQL(string $tableName, string $columnName, ReflectedColumn $newColumn): string
  5111. {
  5112. $p1 = $this->quote($tableName);
  5113. $p2 = $this->quote($columnName);
  5114. $p3 = $this->quote($tableName . '_' . $columnName . '_seq');
  5115. switch ($this->driver) {
  5116. case 'mysql':
  5117. return "select 1";
  5118. case 'pgsql':
  5119. return $newColumn->getPk() ? "CREATE SEQUENCE $p3 OWNED BY $p1.$p2" : "DROP SEQUENCE $p3";
  5120. case 'sqlsrv':
  5121. return $newColumn->getPk() ? "CREATE SEQUENCE $p3" : "DROP SEQUENCE $p3";
  5122. }
  5123. }
  5124. private function getSetColumnPkSequenceStartSQL(string $tableName, string $columnName, ReflectedColumn $newColumn): string
  5125. {
  5126. $p1 = $this->quote($tableName);
  5127. $p2 = $this->quote($columnName);
  5128. switch ($this->driver) {
  5129. case 'mysql':
  5130. return "select 1";
  5131. case 'pgsql':
  5132. $p3 = $this->pdo->quote($tableName . '_' . $columnName . '_seq');
  5133. return "SELECT setval($p3, (SELECT max($p2)+1 FROM $p1));";
  5134. case 'sqlsrv':
  5135. $p3 = $this->quote($tableName . '_' . $columnName . '_seq');
  5136. $p4 = $this->pdo->query("SELECT max($p2)+1 FROM $p1")->fetchColumn();
  5137. return "ALTER SEQUENCE $p3 RESTART WITH $p4";
  5138. }
  5139. }
  5140. private function getSetColumnPkDefaultSQL(string $tableName, string $columnName, ReflectedColumn $newColumn): string
  5141. {
  5142. $p1 = $this->quote($tableName);
  5143. $p2 = $this->quote($columnName);
  5144. switch ($this->driver) {
  5145. case 'mysql':
  5146. $p3 = $this->quote($newColumn->getName());
  5147. $p4 = $this->getColumnType($newColumn, true);
  5148. return "ALTER TABLE $p1 CHANGE $p2 $p3 $p4";
  5149. case 'pgsql':
  5150. if ($newColumn->getPk()) {
  5151. $p3 = $this->pdo->quote($tableName . '_' . $columnName . '_seq');
  5152. $p4 = "SET DEFAULT nextval($p3)";
  5153. } else {
  5154. $p4 = 'DROP DEFAULT';
  5155. }
  5156. return "ALTER TABLE $p1 ALTER COLUMN $p2 $p4";
  5157. case 'sqlsrv':
  5158. $p3 = $this->quote($tableName . '_' . $columnName . '_seq');
  5159. $p4 = $this->quote($tableName . '_' . $columnName . '_def');
  5160. if ($newColumn->getPk()) {
  5161. return "ALTER TABLE $p1 ADD CONSTRAINT $p4 DEFAULT NEXT VALUE FOR $p3 FOR $p2";
  5162. } else {
  5163. return "ALTER TABLE $p1 DROP CONSTRAINT $p4";
  5164. }
  5165. }
  5166. }
  5167. private function getAddColumnFkConstraintSQL(string $tableName, string $columnName, ReflectedColumn $newColumn): string
  5168. {
  5169. $p1 = $this->quote($tableName);
  5170. $p2 = $this->quote($columnName);
  5171. $p3 = $this->quote($tableName . '_' . $columnName . '_fkey');
  5172. $p4 = $this->quote($newColumn->getFk());
  5173. $p5 = $this->quote($this->getPrimaryKey($newColumn->getFk()));
  5174. return "ALTER TABLE $p1 ADD CONSTRAINT $p3 FOREIGN KEY ($p2) REFERENCES $p4 ($p5)";
  5175. }
  5176. private function getRemoveColumnFkConstraintSQL(string $tableName, string $columnName, ReflectedColumn $newColumn): string
  5177. {
  5178. $p1 = $this->quote($tableName);
  5179. $p2 = $this->quote($tableName . '_' . $columnName . '_fkey');
  5180. switch ($this->driver) {
  5181. case 'mysql':
  5182. return "ALTER TABLE $p1 DROP FOREIGN KEY $p2";
  5183. case 'pgsql':
  5184. case 'sqlsrv':
  5185. return "ALTER TABLE $p1 DROP CONSTRAINT $p2";
  5186. }
  5187. }
  5188. private function getAddTableSQL(ReflectedTable $newTable): string
  5189. {
  5190. $tableName = $newTable->getName();
  5191. $p1 = $this->quote($tableName);
  5192. $fields = [];
  5193. $constraints = [];
  5194. foreach ($newTable->getColumnNames() as $columnName) {
  5195. $pkColumn = $this->getPrimaryKey($tableName);
  5196. $newColumn = $newTable->getColumn($columnName);
  5197. $f1 = $this->quote($columnName);
  5198. $f2 = $this->getColumnType($newColumn, false);
  5199. $f3 = $this->quote($tableName . '_' . $columnName . '_fkey');
  5200. $f4 = $this->quote($newColumn->getFk());
  5201. $f5 = $this->quote($this->getPrimaryKey($newColumn->getFk()));
  5202. $f6 = $this->quote($tableName . '_' . $pkColumn . '_pkey');
  5203. if ($this->driver == 'sqlite') {
  5204. if ($newColumn->getPk()) {
  5205. $f2 = str_replace('NULL', 'NULL PRIMARY KEY', $f2);
  5206. }
  5207. $fields[] = "$f1 $f2";
  5208. if ($newColumn->getFk()) {
  5209. $constraints[] = "FOREIGN KEY ($f1) REFERENCES $f4 ($f5)";
  5210. }
  5211. } else {
  5212. $fields[] = "$f1 $f2";
  5213. if ($newColumn->getPk()) {
  5214. $constraints[] = "CONSTRAINT $f6 PRIMARY KEY ($f1)";
  5215. }
  5216. if ($newColumn->getFk()) {
  5217. $constraints[] = "CONSTRAINT $f3 FOREIGN KEY ($f1) REFERENCES $f4 ($f5)";
  5218. }
  5219. }
  5220. }
  5221. $p2 = implode(',', array_merge($fields, $constraints));
  5222. return "CREATE TABLE $p1 ($p2);";
  5223. }
  5224. private function getAddColumnSQL(string $tableName, ReflectedColumn $newColumn): string
  5225. {
  5226. $p1 = $this->quote($tableName);
  5227. $p2 = $this->quote($newColumn->getName());
  5228. $p3 = $this->getColumnType($newColumn, false);
  5229. switch ($this->driver) {
  5230. case 'mysql':
  5231. case 'pgsql':
  5232. return "ALTER TABLE $p1 ADD COLUMN $p2 $p3";
  5233. case 'sqlsrv':
  5234. return "ALTER TABLE $p1 ADD $p2 $p3";
  5235. case 'sqlite':
  5236. return "ALTER TABLE $p1 ADD COLUMN $p2 $p3";
  5237. }
  5238. }
  5239. private function getRemoveTableSQL(string $tableName): string
  5240. {
  5241. $p1 = $this->quote($tableName);
  5242. switch ($this->driver) {
  5243. case 'mysql':
  5244. case 'pgsql':
  5245. return "DROP TABLE $p1 CASCADE;";
  5246. case 'sqlsrv':
  5247. return "DROP TABLE $p1;";
  5248. case 'sqlite':
  5249. return "DROP TABLE $p1;";
  5250. }
  5251. }
  5252. private function getRemoveColumnSQL(string $tableName, string $columnName): string
  5253. {
  5254. $p1 = $this->quote($tableName);
  5255. $p2 = $this->quote($columnName);
  5256. switch ($this->driver) {
  5257. case 'mysql':
  5258. case 'pgsql':
  5259. return "ALTER TABLE $p1 DROP COLUMN $p2 CASCADE;";
  5260. case 'sqlsrv':
  5261. return "ALTER TABLE $p1 DROP COLUMN $p2;";
  5262. case 'sqlite':
  5263. return "ALTER TABLE $p1 DROP COLUMN $p2;";
  5264. }
  5265. }
  5266. public function renameTable(string $tableName, string $newTableName)
  5267. {
  5268. $sql = $this->getTableRenameSQL($tableName, $newTableName);
  5269. return $this->query($sql, []);
  5270. }
  5271. public function renameColumn(string $tableName, string $columnName, ReflectedColumn $newColumn)
  5272. {
  5273. $sql = $this->getColumnRenameSQL($tableName, $columnName, $newColumn);
  5274. return $this->query($sql, []);
  5275. }
  5276. public function retypeColumn(string $tableName, string $columnName, ReflectedColumn $newColumn)
  5277. {
  5278. $sql = $this->getColumnRetypeSQL($tableName, $columnName, $newColumn);
  5279. return $this->query($sql, []);
  5280. }
  5281. public function setColumnNullable(string $tableName, string $columnName, ReflectedColumn $newColumn)
  5282. {
  5283. $sql = $this->getSetColumnNullableSQL($tableName, $columnName, $newColumn);
  5284. return $this->query($sql, []);
  5285. }
  5286. public function addColumnPrimaryKey(string $tableName, string $columnName, ReflectedColumn $newColumn)
  5287. {
  5288. $sql = $this->getSetColumnPkConstraintSQL($tableName, $columnName, $newColumn);
  5289. $this->query($sql, []);
  5290. if ($this->canAutoIncrement($newColumn)) {
  5291. $sql = $this->getSetColumnPkSequenceSQL($tableName, $columnName, $newColumn);
  5292. $this->query($sql, []);
  5293. $sql = $this->getSetColumnPkSequenceStartSQL($tableName, $columnName, $newColumn);
  5294. $this->query($sql, []);
  5295. $sql = $this->getSetColumnPkDefaultSQL($tableName, $columnName, $newColumn);
  5296. $this->query($sql, []);
  5297. }
  5298. return true;
  5299. }
  5300. public function removeColumnPrimaryKey(string $tableName, string $columnName, ReflectedColumn $newColumn)
  5301. {
  5302. if ($this->canAutoIncrement($newColumn)) {
  5303. $sql = $this->getSetColumnPkDefaultSQL($tableName, $columnName, $newColumn);
  5304. $this->query($sql, []);
  5305. $sql = $this->getSetColumnPkSequenceSQL($tableName, $columnName, $newColumn);
  5306. $this->query($sql, []);
  5307. }
  5308. $sql = $this->getSetColumnPkConstraintSQL($tableName, $columnName, $newColumn);
  5309. $this->query($sql, []);
  5310. return true;
  5311. }
  5312. public function addColumnForeignKey(string $tableName, string $columnName, ReflectedColumn $newColumn)
  5313. {
  5314. $sql = $this->getAddColumnFkConstraintSQL($tableName, $columnName, $newColumn);
  5315. return $this->query($sql, []);
  5316. }
  5317. public function removeColumnForeignKey(string $tableName, string $columnName, ReflectedColumn $newColumn)
  5318. {
  5319. $sql = $this->getRemoveColumnFkConstraintSQL($tableName, $columnName, $newColumn);
  5320. return $this->query($sql, []);
  5321. }
  5322. public function addTable(ReflectedTable $newTable)
  5323. {
  5324. $sql = $this->getAddTableSQL($newTable);
  5325. return $this->query($sql, []);
  5326. }
  5327. public function addColumn(string $tableName, ReflectedColumn $newColumn)
  5328. {
  5329. $sql = $this->getAddColumnSQL($tableName, $newColumn);
  5330. return $this->query($sql, []);
  5331. }
  5332. public function removeTable(string $tableName)
  5333. {
  5334. $sql = $this->getRemoveTableSQL($tableName);
  5335. return $this->query($sql, []);
  5336. }
  5337. public function removeColumn(string $tableName, string $columnName)
  5338. {
  5339. $sql = $this->getRemoveColumnSQL($tableName, $columnName);
  5340. return $this->query($sql, []);
  5341. }
  5342. private function query(string $sql, array $arguments): bool
  5343. {
  5344. $stmt = $this->pdo->prepare($sql);
  5345. // echo "- $sql -- " . json_encode($arguments) . "\n";
  5346. return $stmt->execute($arguments);
  5347. }
  5348. }
  5349. }
  5350. // file: src/Tqdev/PhpCrudApi/Database/GenericReflection.php
  5351. namespace Tqdev\PhpCrudApi\Database {
  5352. use Tqdev\PhpCrudApi\Database\LazyPdo;
  5353. class GenericReflection
  5354. {
  5355. private $pdo;
  5356. private $driver;
  5357. private $database;
  5358. private $tables;
  5359. private $typeConverter;
  5360. public function __construct(LazyPdo $pdo, string $driver, string $database, array $tables)
  5361. {
  5362. $this->pdo = $pdo;
  5363. $this->driver = $driver;
  5364. $this->database = $database;
  5365. $this->tables = $tables;
  5366. $this->typeConverter = new TypeConverter($driver);
  5367. }
  5368. public function getIgnoredTables(): array
  5369. {
  5370. switch ($this->driver) {
  5371. case 'mysql':
  5372. return [];
  5373. case 'pgsql':
  5374. return ['spatial_ref_sys', 'raster_columns', 'raster_overviews', 'geography_columns', 'geometry_columns'];
  5375. case 'sqlsrv':
  5376. return [];
  5377. case 'sqlite':
  5378. return [];
  5379. }
  5380. }
  5381. private function getTablesSQL(): string
  5382. {
  5383. switch ($this->driver) {
  5384. case 'mysql':
  5385. return 'SELECT "TABLE_NAME", "TABLE_TYPE" FROM "INFORMATION_SCHEMA"."TABLES" WHERE "TABLE_TYPE" IN (\'BASE TABLE\' , \'VIEW\') AND "TABLE_SCHEMA" = ? ORDER BY BINARY "TABLE_NAME"';
  5386. case 'pgsql':
  5387. return 'SELECT c.relname as "TABLE_NAME", c.relkind as "TABLE_TYPE" FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind IN (\'r\', \'v\') AND n.nspname <> \'pg_catalog\' AND n.nspname <> \'information_schema\' AND n.nspname !~ \'^pg_toast\' AND pg_catalog.pg_table_is_visible(c.oid) AND \'\' <> ? ORDER BY "TABLE_NAME";';
  5388. case 'sqlsrv':
  5389. return 'SELECT o.name as "TABLE_NAME", o.xtype as "TABLE_TYPE" FROM sysobjects o WHERE o.xtype IN (\'U\', \'V\') ORDER BY "TABLE_NAME"';
  5390. case 'sqlite':
  5391. return 'SELECT t.name as "TABLE_NAME", t.type as "TABLE_TYPE" FROM sqlite_master t WHERE t.type IN (\'table\', \'view\') AND \'\' <> ? ORDER BY "TABLE_NAME"';
  5392. }
  5393. }
  5394. private function getTableColumnsSQL(): string
  5395. {
  5396. switch ($this->driver) {
  5397. case 'mysql':
  5398. return 'SELECT "COLUMN_NAME", "IS_NULLABLE", "DATA_TYPE", "CHARACTER_MAXIMUM_LENGTH" as "CHARACTER_MAXIMUM_LENGTH", "NUMERIC_PRECISION", "NUMERIC_SCALE", "COLUMN_TYPE" FROM "INFORMATION_SCHEMA"."COLUMNS" WHERE "TABLE_NAME" = ? AND "TABLE_SCHEMA" = ? ORDER BY "ORDINAL_POSITION"';
  5399. case 'pgsql':
  5400. return 'SELECT a.attname AS "COLUMN_NAME", case when a.attnotnull then \'NO\' else \'YES\' end as "IS_NULLABLE", pg_catalog.format_type(a.atttypid, -1) as "DATA_TYPE", case when a.atttypmod < 0 then NULL else a.atttypmod-4 end as "CHARACTER_MAXIMUM_LENGTH", case when a.atttypid != 1700 then NULL else ((a.atttypmod - 4) >> 16) & 65535 end as "NUMERIC_PRECISION", case when a.atttypid != 1700 then NULL else (a.atttypmod - 4) & 65535 end as "NUMERIC_SCALE", \'\' AS "COLUMN_TYPE" FROM pg_attribute a JOIN pg_class pgc ON pgc.oid = a.attrelid WHERE pgc.relname = ? AND \'\' <> ? AND a.attnum > 0 AND NOT a.attisdropped ORDER BY a.attnum;';
  5401. case 'sqlsrv':
  5402. return 'SELECT c.name AS "COLUMN_NAME", c.is_nullable AS "IS_NULLABLE", t.Name AS "DATA_TYPE", (c.max_length/2) AS "CHARACTER_MAXIMUM_LENGTH", c.precision AS "NUMERIC_PRECISION", c.scale AS "NUMERIC_SCALE", \'\' AS "COLUMN_TYPE" FROM sys.columns c INNER JOIN sys.types t ON c.user_type_id = t.user_type_id WHERE c.object_id = OBJECT_ID(?) AND \'\' <> ? ORDER BY c.column_id';
  5403. case 'sqlite':
  5404. return 'SELECT "name" AS "COLUMN_NAME", case when "notnull"==1 then \'no\' else \'yes\' end as "IS_NULLABLE", lower("type") AS "DATA_TYPE", 2147483647 AS "CHARACTER_MAXIMUM_LENGTH", 0 AS "NUMERIC_PRECISION", 0 AS "NUMERIC_SCALE", \'\' AS "COLUMN_TYPE" FROM pragma_table_info(?) WHERE \'\' <> ? ORDER BY "cid"';
  5405. }
  5406. }
  5407. private function getTablePrimaryKeysSQL(): string
  5408. {
  5409. switch ($this->driver) {
  5410. case 'mysql':
  5411. return 'SELECT "COLUMN_NAME" FROM "INFORMATION_SCHEMA"."KEY_COLUMN_USAGE" WHERE "CONSTRAINT_NAME" = \'PRIMARY\' AND "TABLE_NAME" = ? AND "TABLE_SCHEMA" = ?';
  5412. case 'pgsql':
  5413. return 'SELECT a.attname AS "COLUMN_NAME" FROM pg_attribute a JOIN pg_constraint c ON (c.conrelid, c.conkey[1]) = (a.attrelid, a.attnum) JOIN pg_class pgc ON pgc.oid = a.attrelid WHERE pgc.relname = ? AND \'\' <> ? AND c.contype = \'p\'';
  5414. case 'sqlsrv':
  5415. return 'SELECT c.NAME as "COLUMN_NAME" FROM sys.key_constraints kc inner join sys.objects t on t.object_id = kc.parent_object_id INNER JOIN sys.index_columns ic ON kc.parent_object_id = ic.object_id and kc.unique_index_id = ic.index_id INNER JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id WHERE kc.type = \'PK\' and t.object_id = OBJECT_ID(?) and \'\' <> ?';
  5416. case 'sqlite':
  5417. return 'SELECT "name" as "COLUMN_NAME" FROM pragma_table_info(?) WHERE "pk"=1 AND \'\' <> ?';
  5418. }
  5419. }
  5420. private function getTableForeignKeysSQL(): string
  5421. {
  5422. switch ($this->driver) {
  5423. case 'mysql':
  5424. return 'SELECT "COLUMN_NAME", "REFERENCED_TABLE_NAME" FROM "INFORMATION_SCHEMA"."KEY_COLUMN_USAGE" WHERE "REFERENCED_TABLE_NAME" IS NOT NULL AND "TABLE_NAME" = ? AND "TABLE_SCHEMA" = ?';
  5425. case 'pgsql':
  5426. return 'SELECT a.attname AS "COLUMN_NAME", c.confrelid::regclass::text AS "REFERENCED_TABLE_NAME" FROM pg_attribute a JOIN pg_constraint c ON (c.conrelid, c.conkey[1]) = (a.attrelid, a.attnum) JOIN pg_class pgc ON pgc.oid = a.attrelid WHERE pgc.relname = ? AND \'\' <> ? AND c.contype = \'f\'';
  5427. case 'sqlsrv':
  5428. return 'SELECT COL_NAME(fc.parent_object_id, fc.parent_column_id) AS "COLUMN_NAME", OBJECT_NAME (f.referenced_object_id) AS "REFERENCED_TABLE_NAME" FROM sys.foreign_keys AS f INNER JOIN sys.foreign_key_columns AS fc ON f.OBJECT_ID = fc.constraint_object_id WHERE f.parent_object_id = OBJECT_ID(?) and \'\' <> ?';
  5429. case 'sqlite':
  5430. return 'SELECT "from" AS "COLUMN_NAME", "table" AS "REFERENCED_TABLE_NAME" FROM pragma_foreign_key_list(?) WHERE \'\' <> ?';
  5431. }
  5432. }
  5433. public function getDatabaseName(): string
  5434. {
  5435. return $this->database;
  5436. }
  5437. public function getTables(): array
  5438. {
  5439. $sql = $this->getTablesSQL();
  5440. $results = $this->query($sql, [$this->database]);
  5441. $tables = $this->tables;
  5442. $results = array_filter($results, function ($v) use ($tables) {
  5443. return !$tables || in_array($v['TABLE_NAME'], $tables);
  5444. });
  5445. foreach ($results as &$result) {
  5446. $map = [];
  5447. switch ($this->driver) {
  5448. case 'mysql':
  5449. $map = ['BASE TABLE' => 'table', 'VIEW' => 'view'];
  5450. break;
  5451. case 'pgsql':
  5452. $map = ['r' => 'table', 'v' => 'view'];
  5453. break;
  5454. case 'sqlsrv':
  5455. $map = ['U' => 'table', 'V' => 'view'];
  5456. break;
  5457. case 'sqlite':
  5458. $map = ['table' => 'table', 'view' => 'view'];
  5459. break;
  5460. }
  5461. $result['TABLE_TYPE'] = $map[trim($result['TABLE_TYPE'])];
  5462. }
  5463. return $results;
  5464. }
  5465. public function getTableColumns(string $tableName, string $type): array
  5466. {
  5467. $sql = $this->getTableColumnsSQL();
  5468. $results = $this->query($sql, [$tableName, $this->database]);
  5469. if ($type == 'view') {
  5470. foreach ($results as &$result) {
  5471. $result['IS_NULLABLE'] = false;
  5472. }
  5473. }
  5474. if ($this->driver == 'mysql') {
  5475. foreach ($results as &$result) {
  5476. // mysql does not properly reflect display width of types
  5477. preg_match('|([a-z]+)(\(([0-9]+)(,([0-9]+))?\))?|', $result['DATA_TYPE'], $matches);
  5478. $result['DATA_TYPE'] = $matches[1];
  5479. if (!$result['CHARACTER_MAXIMUM_LENGTH']) {
  5480. if (isset($matches[3])) {
  5481. $result['NUMERIC_PRECISION'] = $matches[3];
  5482. }
  5483. if (isset($matches[5])) {
  5484. $result['NUMERIC_SCALE'] = $matches[5];
  5485. }
  5486. }
  5487. }
  5488. }
  5489. if ($this->driver == 'sqlite') {
  5490. foreach ($results as &$result) {
  5491. // sqlite does not properly reflect display width of types
  5492. preg_match('|([a-z]+)(\(([0-9]+)(,([0-9]+))?\))?|', $result['DATA_TYPE'], $matches);
  5493. if (isset($matches[1])) {
  5494. $result['DATA_TYPE'] = $matches[1];
  5495. } else {
  5496. $result['DATA_TYPE'] = 'integer';
  5497. }
  5498. if (isset($matches[5])) {
  5499. $result['NUMERIC_PRECISION'] = $matches[3];
  5500. $result['NUMERIC_SCALE'] = $matches[5];
  5501. } else if (isset($matches[3])) {
  5502. $result['CHARACTER_MAXIMUM_LENGTH'] = $matches[3];
  5503. }
  5504. }
  5505. }
  5506. return $results;
  5507. }
  5508. public function getTablePrimaryKeys(string $tableName): array
  5509. {
  5510. $sql = $this->getTablePrimaryKeysSQL();
  5511. $results = $this->query($sql, [$tableName, $this->database]);
  5512. $primaryKeys = [];
  5513. foreach ($results as $result) {
  5514. $primaryKeys[] = $result['COLUMN_NAME'];
  5515. }
  5516. return $primaryKeys;
  5517. }
  5518. public function getTableForeignKeys(string $tableName): array
  5519. {
  5520. $sql = $this->getTableForeignKeysSQL();
  5521. $results = $this->query($sql, [$tableName, $this->database]);
  5522. $foreignKeys = [];
  5523. foreach ($results as $result) {
  5524. $foreignKeys[$result['COLUMN_NAME']] = $result['REFERENCED_TABLE_NAME'];
  5525. }
  5526. return $foreignKeys;
  5527. }
  5528. public function toJdbcType(string $type, string $size): string
  5529. {
  5530. return $this->typeConverter->toJdbc($type, $size);
  5531. }
  5532. private function query(string $sql, array $parameters): array
  5533. {
  5534. $stmt = $this->pdo->prepare($sql);
  5535. //echo "- $sql -- " . json_encode($parameters, JSON_UNESCAPED_UNICODE) . "\n";
  5536. $stmt->execute($parameters);
  5537. return $stmt->fetchAll();
  5538. }
  5539. }
  5540. }
  5541. // file: src/Tqdev/PhpCrudApi/Database/LazyPdo.php
  5542. namespace Tqdev\PhpCrudApi\Database {
  5543. class LazyPdo extends \PDO
  5544. {
  5545. private $dsn;
  5546. private $user;
  5547. private $password;
  5548. private $options;
  5549. private $commands;
  5550. private $pdo = null;
  5551. public function __construct(string $dsn, /*?string*/ $user = null, /*?string*/ $password = null, array $options = array())
  5552. {
  5553. $this->dsn = $dsn;
  5554. $this->user = $user;
  5555. $this->password = $password;
  5556. $this->options = $options;
  5557. $this->commands = array();
  5558. // explicitly NOT calling super::__construct
  5559. }
  5560. public function addInitCommand(string $command)/*: void*/
  5561. {
  5562. $this->commands[] = $command;
  5563. }
  5564. private function pdo()
  5565. {
  5566. if (!$this->pdo) {
  5567. $this->pdo = new \PDO($this->dsn, $this->user, $this->password, $this->options);
  5568. foreach ($this->commands as $command) {
  5569. $this->pdo->query($command);
  5570. }
  5571. }
  5572. return $this->pdo;
  5573. }
  5574. public function reconstruct(string $dsn, /*?string*/ $user = null, /*?string*/ $password = null, array $options = array()): bool
  5575. {
  5576. $this->dsn = $dsn;
  5577. $this->user = $user;
  5578. $this->password = $password;
  5579. $this->options = $options;
  5580. $this->commands = array();
  5581. if ($this->pdo) {
  5582. $this->pdo = null;
  5583. return true;
  5584. }
  5585. return false;
  5586. }
  5587. public function inTransaction(): bool
  5588. {
  5589. // Do not call parent method if there is no pdo object
  5590. return $this->pdo && parent::inTransaction();
  5591. }
  5592. public function setAttribute($attribute, $value): bool
  5593. {
  5594. if ($this->pdo) {
  5595. return $this->pdo()->setAttribute($attribute, $value);
  5596. }
  5597. $this->options[$attribute] = $value;
  5598. return true;
  5599. }
  5600. public function getAttribute($attribute): mixed
  5601. {
  5602. return $this->pdo()->getAttribute($attribute);
  5603. }
  5604. public function beginTransaction(): bool
  5605. {
  5606. return $this->pdo()->beginTransaction();
  5607. }
  5608. public function commit(): bool
  5609. {
  5610. return $this->pdo()->commit();
  5611. }
  5612. public function rollBack(): bool
  5613. {
  5614. return $this->pdo()->rollBack();
  5615. }
  5616. public function errorCode(): mixed
  5617. {
  5618. return $this->pdo()->errorCode();
  5619. }
  5620. public function errorInfo(): array
  5621. {
  5622. return $this->pdo()->errorInfo();
  5623. }
  5624. public function exec($query): int
  5625. {
  5626. return $this->pdo()->exec($query);
  5627. }
  5628. public function prepare($statement, $options = array())
  5629. {
  5630. return $this->pdo()->prepare($statement, $options);
  5631. }
  5632. public function quote($string, $parameter_type = null): string
  5633. {
  5634. return $this->pdo()->quote($string, $parameter_type);
  5635. }
  5636. public function lastInsertId(/* ?string */$name = null): string
  5637. {
  5638. return $this->pdo()->lastInsertId($name);
  5639. }
  5640. public function query(string $statement): \PDOStatement
  5641. {
  5642. return call_user_func_array(array($this->pdo(), 'query'), func_get_args());
  5643. }
  5644. }
  5645. }
  5646. // file: src/Tqdev/PhpCrudApi/Database/TypeConverter.php
  5647. namespace Tqdev\PhpCrudApi\Database {
  5648. class TypeConverter
  5649. {
  5650. private $driver;
  5651. public function __construct(string $driver)
  5652. {
  5653. $this->driver = $driver;
  5654. }
  5655. private $fromJdbc = [
  5656. 'mysql' => [
  5657. 'clob' => 'longtext',
  5658. 'boolean' => 'tinyint(1)',
  5659. 'blob' => 'longblob',
  5660. 'timestamp' => 'datetime',
  5661. ],
  5662. 'pgsql' => [
  5663. 'clob' => 'text',
  5664. 'blob' => 'bytea',
  5665. 'float' => 'real',
  5666. 'double' => 'double precision',
  5667. 'varbinary' => 'bytea',
  5668. ],
  5669. 'sqlsrv' => [
  5670. 'boolean' => 'bit',
  5671. 'varchar' => 'nvarchar',
  5672. 'clob' => 'ntext',
  5673. 'blob' => 'image',
  5674. 'time' => 'time(0)',
  5675. 'timestamp' => 'datetime2(0)',
  5676. 'double' => 'float',
  5677. 'float' => 'real',
  5678. ],
  5679. ];
  5680. private $toJdbc = [
  5681. 'simplified' => [
  5682. 'char' => 'varchar',
  5683. 'longvarchar' => 'clob',
  5684. 'nchar' => 'varchar',
  5685. 'nvarchar' => 'varchar',
  5686. 'longnvarchar' => 'clob',
  5687. 'binary' => 'varbinary',
  5688. 'longvarbinary' => 'blob',
  5689. 'tinyint' => 'integer',
  5690. 'smallint' => 'integer',
  5691. 'real' => 'float',
  5692. 'numeric' => 'decimal',
  5693. 'nclob' => 'clob',
  5694. 'time_with_timezone' => 'time',
  5695. 'timestamp_with_timezone' => 'timestamp',
  5696. ],
  5697. 'mysql' => [
  5698. 'tinyint(1)' => 'boolean',
  5699. 'bit(1)' => 'boolean',
  5700. 'tinyblob' => 'blob',
  5701. 'mediumblob' => 'blob',
  5702. 'longblob' => 'blob',
  5703. 'tinytext' => 'clob',
  5704. 'mediumtext' => 'clob',
  5705. 'longtext' => 'clob',
  5706. 'text' => 'clob',
  5707. 'mediumint' => 'integer',
  5708. 'int' => 'integer',
  5709. 'polygon' => 'geometry',
  5710. 'point' => 'geometry',
  5711. 'datetime' => 'timestamp',
  5712. 'year' => 'integer',
  5713. 'enum' => 'varchar',
  5714. 'set' => 'varchar',
  5715. 'json' => 'clob',
  5716. ],
  5717. 'pgsql' => [
  5718. 'bigserial' => 'bigint',
  5719. 'bit varying' => 'bit',
  5720. 'box' => 'geometry',
  5721. 'bytea' => 'blob',
  5722. 'bpchar' => 'char',
  5723. 'character varying' => 'varchar',
  5724. 'character' => 'char',
  5725. 'cidr' => 'varchar',
  5726. 'circle' => 'geometry',
  5727. 'double precision' => 'double',
  5728. 'inet' => 'integer',
  5729. //'interval [ fields ]'
  5730. 'json' => 'clob',
  5731. 'jsonb' => 'clob',
  5732. 'line' => 'geometry',
  5733. 'lseg' => 'geometry',
  5734. 'macaddr' => 'varchar',
  5735. 'money' => 'decimal',
  5736. 'path' => 'geometry',
  5737. 'point' => 'geometry',
  5738. 'polygon' => 'geometry',
  5739. 'real' => 'float',
  5740. 'serial' => 'integer',
  5741. 'text' => 'clob',
  5742. 'time without time zone' => 'time',
  5743. 'time with time zone' => 'time_with_timezone',
  5744. 'timestamp without time zone' => 'timestamp',
  5745. 'timestamp with time zone' => 'timestamp_with_timezone',
  5746. //'tsquery'=
  5747. //'tsvector'
  5748. //'txid_snapshot'
  5749. 'uuid' => 'char',
  5750. 'xml' => 'clob',
  5751. ],
  5752. // source: https://docs.microsoft.com/en-us/sql/connect/jdbc/using-basic-data-types?view=sql-server-2017
  5753. 'sqlsrv' => [
  5754. 'varbinary()' => 'blob',
  5755. 'bit' => 'boolean',
  5756. 'datetime' => 'timestamp',
  5757. 'datetime2' => 'timestamp',
  5758. 'float' => 'double',
  5759. 'image' => 'blob',
  5760. 'int' => 'integer',
  5761. 'money' => 'decimal',
  5762. 'ntext' => 'clob',
  5763. 'smalldatetime' => 'timestamp',
  5764. 'smallmoney' => 'decimal',
  5765. 'text' => 'clob',
  5766. 'timestamp' => 'binary',
  5767. 'udt' => 'varbinary',
  5768. 'uniqueidentifier' => 'char',
  5769. 'xml' => 'clob',
  5770. ],
  5771. 'sqlite' => [
  5772. 'tinytext' => 'clob',
  5773. 'text' => 'clob',
  5774. 'mediumtext' => 'clob',
  5775. 'longtext' => 'clob',
  5776. 'mediumint' => 'integer',
  5777. 'int' => 'integer',
  5778. 'bigint' => 'bigint',
  5779. 'int2' => 'smallint',
  5780. 'int4' => 'integer',
  5781. 'int8' => 'bigint',
  5782. 'double precision' => 'double',
  5783. 'datetime' => 'timestamp'
  5784. ],
  5785. ];
  5786. // source: https://docs.oracle.com/javase/9/docs/api/java/sql/Types.html
  5787. private $valid = [
  5788. //'array' => true,
  5789. 'bigint' => true,
  5790. 'binary' => true,
  5791. 'bit' => true,
  5792. 'blob' => true,
  5793. 'boolean' => true,
  5794. 'char' => true,
  5795. 'clob' => true,
  5796. //'datalink' => true,
  5797. 'date' => true,
  5798. 'decimal' => true,
  5799. //'distinct' => true,
  5800. 'double' => true,
  5801. 'float' => true,
  5802. 'integer' => true,
  5803. //'java_object' => true,
  5804. 'longnvarchar' => true,
  5805. 'longvarbinary' => true,
  5806. 'longvarchar' => true,
  5807. 'nchar' => true,
  5808. 'nclob' => true,
  5809. //'null' => true,
  5810. 'numeric' => true,
  5811. 'nvarchar' => true,
  5812. //'other' => true,
  5813. 'real' => true,
  5814. //'ref' => true,
  5815. //'ref_cursor' => true,
  5816. //'rowid' => true,
  5817. 'smallint' => true,
  5818. //'sqlxml' => true,
  5819. //'struct' => true,
  5820. 'time' => true,
  5821. 'time_with_timezone' => true,
  5822. 'timestamp' => true,
  5823. 'timestamp_with_timezone' => true,
  5824. 'tinyint' => true,
  5825. 'varbinary' => true,
  5826. 'varchar' => true,
  5827. // extra:
  5828. 'geometry' => true,
  5829. ];
  5830. public function toJdbc(string $type, string $size): string
  5831. {
  5832. $jdbcType = strtolower($type);
  5833. if (isset($this->toJdbc[$this->driver]["$jdbcType($size)"])) {
  5834. $jdbcType = $this->toJdbc[$this->driver]["$jdbcType($size)"];
  5835. }
  5836. if (isset($this->toJdbc[$this->driver][$jdbcType])) {
  5837. $jdbcType = $this->toJdbc[$this->driver][$jdbcType];
  5838. }
  5839. if (isset($this->toJdbc['simplified'][$jdbcType])) {
  5840. $jdbcType = $this->toJdbc['simplified'][$jdbcType];
  5841. }
  5842. if (!isset($this->valid[$jdbcType])) {
  5843. //throw new \Exception("Unsupported type '$jdbcType' for driver '$this->driver'");
  5844. $jdbcType = 'clob';
  5845. }
  5846. return $jdbcType;
  5847. }
  5848. public function fromJdbc(string $type): string
  5849. {
  5850. $jdbcType = strtolower($type);
  5851. if (isset($this->fromJdbc[$this->driver][$jdbcType])) {
  5852. $jdbcType = $this->fromJdbc[$this->driver][$jdbcType];
  5853. }
  5854. return $jdbcType;
  5855. }
  5856. }
  5857. }
  5858. // file: src/Tqdev/PhpCrudApi/GeoJson/Feature.php
  5859. namespace Tqdev\PhpCrudApi\GeoJson {
  5860. class Feature implements \JsonSerializable
  5861. {
  5862. private $id;
  5863. private $properties;
  5864. private $geometry;
  5865. public function __construct($id, array $properties, /*?Geometry*/ $geometry)
  5866. {
  5867. $this->id = $id;
  5868. $this->properties = $properties;
  5869. $this->geometry = $geometry;
  5870. }
  5871. public function serialize()
  5872. {
  5873. return [
  5874. 'type' => 'Feature',
  5875. 'id' => $this->id,
  5876. 'properties' => $this->properties,
  5877. 'geometry' => $this->geometry,
  5878. ];
  5879. }
  5880. public function jsonSerialize()
  5881. {
  5882. return $this->serialize();
  5883. }
  5884. }
  5885. }
  5886. // file: src/Tqdev/PhpCrudApi/GeoJson/FeatureCollection.php
  5887. namespace Tqdev\PhpCrudApi\GeoJson {
  5888. class FeatureCollection implements \JsonSerializable
  5889. {
  5890. private $features;
  5891. private $results;
  5892. public function __construct(array $features, int $results)
  5893. {
  5894. $this->features = $features;
  5895. $this->results = $results;
  5896. }
  5897. public function serialize()
  5898. {
  5899. return [
  5900. 'type' => 'FeatureCollection',
  5901. 'features' => $this->features,
  5902. 'results' => $this->results,
  5903. ];
  5904. }
  5905. public function jsonSerialize()
  5906. {
  5907. return array_filter($this->serialize(), function ($v) {
  5908. return $v !== 0;
  5909. });
  5910. }
  5911. }
  5912. }
  5913. // file: src/Tqdev/PhpCrudApi/GeoJson/GeoJsonService.php
  5914. namespace Tqdev\PhpCrudApi\GeoJson {
  5915. use Tqdev\PhpCrudApi\Column\ReflectionService;
  5916. use Tqdev\PhpCrudApi\GeoJson\FeatureCollection;
  5917. use Tqdev\PhpCrudApi\Record\RecordService;
  5918. class GeoJsonService
  5919. {
  5920. private $reflection;
  5921. private $records;
  5922. public function __construct(ReflectionService $reflection, RecordService $records)
  5923. {
  5924. $this->reflection = $reflection;
  5925. $this->records = $records;
  5926. }
  5927. public function hasTable(string $table): bool
  5928. {
  5929. return $this->reflection->hasTable($table);
  5930. }
  5931. public function getType(string $table): string
  5932. {
  5933. return $this->reflection->getType($table);
  5934. }
  5935. private function getGeometryColumnName(string $tableName, array &$params): string
  5936. {
  5937. $geometryParam = isset($params['geometry']) ? $params['geometry'][0] : '';
  5938. $table = $this->reflection->getTable($tableName);
  5939. $geometryColumnName = '';
  5940. foreach ($table->getColumnNames() as $columnName) {
  5941. if ($geometryParam && $geometryParam != $columnName) {
  5942. continue;
  5943. }
  5944. $column = $table->getColumn($columnName);
  5945. if ($column->isGeometry()) {
  5946. $geometryColumnName = $columnName;
  5947. break;
  5948. }
  5949. }
  5950. if ($geometryColumnName) {
  5951. $params['mandatory'][] = $tableName . "." . $geometryColumnName;
  5952. }
  5953. return $geometryColumnName;
  5954. }
  5955. private function setBoudingBoxFilter(string $geometryColumnName, array &$params)
  5956. {
  5957. $boundingBox = isset($params['bbox']) ? $params['bbox'][0] : '';
  5958. if ($boundingBox) {
  5959. $c = explode(',', $boundingBox);
  5960. if (!isset($params['filter'])) {
  5961. $params['filter'] = array();
  5962. }
  5963. $params['filter'][] = "$geometryColumnName,sin,POLYGON(($c[0] $c[1],$c[2] $c[1],$c[2] $c[3],$c[0] $c[3],$c[0] $c[1]))";
  5964. }
  5965. $tile = isset($params['tile']) ? $params['tile'][0] : '';
  5966. if ($tile) {
  5967. $zxy = explode(',', $tile);
  5968. if (count($zxy) == 3) {
  5969. list($z, $x, $y) = $zxy;
  5970. $c = array();
  5971. $c = array_merge($c, $this->convertTileToLatLonOfUpperLeftCorner($z, $x, $y));
  5972. $c = array_merge($c, $this->convertTileToLatLonOfUpperLeftCorner($z, $x + 1, $y + 1));
  5973. $params['filter'][] = "$geometryColumnName,sin,POLYGON(($c[0] $c[1],$c[2] $c[1],$c[2] $c[3],$c[0] $c[3],$c[0] $c[1]))";
  5974. }
  5975. }
  5976. }
  5977. private function convertTileToLatLonOfUpperLeftCorner($z, $x, $y): array
  5978. {
  5979. $n = pow(2, $z);
  5980. $lon = $x / $n * 360.0 - 180.0;
  5981. $lat = rad2deg(atan(sinh(pi() * (1 - 2 * $y / $n))));
  5982. return [$lon, $lat];
  5983. }
  5984. private function convertRecordToFeature(/*object*/$record, string $primaryKeyColumnName, string $geometryColumnName)
  5985. {
  5986. $id = null;
  5987. if ($primaryKeyColumnName) {
  5988. $id = $record[$primaryKeyColumnName];
  5989. }
  5990. $geometry = null;
  5991. if (isset($record[$geometryColumnName])) {
  5992. $geometry = Geometry::fromWkt($record[$geometryColumnName]);
  5993. }
  5994. $properties = array_diff_key($record, [$primaryKeyColumnName => true, $geometryColumnName => true]);
  5995. return new Feature($id, $properties, $geometry);
  5996. }
  5997. private function getPrimaryKeyColumnName(string $tableName, array &$params): string
  5998. {
  5999. $primaryKeyColumn = $this->reflection->getTable($tableName)->getPk();
  6000. if (!$primaryKeyColumn) {
  6001. return '';
  6002. }
  6003. $primaryKeyColumnName = $primaryKeyColumn->getName();
  6004. $params['mandatory'][] = $tableName . "." . $primaryKeyColumnName;
  6005. return $primaryKeyColumnName;
  6006. }
  6007. public function _list(string $tableName, array $params): FeatureCollection
  6008. {
  6009. $geometryColumnName = $this->getGeometryColumnName($tableName, $params);
  6010. $this->setBoudingBoxFilter($geometryColumnName, $params);
  6011. $primaryKeyColumnName = $this->getPrimaryKeyColumnName($tableName, $params);
  6012. $records = $this->records->_list($tableName, $params);
  6013. $features = array();
  6014. foreach ($records->getRecords() as $record) {
  6015. $features[] = $this->convertRecordToFeature($record, $primaryKeyColumnName, $geometryColumnName);
  6016. }
  6017. return new FeatureCollection($features, $records->getResults());
  6018. }
  6019. public function read(string $tableName, string $id, array $params): Feature
  6020. {
  6021. $geometryColumnName = $this->getGeometryColumnName($tableName, $params);
  6022. $primaryKeyColumnName = $this->getPrimaryKeyColumnName($tableName, $params);
  6023. $record = $this->records->read($tableName, $id, $params);
  6024. return $this->convertRecordToFeature($record, $primaryKeyColumnName, $geometryColumnName);
  6025. }
  6026. }
  6027. }
  6028. // file: src/Tqdev/PhpCrudApi/GeoJson/Geometry.php
  6029. namespace Tqdev\PhpCrudApi\GeoJson {
  6030. class Geometry implements \JsonSerializable
  6031. {
  6032. private $type;
  6033. private $geometry;
  6034. public static $types = [
  6035. "Point",
  6036. "MultiPoint",
  6037. "LineString",
  6038. "MultiLineString",
  6039. "Polygon",
  6040. "MultiPolygon",
  6041. //"GeometryCollection",
  6042. ];
  6043. public function __construct(string $type, array $coordinates)
  6044. {
  6045. $this->type = $type;
  6046. $this->coordinates = $coordinates;
  6047. }
  6048. public static function fromWkt(string $wkt): Geometry
  6049. {
  6050. $bracket = strpos($wkt, '(');
  6051. $type = strtoupper(trim(substr($wkt, 0, $bracket)));
  6052. $supported = false;
  6053. foreach (Geometry::$types as $typeName) {
  6054. if (strtoupper($typeName) == $type) {
  6055. $type = $typeName;
  6056. $supported = true;
  6057. }
  6058. }
  6059. if (!$supported) {
  6060. throw new \Exception('Geometry type not supported: ' . $type);
  6061. }
  6062. $coordinates = substr($wkt, $bracket);
  6063. if (substr($type, -5) != 'Point' || ($type == 'MultiPoint' && $coordinates[1] != '(')) {
  6064. $coordinates = preg_replace('|([0-9\-\.]+ )+([0-9\-\.]+)|', '[\1\2]', $coordinates);
  6065. }
  6066. $coordinates = str_replace(['(', ')', ', ', ' '], ['[', ']', ',', ','], $coordinates);
  6067. $coordinates = json_decode($coordinates);
  6068. if (!$coordinates) {
  6069. throw new \Exception('Could not decode WKT: ' . $wkt);
  6070. }
  6071. return new Geometry($type, $coordinates);
  6072. }
  6073. public function serialize()
  6074. {
  6075. return [
  6076. 'type' => $this->type,
  6077. 'coordinates' => $this->coordinates,
  6078. ];
  6079. }
  6080. public function jsonSerialize()
  6081. {
  6082. return $this->serialize();
  6083. }
  6084. }
  6085. }
  6086. // file: src/Tqdev/PhpCrudApi/Middleware/Base/Middleware.php
  6087. namespace Tqdev\PhpCrudApi\Middleware\Base {
  6088. use Psr\Http\Server\MiddlewareInterface;
  6089. use Tqdev\PhpCrudApi\Controller\Responder;
  6090. use Tqdev\PhpCrudApi\Middleware\Router\Router;
  6091. abstract class Middleware implements MiddlewareInterface
  6092. {
  6093. protected $next;
  6094. protected $responder;
  6095. private $properties;
  6096. public function __construct(Router $router, Responder $responder, array $properties)
  6097. {
  6098. $router->load($this);
  6099. $this->responder = $responder;
  6100. $this->properties = $properties;
  6101. }
  6102. protected function getArrayProperty(string $key, string $default): array
  6103. {
  6104. return array_filter(array_map('trim', explode(',', $this->getProperty($key, $default))));
  6105. }
  6106. protected function getMapProperty(string $key, string $default): array
  6107. {
  6108. $pairs = $this->getArrayProperty($key, $default);
  6109. $result = array();
  6110. foreach ($pairs as $pair) {
  6111. if (strpos($pair, ':')) {
  6112. list($k, $v) = explode(':', $pair, 2);
  6113. $result[trim($k)] = trim($v);
  6114. } else {
  6115. $result[] = trim($pair);
  6116. }
  6117. }
  6118. return $result;
  6119. }
  6120. protected function getProperty(string $key, $default)
  6121. {
  6122. return isset($this->properties[$key]) ? $this->properties[$key] : $default;
  6123. }
  6124. }
  6125. }
  6126. // file: src/Tqdev/PhpCrudApi/Middleware/Communication/VariableStore.php
  6127. namespace Tqdev\PhpCrudApi\Middleware\Communication {
  6128. class VariableStore
  6129. {
  6130. public static $values = array();
  6131. public static function get(string $key)
  6132. {
  6133. if (isset(self::$values[$key])) {
  6134. return self::$values[$key];
  6135. }
  6136. return null;
  6137. }
  6138. public static function set(string $key, /* object */ $value)
  6139. {
  6140. self::$values[$key] = $value;
  6141. }
  6142. }
  6143. }
  6144. // file: src/Tqdev/PhpCrudApi/Middleware/Router/Router.php
  6145. namespace Tqdev\PhpCrudApi\Middleware\Router {
  6146. use Psr\Http\Message\ResponseInterface;
  6147. use Psr\Http\Message\ServerRequestInterface;
  6148. use Psr\Http\Server\RequestHandlerInterface;
  6149. use Tqdev\PhpCrudApi\Middleware\Base\Middleware;
  6150. interface Router extends RequestHandlerInterface
  6151. {
  6152. public function register(string $method, string $path, array $handler);
  6153. public function load(Middleware $middleware);
  6154. public function route(ServerRequestInterface $request): ResponseInterface;
  6155. }
  6156. }
  6157. // file: src/Tqdev/PhpCrudApi/Middleware/Router/SimpleRouter.php
  6158. namespace Tqdev\PhpCrudApi\Middleware\Router {
  6159. use Psr\Http\Message\ResponseInterface;
  6160. use Psr\Http\Message\ServerRequestInterface;
  6161. use Tqdev\PhpCrudApi\Cache\Cache;
  6162. use Tqdev\PhpCrudApi\Controller\Responder;
  6163. use Tqdev\PhpCrudApi\Middleware\Base\Middleware;
  6164. use Tqdev\PhpCrudApi\Record\ErrorCode;
  6165. use Tqdev\PhpCrudApi\Record\PathTree;
  6166. use Tqdev\PhpCrudApi\RequestUtils;
  6167. use Tqdev\PhpCrudApi\ResponseUtils;
  6168. class SimpleRouter implements Router
  6169. {
  6170. private $basePath;
  6171. private $responder;
  6172. private $cache;
  6173. private $ttl;
  6174. private $debug;
  6175. private $registration;
  6176. private $routes;
  6177. private $routeHandlers;
  6178. private $middlewares;
  6179. public function __construct(string $basePath, Responder $responder, Cache $cache, int $ttl, bool $debug)
  6180. {
  6181. $this->basePath = rtrim($this->detectBasePath($basePath), '/');
  6182. $this->responder = $responder;
  6183. $this->cache = $cache;
  6184. $this->ttl = $ttl;
  6185. $this->debug = $debug;
  6186. $this->registration = true;
  6187. $this->routes = $this->loadPathTree();
  6188. $this->routeHandlers = [];
  6189. $this->middlewares = array();
  6190. }
  6191. private function detectBasePath(string $basePath): string
  6192. {
  6193. if ($basePath) {
  6194. return $basePath;
  6195. }
  6196. if (isset($_SERVER['REQUEST_URI'])) {
  6197. $fullPath = urldecode(explode('?', $_SERVER['REQUEST_URI'])[0]);
  6198. if (isset($_SERVER['PATH_INFO'])) {
  6199. $path = $_SERVER['PATH_INFO'];
  6200. if (substr($fullPath, -1 * strlen($path)) == $path) {
  6201. return substr($fullPath, 0, -1 * strlen($path));
  6202. }
  6203. }
  6204. if ('/' . basename(__FILE__) == $fullPath) {
  6205. return $fullPath;
  6206. }
  6207. }
  6208. return '/';
  6209. }
  6210. private function loadPathTree(): PathTree
  6211. {
  6212. $data = $this->cache->get('PathTree');
  6213. if ($data != '') {
  6214. $tree = PathTree::fromJson(json_decode(gzuncompress($data)));
  6215. $this->registration = false;
  6216. } else {
  6217. $tree = new PathTree();
  6218. }
  6219. return $tree;
  6220. }
  6221. public function register(string $method, string $path, array $handler)
  6222. {
  6223. $routeNumber = count($this->routeHandlers);
  6224. $this->routeHandlers[$routeNumber] = $handler;
  6225. if ($this->registration) {
  6226. $path = trim($path, '/');
  6227. $parts = array();
  6228. if ($path) {
  6229. $parts = explode('/', $path);
  6230. }
  6231. array_unshift($parts, $method);
  6232. $this->routes->put($parts, $routeNumber);
  6233. }
  6234. }
  6235. public function load(Middleware $middleware) /*: void*/
  6236. {
  6237. array_push($this->middlewares, $middleware);
  6238. }
  6239. public function route(ServerRequestInterface $request): ResponseInterface
  6240. {
  6241. if ($this->registration) {
  6242. $data = gzcompress(json_encode($this->routes, JSON_UNESCAPED_UNICODE));
  6243. $this->cache->set('PathTree', $data, $this->ttl);
  6244. }
  6245. return $this->handle($request);
  6246. }
  6247. private function getRouteNumbers(ServerRequestInterface $request): array
  6248. {
  6249. $method = strtoupper($request->getMethod());
  6250. $path = array();
  6251. $segment = $method;
  6252. for ($i = 1; $segment; $i++) {
  6253. array_push($path, $segment);
  6254. $segment = RequestUtils::getPathSegment($request, $i);
  6255. }
  6256. return $this->routes->match($path);
  6257. }
  6258. private function removeBasePath(ServerRequestInterface $request): ServerRequestInterface
  6259. {
  6260. $path = $request->getUri()->getPath();
  6261. if (substr($path, 0, strlen($this->basePath)) == $this->basePath) {
  6262. $path = substr($path, strlen($this->basePath));
  6263. $request = $request->withUri($request->getUri()->withPath($path));
  6264. }
  6265. return $request;
  6266. }
  6267. public function getBasePath(): string
  6268. {
  6269. return $this->basePath;
  6270. }
  6271. public function handle(ServerRequestInterface $request): ResponseInterface
  6272. {
  6273. $request = $this->removeBasePath($request);
  6274. if (count($this->middlewares)) {
  6275. $handler = array_pop($this->middlewares);
  6276. return $handler->process($request, $this);
  6277. }
  6278. $routeNumbers = $this->getRouteNumbers($request);
  6279. if (count($routeNumbers) == 0) {
  6280. return $this->responder->error(ErrorCode::ROUTE_NOT_FOUND, $request->getUri()->getPath());
  6281. }
  6282. try {
  6283. $response = call_user_func($this->routeHandlers[$routeNumbers[0]], $request);
  6284. } catch (\PDOException $e) {
  6285. if (strpos(strtolower($e->getMessage()), 'duplicate') !== false) {
  6286. $response = $this->responder->error(ErrorCode::DUPLICATE_KEY_EXCEPTION, '');
  6287. } elseif (strpos(strtolower($e->getMessage()), 'unique constraint') !== false) {
  6288. $response = $this->responder->error(ErrorCode::DUPLICATE_KEY_EXCEPTION, '');
  6289. } elseif (strpos(strtolower($e->getMessage()), 'default value') !== false) {
  6290. $response = $this->responder->error(ErrorCode::DATA_INTEGRITY_VIOLATION, '');
  6291. } elseif (strpos(strtolower($e->getMessage()), 'allow nulls') !== false) {
  6292. $response = $this->responder->error(ErrorCode::DATA_INTEGRITY_VIOLATION, '');
  6293. } elseif (strpos(strtolower($e->getMessage()), 'constraint') !== false) {
  6294. $response = $this->responder->error(ErrorCode::DATA_INTEGRITY_VIOLATION, '');
  6295. } else {
  6296. $response = $this->responder->error(ErrorCode::ERROR_NOT_FOUND, '');
  6297. }
  6298. if ($this->debug) {
  6299. $response = ResponseUtils::addExceptionHeaders($response, $e);
  6300. }
  6301. }
  6302. return $response;
  6303. }
  6304. }
  6305. }
  6306. // file: src/Tqdev/PhpCrudApi/Middleware/AjaxOnlyMiddleware.php
  6307. namespace Tqdev\PhpCrudApi\Middleware {
  6308. use Psr\Http\Message\ResponseInterface;
  6309. use Psr\Http\Message\ServerRequestInterface;
  6310. use Psr\Http\Server\RequestHandlerInterface;
  6311. use Tqdev\PhpCrudApi\Controller\Responder;
  6312. use Tqdev\PhpCrudApi\Middleware\Base\Middleware;
  6313. use Tqdev\PhpCrudApi\Record\ErrorCode;
  6314. use Tqdev\PhpCrudApi\RequestUtils;
  6315. class AjaxOnlyMiddleware extends Middleware
  6316. {
  6317. public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface
  6318. {
  6319. $method = $request->getMethod();
  6320. $excludeMethods = $this->getArrayProperty('excludeMethods', 'OPTIONS,GET');
  6321. if (!in_array($method, $excludeMethods)) {
  6322. $headerName = $this->getProperty('headerName', 'X-Requested-With');
  6323. $headerValue = $this->getProperty('headerValue', 'XMLHttpRequest');
  6324. if ($headerValue != RequestUtils::getHeader($request, $headerName)) {
  6325. return $this->responder->error(ErrorCode::ONLY_AJAX_REQUESTS_ALLOWED, $method);
  6326. }
  6327. }
  6328. return $next->handle($request);
  6329. }
  6330. }
  6331. }
  6332. // file: src/Tqdev/PhpCrudApi/Middleware/AuthorizationMiddleware.php
  6333. namespace Tqdev\PhpCrudApi\Middleware {
  6334. use Psr\Http\Message\ResponseInterface;
  6335. use Psr\Http\Message\ServerRequestInterface;
  6336. use Psr\Http\Server\RequestHandlerInterface;
  6337. use Tqdev\PhpCrudApi\Column\ReflectionService;
  6338. use Tqdev\PhpCrudApi\Controller\Responder;
  6339. use Tqdev\PhpCrudApi\Middleware\Base\Middleware;
  6340. use Tqdev\PhpCrudApi\Middleware\Communication\VariableStore;
  6341. use Tqdev\PhpCrudApi\Middleware\Router\Router;
  6342. use Tqdev\PhpCrudApi\Record\FilterInfo;
  6343. use Tqdev\PhpCrudApi\RequestUtils;
  6344. class AuthorizationMiddleware extends Middleware
  6345. {
  6346. private $reflection;
  6347. public function __construct(Router $router, Responder $responder, array $properties, ReflectionService $reflection)
  6348. {
  6349. parent::__construct($router, $responder, $properties);
  6350. $this->reflection = $reflection;
  6351. }
  6352. private function handleColumns(string $operation, string $tableName) /*: void*/
  6353. {
  6354. $columnHandler = $this->getProperty('columnHandler', '');
  6355. if ($columnHandler) {
  6356. $table = $this->reflection->getTable($tableName);
  6357. foreach ($table->getColumnNames() as $columnName) {
  6358. $allowed = call_user_func($columnHandler, $operation, $tableName, $columnName);
  6359. if (!$allowed) {
  6360. $table->removeColumn($columnName);
  6361. }
  6362. }
  6363. }
  6364. }
  6365. private function handleTable(string $operation, string $tableName) /*: void*/
  6366. {
  6367. if (!$this->reflection->hasTable($tableName)) {
  6368. return;
  6369. }
  6370. $allowed = true;
  6371. $tableHandler = $this->getProperty('tableHandler', '');
  6372. if ($tableHandler) {
  6373. $allowed = call_user_func($tableHandler, $operation, $tableName);
  6374. }
  6375. if (!$allowed) {
  6376. $this->reflection->removeTable($tableName);
  6377. } else {
  6378. $this->handleColumns($operation, $tableName);
  6379. }
  6380. }
  6381. private function handleRecords(string $operation, string $tableName) /*: void*/
  6382. {
  6383. if (!$this->reflection->hasTable($tableName)) {
  6384. return;
  6385. }
  6386. $recordHandler = $this->getProperty('recordHandler', '');
  6387. if ($recordHandler) {
  6388. $query = call_user_func($recordHandler, $operation, $tableName);
  6389. $filters = new FilterInfo();
  6390. $table = $this->reflection->getTable($tableName);
  6391. $query = str_replace('][]=', ']=', str_replace('=', '[]=', $query));
  6392. parse_str($query, $params);
  6393. $condition = $filters->getCombinedConditions($table, $params);
  6394. VariableStore::set("authorization.conditions.$tableName", $condition);
  6395. }
  6396. }
  6397. public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface
  6398. {
  6399. $path = RequestUtils::getPathSegment($request, 1);
  6400. $operation = RequestUtils::getOperation($request);
  6401. $tableNames = RequestUtils::getTableNames($request, $this->reflection);
  6402. foreach ($tableNames as $tableName) {
  6403. $this->handleTable($operation, $tableName);
  6404. if ($path == 'records') {
  6405. $this->handleRecords($operation, $tableName);
  6406. }
  6407. }
  6408. if ($path == 'openapi') {
  6409. VariableStore::set('authorization.tableHandler', $this->getProperty('tableHandler', ''));
  6410. VariableStore::set('authorization.columnHandler', $this->getProperty('columnHandler', ''));
  6411. }
  6412. return $next->handle($request);
  6413. }
  6414. }
  6415. }
  6416. // file: src/Tqdev/PhpCrudApi/Middleware/BasicAuthMiddleware.php
  6417. namespace Tqdev\PhpCrudApi\Middleware {
  6418. use Psr\Http\Message\ResponseInterface;
  6419. use Psr\Http\Message\ServerRequestInterface;
  6420. use Psr\Http\Server\RequestHandlerInterface;
  6421. use Tqdev\PhpCrudApi\Controller\Responder;
  6422. use Tqdev\PhpCrudApi\Middleware\Base\Middleware;
  6423. use Tqdev\PhpCrudApi\Record\ErrorCode;
  6424. use Tqdev\PhpCrudApi\RequestUtils;
  6425. class BasicAuthMiddleware extends Middleware
  6426. {
  6427. private function hasCorrectPassword(string $username, string $password, array &$passwords): bool
  6428. {
  6429. $hash = isset($passwords[$username]) ? $passwords[$username] : false;
  6430. if ($hash && password_verify($password, $hash)) {
  6431. if (password_needs_rehash($hash, PASSWORD_DEFAULT)) {
  6432. $passwords[$username] = password_hash($password, PASSWORD_DEFAULT);
  6433. }
  6434. return true;
  6435. }
  6436. return false;
  6437. }
  6438. private function getValidUsername(string $username, string $password, string $passwordFile): string
  6439. {
  6440. $passwords = $this->readPasswords($passwordFile);
  6441. $valid = $this->hasCorrectPassword($username, $password, $passwords);
  6442. $this->writePasswords($passwordFile, $passwords);
  6443. return $valid ? $username : '';
  6444. }
  6445. private function readPasswords(string $passwordFile): array
  6446. {
  6447. $passwords = [];
  6448. $passwordLines = file($passwordFile);
  6449. foreach ($passwordLines as $passwordLine) {
  6450. if (strpos($passwordLine, ':') !== false) {
  6451. list($username, $hash) = explode(':', trim($passwordLine), 2);
  6452. if (strlen($hash) > 0 && $hash[0] != '$') {
  6453. $hash = password_hash($hash, PASSWORD_DEFAULT);
  6454. }
  6455. $passwords[$username] = $hash;
  6456. }
  6457. }
  6458. return $passwords;
  6459. }
  6460. private function writePasswords(string $passwordFile, array $passwords): bool
  6461. {
  6462. $success = false;
  6463. $passwordFileContents = '';
  6464. foreach ($passwords as $username => $hash) {
  6465. $passwordFileContents .= "$username:$hash\n";
  6466. }
  6467. if (file_get_contents($passwordFile) != $passwordFileContents) {
  6468. $success = file_put_contents($passwordFile, $passwordFileContents) !== false;
  6469. }
  6470. return $success;
  6471. }
  6472. private function getAuthorizationCredentials(ServerRequestInterface $request): string
  6473. {
  6474. if (isset($_SERVER['PHP_AUTH_USER'])) {
  6475. return $_SERVER['PHP_AUTH_USER'] . ':' . $_SERVER['PHP_AUTH_PW'];
  6476. }
  6477. $header = RequestUtils::getHeader($request, 'Authorization');
  6478. $parts = explode(' ', trim($header), 2);
  6479. if (count($parts) != 2) {
  6480. return '';
  6481. }
  6482. if ($parts[0] != 'Basic') {
  6483. return '';
  6484. }
  6485. return base64_decode(strtr($parts[1], '-_', '+/'));
  6486. }
  6487. public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface
  6488. {
  6489. if (session_status() == PHP_SESSION_NONE) {
  6490. if (!headers_sent()) {
  6491. $sessionName = $this->getProperty('sessionName', '');
  6492. if ($sessionName) {
  6493. session_name($sessionName);
  6494. }
  6495. session_start();
  6496. }
  6497. }
  6498. $credentials = $this->getAuthorizationCredentials($request);
  6499. if ($credentials) {
  6500. list($username, $password) = array('', '');
  6501. if (strpos($credentials, ':') !== false) {
  6502. list($username, $password) = explode(':', $credentials, 2);
  6503. }
  6504. $passwordFile = $this->getProperty('passwordFile', '.htpasswd');
  6505. $validUser = $this->getValidUsername($username, $password, $passwordFile);
  6506. $_SESSION['username'] = $validUser;
  6507. if (!$validUser) {
  6508. return $this->responder->error(ErrorCode::AUTHENTICATION_FAILED, $username);
  6509. }
  6510. if (!headers_sent()) {
  6511. session_regenerate_id();
  6512. }
  6513. }
  6514. if (!isset($_SESSION['username']) || !$_SESSION['username']) {
  6515. $authenticationMode = $this->getProperty('mode', 'required');
  6516. if ($authenticationMode == 'required') {
  6517. $response = $this->responder->error(ErrorCode::AUTHENTICATION_REQUIRED, '');
  6518. $realm = $this->getProperty('realm', 'Username and password required');
  6519. $response = $response->withHeader('WWW-Authenticate', "Basic realm=\"$realm\"");
  6520. return $response;
  6521. }
  6522. }
  6523. return $next->handle($request);
  6524. }
  6525. }
  6526. }
  6527. // file: src/Tqdev/PhpCrudApi/Middleware/CorsMiddleware.php
  6528. namespace Tqdev\PhpCrudApi\Middleware {
  6529. use Psr\Http\Message\ResponseInterface;
  6530. use Psr\Http\Message\ServerRequestInterface;
  6531. use Psr\Http\Server\RequestHandlerInterface;
  6532. use Tqdev\PhpCrudApi\Middleware\Base\Middleware;
  6533. use Tqdev\PhpCrudApi\Record\ErrorCode;
  6534. use Tqdev\PhpCrudApi\ResponseFactory;
  6535. class CorsMiddleware extends Middleware
  6536. {
  6537. private function isOriginAllowed(string $origin, string $allowedOrigins): bool
  6538. {
  6539. $found = false;
  6540. foreach (explode(',', $allowedOrigins) as $allowedOrigin) {
  6541. $hostname = preg_quote(strtolower(trim($allowedOrigin)));
  6542. $regex = '/^' . str_replace('\*', '.*', $hostname) . '$/';
  6543. if (preg_match($regex, $origin)) {
  6544. $found = true;
  6545. break;
  6546. }
  6547. }
  6548. return $found;
  6549. }
  6550. public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface
  6551. {
  6552. $method = $request->getMethod();
  6553. $origin = count($request->getHeader('Origin')) ? $request->getHeader('Origin')[0] : '';
  6554. $allowedOrigins = $this->getProperty('allowedOrigins', '*');
  6555. if ($origin && !$this->isOriginAllowed($origin, $allowedOrigins)) {
  6556. $response = $this->responder->error(ErrorCode::ORIGIN_FORBIDDEN, $origin);
  6557. } elseif ($method == 'OPTIONS') {
  6558. $response = ResponseFactory::fromStatus(ResponseFactory::OK);
  6559. $allowHeaders = $this->getProperty('allowHeaders', 'Content-Type, X-XSRF-TOKEN, X-Authorization');
  6560. if ($allowHeaders) {
  6561. $response = $response->withHeader('Access-Control-Allow-Headers', $allowHeaders);
  6562. }
  6563. $allowMethods = $this->getProperty('allowMethods', 'OPTIONS, GET, PUT, POST, DELETE, PATCH');
  6564. if ($allowMethods) {
  6565. $response = $response->withHeader('Access-Control-Allow-Methods', $allowMethods);
  6566. }
  6567. $allowCredentials = $this->getProperty('allowCredentials', 'true');
  6568. if ($allowCredentials) {
  6569. $response = $response->withHeader('Access-Control-Allow-Credentials', $allowCredentials);
  6570. }
  6571. $maxAge = $this->getProperty('maxAge', '1728000');
  6572. if ($maxAge) {
  6573. $response = $response->withHeader('Access-Control-Max-Age', $maxAge);
  6574. }
  6575. $exposeHeaders = $this->getProperty('exposeHeaders', '');
  6576. if ($exposeHeaders) {
  6577. $response = $response->withHeader('Access-Control-Expose-Headers', $exposeHeaders);
  6578. }
  6579. } else {
  6580. $response = $next->handle($request);
  6581. }
  6582. if ($origin) {
  6583. $allowCredentials = $this->getProperty('allowCredentials', 'true');
  6584. if ($allowCredentials) {
  6585. $response = $response->withHeader('Access-Control-Allow-Credentials', $allowCredentials);
  6586. }
  6587. $response = $response->withHeader('Access-Control-Allow-Origin', $origin);
  6588. }
  6589. return $response;
  6590. }
  6591. }
  6592. }
  6593. // file: src/Tqdev/PhpCrudApi/Middleware/CustomizationMiddleware.php
  6594. namespace Tqdev\PhpCrudApi\Middleware {
  6595. use Psr\Http\Message\ResponseInterface;
  6596. use Psr\Http\Message\ServerRequestInterface;
  6597. use Psr\Http\Server\RequestHandlerInterface;
  6598. use Tqdev\PhpCrudApi\Column\ReflectionService;
  6599. use Tqdev\PhpCrudApi\Controller\Responder;
  6600. use Tqdev\PhpCrudApi\Middleware\Base\Middleware;
  6601. use Tqdev\PhpCrudApi\Middleware\Router\Router;
  6602. use Tqdev\PhpCrudApi\RequestUtils;
  6603. class CustomizationMiddleware extends Middleware
  6604. {
  6605. private $reflection;
  6606. public function __construct(Router $router, Responder $responder, array $properties, ReflectionService $reflection)
  6607. {
  6608. parent::__construct($router, $responder, $properties);
  6609. $this->reflection = $reflection;
  6610. }
  6611. public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface
  6612. {
  6613. $operation = RequestUtils::getOperation($request);
  6614. $tableName = RequestUtils::getPathSegment($request, 2);
  6615. $beforeHandler = $this->getProperty('beforeHandler', '');
  6616. $environment = (object) array();
  6617. if ($beforeHandler !== '') {
  6618. $result = call_user_func($beforeHandler, $operation, $tableName, $request, $environment);
  6619. $request = $result ?: $request;
  6620. }
  6621. $response = $next->handle($request);
  6622. $afterHandler = $this->getProperty('afterHandler', '');
  6623. if ($afterHandler !== '') {
  6624. $result = call_user_func($afterHandler, $operation, $tableName, $response, $environment);
  6625. $response = $result ?: $response;
  6626. }
  6627. return $response;
  6628. }
  6629. }
  6630. }
  6631. // file: src/Tqdev/PhpCrudApi/Middleware/DbAuthMiddleware.php
  6632. namespace Tqdev\PhpCrudApi\Middleware {
  6633. use Psr\Http\Message\ResponseInterface;
  6634. use Psr\Http\Message\ServerRequestInterface;
  6635. use Psr\Http\Server\RequestHandlerInterface;
  6636. use Tqdev\PhpCrudApi\Column\ReflectionService;
  6637. use Tqdev\PhpCrudApi\Controller\Responder;
  6638. use Tqdev\PhpCrudApi\Database\GenericDB;
  6639. use Tqdev\PhpCrudApi\Middleware\Base\Middleware;
  6640. use Tqdev\PhpCrudApi\Middleware\Router\Router;
  6641. use Tqdev\PhpCrudApi\Record\Condition\ColumnCondition;
  6642. use Tqdev\PhpCrudApi\Record\ErrorCode;
  6643. use Tqdev\PhpCrudApi\Record\OrderingInfo;
  6644. use Tqdev\PhpCrudApi\RequestUtils;
  6645. class DbAuthMiddleware extends Middleware
  6646. {
  6647. private $reflection;
  6648. private $db;
  6649. private $ordering;
  6650. public function __construct(Router $router, Responder $responder, array $properties, ReflectionService $reflection, GenericDB $db)
  6651. {
  6652. parent::__construct($router, $responder, $properties);
  6653. $this->reflection = $reflection;
  6654. $this->db = $db;
  6655. $this->ordering = new OrderingInfo();
  6656. }
  6657. public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface
  6658. {
  6659. if (session_status() == PHP_SESSION_NONE) {
  6660. if (!headers_sent()) {
  6661. $sessionName = $this->getProperty('sessionName', '');
  6662. if ($sessionName) {
  6663. session_name($sessionName);
  6664. }
  6665. session_start();
  6666. }
  6667. }
  6668. $path = RequestUtils::getPathSegment($request, 1);
  6669. $method = $request->getMethod();
  6670. if ($method == 'POST' && $path == 'login') {
  6671. $body = $request->getParsedBody();
  6672. $username = isset($body->username) ? $body->username : '';
  6673. $password = isset($body->password) ? $body->password : '';
  6674. $tableName = $this->getProperty('usersTable', 'users');
  6675. $table = $this->reflection->getTable($tableName);
  6676. $usernameColumnName = $this->getProperty('usernameColumn', 'username');
  6677. $usernameColumn = $table->getColumn($usernameColumnName);
  6678. $passwordColumnName = $this->getProperty('passwordColumn', 'password');
  6679. $passwordColumn = $table->getColumn($passwordColumnName);
  6680. $condition = new ColumnCondition($usernameColumn, 'eq', $username);
  6681. $returnedColumns = $this->getProperty('returnedColumns', '');
  6682. if (!$returnedColumns) {
  6683. $columnNames = $table->getColumnNames();
  6684. } else {
  6685. $columnNames = array_map('trim', explode(',', $returnedColumns));
  6686. $columnNames[] = $passwordColumnName;
  6687. }
  6688. $columnOrdering = $this->ordering->getDefaultColumnOrdering($table);
  6689. $users = $this->db->selectAll($table, $columnNames, $condition, $columnOrdering, 0, 1);
  6690. foreach ($users as $user) {
  6691. if (password_verify($password, $user[$passwordColumnName]) == 1) {
  6692. if (!headers_sent()) {
  6693. session_regenerate_id(true);
  6694. }
  6695. unset($user[$passwordColumnName]);
  6696. $_SESSION['user'] = $user;
  6697. return $this->responder->success($user);
  6698. }
  6699. }
  6700. return $this->responder->error(ErrorCode::AUTHENTICATION_FAILED, $username);
  6701. }
  6702. if ($method == 'POST' && $path == 'logout') {
  6703. if (isset($_SESSION['user'])) {
  6704. $user = $_SESSION['user'];
  6705. unset($_SESSION['user']);
  6706. if (session_status() != PHP_SESSION_NONE) {
  6707. session_destroy();
  6708. }
  6709. return $this->responder->success($user);
  6710. }
  6711. return $this->responder->error(ErrorCode::AUTHENTICATION_REQUIRED, '');
  6712. }
  6713. if (!isset($_SESSION['user']) || !$_SESSION['user']) {
  6714. $authenticationMode = $this->getProperty('mode', 'required');
  6715. if ($authenticationMode == 'required') {
  6716. return $this->responder->error(ErrorCode::AUTHENTICATION_REQUIRED, '');
  6717. }
  6718. }
  6719. return $next->handle($request);
  6720. }
  6721. }
  6722. }
  6723. // file: src/Tqdev/PhpCrudApi/Middleware/FirewallMiddleware.php
  6724. namespace Tqdev\PhpCrudApi\Middleware {
  6725. use Psr\Http\Message\ResponseInterface;
  6726. use Psr\Http\Message\ServerRequestInterface;
  6727. use Psr\Http\Server\RequestHandlerInterface;
  6728. use Tqdev\PhpCrudApi\Controller\Responder;
  6729. use Tqdev\PhpCrudApi\Middleware\Base\Middleware;
  6730. use Tqdev\PhpCrudApi\Record\ErrorCode;
  6731. class FirewallMiddleware extends Middleware
  6732. {
  6733. private function ipMatch(string $ip, string $cidr): bool
  6734. {
  6735. if (strpos($cidr, '/') !== false) {
  6736. list($subnet, $mask) = explode('/', trim($cidr));
  6737. if ((ip2long($ip) & ~((1 << (32 - $mask)) - 1)) == ip2long($subnet)) {
  6738. return true;
  6739. }
  6740. } else {
  6741. if (ip2long($ip) == ip2long($cidr)) {
  6742. return true;
  6743. }
  6744. }
  6745. return false;
  6746. }
  6747. private function isIpAllowed(string $ipAddress, string $allowedIpAddresses): bool
  6748. {
  6749. foreach (explode(',', $allowedIpAddresses) as $allowedIp) {
  6750. if ($this->ipMatch($ipAddress, $allowedIp)) {
  6751. return true;
  6752. }
  6753. }
  6754. return false;
  6755. }
  6756. public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface
  6757. {
  6758. $reverseProxy = $this->getProperty('reverseProxy', '');
  6759. if ($reverseProxy) {
  6760. $ipAddress = array_pop(explode(',', $request->getHeader('X-Forwarded-For')));
  6761. } elseif (isset($_SERVER['REMOTE_ADDR'])) {
  6762. $ipAddress = $_SERVER['REMOTE_ADDR'];
  6763. } else {
  6764. $ipAddress = '127.0.0.1';
  6765. }
  6766. $allowedIpAddresses = $this->getProperty('allowedIpAddresses', '');
  6767. if (!$this->isIpAllowed($ipAddress, $allowedIpAddresses)) {
  6768. $response = $this->responder->error(ErrorCode::TEMPORARY_OR_PERMANENTLY_BLOCKED, '');
  6769. } else {
  6770. $response = $next->handle($request);
  6771. }
  6772. return $response;
  6773. }
  6774. }
  6775. }
  6776. // file: src/Tqdev/PhpCrudApi/Middleware/IpAddressMiddleware.php
  6777. namespace Tqdev\PhpCrudApi\Middleware {
  6778. use Psr\Http\Message\ResponseInterface;
  6779. use Psr\Http\Message\ServerRequestInterface;
  6780. use Psr\Http\Server\RequestHandlerInterface;
  6781. use Tqdev\PhpCrudApi\Column\Reflection\ReflectedTable;
  6782. use Tqdev\PhpCrudApi\Column\ReflectionService;
  6783. use Tqdev\PhpCrudApi\Controller\Responder;
  6784. use Tqdev\PhpCrudApi\Middleware\Base\Middleware;
  6785. use Tqdev\PhpCrudApi\Middleware\Router\Router;
  6786. use Tqdev\PhpCrudApi\RequestUtils;
  6787. class IpAddressMiddleware extends Middleware
  6788. {
  6789. private $reflection;
  6790. public function __construct(Router $router, Responder $responder, array $properties, ReflectionService $reflection)
  6791. {
  6792. parent::__construct($router, $responder, $properties);
  6793. $this->reflection = $reflection;
  6794. }
  6795. private function callHandler($record, string $operation, ReflectedTable $table) /*: object */
  6796. {
  6797. $context = (array) $record;
  6798. $columnNames = $this->getProperty('columns', '');
  6799. if ($columnNames) {
  6800. foreach (explode(',', $columnNames) as $columnName) {
  6801. if ($table->hasColumn($columnName)) {
  6802. if ($operation == 'create') {
  6803. $context[$columnName] = $_SERVER['REMOTE_ADDR'];
  6804. } else {
  6805. unset($context[$columnName]);
  6806. }
  6807. }
  6808. }
  6809. }
  6810. return (object) $context;
  6811. }
  6812. public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface
  6813. {
  6814. $operation = RequestUtils::getOperation($request);
  6815. if (in_array($operation, ['create', 'update', 'increment'])) {
  6816. $tableNames = $this->getProperty('tables', '');
  6817. $tableName = RequestUtils::getPathSegment($request, 2);
  6818. if (!$tableNames || in_array($tableName, explode(',', $tableNames))) {
  6819. if ($this->reflection->hasTable($tableName)) {
  6820. $record = $request->getParsedBody();
  6821. if ($record !== null) {
  6822. $table = $this->reflection->getTable($tableName);
  6823. if (is_array($record)) {
  6824. foreach ($record as &$r) {
  6825. $r = $this->callHandler($r, $operation, $table);
  6826. }
  6827. } else {
  6828. $record = $this->callHandler($record, $operation, $table);
  6829. }
  6830. $request = $request->withParsedBody($record);
  6831. }
  6832. }
  6833. }
  6834. }
  6835. return $next->handle($request);
  6836. }
  6837. }
  6838. }
  6839. // file: src/Tqdev/PhpCrudApi/Middleware/JoinLimitsMiddleware.php
  6840. namespace Tqdev\PhpCrudApi\Middleware {
  6841. use Psr\Http\Message\ResponseInterface;
  6842. use Psr\Http\Message\ServerRequestInterface;
  6843. use Psr\Http\Server\RequestHandlerInterface;
  6844. use Tqdev\PhpCrudApi\Column\ReflectionService;
  6845. use Tqdev\PhpCrudApi\Controller\Responder;
  6846. use Tqdev\PhpCrudApi\Middleware\Base\Middleware;
  6847. use Tqdev\PhpCrudApi\Middleware\Communication\VariableStore;
  6848. use Tqdev\PhpCrudApi\Middleware\Router\Router;
  6849. use Tqdev\PhpCrudApi\RequestUtils;
  6850. class JoinLimitsMiddleware extends Middleware
  6851. {
  6852. private $reflection;
  6853. public function __construct(Router $router, Responder $responder, array $properties, ReflectionService $reflection)
  6854. {
  6855. parent::__construct($router, $responder, $properties);
  6856. $this->reflection = $reflection;
  6857. }
  6858. public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface
  6859. {
  6860. $operation = RequestUtils::getOperation($request);
  6861. $params = RequestUtils::getParams($request);
  6862. if (in_array($operation, ['read', 'list']) && isset($params['join'])) {
  6863. $maxDepth = (int) $this->getProperty('depth', '3');
  6864. $maxTables = (int) $this->getProperty('tables', '10');
  6865. $maxRecords = (int) $this->getProperty('records', '1000');
  6866. $tableCount = 0;
  6867. $joinPaths = array();
  6868. for ($i = 0; $i < count($params['join']); $i++) {
  6869. $joinPath = array();
  6870. $tables = explode(',', $params['join'][$i]);
  6871. for ($depth = 0; $depth < min($maxDepth, count($tables)); $depth++) {
  6872. array_push($joinPath, $tables[$depth]);
  6873. $tableCount += 1;
  6874. if ($tableCount == $maxTables) {
  6875. break;
  6876. }
  6877. }
  6878. array_push($joinPaths, implode(',', $joinPath));
  6879. if ($tableCount == $maxTables) {
  6880. break;
  6881. }
  6882. }
  6883. $params['join'] = $joinPaths;
  6884. $request = RequestUtils::setParams($request, $params);
  6885. VariableStore::set("joinLimits.maxRecords", $maxRecords);
  6886. }
  6887. return $next->handle($request);
  6888. }
  6889. }
  6890. }
  6891. // file: src/Tqdev/PhpCrudApi/Middleware/JwtAuthMiddleware.php
  6892. namespace Tqdev\PhpCrudApi\Middleware {
  6893. use Psr\Http\Message\ResponseInterface;
  6894. use Psr\Http\Message\ServerRequestInterface;
  6895. use Psr\Http\Server\RequestHandlerInterface;
  6896. use Tqdev\PhpCrudApi\Middleware\Base\Middleware;
  6897. use Tqdev\PhpCrudApi\Record\ErrorCode;
  6898. use Tqdev\PhpCrudApi\RequestUtils;
  6899. class JwtAuthMiddleware extends Middleware
  6900. {
  6901. private function getVerifiedClaims(string $token, int $time, int $leeway, int $ttl, array $secrets, array $requirements): array
  6902. {
  6903. $algorithms = array(
  6904. 'HS256' => 'sha256',
  6905. 'HS384' => 'sha384',
  6906. 'HS512' => 'sha512',
  6907. 'RS256' => 'sha256',
  6908. 'RS384' => 'sha384',
  6909. 'RS512' => 'sha512',
  6910. );
  6911. $token = explode('.', $token);
  6912. if (count($token) < 3) {
  6913. return array();
  6914. }
  6915. $header = json_decode(base64_decode(strtr($token[0], '-_', '+/')), true);
  6916. $kid = 0;
  6917. if (isset($header['kid'])) {
  6918. $kid = $header['kid'];
  6919. }
  6920. if (!isset($secrets[$kid])) {
  6921. return array();
  6922. }
  6923. $secret = $secrets[$kid];
  6924. if ($header['typ'] != 'JWT') {
  6925. return array();
  6926. }
  6927. $algorithm = $header['alg'];
  6928. if (!isset($algorithms[$algorithm])) {
  6929. return array();
  6930. }
  6931. if (!empty($requirements['alg']) && !in_array($algorithm, $requirements['alg'])) {
  6932. return array();
  6933. }
  6934. $hmac = $algorithms[$algorithm];
  6935. $signature = base64_decode(strtr($token[2], '-_', '+/'));
  6936. $data = "$token[0].$token[1]";
  6937. switch ($algorithm[0]) {
  6938. case 'H':
  6939. $hash = hash_hmac($hmac, $data, $secret, true);
  6940. $equals = hash_equals($hash, $signature);
  6941. if (!$equals) {
  6942. return array();
  6943. }
  6944. break;
  6945. case 'R':
  6946. $equals = openssl_verify($data, $signature, $secret, $hmac) == 1;
  6947. if (!$equals) {
  6948. return array();
  6949. }
  6950. break;
  6951. }
  6952. $claims = json_decode(base64_decode(strtr($token[1], '-_', '+/')), true);
  6953. if (!$claims) {
  6954. return array();
  6955. }
  6956. foreach ($requirements as $field => $values) {
  6957. if (!empty($values)) {
  6958. if ($field != 'alg') {
  6959. if (!isset($claims[$field]) || !in_array($claims[$field], $values)) {
  6960. return array();
  6961. }
  6962. }
  6963. }
  6964. }
  6965. if (isset($claims['nbf']) && $time + $leeway < $claims['nbf']) {
  6966. return array();
  6967. }
  6968. if (isset($claims['iat']) && $time + $leeway < $claims['iat']) {
  6969. return array();
  6970. }
  6971. if (isset($claims['exp']) && $time - $leeway > $claims['exp']) {
  6972. return array();
  6973. }
  6974. if (isset($claims['iat']) && !isset($claims['exp'])) {
  6975. if ($time - $leeway > $claims['iat'] + $ttl) {
  6976. return array();
  6977. }
  6978. }
  6979. return $claims;
  6980. }
  6981. private function getClaims(string $token): array
  6982. {
  6983. $time = (int) $this->getProperty('time', time());
  6984. $leeway = (int) $this->getProperty('leeway', '5');
  6985. $ttl = (int) $this->getProperty('ttl', '30');
  6986. $secrets = $this->getMapProperty('secrets', '');
  6987. if (!$secrets) {
  6988. $secrets = [$this->getProperty('secret', '')];
  6989. }
  6990. $requirements = array(
  6991. 'alg' => $this->getArrayProperty('algorithms', ''),
  6992. 'aud' => $this->getArrayProperty('audiences', ''),
  6993. 'iss' => $this->getArrayProperty('issuers', ''),
  6994. );
  6995. return $this->getVerifiedClaims($token, $time, $leeway, $ttl, $secrets, $requirements);
  6996. }
  6997. private function getAuthorizationToken(ServerRequestInterface $request): string
  6998. {
  6999. $headerName = $this->getProperty('header', 'X-Authorization');
  7000. $headerValue = RequestUtils::getHeader($request, $headerName);
  7001. $parts = explode(' ', trim($headerValue), 2);
  7002. if (count($parts) != 2) {
  7003. return '';
  7004. }
  7005. if ($parts[0] != 'Bearer') {
  7006. return '';
  7007. }
  7008. return $parts[1];
  7009. }
  7010. public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface
  7011. {
  7012. if (session_status() == PHP_SESSION_NONE) {
  7013. if (!headers_sent()) {
  7014. $sessionName = $this->getProperty('sessionName', '');
  7015. if ($sessionName) {
  7016. session_name($sessionName);
  7017. }
  7018. session_start();
  7019. }
  7020. }
  7021. $token = $this->getAuthorizationToken($request);
  7022. if ($token) {
  7023. $claims = $this->getClaims($token);
  7024. $_SESSION['claims'] = $claims;
  7025. if (empty($claims)) {
  7026. return $this->responder->error(ErrorCode::AUTHENTICATION_FAILED, 'JWT');
  7027. }
  7028. if (!headers_sent()) {
  7029. session_regenerate_id();
  7030. }
  7031. }
  7032. if (empty($_SESSION['claims'])) {
  7033. $authenticationMode = $this->getProperty('mode', 'required');
  7034. if ($authenticationMode == 'required') {
  7035. return $this->responder->error(ErrorCode::AUTHENTICATION_REQUIRED, '');
  7036. }
  7037. }
  7038. return $next->handle($request);
  7039. }
  7040. }
  7041. }
  7042. // file: src/Tqdev/PhpCrudApi/Middleware/MultiTenancyMiddleware.php
  7043. namespace Tqdev\PhpCrudApi\Middleware {
  7044. use Psr\Http\Message\ResponseInterface;
  7045. use Psr\Http\Message\ServerRequestInterface;
  7046. use Psr\Http\Server\RequestHandlerInterface;
  7047. use Tqdev\PhpCrudApi\Column\ReflectionService;
  7048. use Tqdev\PhpCrudApi\Controller\Responder;
  7049. use Tqdev\PhpCrudApi\Middleware\Base\Middleware;
  7050. use Tqdev\PhpCrudApi\Middleware\Communication\VariableStore;
  7051. use Tqdev\PhpCrudApi\Middleware\Router\Router;
  7052. use Tqdev\PhpCrudApi\Record\Condition\ColumnCondition;
  7053. use Tqdev\PhpCrudApi\Record\Condition\Condition;
  7054. use Tqdev\PhpCrudApi\Record\Condition\NoCondition;
  7055. use Tqdev\PhpCrudApi\RequestUtils;
  7056. class MultiTenancyMiddleware extends Middleware
  7057. {
  7058. private $reflection;
  7059. public function __construct(Router $router, Responder $responder, array $properties, ReflectionService $reflection)
  7060. {
  7061. parent::__construct($router, $responder, $properties);
  7062. $this->reflection = $reflection;
  7063. }
  7064. private function getCondition(string $tableName, array $pairs): Condition
  7065. {
  7066. $condition = new NoCondition();
  7067. $table = $this->reflection->getTable($tableName);
  7068. foreach ($pairs as $k => $v) {
  7069. $condition = $condition->_and(new ColumnCondition($table->getColumn($k), 'eq', $v));
  7070. }
  7071. return $condition;
  7072. }
  7073. private function getPairs($handler, string $operation, string $tableName): array
  7074. {
  7075. $result = array();
  7076. $pairs = call_user_func($handler, $operation, $tableName);
  7077. $table = $this->reflection->getTable($tableName);
  7078. foreach ($pairs as $k => $v) {
  7079. if ($table->hasColumn($k)) {
  7080. $result[$k] = $v;
  7081. }
  7082. }
  7083. return $result;
  7084. }
  7085. private function handleRecord(ServerRequestInterface $request, string $operation, array $pairs): ServerRequestInterface
  7086. {
  7087. $record = $request->getParsedBody();
  7088. if ($record === null) {
  7089. return $request;
  7090. }
  7091. $multi = is_array($record);
  7092. $records = $multi ? $record : [$record];
  7093. foreach ($records as &$record) {
  7094. foreach ($pairs as $column => $value) {
  7095. if ($operation == 'create') {
  7096. $record->$column = $value;
  7097. } else {
  7098. if (isset($record->$column)) {
  7099. unset($record->$column);
  7100. }
  7101. }
  7102. }
  7103. }
  7104. return $request->withParsedBody($multi ? $records : $records[0]);
  7105. }
  7106. public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface
  7107. {
  7108. $handler = $this->getProperty('handler', '');
  7109. if ($handler !== '') {
  7110. $path = RequestUtils::getPathSegment($request, 1);
  7111. if ($path == 'records') {
  7112. $operation = RequestUtils::getOperation($request);
  7113. $tableNames = RequestUtils::getTableNames($request, $this->reflection);
  7114. foreach ($tableNames as $i => $tableName) {
  7115. if (!$this->reflection->hasTable($tableName)) {
  7116. continue;
  7117. }
  7118. $pairs = $this->getPairs($handler, $operation, $tableName);
  7119. if ($i == 0) {
  7120. if (in_array($operation, ['create', 'update', 'increment'])) {
  7121. $request = $this->handleRecord($request, $operation, $pairs);
  7122. }
  7123. }
  7124. $condition = $this->getCondition($tableName, $pairs);
  7125. VariableStore::set("multiTenancy.conditions.$tableName", $condition);
  7126. }
  7127. }
  7128. }
  7129. return $next->handle($request);
  7130. }
  7131. }
  7132. }
  7133. // file: src/Tqdev/PhpCrudApi/Middleware/PageLimitsMiddleware.php
  7134. namespace Tqdev\PhpCrudApi\Middleware {
  7135. use Psr\Http\Message\ResponseInterface;
  7136. use Psr\Http\Message\ServerRequestInterface;
  7137. use Psr\Http\Server\RequestHandlerInterface;
  7138. use Tqdev\PhpCrudApi\Column\ReflectionService;
  7139. use Tqdev\PhpCrudApi\Controller\Responder;
  7140. use Tqdev\PhpCrudApi\Middleware\Base\Middleware;
  7141. use Tqdev\PhpCrudApi\Middleware\Router\Router;
  7142. use Tqdev\PhpCrudApi\Record\ErrorCode;
  7143. use Tqdev\PhpCrudApi\RequestUtils;
  7144. class PageLimitsMiddleware extends Middleware
  7145. {
  7146. private $reflection;
  7147. public function __construct(Router $router, Responder $responder, array $properties, ReflectionService $reflection)
  7148. {
  7149. parent::__construct($router, $responder, $properties);
  7150. $this->reflection = $reflection;
  7151. }
  7152. public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface
  7153. {
  7154. $operation = RequestUtils::getOperation($request);
  7155. if ($operation == 'list') {
  7156. $params = RequestUtils::getParams($request);
  7157. $maxPage = (int) $this->getProperty('pages', '100');
  7158. if (isset($params['page']) && $params['page'] && $maxPage > 0) {
  7159. if (strpos($params['page'][0], ',') === false) {
  7160. $page = $params['page'][0];
  7161. } else {
  7162. list($page, $size) = explode(',', $params['page'][0], 2);
  7163. }
  7164. if ($page > $maxPage) {
  7165. return $this->responder->error(ErrorCode::PAGINATION_FORBIDDEN, '');
  7166. }
  7167. }
  7168. $maxSize = (int) $this->getProperty('records', '1000');
  7169. if (!isset($params['size']) || !$params['size'] && $maxSize > 0) {
  7170. $params['size'] = array($maxSize);
  7171. } else {
  7172. $params['size'] = array(min($params['size'][0], $maxSize));
  7173. }
  7174. $request = RequestUtils::setParams($request, $params);
  7175. }
  7176. return $next->handle($request);
  7177. }
  7178. }
  7179. }
  7180. // file: src/Tqdev/PhpCrudApi/Middleware/ReconnectMiddleware.php
  7181. namespace Tqdev\PhpCrudApi\Middleware {
  7182. use Psr\Http\Message\ResponseInterface;
  7183. use Psr\Http\Message\ServerRequestInterface;
  7184. use Psr\Http\Server\RequestHandlerInterface;
  7185. use Tqdev\PhpCrudApi\Column\ReflectionService;
  7186. use Tqdev\PhpCrudApi\Controller\Responder;
  7187. use Tqdev\PhpCrudApi\Database\GenericDB;
  7188. use Tqdev\PhpCrudApi\Middleware\Base\Middleware;
  7189. use Tqdev\PhpCrudApi\Middleware\Router\Router;
  7190. class ReconnectMiddleware extends Middleware
  7191. {
  7192. private $reflection;
  7193. private $db;
  7194. public function __construct(Router $router, Responder $responder, array $properties, ReflectionService $reflection, GenericDB $db)
  7195. {
  7196. parent::__construct($router, $responder, $properties);
  7197. $this->reflection = $reflection;
  7198. $this->db = $db;
  7199. }
  7200. private function getDriver(): string
  7201. {
  7202. $driverHandler = $this->getProperty('driverHandler', '');
  7203. if ($driverHandler) {
  7204. return call_user_func($driverHandler);
  7205. }
  7206. return '';
  7207. }
  7208. private function getAddress(): string
  7209. {
  7210. $addressHandler = $this->getProperty('addressHandler', '');
  7211. if ($addressHandler) {
  7212. return call_user_func($addressHandler);
  7213. }
  7214. return '';
  7215. }
  7216. private function getPort(): int
  7217. {
  7218. $portHandler = $this->getProperty('portHandler', '');
  7219. if ($portHandler) {
  7220. return call_user_func($portHandler);
  7221. }
  7222. return 0;
  7223. }
  7224. private function getDatabase(): string
  7225. {
  7226. $databaseHandler = $this->getProperty('databaseHandler', '');
  7227. if ($databaseHandler) {
  7228. return call_user_func($databaseHandler);
  7229. }
  7230. return '';
  7231. }
  7232. private function getTables(): array
  7233. {
  7234. $tablesHandler = $this->getProperty('tablesHandler', '');
  7235. if ($tablesHandler) {
  7236. return call_user_func($tablesHandler);
  7237. }
  7238. return [];
  7239. }
  7240. private function getUsername(): string
  7241. {
  7242. $usernameHandler = $this->getProperty('usernameHandler', '');
  7243. if ($usernameHandler) {
  7244. return call_user_func($usernameHandler);
  7245. }
  7246. return '';
  7247. }
  7248. private function getPassword(): string
  7249. {
  7250. $passwordHandler = $this->getProperty('passwordHandler', '');
  7251. if ($passwordHandler) {
  7252. return call_user_func($passwordHandler);
  7253. }
  7254. return '';
  7255. }
  7256. public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface
  7257. {
  7258. $driver = $this->getDriver();
  7259. $address = $this->getAddress();
  7260. $port = $this->getPort();
  7261. $database = $this->getDatabase();
  7262. $tables = $this->getTables();
  7263. $username = $this->getUsername();
  7264. $password = $this->getPassword();
  7265. if ($driver || $address || $port || $database || $tables || $username || $password) {
  7266. $this->db->reconstruct($driver, $address, $port, $database, $tables, $username, $password);
  7267. }
  7268. return $next->handle($request);
  7269. }
  7270. }
  7271. }
  7272. // file: src/Tqdev/PhpCrudApi/Middleware/SanitationMiddleware.php
  7273. namespace Tqdev\PhpCrudApi\Middleware {
  7274. use Psr\Http\Message\ResponseInterface;
  7275. use Psr\Http\Message\ServerRequestInterface;
  7276. use Psr\Http\Server\RequestHandlerInterface;
  7277. use Tqdev\PhpCrudApi\Column\Reflection\ReflectedTable;
  7278. use Tqdev\PhpCrudApi\Column\Reflection\ReflectedColumn;
  7279. use Tqdev\PhpCrudApi\Column\ReflectionService;
  7280. use Tqdev\PhpCrudApi\Controller\Responder;
  7281. use Tqdev\PhpCrudApi\Middleware\Base\Middleware;
  7282. use Tqdev\PhpCrudApi\Middleware\Router\Router;
  7283. use Tqdev\PhpCrudApi\RequestUtils;
  7284. class SanitationMiddleware extends Middleware
  7285. {
  7286. private $reflection;
  7287. public function __construct(Router $router, Responder $responder, array $properties, ReflectionService $reflection)
  7288. {
  7289. parent::__construct($router, $responder, $properties);
  7290. $this->reflection = $reflection;
  7291. }
  7292. private function callHandler($handler, $record, string $operation, ReflectedTable $table) /*: object */
  7293. {
  7294. $context = (array) $record;
  7295. $tableName = $table->getName();
  7296. foreach ($context as $columnName => &$value) {
  7297. if ($table->hasColumn($columnName)) {
  7298. $column = $table->getColumn($columnName);
  7299. $value = call_user_func($handler, $operation, $tableName, $column->serialize(), $value);
  7300. $value = $this->sanitizeType($table, $column, $value);
  7301. }
  7302. }
  7303. return (object) $context;
  7304. }
  7305. private function sanitizeType(ReflectedTable $table, ReflectedColumn $column, $value)
  7306. {
  7307. $tables = $this->getArrayProperty('tables', 'all');
  7308. $types = $this->getArrayProperty('types', 'all');
  7309. if (
  7310. (in_array('all', $tables) || in_array($table->getName(), $tables)) &&
  7311. (in_array('all', $types) || in_array($column->getType(), $types))
  7312. ) {
  7313. if (is_null($value)) {
  7314. return $value;
  7315. }
  7316. if (is_string($value)) {
  7317. $newValue = null;
  7318. switch ($column->getType()) {
  7319. case 'integer':
  7320. case 'bigint':
  7321. $newValue = filter_var(trim($value), FILTER_VALIDATE_INT, FILTER_NULL_ON_FAILURE);
  7322. break;
  7323. case 'decimal':
  7324. $newValue = filter_var(trim($value), FILTER_VALIDATE_FLOAT, FILTER_NULL_ON_FAILURE);
  7325. if (is_float($newValue)) {
  7326. $newValue = number_format($newValue, $column->getScale(), '.', '');
  7327. }
  7328. break;
  7329. case 'float':
  7330. case 'double':
  7331. $newValue = filter_var(trim($value), FILTER_VALIDATE_FLOAT, FILTER_NULL_ON_FAILURE);
  7332. break;
  7333. case 'boolean':
  7334. $newValue = filter_var(trim($value), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
  7335. break;
  7336. case 'date':
  7337. $time = strtotime(trim($value));
  7338. if ($time !== false) {
  7339. $newValue = date('Y-m-d', $time);
  7340. }
  7341. break;
  7342. case 'time':
  7343. $time = strtotime(trim($value));
  7344. if ($time !== false) {
  7345. $newValue = date('H:i:s', $time);
  7346. }
  7347. break;
  7348. case 'timestamp':
  7349. $time = strtotime(trim($value));
  7350. if ($time !== false) {
  7351. $newValue = date('Y-m-d H:i:s', $time);
  7352. }
  7353. break;
  7354. case 'blob':
  7355. case 'varbinary':
  7356. // allow base64url format
  7357. $newValue = strtr(trim($value), '-_', '+/');
  7358. break;
  7359. case 'clob':
  7360. case 'varchar':
  7361. $newValue = $value;
  7362. break;
  7363. case 'geometry':
  7364. $newValue = trim($value);
  7365. break;
  7366. }
  7367. if (!is_null($newValue)) {
  7368. $value = $newValue;
  7369. }
  7370. } else {
  7371. switch ($column->getType()) {
  7372. case 'integer':
  7373. case 'bigint':
  7374. if (is_float($value)) {
  7375. $value = (int) round($value);
  7376. }
  7377. break;
  7378. case 'decimal':
  7379. if (is_float($value) || is_int($value)) {
  7380. $value = number_format((float) $value, $column->getScale(), '.', '');
  7381. }
  7382. break;
  7383. }
  7384. }
  7385. // post process
  7386. }
  7387. return $value;
  7388. }
  7389. public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface
  7390. {
  7391. $operation = RequestUtils::getOperation($request);
  7392. if (in_array($operation, ['create', 'update', 'increment'])) {
  7393. $tableName = RequestUtils::getPathSegment($request, 2);
  7394. if ($this->reflection->hasTable($tableName)) {
  7395. $record = $request->getParsedBody();
  7396. if ($record !== null) {
  7397. $handler = $this->getProperty('handler', '');
  7398. if ($handler !== '') {
  7399. $table = $this->reflection->getTable($tableName);
  7400. if (is_array($record)) {
  7401. foreach ($record as &$r) {
  7402. $r = $this->callHandler($handler, $r, $operation, $table);
  7403. }
  7404. } else {
  7405. $record = $this->callHandler($handler, $record, $operation, $table);
  7406. }
  7407. $request = $request->withParsedBody($record);
  7408. }
  7409. }
  7410. }
  7411. }
  7412. return $next->handle($request);
  7413. }
  7414. }
  7415. }
  7416. // file: src/Tqdev/PhpCrudApi/Middleware/SslRedirectMiddleware.php
  7417. namespace Tqdev\PhpCrudApi\Middleware {
  7418. use Psr\Http\Message\ResponseInterface;
  7419. use Psr\Http\Message\ServerRequestInterface;
  7420. use Psr\Http\Server\RequestHandlerInterface;
  7421. use Tqdev\PhpCrudApi\Middleware\Base\Middleware;
  7422. use Tqdev\PhpCrudApi\ResponseFactory;
  7423. class SslRedirectMiddleware extends Middleware
  7424. {
  7425. public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface
  7426. {
  7427. $uri = $request->getUri();
  7428. $scheme = $uri->getScheme();
  7429. if ($scheme == 'http') {
  7430. $uri = $request->getUri();
  7431. $uri = $uri->withScheme('https');
  7432. $response = ResponseFactory::fromStatus(301);
  7433. $response = $response->withHeader('Location', $uri->__toString());
  7434. } else {
  7435. $response = $next->handle($request);
  7436. }
  7437. return $response;
  7438. }
  7439. }
  7440. }
  7441. // file: src/Tqdev/PhpCrudApi/Middleware/ValidationMiddleware.php
  7442. namespace Tqdev\PhpCrudApi\Middleware {
  7443. use Psr\Http\Message\ResponseInterface;
  7444. use Psr\Http\Message\ServerRequestInterface;
  7445. use Psr\Http\Server\RequestHandlerInterface;
  7446. use Tqdev\PhpCrudApi\Column\ReflectionService;
  7447. use Tqdev\PhpCrudApi\Column\Reflection\ReflectedTable;
  7448. use Tqdev\PhpCrudApi\Column\Reflection\ReflectedColumn;
  7449. use Tqdev\PhpCrudApi\Controller\Responder;
  7450. use Tqdev\PhpCrudApi\Middleware\Base\Middleware;
  7451. use Tqdev\PhpCrudApi\Middleware\Router\Router;
  7452. use Tqdev\PhpCrudApi\Record\ErrorCode;
  7453. use Tqdev\PhpCrudApi\RequestUtils;
  7454. class ValidationMiddleware extends Middleware
  7455. {
  7456. private $reflection;
  7457. public function __construct(Router $router, Responder $responder, array $properties, ReflectionService $reflection)
  7458. {
  7459. parent::__construct($router, $responder, $properties);
  7460. $this->reflection = $reflection;
  7461. }
  7462. private function callHandler($handler, $record, string $operation, ReflectedTable $table) /*: ResponseInterface?*/
  7463. {
  7464. $context = (array) $record;
  7465. $details = array();
  7466. $tableName = $table->getName();
  7467. foreach ($context as $columnName => $value) {
  7468. if ($table->hasColumn($columnName)) {
  7469. $column = $table->getColumn($columnName);
  7470. $valid = call_user_func($handler, $operation, $tableName, $column->serialize(), $value, $context);
  7471. if ($valid === true || $valid === '') {
  7472. $valid = $this->validateType($table, $column, $value);
  7473. }
  7474. if ($valid !== true && $valid !== '') {
  7475. $details[$columnName] = $valid;
  7476. }
  7477. }
  7478. }
  7479. if (count($details) > 0) {
  7480. return $this->responder->error(ErrorCode::INPUT_VALIDATION_FAILED, $tableName, $details);
  7481. }
  7482. return null;
  7483. }
  7484. private function validateType(ReflectedTable $table, ReflectedColumn $column, $value)
  7485. {
  7486. $tables = $this->getArrayProperty('tables', 'all');
  7487. $types = $this->getArrayProperty('types', 'all');
  7488. if (
  7489. (in_array('all', $tables) || in_array($table->getName(), $tables)) &&
  7490. (in_array('all', $types) || in_array($column->getType(), $types))
  7491. ) {
  7492. if (is_null($value)) {
  7493. return ($column->getNullable() ? true : "cannot be null");
  7494. }
  7495. if (is_string($value)) {
  7496. // check for whitespace
  7497. switch ($column->getType()) {
  7498. case 'varchar':
  7499. case 'clob':
  7500. break;
  7501. default:
  7502. if (strlen(trim($value)) != strlen($value)) {
  7503. return 'illegal whitespace';
  7504. }
  7505. break;
  7506. }
  7507. // try to parse
  7508. switch ($column->getType()) {
  7509. case 'integer':
  7510. case 'bigint':
  7511. if (
  7512. filter_var($value, FILTER_SANITIZE_NUMBER_INT) !== $value ||
  7513. filter_var($value, FILTER_VALIDATE_INT) === false
  7514. ) {
  7515. return 'invalid integer';
  7516. }
  7517. break;
  7518. case 'decimal':
  7519. if (strpos($value, '.') !== false) {
  7520. list($whole, $decimals) = explode('.', $value, 2);
  7521. } else {
  7522. list($whole, $decimals) = array($value, '');
  7523. }
  7524. if (strlen($whole) > 0 && !ctype_digit($whole)) {
  7525. return 'invalid decimal';
  7526. }
  7527. if (strlen($decimals) > 0 && !ctype_digit($decimals)) {
  7528. return 'invalid decimal';
  7529. }
  7530. if (strlen($whole) > $column->getPrecision() - $column->getScale()) {
  7531. return 'decimal too large';
  7532. }
  7533. if (strlen($decimals) > $column->getScale()) {
  7534. return 'decimal too precise';
  7535. }
  7536. break;
  7537. case 'float':
  7538. case 'double':
  7539. if (
  7540. filter_var($value, FILTER_SANITIZE_NUMBER_FLOAT) !== $value ||
  7541. filter_var($value, FILTER_VALIDATE_FLOAT) === false
  7542. ) {
  7543. return 'invalid float';
  7544. }
  7545. break;
  7546. case 'boolean':
  7547. if (!in_array(strtolower($value), array('true', 'false'))) {
  7548. return 'invalid boolean';
  7549. }
  7550. break;
  7551. case 'date':
  7552. if (date_create_from_format('Y-m-d', $value) === false) {
  7553. return 'invalid date';
  7554. }
  7555. break;
  7556. case 'time':
  7557. if (date_create_from_format('H:i:s', $value) === false) {
  7558. return 'invalid time';
  7559. }
  7560. break;
  7561. case 'timestamp':
  7562. if (date_create_from_format('Y-m-d H:i:s', $value) === false) {
  7563. return 'invalid timestamp';
  7564. }
  7565. break;
  7566. case 'clob':
  7567. case 'varchar':
  7568. if ($column->hasLength() && mb_strlen($value, 'UTF-8') > $column->getLength()) {
  7569. return 'string too long';
  7570. }
  7571. break;
  7572. case 'blob':
  7573. case 'varbinary':
  7574. if (base64_decode($value, true) === false) {
  7575. return 'invalid base64';
  7576. }
  7577. if ($column->hasLength() && strlen(base64_decode($value)) > $column->getLength()) {
  7578. return 'string too long';
  7579. }
  7580. break;
  7581. case 'geometry':
  7582. // no checks yet
  7583. break;
  7584. }
  7585. } else { // check non-string types
  7586. switch ($column->getType()) {
  7587. case 'integer':
  7588. case 'bigint':
  7589. if (!is_int($value)) {
  7590. return 'invalid integer';
  7591. }
  7592. break;
  7593. case 'float':
  7594. case 'double':
  7595. if (!is_float($value) && !is_int($value)) {
  7596. return 'invalid float';
  7597. }
  7598. break;
  7599. case 'boolean':
  7600. if (!is_bool($value) && ($value !== 0) && ($value !== 1)) {
  7601. return 'invalid boolean';
  7602. }
  7603. break;
  7604. default:
  7605. return 'invalid ' . $column->getType();
  7606. }
  7607. }
  7608. // extra checks
  7609. switch ($column->getType()) {
  7610. case 'integer': // 4 byte signed
  7611. $value = filter_var($value, FILTER_VALIDATE_INT);
  7612. if ($value > 2147483647 || $value < -2147483648) {
  7613. return 'invalid integer';
  7614. }
  7615. break;
  7616. }
  7617. }
  7618. return (true);
  7619. }
  7620. public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface
  7621. {
  7622. $operation = RequestUtils::getOperation($request);
  7623. if (in_array($operation, ['create', 'update', 'increment'])) {
  7624. $tableName = RequestUtils::getPathSegment($request, 2);
  7625. if ($this->reflection->hasTable($tableName)) {
  7626. $record = $request->getParsedBody();
  7627. if ($record !== null) {
  7628. $handler = $this->getProperty('handler', '');
  7629. if ($handler !== '') {
  7630. $table = $this->reflection->getTable($tableName);
  7631. if (is_array($record)) {
  7632. foreach ($record as $r) {
  7633. $response = $this->callHandler($handler, $r, $operation, $table);
  7634. if ($response !== null) {
  7635. return $response;
  7636. }
  7637. }
  7638. } else {
  7639. $response = $this->callHandler($handler, $record, $operation, $table);
  7640. if ($response !== null) {
  7641. return $response;
  7642. }
  7643. }
  7644. }
  7645. }
  7646. }
  7647. }
  7648. return $next->handle($request);
  7649. }
  7650. }
  7651. }
  7652. // file: src/Tqdev/PhpCrudApi/Middleware/XmlMiddleware.php
  7653. namespace Tqdev\PhpCrudApi\Middleware {
  7654. use Psr\Http\Message\ResponseInterface;
  7655. use Psr\Http\Message\ServerRequestInterface;
  7656. use Psr\Http\Server\RequestHandlerInterface;
  7657. use Tqdev\PhpCrudApi\Column\ReflectionService;
  7658. use Tqdev\PhpCrudApi\Controller\Responder;
  7659. use Tqdev\PhpCrudApi\Middleware\Base\Middleware;
  7660. use Tqdev\PhpCrudApi\Middleware\Router\Router;
  7661. use Tqdev\PhpCrudApi\RequestUtils;
  7662. use Tqdev\PhpCrudApi\ResponseFactory;
  7663. class XmlMiddleware extends Middleware
  7664. {
  7665. private $reflection;
  7666. public function __construct(Router $router, Responder $responder, array $properties, ReflectionService $reflection)
  7667. {
  7668. parent::__construct($router, $responder, $properties);
  7669. $this->reflection = $reflection;
  7670. }
  7671. private function json2xml($json, $types = 'null,boolean,number,string,object,array')
  7672. {
  7673. $a = json_decode($json);
  7674. $d = new \DOMDocument();
  7675. $c = $d->createElement("root");
  7676. $d->appendChild($c);
  7677. $t = function ($v) {
  7678. $type = gettype($v);
  7679. switch ($type) {
  7680. case 'integer':
  7681. return 'number';
  7682. case 'double':
  7683. return 'number';
  7684. default:
  7685. return strtolower($type);
  7686. }
  7687. };
  7688. $ts = explode(',', $types);
  7689. $f = function ($f, $c, $a, $s = false) use ($t, $d, $ts) {
  7690. if (in_array($t($a), $ts)) {
  7691. $c->setAttribute('type', $t($a));
  7692. }
  7693. if ($t($a) != 'array' && $t($a) != 'object') {
  7694. if ($t($a) == 'boolean') {
  7695. $c->appendChild($d->createTextNode($a ? 'true' : 'false'));
  7696. } else {
  7697. $c->appendChild($d->createTextNode($a));
  7698. }
  7699. } else {
  7700. foreach ($a as $k => $v) {
  7701. if ($k == '__type' && $t($a) == 'object') {
  7702. $c->setAttribute('__type', $v);
  7703. } else {
  7704. if ($t($v) == 'object') {
  7705. $ch = $c->appendChild($d->createElementNS(null, $s ? 'item' : $k));
  7706. $f($f, $ch, $v);
  7707. } else if ($t($v) == 'array') {
  7708. $ch = $c->appendChild($d->createElementNS(null, $s ? 'item' : $k));
  7709. $f($f, $ch, $v, true);
  7710. } else {
  7711. $va = $d->createElementNS(null, $s ? 'item' : $k);
  7712. if ($t($v) == 'boolean') {
  7713. $va->appendChild($d->createTextNode($v ? 'true' : 'false'));
  7714. } else {
  7715. $va->appendChild($d->createTextNode($v));
  7716. }
  7717. $ch = $c->appendChild($va);
  7718. if (in_array($t($v), $ts)) {
  7719. $ch->setAttribute('type', $t($v));
  7720. }
  7721. }
  7722. }
  7723. }
  7724. }
  7725. };
  7726. $f($f, $c, $a, $t($a) == 'array');
  7727. return $d->saveXML($d->documentElement);
  7728. }
  7729. private function xml2json($xml)
  7730. {
  7731. $a = @dom_import_simplexml(simplexml_load_string($xml));
  7732. if (!$a) {
  7733. return null;
  7734. }
  7735. $t = function ($v) {
  7736. $t = $v->getAttribute('type');
  7737. $txt = $v->firstChild->nodeType == XML_TEXT_NODE;
  7738. return $t ?: ($txt ? 'string' : 'object');
  7739. };
  7740. $f = function ($f, $a) use ($t) {
  7741. $c = null;
  7742. if ($t($a) == 'null') {
  7743. $c = null;
  7744. } else if ($t($a) == 'boolean') {
  7745. $b = substr(strtolower($a->textContent), 0, 1);
  7746. $c = in_array($b, array('1', 't'));
  7747. } else if ($t($a) == 'number') {
  7748. $c = $a->textContent + 0;
  7749. } else if ($t($a) == 'string') {
  7750. $c = $a->textContent;
  7751. } else if ($t($a) == 'object') {
  7752. $c = array();
  7753. if ($a->getAttribute('__type')) {
  7754. $c['__type'] = $a->getAttribute('__type');
  7755. }
  7756. for ($i = 0; $i < $a->childNodes->length; $i++) {
  7757. $v = $a->childNodes[$i];
  7758. $c[$v->nodeName] = $f($f, $v);
  7759. }
  7760. $c = (object) $c;
  7761. } else if ($t($a) == 'array') {
  7762. $c = array();
  7763. for ($i = 0; $i < $a->childNodes->length; $i++) {
  7764. $v = $a->childNodes[$i];
  7765. $c[$i] = $f($f, $v);
  7766. }
  7767. }
  7768. return $c;
  7769. };
  7770. $c = $f($f, $a);
  7771. return json_encode($c);
  7772. }
  7773. public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface
  7774. {
  7775. parse_str($request->getUri()->getQuery(), $params);
  7776. $isXml = isset($params['format']) && $params['format'] == 'xml';
  7777. if ($isXml) {
  7778. $body = $request->getBody()->getContents();
  7779. if ($body) {
  7780. $json = $this->xml2json($body);
  7781. $request = $request->withParsedBody(json_decode($json));
  7782. }
  7783. }
  7784. $response = $next->handle($request);
  7785. if ($isXml) {
  7786. $body = $response->getBody()->getContents();
  7787. if ($body) {
  7788. $types = implode(',', $this->getArrayProperty('types', 'null,array'));
  7789. if ($types == '' || $types == 'all') {
  7790. $xml = $this->json2xml($body);
  7791. } else {
  7792. $xml = $this->json2xml($body, $types);
  7793. }
  7794. $response = ResponseFactory::fromXml(ResponseFactory::OK, $xml);
  7795. }
  7796. }
  7797. return $response;
  7798. }
  7799. }
  7800. }
  7801. // file: src/Tqdev/PhpCrudApi/Middleware/XsrfMiddleware.php
  7802. namespace Tqdev\PhpCrudApi\Middleware {
  7803. use Psr\Http\Message\ResponseInterface;
  7804. use Psr\Http\Message\ServerRequestInterface;
  7805. use Psr\Http\Server\RequestHandlerInterface;
  7806. use Tqdev\PhpCrudApi\Controller\Responder;
  7807. use Tqdev\PhpCrudApi\Middleware\Base\Middleware;
  7808. use Tqdev\PhpCrudApi\Record\ErrorCode;
  7809. class XsrfMiddleware extends Middleware
  7810. {
  7811. private function getToken(): string
  7812. {
  7813. $cookieName = $this->getProperty('cookieName', 'XSRF-TOKEN');
  7814. if (isset($_COOKIE[$cookieName])) {
  7815. $token = $_COOKIE[$cookieName];
  7816. } else {
  7817. $secure = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on';
  7818. $token = bin2hex(random_bytes(8));
  7819. if (!headers_sent()) {
  7820. setcookie($cookieName, $token, 0, '', '', $secure);
  7821. }
  7822. }
  7823. return $token;
  7824. }
  7825. public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface
  7826. {
  7827. $token = $this->getToken();
  7828. $method = $request->getMethod();
  7829. $excludeMethods = $this->getArrayProperty('excludeMethods', 'OPTIONS,GET');
  7830. if (!in_array($method, $excludeMethods)) {
  7831. $headerName = $this->getProperty('headerName', 'X-XSRF-TOKEN');
  7832. if ($token != $request->getHeader($headerName)) {
  7833. return $this->responder->error(ErrorCode::BAD_OR_MISSING_XSRF_TOKEN, '');
  7834. }
  7835. }
  7836. return $next->handle($request);
  7837. }
  7838. }
  7839. }
  7840. // file: src/Tqdev/PhpCrudApi/OpenApi/OpenApiBuilder.php
  7841. namespace Tqdev\PhpCrudApi\OpenApi {
  7842. use Tqdev\PhpCrudApi\Column\ReflectionService;
  7843. use Tqdev\PhpCrudApi\OpenApi\OpenApiDefinition;
  7844. class OpenApiBuilder
  7845. {
  7846. private $openapi;
  7847. private $records;
  7848. private $columns;
  7849. private $builders;
  7850. public function __construct(ReflectionService $reflection, array $base, array $controllers, array $builders)
  7851. {
  7852. $this->openapi = new OpenApiDefinition($base);
  7853. $this->records = in_array('records', $controllers) ? new OpenApiRecordsBuilder($this->openapi, $reflection) : null;
  7854. $this->columns = in_array('columns', $controllers) ? new OpenApiColumnsBuilder($this->openapi) : null;
  7855. $this->builders = array();
  7856. foreach ($builders as $className) {
  7857. $this->builders[] = new $className($this->openapi, $reflection);
  7858. }
  7859. }
  7860. private function getServerUrl(): string
  7861. {
  7862. $protocol = @$_SERVER['HTTP_X_FORWARDED_PROTO'] ?: @$_SERVER['REQUEST_SCHEME'] ?: ((isset($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] == "on") ? "https" : "http");
  7863. $port = @intval($_SERVER['HTTP_X_FORWARDED_PORT']) ?: @intval($_SERVER["SERVER_PORT"]) ?: (($protocol === 'https') ? 443 : 80);
  7864. $host = @explode(":", $_SERVER['HTTP_HOST'])[0] ?: @$_SERVER['SERVER_NAME'] ?: @$_SERVER['SERVER_ADDR'];
  7865. $port = ($protocol === 'https' && $port === 443) || ($protocol === 'http' && $port === 80) ? '' : ':' . $port;
  7866. $path = @trim(substr($_SERVER['REQUEST_URI'], 0, strpos($_SERVER['REQUEST_URI'], '/openapi')), '/');
  7867. return sprintf('%s://%s%s/%s', $protocol, $host, $port, $path);
  7868. }
  7869. public function build(): OpenApiDefinition
  7870. {
  7871. $this->openapi->set("openapi", "3.0.0");
  7872. if (!$this->openapi->has("servers") && isset($_SERVER['REQUEST_URI'])) {
  7873. $this->openapi->set("servers|0|url", $this->getServerUrl());
  7874. }
  7875. if ($this->records) {
  7876. $this->records->build();
  7877. }
  7878. if ($this->columns) {
  7879. $this->columns->build();
  7880. }
  7881. foreach ($this->builders as $builder) {
  7882. $builder->build();
  7883. }
  7884. return $this->openapi;
  7885. }
  7886. }
  7887. }
  7888. // file: src/Tqdev/PhpCrudApi/OpenApi/OpenApiColumnsBuilder.php
  7889. namespace Tqdev\PhpCrudApi\OpenApi {
  7890. use Tqdev\PhpCrudApi\OpenApi\OpenApiDefinition;
  7891. class OpenApiColumnsBuilder
  7892. {
  7893. private $openapi;
  7894. private $operations = [
  7895. 'database' => [
  7896. 'read' => 'get',
  7897. ],
  7898. 'table' => [
  7899. 'create' => 'post',
  7900. 'read' => 'get',
  7901. 'update' => 'put', //rename
  7902. 'delete' => 'delete',
  7903. ],
  7904. 'column' => [
  7905. 'create' => 'post',
  7906. 'read' => 'get',
  7907. 'update' => 'put',
  7908. 'delete' => 'delete',
  7909. ]
  7910. ];
  7911. public function __construct(OpenApiDefinition $openapi)
  7912. {
  7913. $this->openapi = $openapi;
  7914. }
  7915. public function build() /*: void*/
  7916. {
  7917. $this->setPaths();
  7918. $this->openapi->set("components|responses|boolSuccess|description", "boolean indicating success or failure");
  7919. $this->openapi->set("components|responses|boolSuccess|content|application/json|schema|type", "boolean");
  7920. $this->setComponentSchema();
  7921. $this->setComponentResponse();
  7922. $this->setComponentRequestBody();
  7923. $this->setComponentParameters();
  7924. foreach (array_keys($this->operations) as $index => $type) {
  7925. $this->setTag($index, $type);
  7926. }
  7927. }
  7928. private function setPaths() /*: void*/
  7929. {
  7930. foreach (array_keys($this->operations) as $type) {
  7931. foreach ($this->operations[$type] as $operation => $method) {
  7932. $parameters = [];
  7933. switch ($type) {
  7934. case 'database':
  7935. $path = '/columns';
  7936. break;
  7937. case 'table':
  7938. $path = $operation == 'create' ? '/columns' : '/columns/{table}';
  7939. break;
  7940. case 'column':
  7941. $path = $operation == 'create' ? '/columns/{table}' : '/columns/{table}/{column}';
  7942. break;
  7943. }
  7944. if (strpos($path, '{table}')) {
  7945. $parameters[] = 'table';
  7946. }
  7947. if (strpos($path, '{column}')) {
  7948. $parameters[] = 'column';
  7949. }
  7950. foreach ($parameters as $p => $parameter) {
  7951. $this->openapi->set("paths|$path|$method|parameters|$p|\$ref", "#/components/parameters/$parameter");
  7952. }
  7953. $operationType = $operation . ucfirst($type);
  7954. if (in_array($operation, ['create', 'update'])) {
  7955. $this->openapi->set("paths|$path|$method|requestBody|\$ref", "#/components/requestBodies/$operationType");
  7956. }
  7957. $this->openapi->set("paths|$path|$method|tags|0", "$type");
  7958. $this->openapi->set("paths|$path|$method|operationId", "$operation" . "_" . "$type");
  7959. if ($operationType == 'updateTable') {
  7960. $this->openapi->set("paths|$path|$method|description", "rename table");
  7961. } else {
  7962. $this->openapi->set("paths|$path|$method|description", "$operation $type");
  7963. }
  7964. switch ($operation) {
  7965. case 'read':
  7966. $this->openapi->set("paths|$path|$method|responses|200|\$ref", "#/components/responses/$operationType");
  7967. break;
  7968. case 'create':
  7969. case 'update':
  7970. case 'delete':
  7971. $this->openapi->set("paths|$path|$method|responses|200|\$ref", "#/components/responses/boolSuccess");
  7972. break;
  7973. }
  7974. }
  7975. }
  7976. }
  7977. private function setComponentSchema() /*: void*/
  7978. {
  7979. foreach (array_keys($this->operations) as $type) {
  7980. foreach (array_keys($this->operations[$type]) as $operation) {
  7981. if ($operation == 'delete') {
  7982. continue;
  7983. }
  7984. $operationType = $operation . ucfirst($type);
  7985. $prefix = "components|schemas|$operationType";
  7986. $this->openapi->set("$prefix|type", "object");
  7987. switch ($type) {
  7988. case 'database':
  7989. $this->openapi->set("$prefix|properties|tables|type", 'array');
  7990. $this->openapi->set("$prefix|properties|tables|items|\$ref", "#/components/schemas/readTable");
  7991. break;
  7992. case 'table':
  7993. if ($operation == 'update') {
  7994. $this->openapi->set("$prefix|required", ['name']);
  7995. $this->openapi->set("$prefix|properties|name|type", 'string');
  7996. } else {
  7997. $this->openapi->set("$prefix|properties|name|type", 'string');
  7998. if ($operation == 'read') {
  7999. $this->openapi->set("$prefix|properties|type|type", 'string');
  8000. }
  8001. $this->openapi->set("$prefix|properties|columns|type", 'array');
  8002. $this->openapi->set("$prefix|properties|columns|items|\$ref", "#/components/schemas/readColumn");
  8003. }
  8004. break;
  8005. case 'column':
  8006. $this->openapi->set("$prefix|required", ['name', 'type']);
  8007. $this->openapi->set("$prefix|properties|name|type", 'string');
  8008. $this->openapi->set("$prefix|properties|type|type", 'string');
  8009. $this->openapi->set("$prefix|properties|length|type", 'integer');
  8010. $this->openapi->set("$prefix|properties|length|format", "int64");
  8011. $this->openapi->set("$prefix|properties|precision|type", 'integer');
  8012. $this->openapi->set("$prefix|properties|precision|format", "int64");
  8013. $this->openapi->set("$prefix|properties|scale|type", 'integer');
  8014. $this->openapi->set("$prefix|properties|scale|format", "int64");
  8015. $this->openapi->set("$prefix|properties|nullable|type", 'boolean');
  8016. $this->openapi->set("$prefix|properties|pk|type", 'boolean');
  8017. $this->openapi->set("$prefix|properties|fk|type", 'string');
  8018. break;
  8019. }
  8020. }
  8021. }
  8022. }
  8023. private function setComponentResponse() /*: void*/
  8024. {
  8025. foreach (array_keys($this->operations) as $type) {
  8026. foreach (array_keys($this->operations[$type]) as $operation) {
  8027. if ($operation != 'read') {
  8028. continue;
  8029. }
  8030. $operationType = $operation . ucfirst($type);
  8031. $this->openapi->set("components|responses|$operationType|description", "single $type record");
  8032. $this->openapi->set("components|responses|$operationType|content|application/json|schema|\$ref", "#/components/schemas/$operationType");
  8033. }
  8034. }
  8035. }
  8036. private function setComponentRequestBody() /*: void*/
  8037. {
  8038. foreach (array_keys($this->operations) as $type) {
  8039. foreach (array_keys($this->operations[$type]) as $operation) {
  8040. if (!in_array($operation, ['create', 'update'])) {
  8041. continue;
  8042. }
  8043. $operationType = $operation . ucfirst($type);
  8044. $this->openapi->set("components|requestBodies|$operationType|description", "single $type record");
  8045. $this->openapi->set("components|requestBodies|$operationType|content|application/json|schema|\$ref", "#/components/schemas/$operationType");
  8046. }
  8047. }
  8048. }
  8049. private function setComponentParameters() /*: void*/
  8050. {
  8051. $this->openapi->set("components|parameters|table|name", "table");
  8052. $this->openapi->set("components|parameters|table|in", "path");
  8053. $this->openapi->set("components|parameters|table|schema|type", "string");
  8054. $this->openapi->set("components|parameters|table|description", "table name");
  8055. $this->openapi->set("components|parameters|table|required", true);
  8056. $this->openapi->set("components|parameters|column|name", "column");
  8057. $this->openapi->set("components|parameters|column|in", "path");
  8058. $this->openapi->set("components|parameters|column|schema|type", "string");
  8059. $this->openapi->set("components|parameters|column|description", "column name");
  8060. $this->openapi->set("components|parameters|column|required", true);
  8061. }
  8062. private function setTag(int $index, string $type) /*: void*/
  8063. {
  8064. $this->openapi->set("tags|$index|name", "$type");
  8065. $this->openapi->set("tags|$index|description", "$type operations");
  8066. }
  8067. }
  8068. }
  8069. // file: src/Tqdev/PhpCrudApi/OpenApi/OpenApiDefinition.php
  8070. namespace Tqdev\PhpCrudApi\OpenApi {
  8071. class OpenApiDefinition implements \JsonSerializable
  8072. {
  8073. private $root;
  8074. public function __construct(array $base)
  8075. {
  8076. $this->root = $base;
  8077. }
  8078. public function set(string $path, $value) /*: void*/
  8079. {
  8080. $parts = explode('|', trim($path, '|'));
  8081. $current = &$this->root;
  8082. while (count($parts) > 0) {
  8083. $part = array_shift($parts);
  8084. if (!isset($current[$part])) {
  8085. $current[$part] = [];
  8086. }
  8087. $current = &$current[$part];
  8088. }
  8089. $current = $value;
  8090. }
  8091. public function has(string $path): bool
  8092. {
  8093. $parts = explode('|', trim($path, '|'));
  8094. $current = &$this->root;
  8095. while (count($parts) > 0) {
  8096. $part = array_shift($parts);
  8097. if (!isset($current[$part])) {
  8098. return false;
  8099. }
  8100. $current = &$current[$part];
  8101. }
  8102. return true;
  8103. }
  8104. public function jsonSerialize()
  8105. {
  8106. return $this->root;
  8107. }
  8108. }
  8109. }
  8110. // file: src/Tqdev/PhpCrudApi/OpenApi/OpenApiRecordsBuilder.php
  8111. namespace Tqdev\PhpCrudApi\OpenApi {
  8112. use Tqdev\PhpCrudApi\Column\ReflectionService;
  8113. use Tqdev\PhpCrudApi\Column\Reflection\ReflectedColumn;
  8114. use Tqdev\PhpCrudApi\Middleware\Communication\VariableStore;
  8115. use Tqdev\PhpCrudApi\OpenApi\OpenApiDefinition;
  8116. class OpenApiRecordsBuilder
  8117. {
  8118. private $openapi;
  8119. private $reflection;
  8120. private $operations = [
  8121. 'list' => 'get',
  8122. 'create' => 'post',
  8123. 'read' => 'get',
  8124. 'update' => 'put',
  8125. 'delete' => 'delete',
  8126. 'increment' => 'patch',
  8127. ];
  8128. private $types = [
  8129. 'integer' => ['type' => 'integer', 'format' => 'int32'],
  8130. 'bigint' => ['type' => 'integer', 'format' => 'int64'],
  8131. 'varchar' => ['type' => 'string'],
  8132. 'clob' => ['type' => 'string', 'format' => 'large-string'], //custom format
  8133. 'varbinary' => ['type' => 'string', 'format' => 'byte'],
  8134. 'blob' => ['type' => 'string', 'format' => 'large-byte'], //custom format
  8135. 'decimal' => ['type' => 'string', 'format' => 'decimal'], //custom format
  8136. 'float' => ['type' => 'number', 'format' => 'float'],
  8137. 'double' => ['type' => 'number', 'format' => 'double'],
  8138. 'date' => ['type' => 'string', 'format' => 'date'],
  8139. 'time' => ['type' => 'string', 'format' => 'time'], //custom format
  8140. 'timestamp' => ['type' => 'string', 'format' => 'date-time'],
  8141. 'geometry' => ['type' => 'string', 'format' => 'geometry'], //custom format
  8142. 'boolean' => ['type' => 'boolean'],
  8143. ];
  8144. public function __construct(OpenApiDefinition $openapi, ReflectionService $reflection)
  8145. {
  8146. $this->openapi = $openapi;
  8147. $this->reflection = $reflection;
  8148. }
  8149. private function getAllTableReferences(): array
  8150. {
  8151. $tableReferences = array();
  8152. foreach ($this->reflection->getTableNames() as $tableName) {
  8153. $table = $this->reflection->getTable($tableName);
  8154. foreach ($table->getColumnNames() as $columnName) {
  8155. $column = $table->getColumn($columnName);
  8156. $referencedTableName = $column->getFk();
  8157. if ($referencedTableName) {
  8158. if (!isset($tableReferences[$referencedTableName])) {
  8159. $tableReferences[$referencedTableName] = array();
  8160. }
  8161. $tableReferences[$referencedTableName][] = "$tableName.$columnName";
  8162. }
  8163. }
  8164. }
  8165. return $tableReferences;
  8166. }
  8167. public function build() /*: void*/
  8168. {
  8169. $tableNames = $this->reflection->getTableNames();
  8170. foreach ($tableNames as $tableName) {
  8171. $this->setPath($tableName);
  8172. }
  8173. $this->openapi->set("components|responses|pk_integer|description", "inserted primary key value (integer)");
  8174. $this->openapi->set("components|responses|pk_integer|content|application/json|schema|type", "integer");
  8175. $this->openapi->set("components|responses|pk_integer|content|application/json|schema|format", "int64");
  8176. $this->openapi->set("components|responses|pk_string|description", "inserted primary key value (string)");
  8177. $this->openapi->set("components|responses|pk_string|content|application/json|schema|type", "string");
  8178. $this->openapi->set("components|responses|pk_string|content|application/json|schema|format", "uuid");
  8179. $this->openapi->set("components|responses|rows_affected|description", "number of rows affected (integer)");
  8180. $this->openapi->set("components|responses|rows_affected|content|application/json|schema|type", "integer");
  8181. $this->openapi->set("components|responses|rows_affected|content|application/json|schema|format", "int64");
  8182. $tableReferences = $this->getAllTableReferences();
  8183. foreach ($tableNames as $tableName) {
  8184. $references = isset($tableReferences[$tableName]) ? $tableReferences[$tableName] : array();
  8185. $this->setComponentSchema($tableName, $references);
  8186. $this->setComponentResponse($tableName);
  8187. $this->setComponentRequestBody($tableName);
  8188. }
  8189. $this->setComponentParameters();
  8190. foreach ($tableNames as $index => $tableName) {
  8191. $this->setTag($index, $tableName);
  8192. }
  8193. }
  8194. private function isOperationOnTableAllowed(string $operation, string $tableName): bool
  8195. {
  8196. $tableHandler = VariableStore::get('authorization.tableHandler');
  8197. if (!$tableHandler) {
  8198. return true;
  8199. }
  8200. return (bool) call_user_func($tableHandler, $operation, $tableName);
  8201. }
  8202. private function isOperationOnColumnAllowed(string $operation, string $tableName, string $columnName): bool
  8203. {
  8204. $columnHandler = VariableStore::get('authorization.columnHandler');
  8205. if (!$columnHandler) {
  8206. return true;
  8207. }
  8208. return (bool) call_user_func($columnHandler, $operation, $tableName, $columnName);
  8209. }
  8210. private function setPath(string $tableName) /*: void*/
  8211. {
  8212. $table = $this->reflection->getTable($tableName);
  8213. $type = $table->getType();
  8214. $pk = $table->getPk();
  8215. $pkName = $pk ? $pk->getName() : '';
  8216. foreach ($this->operations as $operation => $method) {
  8217. if (!$pkName && $operation != 'list') {
  8218. continue;
  8219. }
  8220. if ($type != 'table' && $operation != 'list') {
  8221. continue;
  8222. }
  8223. if (!$this->isOperationOnTableAllowed($operation, $tableName)) {
  8224. continue;
  8225. }
  8226. $parameters = [];
  8227. if (in_array($operation, ['list', 'create'])) {
  8228. $path = sprintf('/records/%s', $tableName);
  8229. if ($operation == 'list') {
  8230. $parameters = ['filter', 'include', 'exclude', 'order', 'size', 'page', 'join'];
  8231. }
  8232. } else {
  8233. $path = sprintf('/records/%s/{id}', $tableName);
  8234. if ($operation == 'read') {
  8235. $parameters = ['pk', 'include', 'exclude', 'join'];
  8236. } else {
  8237. $parameters = ['pk'];
  8238. }
  8239. }
  8240. foreach ($parameters as $p => $parameter) {
  8241. $this->openapi->set("paths|$path|$method|parameters|$p|\$ref", "#/components/parameters/$parameter");
  8242. }
  8243. if (in_array($operation, ['create', 'update', 'increment'])) {
  8244. $this->openapi->set("paths|$path|$method|requestBody|\$ref", "#/components/requestBodies/$operation-" . rawurlencode($tableName));
  8245. }
  8246. $this->openapi->set("paths|$path|$method|tags|0", "$tableName");
  8247. $this->openapi->set("paths|$path|$method|operationId", "$operation" . "_" . "$tableName");
  8248. $this->openapi->set("paths|$path|$method|description", "$operation $tableName");
  8249. switch ($operation) {
  8250. case 'list':
  8251. $this->openapi->set("paths|$path|$method|responses|200|\$ref", "#/components/responses/$operation-" . rawurlencode($tableName));
  8252. break;
  8253. case 'create':
  8254. if ($pk->getType() == 'integer') {
  8255. $this->openapi->set("paths|$path|$method|responses|200|\$ref", "#/components/responses/pk_integer");
  8256. } else {
  8257. $this->openapi->set("paths|$path|$method|responses|200|\$ref", "#/components/responses/pk_string");
  8258. }
  8259. break;
  8260. case 'read':
  8261. $this->openapi->set("paths|$path|$method|responses|200|\$ref", "#/components/responses/$operation-" . rawurlencode($tableName));
  8262. break;
  8263. case 'update':
  8264. case 'delete':
  8265. case 'increment':
  8266. $this->openapi->set("paths|$path|$method|responses|200|\$ref", "#/components/responses/rows_affected");
  8267. break;
  8268. }
  8269. }
  8270. }
  8271. private function getPattern(ReflectedColumn $column): string
  8272. {
  8273. switch ($column->getType()) {
  8274. case 'integer':
  8275. $n = strlen(pow(2, 31));
  8276. return '^-?[0-9]{1,' . $n . '}$';
  8277. case 'bigint':
  8278. $n = strlen(pow(2, 63));
  8279. return '^-?[0-9]{1,' . $n . '}$';
  8280. case 'varchar':
  8281. $l = $column->getLength();
  8282. return '^.{0,' . $l . '}$';
  8283. case 'clob':
  8284. return '^.*$';
  8285. case 'varbinary':
  8286. $l = $column->getLength();
  8287. $b = (int) 4 * ceil($l / 3);
  8288. return '^[A-Za-z0-9+/]{0,' . $b . '}=*$';
  8289. case 'blob':
  8290. return '^[A-Za-z0-9+/]*=*$';
  8291. case 'decimal':
  8292. $p = $column->getPrecision();
  8293. $s = $column->getScale();
  8294. return '^-?[0-9]{1,' . ($p - $s) . '}(\.[0-9]{1,' . $s . '})?$';
  8295. case 'float':
  8296. return '^-?[0-9]+(\.[0-9]+)?([eE]-?[0-9]+)?$';
  8297. case 'double':
  8298. return '^-?[0-9]+(\.[0-9]+)?([eE]-?[0-9]+)?$';
  8299. case 'date':
  8300. return '^[0-9]{4}-[0-9]{2}-[0-9]{2}$';
  8301. case 'time':
  8302. return '^[0-9]{2}:[0-9]{2}:[0-9]{2}$';
  8303. case 'timestamp':
  8304. return '^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}$';
  8305. return '';
  8306. case 'geometry':
  8307. return '^(POINT|LINESTRING|POLYGON|MULTIPOINT|MULTILINESTRING|MULTIPOLYGON)\s*\(.*$';
  8308. case 'boolean':
  8309. return '^(true|false)$';
  8310. }
  8311. return '';
  8312. }
  8313. private function setComponentSchema(string $tableName, array $references) /*: void*/
  8314. {
  8315. $table = $this->reflection->getTable($tableName);
  8316. $type = $table->getType();
  8317. $pk = $table->getPk();
  8318. $pkName = $pk ? $pk->getName() : '';
  8319. foreach ($this->operations as $operation => $method) {
  8320. if (!$pkName && $operation != 'list') {
  8321. continue;
  8322. }
  8323. if ($type == 'view' && !in_array($operation, array('read', 'list'))) {
  8324. continue;
  8325. }
  8326. if ($type == 'view' && !$pkName && $operation == 'read') {
  8327. continue;
  8328. }
  8329. if ($operation == 'delete') {
  8330. continue;
  8331. }
  8332. if (!$this->isOperationOnTableAllowed($operation, $tableName)) {
  8333. continue;
  8334. }
  8335. if ($operation == 'list') {
  8336. $this->openapi->set("components|schemas|$operation-$tableName|type", "object");
  8337. $this->openapi->set("components|schemas|$operation-$tableName|properties|results|type", "integer");
  8338. $this->openapi->set("components|schemas|$operation-$tableName|properties|results|format", "int64");
  8339. $this->openapi->set("components|schemas|$operation-$tableName|properties|records|type", "array");
  8340. $prefix = "components|schemas|$operation-$tableName|properties|records|items";
  8341. } else {
  8342. $prefix = "components|schemas|$operation-$tableName";
  8343. }
  8344. $this->openapi->set("$prefix|type", "object");
  8345. foreach ($table->getColumnNames() as $columnName) {
  8346. if (!$this->isOperationOnColumnAllowed($operation, $tableName, $columnName)) {
  8347. continue;
  8348. }
  8349. $column = $table->getColumn($columnName);
  8350. $properties = $this->types[$column->getType()];
  8351. $properties['maxLength'] = $column->hasLength() ? $column->getLength() : 0;
  8352. $properties['nullable'] = $column->getNullable();
  8353. $properties['pattern'] = $this->getPattern($column);
  8354. foreach ($properties as $key => $value) {
  8355. if ($value) {
  8356. $this->openapi->set("$prefix|properties|$columnName|$key", $value);
  8357. }
  8358. }
  8359. if ($column->getPk()) {
  8360. $this->openapi->set("$prefix|properties|$columnName|x-primary-key", true);
  8361. $this->openapi->set("$prefix|properties|$columnName|x-referenced", $references);
  8362. }
  8363. $fk = $column->getFk();
  8364. if ($fk) {
  8365. $this->openapi->set("$prefix|properties|$columnName|x-references", $fk);
  8366. }
  8367. }
  8368. }
  8369. }
  8370. private function setComponentResponse(string $tableName) /*: void*/
  8371. {
  8372. $table = $this->reflection->getTable($tableName);
  8373. $type = $table->getType();
  8374. $pk = $table->getPk();
  8375. $pkName = $pk ? $pk->getName() : '';
  8376. foreach (['list', 'read'] as $operation) {
  8377. if (!$pkName && $operation != 'list') {
  8378. continue;
  8379. }
  8380. if ($type != 'table' && $operation != 'list') {
  8381. continue;
  8382. }
  8383. if (!$this->isOperationOnTableAllowed($operation, $tableName)) {
  8384. continue;
  8385. }
  8386. if ($operation == 'list') {
  8387. $this->openapi->set("components|responses|$operation-$tableName|description", "list of $tableName records");
  8388. } else {
  8389. $this->openapi->set("components|responses|$operation-$tableName|description", "single $tableName record");
  8390. }
  8391. $this->openapi->set("components|responses|$operation-$tableName|content|application/json|schema|\$ref", "#/components/schemas/$operation-" . rawurlencode($tableName));
  8392. }
  8393. }
  8394. private function setComponentRequestBody(string $tableName) /*: void*/
  8395. {
  8396. $table = $this->reflection->getTable($tableName);
  8397. $type = $table->getType();
  8398. $pk = $table->getPk();
  8399. $pkName = $pk ? $pk->getName() : '';
  8400. if ($pkName && $type == 'table') {
  8401. foreach (['create', 'update', 'increment'] as $operation) {
  8402. if (!$this->isOperationOnTableAllowed($operation, $tableName)) {
  8403. continue;
  8404. }
  8405. $this->openapi->set("components|requestBodies|$operation-$tableName|description", "single $tableName record");
  8406. $this->openapi->set("components|requestBodies|$operation-$tableName|content|application/json|schema|\$ref", "#/components/schemas/$operation-" . rawurlencode($tableName));
  8407. }
  8408. }
  8409. }
  8410. private function setComponentParameters() /*: void*/
  8411. {
  8412. $this->openapi->set("components|parameters|pk|name", "id");
  8413. $this->openapi->set("components|parameters|pk|in", "path");
  8414. $this->openapi->set("components|parameters|pk|schema|type", "string");
  8415. $this->openapi->set("components|parameters|pk|description", "primary key value");
  8416. $this->openapi->set("components|parameters|pk|required", true);
  8417. $this->openapi->set("components|parameters|filter|name", "filter");
  8418. $this->openapi->set("components|parameters|filter|in", "query");
  8419. $this->openapi->set("components|parameters|filter|schema|type", "array");
  8420. $this->openapi->set("components|parameters|filter|schema|items|type", "string");
  8421. $this->openapi->set("components|parameters|filter|description", "Filters to be applied. Each filter consists of a column, an operator and a value (comma separated). Example: id,eq,1");
  8422. $this->openapi->set("components|parameters|filter|required", false);
  8423. $this->openapi->set("components|parameters|include|name", "include");
  8424. $this->openapi->set("components|parameters|include|in", "query");
  8425. $this->openapi->set("components|parameters|include|schema|type", "string");
  8426. $this->openapi->set("components|parameters|include|description", "Columns you want to include in the output (comma separated). Example: posts.*,categories.name");
  8427. $this->openapi->set("components|parameters|include|required", false);
  8428. $this->openapi->set("components|parameters|exclude|name", "exclude");
  8429. $this->openapi->set("components|parameters|exclude|in", "query");
  8430. $this->openapi->set("components|parameters|exclude|schema|type", "string");
  8431. $this->openapi->set("components|parameters|exclude|description", "Columns you want to exclude from the output (comma separated). Example: posts.content");
  8432. $this->openapi->set("components|parameters|exclude|required", false);
  8433. $this->openapi->set("components|parameters|order|name", "order");
  8434. $this->openapi->set("components|parameters|order|in", "query");
  8435. $this->openapi->set("components|parameters|order|schema|type", "array");
  8436. $this->openapi->set("components|parameters|order|schema|items|type", "string");
  8437. $this->openapi->set("components|parameters|order|description", "Column you want to sort on and the sort direction (comma separated). Example: id,desc");
  8438. $this->openapi->set("components|parameters|order|required", false);
  8439. $this->openapi->set("components|parameters|size|name", "size");
  8440. $this->openapi->set("components|parameters|size|in", "query");
  8441. $this->openapi->set("components|parameters|size|schema|type", "string");
  8442. $this->openapi->set("components|parameters|size|description", "Maximum number of results (for top lists). Example: 10");
  8443. $this->openapi->set("components|parameters|size|required", false);
  8444. $this->openapi->set("components|parameters|page|name", "page");
  8445. $this->openapi->set("components|parameters|page|in", "query");
  8446. $this->openapi->set("components|parameters|page|schema|type", "string");
  8447. $this->openapi->set("components|parameters|page|description", "Page number and page size (comma separated). Example: 1,10");
  8448. $this->openapi->set("components|parameters|page|required", false);
  8449. $this->openapi->set("components|parameters|join|name", "join");
  8450. $this->openapi->set("components|parameters|join|in", "query");
  8451. $this->openapi->set("components|parameters|join|schema|type", "array");
  8452. $this->openapi->set("components|parameters|join|schema|items|type", "string");
  8453. $this->openapi->set("components|parameters|join|description", "Paths (comma separated) to related entities that you want to include. Example: comments,users");
  8454. $this->openapi->set("components|parameters|join|required", false);
  8455. }
  8456. private function setTag(int $index, string $tableName) /*: void*/
  8457. {
  8458. $this->openapi->set("tags|$index|name", "$tableName");
  8459. $this->openapi->set("tags|$index|description", "$tableName operations");
  8460. }
  8461. }
  8462. }
  8463. // file: src/Tqdev/PhpCrudApi/OpenApi/OpenApiService.php
  8464. namespace Tqdev\PhpCrudApi\OpenApi {
  8465. use Tqdev\PhpCrudApi\Column\ReflectionService;
  8466. use Tqdev\PhpCrudApi\OpenApi\OpenApiBuilder;
  8467. class OpenApiService
  8468. {
  8469. private $builder;
  8470. public function __construct(ReflectionService $reflection, array $base, array $controllers, array $customBuilders)
  8471. {
  8472. $this->builder = new OpenApiBuilder($reflection, $base, $controllers, $customBuilders);
  8473. }
  8474. public function get(): OpenApiDefinition
  8475. {
  8476. return $this->builder->build();
  8477. }
  8478. }
  8479. }
  8480. // file: src/Tqdev/PhpCrudApi/Record/Condition/AndCondition.php
  8481. namespace Tqdev\PhpCrudApi\Record\Condition {
  8482. class AndCondition extends Condition
  8483. {
  8484. private $conditions;
  8485. public function __construct(Condition $condition1, Condition $condition2)
  8486. {
  8487. $this->conditions = [$condition1, $condition2];
  8488. }
  8489. public function _and(Condition $condition): Condition
  8490. {
  8491. if ($condition instanceof NoCondition) {
  8492. return $this;
  8493. }
  8494. $this->conditions[] = $condition;
  8495. return $this;
  8496. }
  8497. public function getConditions(): array
  8498. {
  8499. return $this->conditions;
  8500. }
  8501. public static function fromArray(array $conditions): Condition
  8502. {
  8503. $condition = new NoCondition();
  8504. foreach ($conditions as $c) {
  8505. $condition = $condition->_and($c);
  8506. }
  8507. return $condition;
  8508. }
  8509. }
  8510. }
  8511. // file: src/Tqdev/PhpCrudApi/Record/Condition/ColumnCondition.php
  8512. namespace Tqdev\PhpCrudApi\Record\Condition {
  8513. use Tqdev\PhpCrudApi\Column\Reflection\ReflectedColumn;
  8514. class ColumnCondition extends Condition
  8515. {
  8516. private $column;
  8517. private $operator;
  8518. private $value;
  8519. public function __construct(ReflectedColumn $column, string $operator, string $value)
  8520. {
  8521. $this->column = $column;
  8522. $this->operator = $operator;
  8523. $this->value = $value;
  8524. }
  8525. public function getColumn(): ReflectedColumn
  8526. {
  8527. return $this->column;
  8528. }
  8529. public function getOperator(): string
  8530. {
  8531. return $this->operator;
  8532. }
  8533. public function getValue(): string
  8534. {
  8535. return $this->value;
  8536. }
  8537. }
  8538. }
  8539. // file: src/Tqdev/PhpCrudApi/Record/Condition/Condition.php
  8540. namespace Tqdev\PhpCrudApi\Record\Condition {
  8541. use Tqdev\PhpCrudApi\Column\Reflection\ReflectedTable;
  8542. abstract class Condition
  8543. {
  8544. public function _and(Condition $condition): Condition
  8545. {
  8546. if ($condition instanceof NoCondition) {
  8547. return $this;
  8548. }
  8549. return new AndCondition($this, $condition);
  8550. }
  8551. public function _or(Condition $condition): Condition
  8552. {
  8553. if ($condition instanceof NoCondition) {
  8554. return $this;
  8555. }
  8556. return new OrCondition($this, $condition);
  8557. }
  8558. public function _not(): Condition
  8559. {
  8560. return new NotCondition($this);
  8561. }
  8562. public static function fromString(ReflectedTable $table, string $value): Condition
  8563. {
  8564. $condition = new NoCondition();
  8565. $parts = explode(',', $value, 3);
  8566. if (count($parts) < 2) {
  8567. return $condition;
  8568. }
  8569. if (count($parts) < 3) {
  8570. $parts[2] = '';
  8571. }
  8572. $field = $table->getColumn($parts[0]);
  8573. $command = $parts[1];
  8574. $negate = false;
  8575. $spatial = false;
  8576. if (strlen($command) > 2) {
  8577. if (substr($command, 0, 1) == 'n') {
  8578. $negate = true;
  8579. $command = substr($command, 1);
  8580. }
  8581. if (substr($command, 0, 1) == 's') {
  8582. $spatial = true;
  8583. $command = substr($command, 1);
  8584. }
  8585. }
  8586. if ($spatial) {
  8587. if (in_array($command, ['co', 'cr', 'di', 'eq', 'in', 'ov', 'to', 'wi', 'ic', 'is', 'iv'])) {
  8588. $condition = new SpatialCondition($field, $command, $parts[2]);
  8589. }
  8590. } else {
  8591. if (in_array($command, ['cs', 'sw', 'ew', 'eq', 'lt', 'le', 'ge', 'gt', 'bt', 'in', 'is'])) {
  8592. $condition = new ColumnCondition($field, $command, $parts[2]);
  8593. }
  8594. }
  8595. if ($negate) {
  8596. $condition = $condition->_not();
  8597. }
  8598. return $condition;
  8599. }
  8600. }
  8601. }
  8602. // file: src/Tqdev/PhpCrudApi/Record/Condition/NoCondition.php
  8603. namespace Tqdev\PhpCrudApi\Record\Condition {
  8604. class NoCondition extends Condition
  8605. {
  8606. public function _and(Condition $condition): Condition
  8607. {
  8608. return $condition;
  8609. }
  8610. public function _or(Condition $condition): Condition
  8611. {
  8612. return $condition;
  8613. }
  8614. public function _not(): Condition
  8615. {
  8616. return $this;
  8617. }
  8618. }
  8619. }
  8620. // file: src/Tqdev/PhpCrudApi/Record/Condition/NotCondition.php
  8621. namespace Tqdev\PhpCrudApi\Record\Condition {
  8622. class NotCondition extends Condition
  8623. {
  8624. private $condition;
  8625. public function __construct(Condition $condition)
  8626. {
  8627. $this->condition = $condition;
  8628. }
  8629. public function getCondition(): Condition
  8630. {
  8631. return $this->condition;
  8632. }
  8633. }
  8634. }
  8635. // file: src/Tqdev/PhpCrudApi/Record/Condition/OrCondition.php
  8636. namespace Tqdev\PhpCrudApi\Record\Condition {
  8637. class OrCondition extends Condition
  8638. {
  8639. private $conditions;
  8640. public function __construct(Condition $condition1, Condition $condition2)
  8641. {
  8642. $this->conditions = [$condition1, $condition2];
  8643. }
  8644. public function _or(Condition $condition): Condition
  8645. {
  8646. if ($condition instanceof NoCondition) {
  8647. return $this;
  8648. }
  8649. $this->conditions[] = $condition;
  8650. return $this;
  8651. }
  8652. public function getConditions(): array
  8653. {
  8654. return $this->conditions;
  8655. }
  8656. public static function fromArray(array $conditions): Condition
  8657. {
  8658. $condition = new NoCondition();
  8659. foreach ($conditions as $c) {
  8660. $condition = $condition->_or($c);
  8661. }
  8662. return $condition;
  8663. }
  8664. }
  8665. }
  8666. // file: src/Tqdev/PhpCrudApi/Record/Condition/SpatialCondition.php
  8667. namespace Tqdev\PhpCrudApi\Record\Condition {
  8668. class SpatialCondition extends ColumnCondition
  8669. {
  8670. }
  8671. }
  8672. // file: src/Tqdev/PhpCrudApi/Record/Document/ErrorDocument.php
  8673. namespace Tqdev\PhpCrudApi\Record\Document {
  8674. use Tqdev\PhpCrudApi\Record\ErrorCode;
  8675. class ErrorDocument implements \JsonSerializable
  8676. {
  8677. public $code;
  8678. public $message;
  8679. public $details;
  8680. public function __construct(ErrorCode $errorCode, string $argument, $details)
  8681. {
  8682. $this->code = $errorCode->getCode();
  8683. $this->message = $errorCode->getMessage($argument);
  8684. $this->details = $details;
  8685. }
  8686. public function getCode(): int
  8687. {
  8688. return $this->code;
  8689. }
  8690. public function getMessage(): string
  8691. {
  8692. return $this->message;
  8693. }
  8694. public function serialize()
  8695. {
  8696. return [
  8697. 'code' => $this->code,
  8698. 'message' => $this->message,
  8699. 'details' => $this->details,
  8700. ];
  8701. }
  8702. public function jsonSerialize()
  8703. {
  8704. return array_filter($this->serialize());
  8705. }
  8706. }
  8707. }
  8708. // file: src/Tqdev/PhpCrudApi/Record/Document/ListDocument.php
  8709. namespace Tqdev\PhpCrudApi\Record\Document {
  8710. class ListDocument implements \JsonSerializable
  8711. {
  8712. private $records;
  8713. private $results;
  8714. public function __construct(array $records, int $results)
  8715. {
  8716. $this->records = $records;
  8717. $this->results = $results;
  8718. }
  8719. public function getRecords(): array
  8720. {
  8721. return $this->records;
  8722. }
  8723. public function getResults(): int
  8724. {
  8725. return $this->results;
  8726. }
  8727. public function serialize()
  8728. {
  8729. return [
  8730. 'records' => $this->records,
  8731. 'results' => $this->results,
  8732. ];
  8733. }
  8734. public function jsonSerialize()
  8735. {
  8736. return array_filter($this->serialize(), function ($v) {
  8737. return $v !== 0;
  8738. });
  8739. }
  8740. }
  8741. }
  8742. // file: src/Tqdev/PhpCrudApi/Record/ColumnIncluder.php
  8743. namespace Tqdev\PhpCrudApi\Record {
  8744. use Tqdev\PhpCrudApi\Column\Reflection\ReflectedTable;
  8745. class ColumnIncluder
  8746. {
  8747. private function isMandatory(string $tableName, string $columnName, array $params): bool
  8748. {
  8749. return isset($params['mandatory']) && in_array($tableName . "." . $columnName, $params['mandatory']);
  8750. }
  8751. private function select(
  8752. string $tableName,
  8753. bool $primaryTable,
  8754. array $params,
  8755. string $paramName,
  8756. array $columnNames,
  8757. bool $include
  8758. ): array {
  8759. if (!isset($params[$paramName])) {
  8760. return $columnNames;
  8761. }
  8762. $columns = array();
  8763. foreach (explode(',', $params[$paramName][0]) as $columnName) {
  8764. $columns[$columnName] = true;
  8765. }
  8766. $result = array();
  8767. foreach ($columnNames as $columnName) {
  8768. $match = isset($columns['*.*']);
  8769. if (!$match) {
  8770. $match = isset($columns[$tableName . '.*']) || isset($columns[$tableName . '.' . $columnName]);
  8771. }
  8772. if ($primaryTable && !$match) {
  8773. $match = isset($columns['*']) || isset($columns[$columnName]);
  8774. }
  8775. if ($match) {
  8776. if ($include || $this->isMandatory($tableName, $columnName, $params)) {
  8777. $result[] = $columnName;
  8778. }
  8779. } else {
  8780. if (!$include || $this->isMandatory($tableName, $columnName, $params)) {
  8781. $result[] = $columnName;
  8782. }
  8783. }
  8784. }
  8785. return $result;
  8786. }
  8787. public function getNames(ReflectedTable $table, bool $primaryTable, array $params): array
  8788. {
  8789. $tableName = $table->getName();
  8790. $results = $table->getColumnNames();
  8791. $results = $this->select($tableName, $primaryTable, $params, 'include', $results, true);
  8792. $results = $this->select($tableName, $primaryTable, $params, 'exclude', $results, false);
  8793. return $results;
  8794. }
  8795. public function getValues(ReflectedTable $table, bool $primaryTable, /* object */ $record, array $params): array
  8796. {
  8797. $results = array();
  8798. $columnNames = $this->getNames($table, $primaryTable, $params);
  8799. foreach ($columnNames as $columnName) {
  8800. if (property_exists($record, $columnName)) {
  8801. $results[$columnName] = $record->$columnName;
  8802. }
  8803. }
  8804. return $results;
  8805. }
  8806. }
  8807. }
  8808. // file: src/Tqdev/PhpCrudApi/Record/ErrorCode.php
  8809. namespace Tqdev\PhpCrudApi\Record {
  8810. use Tqdev\PhpCrudApi\ResponseFactory;
  8811. class ErrorCode
  8812. {
  8813. private $code;
  8814. private $message;
  8815. private $status;
  8816. const ERROR_NOT_FOUND = 9999;
  8817. const ROUTE_NOT_FOUND = 1000;
  8818. const TABLE_NOT_FOUND = 1001;
  8819. const ARGUMENT_COUNT_MISMATCH = 1002;
  8820. const RECORD_NOT_FOUND = 1003;
  8821. const ORIGIN_FORBIDDEN = 1004;
  8822. const COLUMN_NOT_FOUND = 1005;
  8823. const TABLE_ALREADY_EXISTS = 1006;
  8824. const COLUMN_ALREADY_EXISTS = 1007;
  8825. const HTTP_MESSAGE_NOT_READABLE = 1008;
  8826. const DUPLICATE_KEY_EXCEPTION = 1009;
  8827. const DATA_INTEGRITY_VIOLATION = 1010;
  8828. const AUTHENTICATION_REQUIRED = 1011;
  8829. const AUTHENTICATION_FAILED = 1012;
  8830. const INPUT_VALIDATION_FAILED = 1013;
  8831. const OPERATION_FORBIDDEN = 1014;
  8832. const OPERATION_NOT_SUPPORTED = 1015;
  8833. const TEMPORARY_OR_PERMANENTLY_BLOCKED = 1016;
  8834. const BAD_OR_MISSING_XSRF_TOKEN = 1017;
  8835. const ONLY_AJAX_REQUESTS_ALLOWED = 1018;
  8836. const PAGINATION_FORBIDDEN = 1019;
  8837. private $values = [
  8838. 9999 => ["%s", ResponseFactory::INTERNAL_SERVER_ERROR],
  8839. 1000 => ["Route '%s' not found", ResponseFactory::NOT_FOUND],
  8840. 1001 => ["Table '%s' not found", ResponseFactory::NOT_FOUND],
  8841. 1002 => ["Argument count mismatch in '%s'", ResponseFactory::UNPROCESSABLE_ENTITY],
  8842. 1003 => ["Record '%s' not found", ResponseFactory::NOT_FOUND],
  8843. 1004 => ["Origin '%s' is forbidden", ResponseFactory::FORBIDDEN],
  8844. 1005 => ["Column '%s' not found", ResponseFactory::NOT_FOUND],
  8845. 1006 => ["Table '%s' already exists", ResponseFactory::CONFLICT],
  8846. 1007 => ["Column '%s' already exists", ResponseFactory::CONFLICT],
  8847. 1008 => ["Cannot read HTTP message", ResponseFactory::UNPROCESSABLE_ENTITY],
  8848. 1009 => ["Duplicate key exception", ResponseFactory::CONFLICT],
  8849. 1010 => ["Data integrity violation", ResponseFactory::CONFLICT],
  8850. 1011 => ["Authentication required", ResponseFactory::UNAUTHORIZED],
  8851. 1012 => ["Authentication failed for '%s'", ResponseFactory::FORBIDDEN],
  8852. 1013 => ["Input validation failed for '%s'", ResponseFactory::UNPROCESSABLE_ENTITY],
  8853. 1014 => ["Operation forbidden", ResponseFactory::FORBIDDEN],
  8854. 1015 => ["Operation '%s' not supported", ResponseFactory::METHOD_NOT_ALLOWED],
  8855. 1016 => ["Temporary or permanently blocked", ResponseFactory::FORBIDDEN],
  8856. 1017 => ["Bad or missing XSRF token", ResponseFactory::FORBIDDEN],
  8857. 1018 => ["Only AJAX requests allowed for '%s'", ResponseFactory::FORBIDDEN],
  8858. 1019 => ["Pagination forbidden", ResponseFactory::FORBIDDEN],
  8859. ];
  8860. public function __construct(int $code)
  8861. {
  8862. if (!isset($this->values[$code])) {
  8863. $code = 9999;
  8864. }
  8865. $this->code = $code;
  8866. $this->message = $this->values[$code][0];
  8867. $this->status = $this->values[$code][1];
  8868. }
  8869. public function getCode(): int
  8870. {
  8871. return $this->code;
  8872. }
  8873. public function getMessage(string $argument): string
  8874. {
  8875. return sprintf($this->message, $argument);
  8876. }
  8877. public function getStatus(): int
  8878. {
  8879. return $this->status;
  8880. }
  8881. }
  8882. }
  8883. // file: src/Tqdev/PhpCrudApi/Record/FilterInfo.php
  8884. namespace Tqdev\PhpCrudApi\Record {
  8885. use Tqdev\PhpCrudApi\Column\Reflection\ReflectedTable;
  8886. use Tqdev\PhpCrudApi\Record\Condition\AndCondition;
  8887. use Tqdev\PhpCrudApi\Record\Condition\Condition;
  8888. use Tqdev\PhpCrudApi\Record\Condition\NoCondition;
  8889. use Tqdev\PhpCrudApi\Record\Condition\OrCondition;
  8890. class FilterInfo
  8891. {
  8892. private function addConditionFromFilterPath(PathTree $conditions, array $path, ReflectedTable $table, array $params)
  8893. {
  8894. $key = 'filter' . implode('', $path);
  8895. if (isset($params[$key])) {
  8896. foreach ($params[$key] as $filter) {
  8897. $condition = Condition::fromString($table, $filter);
  8898. if (($condition instanceof NoCondition) == false) {
  8899. $conditions->put($path, $condition);
  8900. }
  8901. }
  8902. }
  8903. }
  8904. private function getConditionsAsPathTree(ReflectedTable $table, array $params): PathTree
  8905. {
  8906. $conditions = new PathTree();
  8907. $this->addConditionFromFilterPath($conditions, [], $table, $params);
  8908. for ($n = ord('0'); $n <= ord('9'); $n++) {
  8909. $this->addConditionFromFilterPath($conditions, [chr($n)], $table, $params);
  8910. for ($l = ord('a'); $l <= ord('f'); $l++) {
  8911. $this->addConditionFromFilterPath($conditions, [chr($n), chr($l)], $table, $params);
  8912. }
  8913. }
  8914. return $conditions;
  8915. }
  8916. private function combinePathTreeOfConditions(PathTree $tree): Condition
  8917. {
  8918. $andConditions = $tree->getValues();
  8919. $and = AndCondition::fromArray($andConditions);
  8920. $orConditions = [];
  8921. foreach ($tree->getKeys() as $p) {
  8922. $orConditions[] = $this->combinePathTreeOfConditions($tree->get($p));
  8923. }
  8924. $or = OrCondition::fromArray($orConditions);
  8925. return $and->_and($or);
  8926. }
  8927. public function getCombinedConditions(ReflectedTable $table, array $params): Condition
  8928. {
  8929. return $this->combinePathTreeOfConditions($this->getConditionsAsPathTree($table, $params));
  8930. }
  8931. }
  8932. }
  8933. // file: src/Tqdev/PhpCrudApi/Record/HabtmValues.php
  8934. namespace Tqdev\PhpCrudApi\Record {
  8935. class HabtmValues
  8936. {
  8937. public $pkValues;
  8938. public $fkValues;
  8939. public function __construct(array $pkValues, array $fkValues)
  8940. {
  8941. $this->pkValues = $pkValues;
  8942. $this->fkValues = $fkValues;
  8943. }
  8944. }
  8945. }
  8946. // file: src/Tqdev/PhpCrudApi/Record/OrderingInfo.php
  8947. namespace Tqdev\PhpCrudApi\Record {
  8948. use Tqdev\PhpCrudApi\Column\Reflection\ReflectedTable;
  8949. class OrderingInfo
  8950. {
  8951. public function getColumnOrdering(ReflectedTable $table, array $params): array
  8952. {
  8953. $fields = array();
  8954. if (isset($params['order'])) {
  8955. foreach ($params['order'] as $order) {
  8956. $parts = explode(',', $order, 3);
  8957. $columnName = $parts[0];
  8958. if (!$table->hasColumn($columnName)) {
  8959. continue;
  8960. }
  8961. $ascending = 'ASC';
  8962. if (count($parts) > 1) {
  8963. if (substr(strtoupper($parts[1]), 0, 4) == "DESC") {
  8964. $ascending = 'DESC';
  8965. }
  8966. }
  8967. $fields[] = [$columnName, $ascending];
  8968. }
  8969. }
  8970. if (count($fields) == 0) {
  8971. return $this->getDefaultColumnOrdering($table);
  8972. }
  8973. return $fields;
  8974. }
  8975. public function getDefaultColumnOrdering(ReflectedTable $table): array
  8976. {
  8977. $fields = array();
  8978. $pk = $table->getPk();
  8979. if ($pk) {
  8980. $fields[] = [$pk->getName(), 'ASC'];
  8981. } else {
  8982. foreach ($table->getColumnNames() as $columnName) {
  8983. $fields[] = [$columnName, 'ASC'];
  8984. }
  8985. }
  8986. return $fields;
  8987. }
  8988. }
  8989. }
  8990. // file: src/Tqdev/PhpCrudApi/Record/PaginationInfo.php
  8991. namespace Tqdev\PhpCrudApi\Record {
  8992. class PaginationInfo
  8993. {
  8994. public $DEFAULT_PAGE_SIZE = 20;
  8995. public function hasPage(array $params): bool
  8996. {
  8997. return isset($params['page']);
  8998. }
  8999. public function getPageOffset(array $params): int
  9000. {
  9001. $offset = 0;
  9002. $pageSize = $this->getPageSize($params);
  9003. if (isset($params['page'])) {
  9004. foreach ($params['page'] as $page) {
  9005. $parts = explode(',', $page, 2);
  9006. $page = intval($parts[0]) - 1;
  9007. $offset = $page * $pageSize;
  9008. }
  9009. }
  9010. return $offset;
  9011. }
  9012. private function getPageSize(array $params): int
  9013. {
  9014. $pageSize = $this->DEFAULT_PAGE_SIZE;
  9015. if (isset($params['page'])) {
  9016. foreach ($params['page'] as $page) {
  9017. $parts = explode(',', $page, 2);
  9018. if (count($parts) > 1) {
  9019. $pageSize = intval($parts[1]);
  9020. }
  9021. }
  9022. }
  9023. return $pageSize;
  9024. }
  9025. public function getResultSize(array $params): int
  9026. {
  9027. $numberOfRows = -1;
  9028. if (isset($params['size'])) {
  9029. foreach ($params['size'] as $size) {
  9030. $numberOfRows = intval($size);
  9031. }
  9032. }
  9033. return $numberOfRows;
  9034. }
  9035. public function getPageLimit(array $params): int
  9036. {
  9037. $pageLimit = -1;
  9038. if ($this->hasPage($params)) {
  9039. $pageLimit = $this->getPageSize($params);
  9040. }
  9041. $resultSize = $this->getResultSize($params);
  9042. if ($resultSize >= 0) {
  9043. if ($pageLimit >= 0) {
  9044. $pageLimit = min($pageLimit, $resultSize);
  9045. } else {
  9046. $pageLimit = $resultSize;
  9047. }
  9048. }
  9049. return $pageLimit;
  9050. }
  9051. }
  9052. }
  9053. // file: src/Tqdev/PhpCrudApi/Record/PathTree.php
  9054. namespace Tqdev\PhpCrudApi\Record {
  9055. class PathTree implements \JsonSerializable
  9056. {
  9057. const WILDCARD = '*';
  9058. private $tree;
  9059. public function __construct(/* object */&$tree = null)
  9060. {
  9061. if (!$tree) {
  9062. $tree = $this->newTree();
  9063. }
  9064. $this->tree = &$tree;
  9065. }
  9066. public function newTree()
  9067. {
  9068. return (object) ['values' => [], 'branches' => (object) []];
  9069. }
  9070. public function getKeys(): array
  9071. {
  9072. $branches = (array) $this->tree->branches;
  9073. return array_keys($branches);
  9074. }
  9075. public function getValues(): array
  9076. {
  9077. return $this->tree->values;
  9078. }
  9079. public function get(string $key): PathTree
  9080. {
  9081. if (!isset($this->tree->branches->$key)) {
  9082. return null;
  9083. }
  9084. return new PathTree($this->tree->branches->$key);
  9085. }
  9086. public function put(array $path, $value)
  9087. {
  9088. $tree = &$this->tree;
  9089. foreach ($path as $key) {
  9090. if (!isset($tree->branches->$key)) {
  9091. $tree->branches->$key = $this->newTree();
  9092. }
  9093. $tree = &$tree->branches->$key;
  9094. }
  9095. $tree->values[] = $value;
  9096. }
  9097. public function match(array $path): array
  9098. {
  9099. $star = self::WILDCARD;
  9100. $tree = &$this->tree;
  9101. foreach ($path as $key) {
  9102. if (isset($tree->branches->$key)) {
  9103. $tree = &$tree->branches->$key;
  9104. } elseif (isset($tree->branches->$star)) {
  9105. $tree = &$tree->branches->$star;
  9106. } else {
  9107. return [];
  9108. }
  9109. }
  9110. return $tree->values;
  9111. }
  9112. public static function fromJson(/* object */$tree): PathTree
  9113. {
  9114. return new PathTree($tree);
  9115. }
  9116. public function jsonSerialize()
  9117. {
  9118. return $this->tree;
  9119. }
  9120. }
  9121. }
  9122. // file: src/Tqdev/PhpCrudApi/Record/RecordService.php
  9123. namespace Tqdev\PhpCrudApi\Record {
  9124. use Tqdev\PhpCrudApi\Column\ReflectionService;
  9125. use Tqdev\PhpCrudApi\Database\GenericDB;
  9126. use Tqdev\PhpCrudApi\Record\Document\ListDocument;
  9127. class RecordService
  9128. {
  9129. private $db;
  9130. private $reflection;
  9131. private $columns;
  9132. private $joiner;
  9133. private $filters;
  9134. private $ordering;
  9135. private $pagination;
  9136. public function __construct(GenericDB $db, ReflectionService $reflection)
  9137. {
  9138. $this->db = $db;
  9139. $this->reflection = $reflection;
  9140. $this->columns = new ColumnIncluder();
  9141. $this->joiner = new RelationJoiner($reflection, $this->columns);
  9142. $this->filters = new FilterInfo();
  9143. $this->ordering = new OrderingInfo();
  9144. $this->pagination = new PaginationInfo();
  9145. }
  9146. private function sanitizeRecord(string $tableName, /* object */ $record, string $id)
  9147. {
  9148. $keyset = array_keys((array) $record);
  9149. foreach ($keyset as $key) {
  9150. if (!$this->reflection->getTable($tableName)->hasColumn($key)) {
  9151. unset($record->$key);
  9152. }
  9153. }
  9154. if ($id != '') {
  9155. $pk = $this->reflection->getTable($tableName)->getPk();
  9156. foreach ($this->reflection->getTable($tableName)->getColumnNames() as $key) {
  9157. $field = $this->reflection->getTable($tableName)->getColumn($key);
  9158. if ($field->getName() == $pk->getName()) {
  9159. unset($record->$key);
  9160. }
  9161. }
  9162. }
  9163. }
  9164. public function hasTable(string $table): bool
  9165. {
  9166. return $this->reflection->hasTable($table);
  9167. }
  9168. public function getType(string $table): string
  9169. {
  9170. return $this->reflection->getType($table);
  9171. }
  9172. public function create(string $tableName, /* object */ $record, array $params) /*: ?int*/
  9173. {
  9174. $this->sanitizeRecord($tableName, $record, '');
  9175. $table = $this->reflection->getTable($tableName);
  9176. $columnValues = $this->columns->getValues($table, true, $record, $params);
  9177. return $this->db->createSingle($table, $columnValues);
  9178. }
  9179. public function read(string $tableName, string $id, array $params) /*: ?object*/
  9180. {
  9181. $table = $this->reflection->getTable($tableName);
  9182. $this->joiner->addMandatoryColumns($table, $params);
  9183. $columnNames = $this->columns->getNames($table, true, $params);
  9184. $record = $this->db->selectSingle($table, $columnNames, $id);
  9185. if ($record == null) {
  9186. return null;
  9187. }
  9188. $records = array($record);
  9189. $this->joiner->addJoins($table, $records, $params, $this->db);
  9190. return $records[0];
  9191. }
  9192. public function update(string $tableName, string $id, /* object */ $record, array $params) /*: ?int*/
  9193. {
  9194. $this->sanitizeRecord($tableName, $record, $id);
  9195. $table = $this->reflection->getTable($tableName);
  9196. $columnValues = $this->columns->getValues($table, true, $record, $params);
  9197. return $this->db->updateSingle($table, $columnValues, $id);
  9198. }
  9199. public function delete(string $tableName, string $id, array $params) /*: ?int*/
  9200. {
  9201. $table = $this->reflection->getTable($tableName);
  9202. return $this->db->deleteSingle($table, $id);
  9203. }
  9204. public function increment(string $tableName, string $id, /* object */ $record, array $params) /*: ?int*/
  9205. {
  9206. $this->sanitizeRecord($tableName, $record, $id);
  9207. $table = $this->reflection->getTable($tableName);
  9208. $columnValues = $this->columns->getValues($table, true, $record, $params);
  9209. return $this->db->incrementSingle($table, $columnValues, $id);
  9210. }
  9211. public function _list(string $tableName, array $params): ListDocument
  9212. {
  9213. $table = $this->reflection->getTable($tableName);
  9214. $this->joiner->addMandatoryColumns($table, $params);
  9215. $columnNames = $this->columns->getNames($table, true, $params);
  9216. $condition = $this->filters->getCombinedConditions($table, $params);
  9217. $columnOrdering = $this->ordering->getColumnOrdering($table, $params);
  9218. if (!$this->pagination->hasPage($params)) {
  9219. $offset = 0;
  9220. $limit = $this->pagination->getPageLimit($params);
  9221. $count = 0;
  9222. } else {
  9223. $offset = $this->pagination->getPageOffset($params);
  9224. $limit = $this->pagination->getPageLimit($params);
  9225. $count = $this->db->selectCount($table, $condition);
  9226. }
  9227. $records = $this->db->selectAll($table, $columnNames, $condition, $columnOrdering, $offset, $limit);
  9228. $this->joiner->addJoins($table, $records, $params, $this->db);
  9229. return new ListDocument($records, $count);
  9230. }
  9231. }
  9232. }
  9233. // file: src/Tqdev/PhpCrudApi/Record/RelationJoiner.php
  9234. namespace Tqdev\PhpCrudApi\Record {
  9235. use Tqdev\PhpCrudApi\Column\Reflection\ReflectedTable;
  9236. use Tqdev\PhpCrudApi\Column\ReflectionService;
  9237. use Tqdev\PhpCrudApi\Database\GenericDB;
  9238. use Tqdev\PhpCrudApi\Middleware\Communication\VariableStore;
  9239. use Tqdev\PhpCrudApi\Record\Condition\ColumnCondition;
  9240. use Tqdev\PhpCrudApi\Record\Condition\OrCondition;
  9241. class RelationJoiner
  9242. {
  9243. private $reflection;
  9244. private $ordering;
  9245. private $columns;
  9246. public function __construct(ReflectionService $reflection, ColumnIncluder $columns)
  9247. {
  9248. $this->reflection = $reflection;
  9249. $this->ordering = new OrderingInfo();
  9250. $this->columns = $columns;
  9251. }
  9252. public function addMandatoryColumns(ReflectedTable $table, array &$params) /*: void*/
  9253. {
  9254. if (!isset($params['join']) || !isset($params['include'])) {
  9255. return;
  9256. }
  9257. $params['mandatory'] = array();
  9258. foreach ($params['join'] as $tableNames) {
  9259. $t1 = $table;
  9260. foreach (explode(',', $tableNames) as $tableName) {
  9261. if (!$this->reflection->hasTable($tableName)) {
  9262. continue;
  9263. }
  9264. $t2 = $this->reflection->getTable($tableName);
  9265. $fks1 = $t1->getFksTo($t2->getName());
  9266. $t3 = $this->hasAndBelongsToMany($t1, $t2);
  9267. if ($t3 != null || count($fks1) > 0) {
  9268. $params['mandatory'][] = $t2->getName() . '.' . $t2->getPk()->getName();
  9269. }
  9270. foreach ($fks1 as $fk) {
  9271. $params['mandatory'][] = $t1->getName() . '.' . $fk->getName();
  9272. }
  9273. $fks2 = $t2->getFksTo($t1->getName());
  9274. if ($t3 != null || count($fks2) > 0) {
  9275. $params['mandatory'][] = $t1->getName() . '.' . $t1->getPk()->getName();
  9276. }
  9277. foreach ($fks2 as $fk) {
  9278. $params['mandatory'][] = $t2->getName() . '.' . $fk->getName();
  9279. }
  9280. $t1 = $t2;
  9281. }
  9282. }
  9283. }
  9284. private function getJoinsAsPathTree(array $params): PathTree
  9285. {
  9286. $joins = new PathTree();
  9287. if (isset($params['join'])) {
  9288. foreach ($params['join'] as $tableNames) {
  9289. $path = array();
  9290. foreach (explode(',', $tableNames) as $tableName) {
  9291. if (!$this->reflection->hasTable($tableName)) {
  9292. continue;
  9293. }
  9294. $t = $this->reflection->getTable($tableName);
  9295. if ($t != null) {
  9296. $path[] = $t->getName();
  9297. }
  9298. }
  9299. $joins->put($path, true);
  9300. }
  9301. }
  9302. return $joins;
  9303. }
  9304. public function addJoins(ReflectedTable $table, array &$records, array $params, GenericDB $db) /*: void*/
  9305. {
  9306. $joins = $this->getJoinsAsPathTree($params);
  9307. $this->addJoinsForTables($table, $joins, $records, $params, $db);
  9308. }
  9309. private function hasAndBelongsToMany(ReflectedTable $t1, ReflectedTable $t2) /*: ?ReflectedTable*/
  9310. {
  9311. foreach ($this->reflection->getTableNames() as $tableName) {
  9312. $t3 = $this->reflection->getTable($tableName);
  9313. if (count($t3->getFksTo($t1->getName())) > 0 && count($t3->getFksTo($t2->getName())) > 0) {
  9314. return $t3;
  9315. }
  9316. }
  9317. return null;
  9318. }
  9319. private function addJoinsForTables(ReflectedTable $t1, PathTree $joins, array &$records, array $params, GenericDB $db)
  9320. {
  9321. foreach ($joins->getKeys() as $t2Name) {
  9322. $t2 = $this->reflection->getTable($t2Name);
  9323. $belongsTo = count($t1->getFksTo($t2->getName())) > 0;
  9324. $hasMany = count($t2->getFksTo($t1->getName())) > 0;
  9325. if (!$belongsTo && !$hasMany) {
  9326. $t3 = $this->hasAndBelongsToMany($t1, $t2);
  9327. } else {
  9328. $t3 = null;
  9329. }
  9330. $hasAndBelongsToMany = ($t3 != null);
  9331. $newRecords = array();
  9332. $fkValues = null;
  9333. $pkValues = null;
  9334. $habtmValues = null;
  9335. if ($belongsTo) {
  9336. $fkValues = $this->getFkEmptyValues($t1, $t2, $records);
  9337. $this->addFkRecords($t2, $fkValues, $params, $db, $newRecords);
  9338. }
  9339. if ($hasMany) {
  9340. $pkValues = $this->getPkEmptyValues($t1, $records);
  9341. $this->addPkRecords($t1, $t2, $pkValues, $params, $db, $newRecords);
  9342. }
  9343. if ($hasAndBelongsToMany) {
  9344. $habtmValues = $this->getHabtmEmptyValues($t1, $t2, $t3, $db, $records);
  9345. $this->addFkRecords($t2, $habtmValues->fkValues, $params, $db, $newRecords);
  9346. }
  9347. $this->addJoinsForTables($t2, $joins->get($t2Name), $newRecords, $params, $db);
  9348. if ($fkValues != null) {
  9349. $this->fillFkValues($t2, $newRecords, $fkValues);
  9350. $this->setFkValues($t1, $t2, $records, $fkValues);
  9351. }
  9352. if ($pkValues != null) {
  9353. $this->fillPkValues($t1, $t2, $newRecords, $pkValues);
  9354. $this->setPkValues($t1, $t2, $records, $pkValues);
  9355. }
  9356. if ($habtmValues != null) {
  9357. $this->fillFkValues($t2, $newRecords, $habtmValues->fkValues);
  9358. $this->setHabtmValues($t1, $t2, $records, $habtmValues);
  9359. }
  9360. }
  9361. }
  9362. private function getFkEmptyValues(ReflectedTable $t1, ReflectedTable $t2, array $records): array
  9363. {
  9364. $fkValues = array();
  9365. $fks = $t1->getFksTo($t2->getName());
  9366. foreach ($fks as $fk) {
  9367. $fkName = $fk->getName();
  9368. foreach ($records as $record) {
  9369. if (isset($record[$fkName])) {
  9370. $fkValue = $record[$fkName];
  9371. $fkValues[$fkValue] = null;
  9372. }
  9373. }
  9374. }
  9375. return $fkValues;
  9376. }
  9377. private function addFkRecords(ReflectedTable $t2, array $fkValues, array $params, GenericDB $db, array &$records) /*: void*/
  9378. {
  9379. $columnNames = $this->columns->getNames($t2, false, $params);
  9380. $fkIds = array_keys($fkValues);
  9381. foreach ($db->selectMultiple($t2, $columnNames, $fkIds) as $record) {
  9382. $records[] = $record;
  9383. }
  9384. }
  9385. private function fillFkValues(ReflectedTable $t2, array $fkRecords, array &$fkValues) /*: void*/
  9386. {
  9387. $pkName = $t2->getPk()->getName();
  9388. foreach ($fkRecords as $fkRecord) {
  9389. $pkValue = $fkRecord[$pkName];
  9390. $fkValues[$pkValue] = $fkRecord;
  9391. }
  9392. }
  9393. private function setFkValues(ReflectedTable $t1, ReflectedTable $t2, array &$records, array $fkValues) /*: void*/
  9394. {
  9395. $fks = $t1->getFksTo($t2->getName());
  9396. foreach ($fks as $fk) {
  9397. $fkName = $fk->getName();
  9398. foreach ($records as $i => $record) {
  9399. if (isset($record[$fkName])) {
  9400. $key = $record[$fkName];
  9401. $records[$i][$fkName] = $fkValues[$key];
  9402. }
  9403. }
  9404. }
  9405. }
  9406. private function getPkEmptyValues(ReflectedTable $t1, array $records): array
  9407. {
  9408. $pkValues = array();
  9409. $pkName = $t1->getPk()->getName();
  9410. foreach ($records as $record) {
  9411. $key = $record[$pkName];
  9412. $pkValues[$key] = array();
  9413. }
  9414. return $pkValues;
  9415. }
  9416. private function addPkRecords(ReflectedTable $t1, ReflectedTable $t2, array $pkValues, array $params, GenericDB $db, array &$records) /*: void*/
  9417. {
  9418. $fks = $t2->getFksTo($t1->getName());
  9419. $columnNames = $this->columns->getNames($t2, false, $params);
  9420. $pkValueKeys = implode(',', array_keys($pkValues));
  9421. $conditions = array();
  9422. foreach ($fks as $fk) {
  9423. $conditions[] = new ColumnCondition($fk, 'in', $pkValueKeys);
  9424. }
  9425. $condition = OrCondition::fromArray($conditions);
  9426. $columnOrdering = array();
  9427. $limit = VariableStore::get("joinLimits.maxRecords") ?: -1;
  9428. if ($limit != -1) {
  9429. $columnOrdering = $this->ordering->getDefaultColumnOrdering($t2);
  9430. }
  9431. foreach ($db->selectAll($t2, $columnNames, $condition, $columnOrdering, 0, $limit) as $record) {
  9432. $records[] = $record;
  9433. }
  9434. }
  9435. private function fillPkValues(ReflectedTable $t1, ReflectedTable $t2, array $pkRecords, array &$pkValues) /*: void*/
  9436. {
  9437. $fks = $t2->getFksTo($t1->getName());
  9438. foreach ($fks as $fk) {
  9439. $fkName = $fk->getName();
  9440. foreach ($pkRecords as $pkRecord) {
  9441. $key = $pkRecord[$fkName];
  9442. if (isset($pkValues[$key])) {
  9443. $pkValues[$key][] = $pkRecord;
  9444. }
  9445. }
  9446. }
  9447. }
  9448. private function setPkValues(ReflectedTable $t1, ReflectedTable $t2, array &$records, array $pkValues) /*: void*/
  9449. {
  9450. $pkName = $t1->getPk()->getName();
  9451. $t2Name = $t2->getName();
  9452. foreach ($records as $i => $record) {
  9453. $key = $record[$pkName];
  9454. $records[$i][$t2Name] = $pkValues[$key];
  9455. }
  9456. }
  9457. private function getHabtmEmptyValues(ReflectedTable $t1, ReflectedTable $t2, ReflectedTable $t3, GenericDB $db, array $records): HabtmValues
  9458. {
  9459. $pkValues = $this->getPkEmptyValues($t1, $records);
  9460. $fkValues = array();
  9461. $fk1 = $t3->getFksTo($t1->getName())[0];
  9462. $fk2 = $t3->getFksTo($t2->getName())[0];
  9463. $fk1Name = $fk1->getName();
  9464. $fk2Name = $fk2->getName();
  9465. $columnNames = array($fk1Name, $fk2Name);
  9466. $pkIds = implode(',', array_keys($pkValues));
  9467. $condition = new ColumnCondition($t3->getColumn($fk1Name), 'in', $pkIds);
  9468. $columnOrdering = array();
  9469. $limit = VariableStore::get("joinLimits.maxRecords") ?: -1;
  9470. if ($limit != -1) {
  9471. $columnOrdering = $this->ordering->getDefaultColumnOrdering($t3);
  9472. }
  9473. $records = $db->selectAll($t3, $columnNames, $condition, $columnOrdering, 0, $limit);
  9474. foreach ($records as $record) {
  9475. $val1 = $record[$fk1Name];
  9476. $val2 = $record[$fk2Name];
  9477. $pkValues[$val1][] = $val2;
  9478. $fkValues[$val2] = null;
  9479. }
  9480. return new HabtmValues($pkValues, $fkValues);
  9481. }
  9482. private function setHabtmValues(ReflectedTable $t1, ReflectedTable $t2, array &$records, HabtmValues $habtmValues) /*: void*/
  9483. {
  9484. $pkName = $t1->getPk()->getName();
  9485. $t2Name = $t2->getName();
  9486. foreach ($records as $i => $record) {
  9487. $key = $record[$pkName];
  9488. $val = array();
  9489. $fks = $habtmValues->pkValues[$key];
  9490. foreach ($fks as $fk) {
  9491. $val[] = $habtmValues->fkValues[$fk];
  9492. }
  9493. $records[$i][$t2Name] = $val;
  9494. }
  9495. }
  9496. }
  9497. }
  9498. // file: src/Tqdev/PhpCrudApi/Api.php
  9499. namespace Tqdev\PhpCrudApi {
  9500. use Psr\Http\Message\ResponseInterface;
  9501. use Psr\Http\Message\ServerRequestInterface;
  9502. use Psr\Http\Server\RequestHandlerInterface;
  9503. use Tqdev\PhpCrudApi\Cache\CacheFactory;
  9504. use Tqdev\PhpCrudApi\Column\DefinitionService;
  9505. use Tqdev\PhpCrudApi\Column\ReflectionService;
  9506. use Tqdev\PhpCrudApi\Controller\CacheController;
  9507. use Tqdev\PhpCrudApi\Controller\ColumnController;
  9508. use Tqdev\PhpCrudApi\Controller\GeoJsonController;
  9509. use Tqdev\PhpCrudApi\Controller\JsonResponder;
  9510. use Tqdev\PhpCrudApi\Controller\OpenApiController;
  9511. use Tqdev\PhpCrudApi\Controller\RecordController;
  9512. use Tqdev\PhpCrudApi\Database\GenericDB;
  9513. use Tqdev\PhpCrudApi\GeoJson\GeoJsonService;
  9514. use Tqdev\PhpCrudApi\Middleware\AuthorizationMiddleware;
  9515. use Tqdev\PhpCrudApi\Middleware\BasicAuthMiddleware;
  9516. use Tqdev\PhpCrudApi\Middleware\CorsMiddleware;
  9517. use Tqdev\PhpCrudApi\Middleware\CustomizationMiddleware;
  9518. use Tqdev\PhpCrudApi\Middleware\DbAuthMiddleware;
  9519. use Tqdev\PhpCrudApi\Middleware\FirewallMiddleware;
  9520. use Tqdev\PhpCrudApi\Middleware\IpAddressMiddleware;
  9521. use Tqdev\PhpCrudApi\Middleware\JoinLimitsMiddleware;
  9522. use Tqdev\PhpCrudApi\Middleware\JwtAuthMiddleware;
  9523. use Tqdev\PhpCrudApi\Middleware\MultiTenancyMiddleware;
  9524. use Tqdev\PhpCrudApi\Middleware\PageLimitsMiddleware;
  9525. use Tqdev\PhpCrudApi\Middleware\ReconnectMiddleware;
  9526. use Tqdev\PhpCrudApi\Middleware\Router\SimpleRouter;
  9527. use Tqdev\PhpCrudApi\Middleware\SanitationMiddleware;
  9528. use Tqdev\PhpCrudApi\Middleware\SslRedirectMiddleware;
  9529. use Tqdev\PhpCrudApi\Middleware\ValidationMiddleware;
  9530. use Tqdev\PhpCrudApi\Middleware\XmlMiddleware;
  9531. use Tqdev\PhpCrudApi\Middleware\XsrfMiddleware;
  9532. use Tqdev\PhpCrudApi\OpenApi\OpenApiService;
  9533. use Tqdev\PhpCrudApi\Record\ErrorCode;
  9534. use Tqdev\PhpCrudApi\Record\RecordService;
  9535. use Tqdev\PhpCrudApi\ResponseUtils;
  9536. class Api implements RequestHandlerInterface
  9537. {
  9538. private $router;
  9539. private $responder;
  9540. private $debug;
  9541. public function __construct(Config $config)
  9542. {
  9543. $db = new GenericDB(
  9544. $config->getDriver(),
  9545. $config->getAddress(),
  9546. $config->getPort(),
  9547. $config->getDatabase(),
  9548. $config->getTables(),
  9549. $config->getUsername(),
  9550. $config->getPassword()
  9551. );
  9552. $prefix = sprintf('phpcrudapi-%s-', substr(md5(__FILE__), 0, 8));
  9553. $cache = CacheFactory::create($config->getCacheType(), $prefix, $config->getCachePath());
  9554. $reflection = new ReflectionService($db, $cache, $config->getCacheTime());
  9555. $responder = new JsonResponder();
  9556. $router = new SimpleRouter($config->getBasePath(), $responder, $cache, $config->getCacheTime(), $config->getDebug());
  9557. foreach ($config->getMiddlewares() as $middleware => $properties) {
  9558. switch ($middleware) {
  9559. case 'sslRedirect':
  9560. new SslRedirectMiddleware($router, $responder, $properties);
  9561. break;
  9562. case 'cors':
  9563. new CorsMiddleware($router, $responder, $properties);
  9564. break;
  9565. case 'firewall':
  9566. new FirewallMiddleware($router, $responder, $properties);
  9567. break;
  9568. case 'basicAuth':
  9569. new BasicAuthMiddleware($router, $responder, $properties);
  9570. break;
  9571. case 'jwtAuth':
  9572. new JwtAuthMiddleware($router, $responder, $properties);
  9573. break;
  9574. case 'dbAuth':
  9575. new DbAuthMiddleware($router, $responder, $properties, $reflection, $db);
  9576. break;
  9577. case 'reconnect':
  9578. new ReconnectMiddleware($router, $responder, $properties, $reflection, $db);
  9579. break;
  9580. case 'validation':
  9581. new ValidationMiddleware($router, $responder, $properties, $reflection);
  9582. break;
  9583. case 'ipAddress':
  9584. new IpAddressMiddleware($router, $responder, $properties, $reflection);
  9585. break;
  9586. case 'sanitation':
  9587. new SanitationMiddleware($router, $responder, $properties, $reflection);
  9588. break;
  9589. case 'multiTenancy':
  9590. new MultiTenancyMiddleware($router, $responder, $properties, $reflection);
  9591. break;
  9592. case 'authorization':
  9593. new AuthorizationMiddleware($router, $responder, $properties, $reflection);
  9594. break;
  9595. case 'xsrf':
  9596. new XsrfMiddleware($router, $responder, $properties);
  9597. break;
  9598. case 'pageLimits':
  9599. new PageLimitsMiddleware($router, $responder, $properties, $reflection);
  9600. break;
  9601. case 'joinLimits':
  9602. new JoinLimitsMiddleware($router, $responder, $properties, $reflection);
  9603. break;
  9604. case 'customization':
  9605. new CustomizationMiddleware($router, $responder, $properties, $reflection);
  9606. break;
  9607. case 'xml':
  9608. new XmlMiddleware($router, $responder, $properties, $reflection);
  9609. break;
  9610. }
  9611. }
  9612. foreach ($config->getControllers() as $controller) {
  9613. switch ($controller) {
  9614. case 'records':
  9615. $records = new RecordService($db, $reflection);
  9616. new RecordController($router, $responder, $records);
  9617. break;
  9618. case 'columns':
  9619. $definition = new DefinitionService($db, $reflection);
  9620. new ColumnController($router, $responder, $reflection, $definition);
  9621. break;
  9622. case 'cache':
  9623. new CacheController($router, $responder, $cache);
  9624. break;
  9625. case 'openapi':
  9626. $openApi = new OpenApiService($reflection, $config->getOpenApiBase(), $config->getControllers(), $config->getCustomOpenApiBuilders());
  9627. new OpenApiController($router, $responder, $openApi);
  9628. break;
  9629. case 'geojson':
  9630. $records = new RecordService($db, $reflection);
  9631. $geoJson = new GeoJsonService($reflection, $records);
  9632. new GeoJsonController($router, $responder, $geoJson);
  9633. break;
  9634. }
  9635. }
  9636. foreach ($config->getCustomControllers() as $className) {
  9637. if (class_exists($className)) {
  9638. $records = new RecordService($db, $reflection);
  9639. new $className($router, $responder, $records);
  9640. }
  9641. }
  9642. $this->router = $router;
  9643. $this->responder = $responder;
  9644. $this->debug = $config->getDebug();
  9645. }
  9646. private function parseBody(string $body) /*: ?object*/
  9647. {
  9648. $first = substr($body, 0, 1);
  9649. if ($first == '[' || $first == '{') {
  9650. $object = json_decode($body);
  9651. $causeCode = json_last_error();
  9652. if ($causeCode !== JSON_ERROR_NONE) {
  9653. $object = null;
  9654. }
  9655. } else {
  9656. parse_str($body, $input);
  9657. foreach ($input as $key => $value) {
  9658. if (substr($key, -9) == '__is_null') {
  9659. $input[substr($key, 0, -9)] = null;
  9660. unset($input[$key]);
  9661. }
  9662. }
  9663. $object = (object) $input;
  9664. }
  9665. return $object;
  9666. }
  9667. private function addParsedBody(ServerRequestInterface $request): ServerRequestInterface
  9668. {
  9669. $parsedBody = $request->getParsedBody();
  9670. if ($parsedBody) {
  9671. $request = $this->applyParsedBodyHack($request);
  9672. } else {
  9673. $body = $request->getBody();
  9674. if ($body->isReadable()) {
  9675. if ($body->isSeekable()) {
  9676. $body->rewind();
  9677. }
  9678. $contents = $body->getContents();
  9679. if ($body->isSeekable()) {
  9680. $body->rewind();
  9681. }
  9682. if ($contents) {
  9683. $parsedBody = $this->parseBody($contents);
  9684. $request = $request->withParsedBody($parsedBody);
  9685. }
  9686. }
  9687. }
  9688. return $request;
  9689. }
  9690. private function applyParsedBodyHack(ServerRequestInterface $request): ServerRequestInterface
  9691. {
  9692. $parsedBody = $request->getParsedBody();
  9693. if (is_array($parsedBody)) { // is it really?
  9694. $contents = json_encode($parsedBody);
  9695. $parsedBody = $this->parseBody($contents);
  9696. $request = $request->withParsedBody($parsedBody);
  9697. }
  9698. return $request;
  9699. }
  9700. public function handle(ServerRequestInterface $request): ResponseInterface
  9701. {
  9702. $response = null;
  9703. try {
  9704. $response = $this->router->route($this->addParsedBody($request));
  9705. } catch (\Throwable $e) {
  9706. $response = $this->responder->error(ErrorCode::ERROR_NOT_FOUND, $e->getMessage());
  9707. if ($this->debug) {
  9708. $response = ResponseUtils::addExceptionHeaders($response, $e);
  9709. }
  9710. }
  9711. return $response;
  9712. }
  9713. }
  9714. }
  9715. // file: src/Tqdev/PhpCrudApi/Config.php
  9716. namespace Tqdev\PhpCrudApi {
  9717. class Config
  9718. {
  9719. private $values = [
  9720. 'driver' => null,
  9721. 'address' => 'localhost',
  9722. 'port' => null,
  9723. 'username' => null,
  9724. 'password' => null,
  9725. 'database' => null,
  9726. 'tables' => '',
  9727. 'middlewares' => 'cors',
  9728. 'controllers' => 'records,geojson,openapi',
  9729. 'customControllers' => '',
  9730. 'customOpenApiBuilders' => '',
  9731. 'cacheType' => 'TempFile',
  9732. 'cachePath' => '',
  9733. 'cacheTime' => 10,
  9734. 'debug' => false,
  9735. 'basePath' => '',
  9736. 'openApiBase' => '{"info":{"title":"PHP-CRUD-API","version":"1.0.0"}}',
  9737. ];
  9738. private function getDefaultDriver(array $values): string
  9739. {
  9740. if (isset($values['driver'])) {
  9741. return $values['driver'];
  9742. }
  9743. return 'mysql';
  9744. }
  9745. private function getDefaultPort(string $driver): int
  9746. {
  9747. switch ($driver) {
  9748. case 'mysql':
  9749. return 3306;
  9750. case 'pgsql':
  9751. return 5432;
  9752. case 'sqlsrv':
  9753. return 1433;
  9754. case 'sqlite':
  9755. return 0;
  9756. }
  9757. }
  9758. private function getDefaultAddress(string $driver): string
  9759. {
  9760. switch ($driver) {
  9761. case 'mysql':
  9762. return 'localhost';
  9763. case 'pgsql':
  9764. return 'localhost';
  9765. case 'sqlsrv':
  9766. return 'localhost';
  9767. case 'sqlite':
  9768. return 'data.db';
  9769. }
  9770. }
  9771. private function getDriverDefaults(string $driver): array
  9772. {
  9773. return [
  9774. 'driver' => $driver,
  9775. 'address' => $this->getDefaultAddress($driver),
  9776. 'port' => $this->getDefaultPort($driver),
  9777. ];
  9778. }
  9779. private function applyEnvironmentVariables(array $values): array
  9780. {
  9781. $newValues = array();
  9782. foreach ($values as $key => $value) {
  9783. $environmentKey = 'PHP_CRUD_API_' . strtoupper(preg_replace('/(?<!^)[A-Z]/', '_$0', str_replace('.', '_', $key)));
  9784. $newValues[$key] = getenv($environmentKey, true) ?: $value;
  9785. }
  9786. return $newValues;
  9787. }
  9788. public function __construct(array $values)
  9789. {
  9790. $driver = $this->getDefaultDriver($values);
  9791. $defaults = $this->getDriverDefaults($driver);
  9792. $newValues = array_merge($this->values, $defaults, $values);
  9793. $newValues = $this->parseMiddlewares($newValues);
  9794. $diff = array_diff_key($newValues, $this->values);
  9795. if (!empty($diff)) {
  9796. $key = array_keys($diff)[0];
  9797. throw new \Exception("Config has invalid value '$key'");
  9798. }
  9799. $newValues = $this->applyEnvironmentVariables($newValues);
  9800. $this->values = $newValues;
  9801. }
  9802. private function parseMiddlewares(array $values): array
  9803. {
  9804. $newValues = array();
  9805. $properties = array();
  9806. $middlewares = array_map('trim', explode(',', $values['middlewares']));
  9807. foreach ($middlewares as $middleware) {
  9808. $properties[$middleware] = [];
  9809. }
  9810. foreach ($values as $key => $value) {
  9811. if (strpos($key, '.') === false) {
  9812. $newValues[$key] = $value;
  9813. } else {
  9814. list($middleware, $key2) = explode('.', $key, 2);
  9815. if (isset($properties[$middleware])) {
  9816. $properties[$middleware][$key2] = $value;
  9817. } else {
  9818. throw new \Exception("Config has invalid value '$key'");
  9819. }
  9820. }
  9821. }
  9822. $newValues['middlewares'] = array_reverse($properties, true);
  9823. return $newValues;
  9824. }
  9825. public function getDriver(): string
  9826. {
  9827. return $this->values['driver'];
  9828. }
  9829. public function getAddress(): string
  9830. {
  9831. return $this->values['address'];
  9832. }
  9833. public function getPort(): int
  9834. {
  9835. return $this->values['port'];
  9836. }
  9837. public function getUsername(): string
  9838. {
  9839. return $this->values['username'];
  9840. }
  9841. public function getPassword(): string
  9842. {
  9843. return $this->values['password'];
  9844. }
  9845. public function getDatabase(): string
  9846. {
  9847. return $this->values['database'];
  9848. }
  9849. public function getTables(): array
  9850. {
  9851. return array_filter(array_map('trim', explode(',', $this->values['tables'])));
  9852. }
  9853. public function getMiddlewares(): array
  9854. {
  9855. return $this->values['middlewares'];
  9856. }
  9857. public function getControllers(): array
  9858. {
  9859. return array_filter(array_map('trim', explode(',', $this->values['controllers'])));
  9860. }
  9861. public function getCustomControllers(): array
  9862. {
  9863. return array_filter(array_map('trim', explode(',', $this->values['customControllers'])));
  9864. }
  9865. public function getCustomOpenApiBuilders(): array
  9866. {
  9867. return array_filter(array_map('trim', explode(',', $this->values['customOpenApiBuilders'])));
  9868. }
  9869. public function getCacheType(): string
  9870. {
  9871. return $this->values['cacheType'];
  9872. }
  9873. public function getCachePath(): string
  9874. {
  9875. return $this->values['cachePath'];
  9876. }
  9877. public function getCacheTime(): int
  9878. {
  9879. return $this->values['cacheTime'];
  9880. }
  9881. public function getDebug(): bool
  9882. {
  9883. return $this->values['debug'];
  9884. }
  9885. public function getBasePath(): string
  9886. {
  9887. return $this->values['basePath'];
  9888. }
  9889. public function getOpenApiBase(): array
  9890. {
  9891. return json_decode($this->values['openApiBase'], true);
  9892. }
  9893. }
  9894. }
  9895. // file: src/Tqdev/PhpCrudApi/RequestFactory.php
  9896. namespace Tqdev\PhpCrudApi {
  9897. use Nyholm\Psr7\Factory\Psr17Factory;
  9898. use Nyholm\Psr7Server\ServerRequestCreator;
  9899. use Psr\Http\Message\ServerRequestInterface;
  9900. class RequestFactory
  9901. {
  9902. public static function fromGlobals(): ServerRequestInterface
  9903. {
  9904. $psr17Factory = new Psr17Factory();
  9905. $creator = new ServerRequestCreator($psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory);
  9906. $serverRequest = $creator->fromGlobals();
  9907. $stream = $psr17Factory->createStreamFromFile('php://input');
  9908. $serverRequest = $serverRequest->withBody($stream);
  9909. return $serverRequest;
  9910. }
  9911. public static function fromString(string $request): ServerRequestInterface
  9912. {
  9913. $parts = explode("\n\n", trim($request), 2);
  9914. $lines = explode("\n", $parts[0]);
  9915. $first = explode(' ', trim(array_shift($lines)), 2);
  9916. $method = $first[0];
  9917. $body = isset($parts[1]) ? $parts[1] : '';
  9918. $url = isset($first[1]) ? $first[1] : '';
  9919. $psr17Factory = new Psr17Factory();
  9920. $serverRequest = $psr17Factory->createServerRequest($method, $url);
  9921. foreach ($lines as $line) {
  9922. list($key, $value) = explode(':', $line, 2);
  9923. $serverRequest = $serverRequest->withAddedHeader($key, $value);
  9924. }
  9925. if ($body) {
  9926. $stream = $psr17Factory->createStream($body);
  9927. $stream->rewind();
  9928. $serverRequest = $serverRequest->withBody($stream);
  9929. }
  9930. return $serverRequest;
  9931. }
  9932. }
  9933. }
  9934. // file: src/Tqdev/PhpCrudApi/RequestUtils.php
  9935. namespace Tqdev\PhpCrudApi {
  9936. use Psr\Http\Message\ServerRequestInterface;
  9937. use Tqdev\PhpCrudApi\Column\ReflectionService;
  9938. class RequestUtils
  9939. {
  9940. public static function setParams(ServerRequestInterface $request, array $params): ServerRequestInterface
  9941. {
  9942. $query = preg_replace('|%5B[0-9]+%5D=|', '=', http_build_query($params));
  9943. return $request->withUri($request->getUri()->withQuery($query));
  9944. }
  9945. public static function getHeader(ServerRequestInterface $request, string $header): string
  9946. {
  9947. $headers = $request->getHeader($header);
  9948. return isset($headers[0]) ? $headers[0] : '';
  9949. }
  9950. public static function getParams(ServerRequestInterface $request): array
  9951. {
  9952. $params = array();
  9953. $query = $request->getUri()->getQuery();
  9954. //$query = str_replace('][]=', ']=', str_replace('=', '[]=', $query));
  9955. $query = str_replace('%5D%5B%5D=', '%5D=', str_replace('=', '%5B%5D=', $query));
  9956. parse_str($query, $params);
  9957. return $params;
  9958. }
  9959. public static function getPathSegment(ServerRequestInterface $request, int $part): string
  9960. {
  9961. $path = $request->getUri()->getPath();
  9962. $pathSegments = explode('/', rtrim($path, '/'));
  9963. if ($part < 0 || $part >= count($pathSegments)) {
  9964. return '';
  9965. }
  9966. return urldecode($pathSegments[$part]);
  9967. }
  9968. public static function getOperation(ServerRequestInterface $request): string
  9969. {
  9970. $method = $request->getMethod();
  9971. $path = RequestUtils::getPathSegment($request, 1);
  9972. $hasPk = RequestUtils::getPathSegment($request, 3) != '';
  9973. switch ($path) {
  9974. case 'openapi':
  9975. return 'document';
  9976. case 'columns':
  9977. return $method == 'get' ? 'reflect' : 'remodel';
  9978. case 'geojson':
  9979. case 'records':
  9980. switch ($method) {
  9981. case 'POST':
  9982. return 'create';
  9983. case 'GET':
  9984. return $hasPk ? 'read' : 'list';
  9985. case 'PUT':
  9986. return 'update';
  9987. case 'DELETE':
  9988. return 'delete';
  9989. case 'PATCH':
  9990. return 'increment';
  9991. }
  9992. }
  9993. return 'unknown';
  9994. }
  9995. private static function getJoinTables(string $tableName, array $parameters): array
  9996. {
  9997. $uniqueTableNames = array();
  9998. $uniqueTableNames[$tableName] = true;
  9999. if (isset($parameters['join'])) {
  10000. foreach ($parameters['join'] as $parameter) {
  10001. $tableNames = explode(',', trim($parameter));
  10002. foreach ($tableNames as $tableName) {
  10003. $uniqueTableNames[$tableName] = true;
  10004. }
  10005. }
  10006. }
  10007. return array_keys($uniqueTableNames);
  10008. }
  10009. public static function getTableNames(ServerRequestInterface $request, ReflectionService $reflection): array
  10010. {
  10011. $path = RequestUtils::getPathSegment($request, 1);
  10012. $tableName = RequestUtils::getPathSegment($request, 2);
  10013. $allTableNames = $reflection->getTableNames();
  10014. switch ($path) {
  10015. case 'openapi':
  10016. return $allTableNames;
  10017. case 'columns':
  10018. return $tableName ? [$tableName] : $allTableNames;
  10019. case 'records':
  10020. return self::getJoinTables($tableName, RequestUtils::getParams($request));
  10021. }
  10022. return $allTableNames;
  10023. }
  10024. }
  10025. }
  10026. // file: src/Tqdev/PhpCrudApi/ResponseFactory.php
  10027. namespace Tqdev\PhpCrudApi {
  10028. use Nyholm\Psr7\Factory\Psr17Factory;
  10029. use Psr\Http\Message\ResponseInterface;
  10030. class ResponseFactory
  10031. {
  10032. const OK = 200;
  10033. const MOVED_PERMANENTLY = 301;
  10034. const FOUND = 302;
  10035. const UNAUTHORIZED = 401;
  10036. const FORBIDDEN = 403;
  10037. const NOT_FOUND = 404;
  10038. const METHOD_NOT_ALLOWED = 405;
  10039. const CONFLICT = 409;
  10040. const UNPROCESSABLE_ENTITY = 422;
  10041. const INTERNAL_SERVER_ERROR = 500;
  10042. public static function fromXml(int $status, string $xml): ResponseInterface
  10043. {
  10044. return self::from($status, 'text/xml', $xml);
  10045. }
  10046. public static function fromCsv(int $status, string $csv): ResponseInterface
  10047. {
  10048. return self::from($status, 'text/csv', $csv);
  10049. }
  10050. public static function fromHtml(int $status, string $html): ResponseInterface
  10051. {
  10052. return self::from($status, 'text/html', $html);
  10053. }
  10054. public static function fromObject(int $status, $body): ResponseInterface
  10055. {
  10056. $content = json_encode($body, JSON_UNESCAPED_UNICODE);
  10057. return self::from($status, 'application/json', $content);
  10058. }
  10059. public static function from(int $status, string $contentType, string $content): ResponseInterface
  10060. {
  10061. $psr17Factory = new Psr17Factory();
  10062. $response = $psr17Factory->createResponse($status);
  10063. $stream = $psr17Factory->createStream($content);
  10064. $stream->rewind();
  10065. $response = $response->withBody($stream);
  10066. $response = $response->withHeader('Content-Type', $contentType . '; charset=utf-8');
  10067. $response = $response->withHeader('Content-Length', strlen($content));
  10068. return $response;
  10069. }
  10070. public static function fromStatus(int $status): ResponseInterface
  10071. {
  10072. $psr17Factory = new Psr17Factory();
  10073. return $psr17Factory->createResponse($status);
  10074. }
  10075. }
  10076. }
  10077. // file: src/Tqdev/PhpCrudApi/ResponseUtils.php
  10078. namespace Tqdev\PhpCrudApi {
  10079. use Psr\Http\Message\ResponseInterface;
  10080. class ResponseUtils
  10081. {
  10082. public static function output(ResponseInterface $response)
  10083. {
  10084. $status = $response->getStatusCode();
  10085. $headers = $response->getHeaders();
  10086. $body = $response->getBody()->getContents();
  10087. http_response_code($status);
  10088. foreach ($headers as $key => $values) {
  10089. foreach ($values as $value) {
  10090. header("$key: $value");
  10091. }
  10092. }
  10093. echo $body;
  10094. }
  10095. public static function addExceptionHeaders(ResponseInterface $response, \Throwable $e): ResponseInterface
  10096. {
  10097. $response = $response->withHeader('X-Exception-Name', get_class($e));
  10098. $response = $response->withHeader('X-Exception-Message', preg_replace('|\n|', ' ', trim($e->getMessage())));
  10099. $response = $response->withHeader('X-Exception-File', $e->getFile() . ':' . $e->getLine());
  10100. return $response;
  10101. }
  10102. public static function toString(ResponseInterface $response): string
  10103. {
  10104. $status = $response->getStatusCode();
  10105. $headers = $response->getHeaders();
  10106. $body = $response->getBody()->getContents();
  10107. $str = "$status\n";
  10108. foreach ($headers as $key => $values) {
  10109. foreach ($values as $value) {
  10110. $str .= "$key: $value\n";
  10111. }
  10112. }
  10113. if ($body !== '') {
  10114. $str .= "\n";
  10115. $str .= "$body\n";
  10116. }
  10117. return $str;
  10118. }
  10119. }
  10120. }
  10121. // file: src/index.php
  10122. namespace Tqdev\PhpCrudApi {
  10123. use Tqdev\PhpCrudApi\Api;
  10124. use Tqdev\PhpCrudApi\Config;
  10125. use Tqdev\PhpCrudApi\RequestFactory;
  10126. use Tqdev\PhpCrudApi\ResponseUtils;
  10127. $config = new Config([
  10128. // 'driver' => 'mysql',
  10129. // 'address' => 'localhost',
  10130. 'username' => 'php-crud-api',
  10131. 'password' => 'php-crud-api',
  10132. 'database' => 'php-crud-api',
  10133. // 'debug' => false
  10134. ]);
  10135. $request = RequestFactory::fromGlobals();
  10136. $api = new Api($config);
  10137. $response = $api->handle($request);
  10138. ResponseUtils::output($response);
  10139. }