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.

tmp_api.php 295KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024702570267027702870297030703170327033703470357036703770387039704070417042704370447045704670477048704970507051705270537054705570567057705870597060706170627063706470657066706770687069707070717072707370747075707670777078707970807081708270837084708570867087708870897090709170927093709470957096709770987099710071017102710371047105710671077108710971107111711271137114711571167117711871197120712171227123712471257126712771287129713071317132713371347135713671377138713971407141714271437144714571467147714871497150715171527153715471557156715771587159716071617162716371647165716671677168716971707171717271737174717571767177717871797180718171827183718471857186718771887189719071917192719371947195719671977198719972007201720272037204720572067207720872097210721172127213721472157216721772187219722072217222722372247225722672277228722972307231723272337234723572367237723872397240724172427243724472457246724772487249725072517252725372547255725672577258725972607261726272637264726572667267726872697270727172727273727472757276727772787279728072817282728372847285728672877288728972907291729272937294729572967297729872997300730173027303730473057306730773087309731073117312731373147315731673177318731973207321732273237324732573267327732873297330733173327333733473357336733773387339734073417342734373447345734673477348734973507351735273537354735573567357735873597360736173627363736473657366736773687369737073717372737373747375737673777378737973807381738273837384738573867387738873897390739173927393739473957396739773987399740074017402740374047405740674077408740974107411741274137414741574167417741874197420742174227423742474257426742774287429743074317432743374347435743674377438743974407441744274437444744574467447744874497450745174527453745474557456745774587459746074617462746374647465746674677468746974707471747274737474747574767477747874797480748174827483748474857486748774887489749074917492749374947495749674977498749975007501750275037504750575067507750875097510751175127513751475157516751775187519752075217522752375247525752675277528752975307531753275337534753575367537753875397540754175427543754475457546754775487549755075517552755375547555755675577558755975607561756275637564756575667567756875697570757175727573757475757576757775787579758075817582758375847585758675877588758975907591759275937594759575967597759875997600760176027603760476057606760776087609761076117612761376147615761676177618761976207621762276237624762576267627762876297630763176327633763476357636763776387639764076417642764376447645764676477648764976507651765276537654765576567657765876597660766176627663766476657666766776687669767076717672767376747675767676777678767976807681768276837684768576867687768876897690769176927693769476957696769776987699770077017702770377047705770677077708770977107711771277137714771577167717771877197720772177227723772477257726772777287729773077317732773377347735773677377738773977407741774277437744774577467747774877497750775177527753775477557756775777587759776077617762776377647765776677677768776977707771777277737774777577767777777877797780778177827783778477857786778777887789779077917792779377947795779677977798779978007801780278037804780578067807780878097810781178127813781478157816781778187819782078217822782378247825782678277828782978307831783278337834783578367837783878397840784178427843784478457846784778487849785078517852785378547855785678577858785978607861786278637864786578667867786878697870787178727873787478757876787778787879788078817882788378847885788678877888788978907891789278937894789578967897789878997900790179027903790479057906790779087909791079117912791379147915791679177918791979207921792279237924792579267927792879297930793179327933793479357936793779387939794079417942794379447945794679477948794979507951795279537954795579567957795879597960796179627963796479657966796779687969797079717972797379747975797679777978797979807981798279837984798579867987798879897990799179927993799479957996799779987999800080018002800380048005800680078008800980108011801280138014801580168017801880198020802180228023802480258026802780288029803080318032803380348035803680378038803980408041804280438044804580468047804880498050805180528053805480558056805780588059806080618062806380648065806680678068806980708071807280738074807580768077807880798080808180828083808480858086808780888089809080918092809380948095809680978098809981008101810281038104810581068107810881098110811181128113811481158116811781188119812081218122812381248125812681278128812981308131813281338134813581368137813881398140814181428143814481458146814781488149815081518152815381548155815681578158815981608161816281638164816581668167816881698170817181728173817481758176817781788179818081818182818381848185818681878188818981908191819281938194819581968197819881998200820182028203820482058206820782088209821082118212821382148215821682178218821982208221822282238224822582268227822882298230823182328233823482358236823782388239824082418242824382448245824682478248824982508251825282538254825582568257825882598260826182628263826482658266826782688269827082718272827382748275827682778278827982808281828282838284828582868287828882898290829182928293829482958296829782988299830083018302830383048305830683078308830983108311831283138314831583168317831883198320832183228323832483258326832783288329833083318332833383348335833683378338833983408341834283438344834583468347834883498350835183528353835483558356835783588359836083618362836383648365836683678368836983708371837283738374837583768377837883798380838183828383838483858386838783888389839083918392839383948395839683978398839984008401840284038404840584068407840884098410841184128413841484158416841784188419842084218422842384248425842684278428842984308431843284338434843584368437843884398440844184428443844484458446844784488449845084518452845384548455845684578458845984608461846284638464846584668467846884698470847184728473847484758476847784788479848084818482848384848485848684878488848984908491849284938494849584968497849884998500850185028503850485058506850785088509851085118512851385148515851685178518851985208521852285238524852585268527852885298530853185328533853485358536853785388539854085418542854385448545854685478548854985508551855285538554855585568557855885598560856185628563856485658566856785688569857085718572857385748575857685778578857985808581858285838584858585868587858885898590859185928593859485958596859785988599860086018602860386048605860686078608860986108611861286138614861586168617861886198620862186228623862486258626862786288629863086318632863386348635863686378638863986408641864286438644864586468647864886498650865186528653865486558656865786588659866086618662866386648665866686678668866986708671867286738674867586768677867886798680868186828683868486858686868786888689869086918692869386948695869686978698869987008701870287038704870587068707870887098710871187128713871487158716871787188719872087218722872387248725872687278728872987308731873287338734873587368737873887398740874187428743874487458746874787488749875087518752875387548755875687578758875987608761876287638764876587668767876887698770877187728773877487758776877787788779878087818782878387848785878687878788878987908791879287938794879587968797879887998800880188028803880488058806880788088809881088118812881388148815881688178818881988208821882288238824882588268827882888298830883188328833883488358836883788388839884088418842884388448845884688478848884988508851885288538854
  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. namespace Tqdev\PhpCrudApi;
  8. // file: src/Psr/Http/Message/MessageInterface.php
  9. /**
  10. * HTTP messages consist of requests from a client to a server and responses
  11. * from a server to a client. This interface defines the methods common to
  12. * each.
  13. *
  14. * Messages are considered immutable; all methods that might change state MUST
  15. * be implemented such that they retain the internal state of the current
  16. * message and return an instance that contains the changed state.
  17. *
  18. * @link http://www.ietf.org/rfc/rfc7230.txt
  19. * @link http://www.ietf.org/rfc/rfc7231.txt
  20. */
  21. interface MessageInterface
  22. {
  23. /**
  24. * Retrieves the HTTP protocol version as a string.
  25. *
  26. * The string MUST contain only the HTTP version number (e.g., "1.1", "1.0").
  27. *
  28. * @return string HTTP protocol version.
  29. */
  30. public function getProtocolVersion();
  31. /**
  32. * Return an instance with the specified HTTP protocol version.
  33. *
  34. * The version string MUST contain only the HTTP version number (e.g.,
  35. * "1.1", "1.0").
  36. *
  37. * This method MUST be implemented in such a way as to retain the
  38. * immutability of the message, and MUST return an instance that has the
  39. * new protocol version.
  40. *
  41. * @param string $version HTTP protocol version
  42. * @return static
  43. */
  44. public function withProtocolVersion($version);
  45. /**
  46. * Retrieves all message header values.
  47. *
  48. * The keys represent the header name as it will be sent over the wire, and
  49. * each value is an array of strings associated with the header.
  50. *
  51. * // Represent the headers as a string
  52. * foreach ($message->getHeaders() as $name => $values) {
  53. * echo $name . ": " . implode(", ", $values);
  54. * }
  55. *
  56. * // Emit headers iteratively:
  57. * foreach ($message->getHeaders() as $name => $values) {
  58. * foreach ($values as $value) {
  59. * header(sprintf('%s: %s', $name, $value), false);
  60. * }
  61. * }
  62. *
  63. * While header names are not case-sensitive, getHeaders() will preserve the
  64. * exact case in which headers were originally specified.
  65. *
  66. * @return string[][] Returns an associative array of the message's headers. Each
  67. * key MUST be a header name, and each value MUST be an array of strings
  68. * for that header.
  69. */
  70. public function getHeaders();
  71. /**
  72. * Checks if a header exists by the given case-insensitive name.
  73. *
  74. * @param string $name Case-insensitive header field name.
  75. * @return bool Returns true if any header names match the given header
  76. * name using a case-insensitive string comparison. Returns false if
  77. * no matching header name is found in the message.
  78. */
  79. public function hasHeader($name);
  80. /**
  81. * Retrieves a message header value by the given case-insensitive name.
  82. *
  83. * This method returns an array of all the header values of the given
  84. * case-insensitive header name.
  85. *
  86. * If the header does not appear in the message, this method MUST return an
  87. * empty array.
  88. *
  89. * @param string $name Case-insensitive header field name.
  90. * @return string[] An array of string values as provided for the given
  91. * header. If the header does not appear in the message, this method MUST
  92. * return an empty array.
  93. */
  94. public function getHeader($name);
  95. /**
  96. * Retrieves a comma-separated string of the values for a single header.
  97. *
  98. * This method returns all of the header values of the given
  99. * case-insensitive header name as a string concatenated together using
  100. * a comma.
  101. *
  102. * NOTE: Not all header values may be appropriately represented using
  103. * comma concatenation. For such headers, use getHeader() instead
  104. * and supply your own delimiter when concatenating.
  105. *
  106. * If the header does not appear in the message, this method MUST return
  107. * an empty string.
  108. *
  109. * @param string $name Case-insensitive header field name.
  110. * @return string A string of values as provided for the given header
  111. * concatenated together using a comma. If the header does not appear in
  112. * the message, this method MUST return an empty string.
  113. */
  114. public function getHeaderLine($name);
  115. /**
  116. * Return an instance with the provided value replacing the specified header.
  117. *
  118. * While header names are case-insensitive, the casing of the header will
  119. * be preserved by this function, and returned from getHeaders().
  120. *
  121. * This method MUST be implemented in such a way as to retain the
  122. * immutability of the message, and MUST return an instance that has the
  123. * new and/or updated header and value.
  124. *
  125. * @param string $name Case-insensitive header field name.
  126. * @param string|string[] $value Header value(s).
  127. * @return static
  128. * @throws \InvalidArgumentException for invalid header names or values.
  129. */
  130. public function withHeader($name, $value);
  131. /**
  132. * Return an instance with the specified header appended with the given value.
  133. *
  134. * Existing values for the specified header will be maintained. The new
  135. * value(s) will be appended to the existing list. If the header did not
  136. * exist previously, it will be added.
  137. *
  138. * This method MUST be implemented in such a way as to retain the
  139. * immutability of the message, and MUST return an instance that has the
  140. * new header and/or value.
  141. *
  142. * @param string $name Case-insensitive header field name to add.
  143. * @param string|string[] $value Header value(s).
  144. * @return static
  145. * @throws \InvalidArgumentException for invalid header names or values.
  146. */
  147. public function withAddedHeader($name, $value);
  148. /**
  149. * Return an instance without the specified header.
  150. *
  151. * Header resolution MUST be done without case-sensitivity.
  152. *
  153. * This method MUST be implemented in such a way as to retain the
  154. * immutability of the message, and MUST return an instance that removes
  155. * the named header.
  156. *
  157. * @param string $name Case-insensitive header field name to remove.
  158. * @return static
  159. */
  160. public function withoutHeader($name);
  161. /**
  162. * Gets the body of the message.
  163. *
  164. * @return StreamInterface Returns the body as a stream.
  165. */
  166. public function getBody();
  167. /**
  168. * Return an instance with the specified message body.
  169. *
  170. * The body MUST be a StreamInterface object.
  171. *
  172. * This method MUST be implemented in such a way as to retain the
  173. * immutability of the message, and MUST return a new instance that has the
  174. * new body stream.
  175. *
  176. * @param StreamInterface $body Body.
  177. * @return static
  178. * @throws \InvalidArgumentException When the body is not valid.
  179. */
  180. public function withBody(StreamInterface $body);
  181. }
  182. // file: src/Psr/Http/Message/RequestFactoryInterface.php
  183. interface RequestFactoryInterface
  184. {
  185. /**
  186. * Create a new request.
  187. *
  188. * @param string $method The HTTP method associated with the request.
  189. * @param UriInterface|string $uri The URI associated with the request. If
  190. * the value is a string, the factory MUST create a UriInterface
  191. * instance based on it.
  192. *
  193. * @return RequestInterface
  194. */
  195. public function createRequest(string $method, $uri): RequestInterface;
  196. }
  197. // file: src/Psr/Http/Message/RequestInterface.php
  198. /**
  199. * Representation of an outgoing, client-side request.
  200. *
  201. * Per the HTTP specification, this interface includes properties for
  202. * each of the following:
  203. *
  204. * - Protocol version
  205. * - HTTP method
  206. * - URI
  207. * - Headers
  208. * - Message body
  209. *
  210. * During construction, implementations MUST attempt to set the Host header from
  211. * a provided URI if no Host header is provided.
  212. *
  213. * Requests are considered immutable; all methods that might change state MUST
  214. * be implemented such that they retain the internal state of the current
  215. * message and return an instance that contains the changed state.
  216. */
  217. interface RequestInterface extends MessageInterface
  218. {
  219. /**
  220. * Retrieves the message's request target.
  221. *
  222. * Retrieves the message's request-target either as it will appear (for
  223. * clients), as it appeared at request (for servers), or as it was
  224. * specified for the instance (see withRequestTarget()).
  225. *
  226. * In most cases, this will be the origin-form of the composed URI,
  227. * unless a value was provided to the concrete implementation (see
  228. * withRequestTarget() below).
  229. *
  230. * If no URI is available, and no request-target has been specifically
  231. * provided, this method MUST return the string "/".
  232. *
  233. * @return string
  234. */
  235. public function getRequestTarget();
  236. /**
  237. * Return an instance with the specific request-target.
  238. *
  239. * If the request needs a non-origin-form request-target — e.g., for
  240. * specifying an absolute-form, authority-form, or asterisk-form —
  241. * this method may be used to create an instance with the specified
  242. * request-target, verbatim.
  243. *
  244. * This method MUST be implemented in such a way as to retain the
  245. * immutability of the message, and MUST return an instance that has the
  246. * changed request target.
  247. *
  248. * @link http://tools.ietf.org/html/rfc7230#section-5.3 (for the various
  249. * request-target forms allowed in request messages)
  250. * @param mixed $requestTarget
  251. * @return static
  252. */
  253. public function withRequestTarget($requestTarget);
  254. /**
  255. * Retrieves the HTTP method of the request.
  256. *
  257. * @return string Returns the request method.
  258. */
  259. public function getMethod();
  260. /**
  261. * Return an instance with the provided HTTP method.
  262. *
  263. * While HTTP method names are typically all uppercase characters, HTTP
  264. * method names are case-sensitive and thus implementations SHOULD NOT
  265. * modify the given string.
  266. *
  267. * This method MUST be implemented in such a way as to retain the
  268. * immutability of the message, and MUST return an instance that has the
  269. * changed request method.
  270. *
  271. * @param string $method Case-sensitive method.
  272. * @return static
  273. * @throws \InvalidArgumentException for invalid HTTP methods.
  274. */
  275. public function withMethod($method);
  276. /**
  277. * Retrieves the URI instance.
  278. *
  279. * This method MUST return a UriInterface instance.
  280. *
  281. * @link http://tools.ietf.org/html/rfc3986#section-4.3
  282. * @return UriInterface Returns a UriInterface instance
  283. * representing the URI of the request.
  284. */
  285. public function getUri();
  286. /**
  287. * Returns an instance with the provided URI.
  288. *
  289. * This method MUST update the Host header of the returned request by
  290. * default if the URI contains a host component. If the URI does not
  291. * contain a host component, any pre-existing Host header MUST be carried
  292. * over to the returned request.
  293. *
  294. * You can opt-in to preserving the original state of the Host header by
  295. * setting `$preserveHost` to `true`. When `$preserveHost` is set to
  296. * `true`, this method interacts with the Host header in the following ways:
  297. *
  298. * - If the Host header is missing or empty, and the new URI contains
  299. * a host component, this method MUST update the Host header in the returned
  300. * request.
  301. * - If the Host header is missing or empty, and the new URI does not contain a
  302. * host component, this method MUST NOT update the Host header in the returned
  303. * request.
  304. * - If a Host header is present and non-empty, this method MUST NOT update
  305. * the Host header in the returned request.
  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 has the
  309. * new UriInterface instance.
  310. *
  311. * @link http://tools.ietf.org/html/rfc3986#section-4.3
  312. * @param UriInterface $uri New request URI to use.
  313. * @param bool $preserveHost Preserve the original state of the Host header.
  314. * @return static
  315. */
  316. public function withUri(UriInterface $uri, $preserveHost = false);
  317. }
  318. // file: src/Psr/Http/Message/ResponseFactoryInterface.php
  319. interface ResponseFactoryInterface
  320. {
  321. /**
  322. * Create a new response.
  323. *
  324. * @param int $code HTTP status code; defaults to 200
  325. * @param string $reasonPhrase Reason phrase to associate with status code
  326. * in generated response; if none is provided implementations MAY use
  327. * the defaults as suggested in the HTTP specification.
  328. *
  329. * @return ResponseInterface
  330. */
  331. public function createResponse(int $code = 200, string $reasonPhrase = ''): ResponseInterface;
  332. }
  333. // file: src/Psr/Http/Message/ResponseInterface.php
  334. /**
  335. * Representation of an outgoing, server-side response.
  336. *
  337. * Per the HTTP specification, this interface includes properties for
  338. * each of the following:
  339. *
  340. * - Protocol version
  341. * - Status code and reason phrase
  342. * - Headers
  343. * - Message body
  344. *
  345. * Responses are considered immutable; all methods that might change state MUST
  346. * be implemented such that they retain the internal state of the current
  347. * message and return an instance that contains the changed state.
  348. */
  349. interface ResponseInterface extends MessageInterface
  350. {
  351. /**
  352. * Gets the response status code.
  353. *
  354. * The status code is a 3-digit integer result code of the server's attempt
  355. * to understand and satisfy the request.
  356. *
  357. * @return int Status code.
  358. */
  359. public function getStatusCode();
  360. /**
  361. * Return an instance with the specified status code and, optionally, reason phrase.
  362. *
  363. * If no reason phrase is specified, implementations MAY choose to default
  364. * to the RFC 7231 or IANA recommended reason phrase for the response's
  365. * status code.
  366. *
  367. * This method MUST be implemented in such a way as to retain the
  368. * immutability of the message, and MUST return an instance that has the
  369. * updated status and reason phrase.
  370. *
  371. * @link http://tools.ietf.org/html/rfc7231#section-6
  372. * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
  373. * @param int $code The 3-digit integer result code to set.
  374. * @param string $reasonPhrase The reason phrase to use with the
  375. * provided status code; if none is provided, implementations MAY
  376. * use the defaults as suggested in the HTTP specification.
  377. * @return static
  378. * @throws \InvalidArgumentException For invalid status code arguments.
  379. */
  380. public function withStatus($code, $reasonPhrase = '');
  381. /**
  382. * Gets the response reason phrase associated with the status code.
  383. *
  384. * Because a reason phrase is not a required element in a response
  385. * status line, the reason phrase value MAY be null. Implementations MAY
  386. * choose to return the default RFC 7231 recommended reason phrase (or those
  387. * listed in the IANA HTTP Status Code Registry) for the response's
  388. * status code.
  389. *
  390. * @link http://tools.ietf.org/html/rfc7231#section-6
  391. * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
  392. * @return string Reason phrase; must return an empty string if none present.
  393. */
  394. public function getReasonPhrase();
  395. }
  396. // file: src/Psr/Http/Message/ServerRequestFactoryInterface.php
  397. interface ServerRequestFactoryInterface
  398. {
  399. /**
  400. * Create a new server request.
  401. *
  402. * Note that server-params are taken precisely as given - no parsing/processing
  403. * of the given values is performed, and, in particular, no attempt is made to
  404. * determine the HTTP method or URI, which must be provided explicitly.
  405. *
  406. * @param string $method The HTTP method associated with the request.
  407. * @param UriInterface|string $uri The URI associated with the request. If
  408. * the value is a string, the factory MUST create a UriInterface
  409. * instance based on it.
  410. * @param array $serverParams Array of SAPI parameters with which to seed
  411. * the generated request instance.
  412. *
  413. * @return ServerRequestInterface
  414. */
  415. public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface;
  416. }
  417. // file: src/Psr/Http/Message/ServerRequestInterface.php
  418. /**
  419. * Representation of an incoming, server-side HTTP request.
  420. *
  421. * Per the HTTP specification, this interface includes properties for
  422. * each of the following:
  423. *
  424. * - Protocol version
  425. * - HTTP method
  426. * - URI
  427. * - Headers
  428. * - Message body
  429. *
  430. * Additionally, it encapsulates all data as it has arrived to the
  431. * application from the CGI and/or PHP environment, including:
  432. *
  433. * - The values represented in $_SERVER.
  434. * - Any cookies provided (generally via $_COOKIE)
  435. * - Query string arguments (generally via $_GET, or as parsed via parse_str())
  436. * - Upload files, if any (as represented by $_FILES)
  437. * - Deserialized body parameters (generally from $_POST)
  438. *
  439. * $_SERVER values MUST be treated as immutable, as they represent application
  440. * state at the time of request; as such, no methods are provided to allow
  441. * modification of those values. The other values provide such methods, as they
  442. * can be restored from $_SERVER or the request body, and may need treatment
  443. * during the application (e.g., body parameters may be deserialized based on
  444. * content type).
  445. *
  446. * Additionally, this interface recognizes the utility of introspecting a
  447. * request to derive and match additional parameters (e.g., via URI path
  448. * matching, decrypting cookie values, deserializing non-form-encoded body
  449. * content, matching authorization headers to users, etc). These parameters
  450. * are stored in an "attributes" property.
  451. *
  452. * Requests are considered immutable; all methods that might change state MUST
  453. * be implemented such that they retain the internal state of the current
  454. * message and return an instance that contains the changed state.
  455. */
  456. interface ServerRequestInterface extends RequestInterface
  457. {
  458. /**
  459. * Retrieve server parameters.
  460. *
  461. * Retrieves data related to the incoming request environment,
  462. * typically derived from PHP's $_SERVER superglobal. The data IS NOT
  463. * REQUIRED to originate from $_SERVER.
  464. *
  465. * @return array
  466. */
  467. public function getServerParams();
  468. /**
  469. * Retrieve cookies.
  470. *
  471. * Retrieves cookies sent by the client to the server.
  472. *
  473. * The data MUST be compatible with the structure of the $_COOKIE
  474. * superglobal.
  475. *
  476. * @return array
  477. */
  478. public function getCookieParams();
  479. /**
  480. * Return an instance with the specified cookies.
  481. *
  482. * The data IS NOT REQUIRED to come from the $_COOKIE superglobal, but MUST
  483. * be compatible with the structure of $_COOKIE. Typically, this data will
  484. * be injected at instantiation.
  485. *
  486. * This method MUST NOT update the related Cookie header of the request
  487. * instance, nor related values in the server params.
  488. *
  489. * This method MUST be implemented in such a way as to retain the
  490. * immutability of the message, and MUST return an instance that has the
  491. * updated cookie values.
  492. *
  493. * @param array $cookies Array of key/value pairs representing cookies.
  494. * @return static
  495. */
  496. public function withCookieParams(array $cookies);
  497. /**
  498. * Retrieve query string arguments.
  499. *
  500. * Retrieves the deserialized query string arguments, if any.
  501. *
  502. * Note: the query params might not be in sync with the URI or server
  503. * params. If you need to ensure you are only getting the original
  504. * values, you may need to parse the query string from `getUri()->getQuery()`
  505. * or from the `QUERY_STRING` server param.
  506. *
  507. * @return array
  508. */
  509. public function getQueryParams();
  510. /**
  511. * Return an instance with the specified query string arguments.
  512. *
  513. * These values SHOULD remain immutable over the course of the incoming
  514. * request. They MAY be injected during instantiation, such as from PHP's
  515. * $_GET superglobal, or MAY be derived from some other value such as the
  516. * URI. In cases where the arguments are parsed from the URI, the data
  517. * MUST be compatible with what PHP's parse_str() would return for
  518. * purposes of how duplicate query parameters are handled, and how nested
  519. * sets are handled.
  520. *
  521. * Setting query string arguments MUST NOT change the URI stored by the
  522. * request, nor the values in the server params.
  523. *
  524. * This method MUST be implemented in such a way as to retain the
  525. * immutability of the message, and MUST return an instance that has the
  526. * updated query string arguments.
  527. *
  528. * @param array $query Array of query string arguments, typically from
  529. * $_GET.
  530. * @return static
  531. */
  532. public function withQueryParams(array $query);
  533. /**
  534. * Retrieve normalized file upload data.
  535. *
  536. * This method returns upload metadata in a normalized tree, with each leaf
  537. * an instance of Psr\Http\Message\UploadedFileInterface.
  538. *
  539. * These values MAY be prepared from $_FILES or the message body during
  540. * instantiation, or MAY be injected via withUploadedFiles().
  541. *
  542. * @return array An array tree of UploadedFileInterface instances; an empty
  543. * array MUST be returned if no data is present.
  544. */
  545. public function getUploadedFiles();
  546. /**
  547. * Create a new instance with the specified uploaded files.
  548. *
  549. * This method MUST be implemented in such a way as to retain the
  550. * immutability of the message, and MUST return an instance that has the
  551. * updated body parameters.
  552. *
  553. * @param array $uploadedFiles An array tree of UploadedFileInterface instances.
  554. * @return static
  555. * @throws \InvalidArgumentException if an invalid structure is provided.
  556. */
  557. public function withUploadedFiles(array $uploadedFiles);
  558. /**
  559. * Retrieve any parameters provided in the request body.
  560. *
  561. * If the request Content-Type is either application/x-www-form-urlencoded
  562. * or multipart/form-data, and the request method is POST, this method MUST
  563. * return the contents of $_POST.
  564. *
  565. * Otherwise, this method may return any results of deserializing
  566. * the request body content; as parsing returns structured content, the
  567. * potential types MUST be arrays or objects only. A null value indicates
  568. * the absence of body content.
  569. *
  570. * @return null|array|object The deserialized body parameters, if any.
  571. * These will typically be an array or object.
  572. */
  573. public function getParsedBody();
  574. /**
  575. * Return an instance with the specified body parameters.
  576. *
  577. * These MAY be injected during instantiation.
  578. *
  579. * If the request Content-Type is either application/x-www-form-urlencoded
  580. * or multipart/form-data, and the request method is POST, use this method
  581. * ONLY to inject the contents of $_POST.
  582. *
  583. * The data IS NOT REQUIRED to come from $_POST, but MUST be the results of
  584. * deserializing the request body content. Deserialization/parsing returns
  585. * structured data, and, as such, this method ONLY accepts arrays or objects,
  586. * or a null value if nothing was available to parse.
  587. *
  588. * As an example, if content negotiation determines that the request data
  589. * is a JSON payload, this method could be used to create a request
  590. * instance with the deserialized parameters.
  591. *
  592. * This method MUST be implemented in such a way as to retain the
  593. * immutability of the message, and MUST return an instance that has the
  594. * updated body parameters.
  595. *
  596. * @param null|array|object $data The deserialized body data. This will
  597. * typically be in an array or object.
  598. * @return static
  599. * @throws \InvalidArgumentException if an unsupported argument type is
  600. * provided.
  601. */
  602. public function withParsedBody($data);
  603. /**
  604. * Retrieve attributes derived from the request.
  605. *
  606. * The request "attributes" may be used to allow injection of any
  607. * parameters derived from the request: e.g., the results of path
  608. * match operations; the results of decrypting cookies; the results of
  609. * deserializing non-form-encoded message bodies; etc. Attributes
  610. * will be application and request specific, and CAN be mutable.
  611. *
  612. * @return array Attributes derived from the request.
  613. */
  614. public function getAttributes();
  615. /**
  616. * Retrieve a single derived request attribute.
  617. *
  618. * Retrieves a single derived request attribute as described in
  619. * getAttributes(). If the attribute has not been previously set, returns
  620. * the default value as provided.
  621. *
  622. * This method obviates the need for a hasAttribute() method, as it allows
  623. * specifying a default value to return if the attribute is not found.
  624. *
  625. * @see getAttributes()
  626. * @param string $name The attribute name.
  627. * @param mixed $default Default value to return if the attribute does not exist.
  628. * @return mixed
  629. */
  630. public function getAttribute($name, $default = null);
  631. /**
  632. * Return an instance with the specified derived request attribute.
  633. *
  634. * This method allows setting a single derived request attribute as
  635. * described in getAttributes().
  636. *
  637. * This method MUST be implemented in such a way as to retain the
  638. * immutability of the message, and MUST return an instance that has the
  639. * updated attribute.
  640. *
  641. * @see getAttributes()
  642. * @param string $name The attribute name.
  643. * @param mixed $value The value of the attribute.
  644. * @return static
  645. */
  646. public function withAttribute($name, $value);
  647. /**
  648. * Return an instance that removes the specified derived request attribute.
  649. *
  650. * This method allows removing a single derived request attribute as
  651. * described in getAttributes().
  652. *
  653. * This method MUST be implemented in such a way as to retain the
  654. * immutability of the message, and MUST return an instance that removes
  655. * the attribute.
  656. *
  657. * @see getAttributes()
  658. * @param string $name The attribute name.
  659. * @return static
  660. */
  661. public function withoutAttribute($name);
  662. }
  663. // file: src/Psr/Http/Message/StreamFactoryInterface.php
  664. interface StreamFactoryInterface
  665. {
  666. /**
  667. * Create a new stream from a string.
  668. *
  669. * The stream SHOULD be created with a temporary resource.
  670. *
  671. * @param string $content String content with which to populate the stream.
  672. *
  673. * @return StreamInterface
  674. */
  675. public function createStream(string $content = ''): StreamInterface;
  676. /**
  677. * Create a stream from an existing file.
  678. *
  679. * The file MUST be opened using the given mode, which may be any mode
  680. * supported by the `fopen` function.
  681. *
  682. * The `$filename` MAY be any string supported by `fopen()`.
  683. *
  684. * @param string $filename Filename or stream URI to use as basis of stream.
  685. * @param string $mode Mode with which to open the underlying filename/stream.
  686. *
  687. * @return StreamInterface
  688. */
  689. public function createStreamFromFile(string $filename, string $mode = 'r'): StreamInterface;
  690. /**
  691. * Create a new stream from an existing resource.
  692. *
  693. * The stream MUST be readable and may be writable.
  694. *
  695. * @param resource $resource PHP resource to use as basis of stream.
  696. *
  697. * @return StreamInterface
  698. */
  699. public function createStreamFromResource($resource): StreamInterface;
  700. }
  701. // file: src/Psr/Http/Message/StreamInterface.php
  702. /**
  703. * Describes a data stream.
  704. *
  705. * Typically, an instance will wrap a PHP stream; this interface provides
  706. * a wrapper around the most common operations, including serialization of
  707. * the entire stream to a string.
  708. */
  709. interface StreamInterface
  710. {
  711. /**
  712. * Reads all data from the stream into a string, from the beginning to end.
  713. *
  714. * This method MUST attempt to seek to the beginning of the stream before
  715. * reading data and read the stream until the end is reached.
  716. *
  717. * Warning: This could attempt to load a large amount of data into memory.
  718. *
  719. * This method MUST NOT raise an exception in order to conform with PHP's
  720. * string casting operations.
  721. *
  722. * @see http://php.net/manual/en/language.oop5.magic.php#object.tostring
  723. * @return string
  724. */
  725. public function __toString();
  726. /**
  727. * Closes the stream and any underlying resources.
  728. *
  729. * @return void
  730. */
  731. public function close();
  732. /**
  733. * Separates any underlying resources from the stream.
  734. *
  735. * After the stream has been detached, the stream is in an unusable state.
  736. *
  737. * @return resource|null Underlying PHP stream, if any
  738. */
  739. public function detach();
  740. /**
  741. * Get the size of the stream if known.
  742. *
  743. * @return int|null Returns the size in bytes if known, or null if unknown.
  744. */
  745. public function getSize();
  746. /**
  747. * Returns the current position of the file read/write pointer
  748. *
  749. * @return int Position of the file pointer
  750. * @throws \RuntimeException on error.
  751. */
  752. public function tell();
  753. /**
  754. * Returns true if the stream is at the end of the stream.
  755. *
  756. * @return bool
  757. */
  758. public function eof();
  759. /**
  760. * Returns whether or not the stream is seekable.
  761. *
  762. * @return bool
  763. */
  764. public function isSeekable();
  765. /**
  766. * Seek to a position in the stream.
  767. *
  768. * @link http://www.php.net/manual/en/function.fseek.php
  769. * @param int $offset Stream offset
  770. * @param int $whence Specifies how the cursor position will be calculated
  771. * based on the seek offset. Valid values are identical to the built-in
  772. * PHP $whence values for `fseek()`. SEEK_SET: Set position equal to
  773. * offset bytes SEEK_CUR: Set position to current location plus offset
  774. * SEEK_END: Set position to end-of-stream plus offset.
  775. * @throws \RuntimeException on failure.
  776. */
  777. public function seek($offset, $whence = SEEK_SET);
  778. /**
  779. * Seek to the beginning of the stream.
  780. *
  781. * If the stream is not seekable, this method will raise an exception;
  782. * otherwise, it will perform a seek(0).
  783. *
  784. * @see seek()
  785. * @link http://www.php.net/manual/en/function.fseek.php
  786. * @throws \RuntimeException on failure.
  787. */
  788. public function rewind();
  789. /**
  790. * Returns whether or not the stream is writable.
  791. *
  792. * @return bool
  793. */
  794. public function isWritable();
  795. /**
  796. * Write data to the stream.
  797. *
  798. * @param string $string The string that is to be written.
  799. * @return int Returns the number of bytes written to the stream.
  800. * @throws \RuntimeException on failure.
  801. */
  802. public function write($string);
  803. /**
  804. * Returns whether or not the stream is readable.
  805. *
  806. * @return bool
  807. */
  808. public function isReadable();
  809. /**
  810. * Read data from the stream.
  811. *
  812. * @param int $length Read up to $length bytes from the object and return
  813. * them. Fewer than $length bytes may be returned if underlying stream
  814. * call returns fewer bytes.
  815. * @return string Returns the data read from the stream, or an empty string
  816. * if no bytes are available.
  817. * @throws \RuntimeException if an error occurs.
  818. */
  819. public function read($length);
  820. /**
  821. * Returns the remaining contents in a string
  822. *
  823. * @return string
  824. * @throws \RuntimeException if unable to read or an error occurs while
  825. * reading.
  826. */
  827. public function getContents();
  828. /**
  829. * Get stream metadata as an associative array or retrieve a specific key.
  830. *
  831. * The keys returned are identical to the keys returned from PHP's
  832. * stream_get_meta_data() function.
  833. *
  834. * @link http://php.net/manual/en/function.stream-get-meta-data.php
  835. * @param string $key Specific metadata to retrieve.
  836. * @return array|mixed|null Returns an associative array if no key is
  837. * provided. Returns a specific key value if a key is provided and the
  838. * value is found, or null if the key is not found.
  839. */
  840. public function getMetadata($key = null);
  841. }
  842. // file: src/Psr/Http/Message/UploadedFileFactoryInterface.php
  843. interface UploadedFileFactoryInterface
  844. {
  845. /**
  846. * Create a new uploaded file.
  847. *
  848. * If a size is not provided it will be determined by checking the size of
  849. * the file.
  850. *
  851. * @see http://php.net/manual/features.file-upload.post-method.php
  852. * @see http://php.net/manual/features.file-upload.errors.php
  853. *
  854. * @param StreamInterface $stream Underlying stream representing the
  855. * uploaded file content.
  856. * @param int $size in bytes
  857. * @param int $error PHP file upload error
  858. * @param string $clientFilename Filename as provided by the client, if any.
  859. * @param string $clientMediaType Media type as provided by the client, if any.
  860. *
  861. * @return UploadedFileInterface
  862. *
  863. * @throws \InvalidArgumentException If the file resource is not readable.
  864. */
  865. public function createUploadedFile(
  866. StreamInterface $stream,
  867. int $size = null,
  868. int $error = \UPLOAD_ERR_OK,
  869. string $clientFilename = null,
  870. string $clientMediaType = null
  871. ): UploadedFileInterface;
  872. }
  873. // file: src/Psr/Http/Message/UploadedFileInterface.php
  874. /**
  875. * Value object representing a file uploaded through an HTTP request.
  876. *
  877. * Instances of this interface are considered immutable; all methods that
  878. * might change state MUST be implemented such that they retain the internal
  879. * state of the current instance and return an instance that contains the
  880. * changed state.
  881. */
  882. interface UploadedFileInterface
  883. {
  884. /**
  885. * Retrieve a stream representing the uploaded file.
  886. *
  887. * This method MUST return a StreamInterface instance, representing the
  888. * uploaded file. The purpose of this method is to allow utilizing native PHP
  889. * stream functionality to manipulate the file upload, such as
  890. * stream_copy_to_stream() (though the result will need to be decorated in a
  891. * native PHP stream wrapper to work with such functions).
  892. *
  893. * If the moveTo() method has been called previously, this method MUST raise
  894. * an exception.
  895. *
  896. * @return StreamInterface Stream representation of the uploaded file.
  897. * @throws \RuntimeException in cases when no stream is available or can be
  898. * created.
  899. */
  900. public function getStream();
  901. /**
  902. * Move the uploaded file to a new location.
  903. *
  904. * Use this method as an alternative to move_uploaded_file(). This method is
  905. * guaranteed to work in both SAPI and non-SAPI environments.
  906. * Implementations must determine which environment they are in, and use the
  907. * appropriate method (move_uploaded_file(), rename(), or a stream
  908. * operation) to perform the operation.
  909. *
  910. * $targetPath may be an absolute path, or a relative path. If it is a
  911. * relative path, resolution should be the same as used by PHP's rename()
  912. * function.
  913. *
  914. * The original file or stream MUST be removed on completion.
  915. *
  916. * If this method is called more than once, any subsequent calls MUST raise
  917. * an exception.
  918. *
  919. * When used in an SAPI environment where $_FILES is populated, when writing
  920. * files via moveTo(), is_uploaded_file() and move_uploaded_file() SHOULD be
  921. * used to ensure permissions and upload status are verified correctly.
  922. *
  923. * If you wish to move to a stream, use getStream(), as SAPI operations
  924. * cannot guarantee writing to stream destinations.
  925. *
  926. * @see http://php.net/is_uploaded_file
  927. * @see http://php.net/move_uploaded_file
  928. * @param string $targetPath Path to which to move the uploaded file.
  929. * @throws \InvalidArgumentException if the $targetPath specified is invalid.
  930. * @throws \RuntimeException on any error during the move operation, or on
  931. * the second or subsequent call to the method.
  932. */
  933. public function moveTo($targetPath);
  934. /**
  935. * Retrieve the file size.
  936. *
  937. * Implementations SHOULD return the value stored in the "size" key of
  938. * the file in the $_FILES array if available, as PHP calculates this based
  939. * on the actual size transmitted.
  940. *
  941. * @return int|null The file size in bytes or null if unknown.
  942. */
  943. public function getSize();
  944. /**
  945. * Retrieve the error associated with the uploaded file.
  946. *
  947. * The return value MUST be one of PHP's UPLOAD_ERR_XXX constants.
  948. *
  949. * If the file was uploaded successfully, this method MUST return
  950. * UPLOAD_ERR_OK.
  951. *
  952. * Implementations SHOULD return the value stored in the "error" key of
  953. * the file in the $_FILES array.
  954. *
  955. * @see http://php.net/manual/en/features.file-upload.errors.php
  956. * @return int One of PHP's UPLOAD_ERR_XXX constants.
  957. */
  958. public function getError();
  959. /**
  960. * Retrieve the filename sent by the client.
  961. *
  962. * Do not trust the value returned by this method. A client could send
  963. * a malicious filename with the intention to corrupt or hack your
  964. * application.
  965. *
  966. * Implementations SHOULD return the value stored in the "name" key of
  967. * the file in the $_FILES array.
  968. *
  969. * @return string|null The filename sent by the client or null if none
  970. * was provided.
  971. */
  972. public function getClientFilename();
  973. /**
  974. * Retrieve the media type sent by the client.
  975. *
  976. * Do not trust the value returned by this method. A client could send
  977. * a malicious media type with the intention to corrupt or hack your
  978. * application.
  979. *
  980. * Implementations SHOULD return the value stored in the "type" key of
  981. * the file in the $_FILES array.
  982. *
  983. * @return string|null The media type sent by the client or null if none
  984. * was provided.
  985. */
  986. public function getClientMediaType();
  987. }
  988. // file: src/Psr/Http/Message/UriFactoryInterface.php
  989. interface UriFactoryInterface
  990. {
  991. /**
  992. * Create a new URI.
  993. *
  994. * @param string $uri
  995. *
  996. * @return UriInterface
  997. *
  998. * @throws \InvalidArgumentException If the given URI cannot be parsed.
  999. */
  1000. public function createUri(string $uri = ''): UriInterface;
  1001. }
  1002. // file: src/Psr/Http/Message/UriInterface.php
  1003. /**
  1004. * Value object representing a URI.
  1005. *
  1006. * This interface is meant to represent URIs according to RFC 3986 and to
  1007. * provide methods for most common operations. Additional functionality for
  1008. * working with URIs can be provided on top of the interface or externally.
  1009. * Its primary use is for HTTP requests, but may also be used in other
  1010. * contexts.
  1011. *
  1012. * Instances of this interface are considered immutable; all methods that
  1013. * might change state MUST be implemented such that they retain the internal
  1014. * state of the current instance and return an instance that contains the
  1015. * changed state.
  1016. *
  1017. * Typically the Host header will be also be present in the request message.
  1018. * For server-side requests, the scheme will typically be discoverable in the
  1019. * server parameters.
  1020. *
  1021. * @link http://tools.ietf.org/html/rfc3986 (the URI specification)
  1022. */
  1023. interface UriInterface
  1024. {
  1025. /**
  1026. * Retrieve the scheme component of the URI.
  1027. *
  1028. * If no scheme is present, this method MUST return an empty string.
  1029. *
  1030. * The value returned MUST be normalized to lowercase, per RFC 3986
  1031. * Section 3.1.
  1032. *
  1033. * The trailing ":" character is not part of the scheme and MUST NOT be
  1034. * added.
  1035. *
  1036. * @see https://tools.ietf.org/html/rfc3986#section-3.1
  1037. * @return string The URI scheme.
  1038. */
  1039. public function getScheme();
  1040. /**
  1041. * Retrieve the authority component of the URI.
  1042. *
  1043. * If no authority information is present, this method MUST return an empty
  1044. * string.
  1045. *
  1046. * The authority syntax of the URI is:
  1047. *
  1048. * <pre>
  1049. * [user-info@]host[:port]
  1050. * </pre>
  1051. *
  1052. * If the port component is not set or is the standard port for the current
  1053. * scheme, it SHOULD NOT be included.
  1054. *
  1055. * @see https://tools.ietf.org/html/rfc3986#section-3.2
  1056. * @return string The URI authority, in "[user-info@]host[:port]" format.
  1057. */
  1058. public function getAuthority();
  1059. /**
  1060. * Retrieve the user information component of the URI.
  1061. *
  1062. * If no user information is present, this method MUST return an empty
  1063. * string.
  1064. *
  1065. * If a user is present in the URI, this will return that value;
  1066. * additionally, if the password is also present, it will be appended to the
  1067. * user value, with a colon (":") separating the values.
  1068. *
  1069. * The trailing "@" character is not part of the user information and MUST
  1070. * NOT be added.
  1071. *
  1072. * @return string The URI user information, in "username[:password]" format.
  1073. */
  1074. public function getUserInfo();
  1075. /**
  1076. * Retrieve the host component of the URI.
  1077. *
  1078. * If no host is present, this method MUST return an empty string.
  1079. *
  1080. * The value returned MUST be normalized to lowercase, per RFC 3986
  1081. * Section 3.2.2.
  1082. *
  1083. * @see http://tools.ietf.org/html/rfc3986#section-3.2.2
  1084. * @return string The URI host.
  1085. */
  1086. public function getHost();
  1087. /**
  1088. * Retrieve the port component of the URI.
  1089. *
  1090. * If a port is present, and it is non-standard for the current scheme,
  1091. * this method MUST return it as an integer. If the port is the standard port
  1092. * used with the current scheme, this method SHOULD return null.
  1093. *
  1094. * If no port is present, and no scheme is present, this method MUST return
  1095. * a null value.
  1096. *
  1097. * If no port is present, but a scheme is present, this method MAY return
  1098. * the standard port for that scheme, but SHOULD return null.
  1099. *
  1100. * @return null|int The URI port.
  1101. */
  1102. public function getPort();
  1103. /**
  1104. * Retrieve the path component of the URI.
  1105. *
  1106. * The path can either be empty or absolute (starting with a slash) or
  1107. * rootless (not starting with a slash). Implementations MUST support all
  1108. * three syntaxes.
  1109. *
  1110. * Normally, the empty path "" and absolute path "/" are considered equal as
  1111. * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically
  1112. * do this normalization because in contexts with a trimmed base path, e.g.
  1113. * the front controller, this difference becomes significant. It's the task
  1114. * of the user to handle both "" and "/".
  1115. *
  1116. * The value returned MUST be percent-encoded, but MUST NOT double-encode
  1117. * any characters. To determine what characters to encode, please refer to
  1118. * RFC 3986, Sections 2 and 3.3.
  1119. *
  1120. * As an example, if the value should include a slash ("/") not intended as
  1121. * delimiter between path segments, that value MUST be passed in encoded
  1122. * form (e.g., "%2F") to the instance.
  1123. *
  1124. * @see https://tools.ietf.org/html/rfc3986#section-2
  1125. * @see https://tools.ietf.org/html/rfc3986#section-3.3
  1126. * @return string The URI path.
  1127. */
  1128. public function getPath();
  1129. /**
  1130. * Retrieve the query string of the URI.
  1131. *
  1132. * If no query string is present, this method MUST return an empty string.
  1133. *
  1134. * The leading "?" character is not part of the query and MUST NOT be
  1135. * added.
  1136. *
  1137. * The value returned MUST be percent-encoded, but MUST NOT double-encode
  1138. * any characters. To determine what characters to encode, please refer to
  1139. * RFC 3986, Sections 2 and 3.4.
  1140. *
  1141. * As an example, if a value in a key/value pair of the query string should
  1142. * include an ampersand ("&") not intended as a delimiter between values,
  1143. * that value MUST be passed in encoded form (e.g., "%26") to the instance.
  1144. *
  1145. * @see https://tools.ietf.org/html/rfc3986#section-2
  1146. * @see https://tools.ietf.org/html/rfc3986#section-3.4
  1147. * @return string The URI query string.
  1148. */
  1149. public function getQuery();
  1150. /**
  1151. * Retrieve the fragment component of the URI.
  1152. *
  1153. * If no fragment is present, this method MUST return an empty string.
  1154. *
  1155. * The leading "#" character is not part of the fragment and MUST NOT be
  1156. * added.
  1157. *
  1158. * The value returned MUST be percent-encoded, but MUST NOT double-encode
  1159. * any characters. To determine what characters to encode, please refer to
  1160. * RFC 3986, Sections 2 and 3.5.
  1161. *
  1162. * @see https://tools.ietf.org/html/rfc3986#section-2
  1163. * @see https://tools.ietf.org/html/rfc3986#section-3.5
  1164. * @return string The URI fragment.
  1165. */
  1166. public function getFragment();
  1167. /**
  1168. * Return an instance with the specified scheme.
  1169. *
  1170. * This method MUST retain the state of the current instance, and return
  1171. * an instance that contains the specified scheme.
  1172. *
  1173. * Implementations MUST support the schemes "http" and "https" case
  1174. * insensitively, and MAY accommodate other schemes if required.
  1175. *
  1176. * An empty scheme is equivalent to removing the scheme.
  1177. *
  1178. * @param string $scheme The scheme to use with the new instance.
  1179. * @return static A new instance with the specified scheme.
  1180. * @throws \InvalidArgumentException for invalid or unsupported schemes.
  1181. */
  1182. public function withScheme($scheme);
  1183. /**
  1184. * Return an instance with the specified user information.
  1185. *
  1186. * This method MUST retain the state of the current instance, and return
  1187. * an instance that contains the specified user information.
  1188. *
  1189. * Password is optional, but the user information MUST include the
  1190. * user; an empty string for the user is equivalent to removing user
  1191. * information.
  1192. *
  1193. * @param string $user The user name to use for authority.
  1194. * @param null|string $password The password associated with $user.
  1195. * @return static A new instance with the specified user information.
  1196. */
  1197. public function withUserInfo($user, $password = null);
  1198. /**
  1199. * Return an instance with the specified host.
  1200. *
  1201. * This method MUST retain the state of the current instance, and return
  1202. * an instance that contains the specified host.
  1203. *
  1204. * An empty host value is equivalent to removing the host.
  1205. *
  1206. * @param string $host The hostname to use with the new instance.
  1207. * @return static A new instance with the specified host.
  1208. * @throws \InvalidArgumentException for invalid hostnames.
  1209. */
  1210. public function withHost($host);
  1211. /**
  1212. * Return an instance with the specified port.
  1213. *
  1214. * This method MUST retain the state of the current instance, and return
  1215. * an instance that contains the specified port.
  1216. *
  1217. * Implementations MUST raise an exception for ports outside the
  1218. * established TCP and UDP port ranges.
  1219. *
  1220. * A null value provided for the port is equivalent to removing the port
  1221. * information.
  1222. *
  1223. * @param null|int $port The port to use with the new instance; a null value
  1224. * removes the port information.
  1225. * @return static A new instance with the specified port.
  1226. * @throws \InvalidArgumentException for invalid ports.
  1227. */
  1228. public function withPort($port);
  1229. /**
  1230. * Return an instance with the specified path.
  1231. *
  1232. * This method MUST retain the state of the current instance, and return
  1233. * an instance that contains the specified path.
  1234. *
  1235. * The path can either be empty or absolute (starting with a slash) or
  1236. * rootless (not starting with a slash). Implementations MUST support all
  1237. * three syntaxes.
  1238. *
  1239. * If the path is intended to be domain-relative rather than path relative then
  1240. * it must begin with a slash ("/"). Paths not starting with a slash ("/")
  1241. * are assumed to be relative to some base path known to the application or
  1242. * consumer.
  1243. *
  1244. * Users can provide both encoded and decoded path characters.
  1245. * Implementations ensure the correct encoding as outlined in getPath().
  1246. *
  1247. * @param string $path The path to use with the new instance.
  1248. * @return static A new instance with the specified path.
  1249. * @throws \InvalidArgumentException for invalid paths.
  1250. */
  1251. public function withPath($path);
  1252. /**
  1253. * Return an instance with the specified query string.
  1254. *
  1255. * This method MUST retain the state of the current instance, and return
  1256. * an instance that contains the specified query string.
  1257. *
  1258. * Users can provide both encoded and decoded query characters.
  1259. * Implementations ensure the correct encoding as outlined in getQuery().
  1260. *
  1261. * An empty query string value is equivalent to removing the query string.
  1262. *
  1263. * @param string $query The query string to use with the new instance.
  1264. * @return static A new instance with the specified query string.
  1265. * @throws \InvalidArgumentException for invalid query strings.
  1266. */
  1267. public function withQuery($query);
  1268. /**
  1269. * Return an instance with the specified URI fragment.
  1270. *
  1271. * This method MUST retain the state of the current instance, and return
  1272. * an instance that contains the specified URI fragment.
  1273. *
  1274. * Users can provide both encoded and decoded fragment characters.
  1275. * Implementations ensure the correct encoding as outlined in getFragment().
  1276. *
  1277. * An empty fragment value is equivalent to removing the fragment.
  1278. *
  1279. * @param string $fragment The fragment to use with the new instance.
  1280. * @return static A new instance with the specified fragment.
  1281. */
  1282. public function withFragment($fragment);
  1283. /**
  1284. * Return the string representation as a URI reference.
  1285. *
  1286. * Depending on which components of the URI are present, the resulting
  1287. * string is either a full URI or relative reference according to RFC 3986,
  1288. * Section 4.1. The method concatenates the various components of the URI,
  1289. * using the appropriate delimiters:
  1290. *
  1291. * - If a scheme is present, it MUST be suffixed by ":".
  1292. * - If an authority is present, it MUST be prefixed by "//".
  1293. * - The path can be concatenated without delimiters. But there are two
  1294. * cases where the path has to be adjusted to make the URI reference
  1295. * valid as PHP does not allow to throw an exception in __toString():
  1296. * - If the path is rootless and an authority is present, the path MUST
  1297. * be prefixed by "/".
  1298. * - If the path is starting with more than one "/" and no authority is
  1299. * present, the starting slashes MUST be reduced to one.
  1300. * - If a query is present, it MUST be prefixed by "?".
  1301. * - If a fragment is present, it MUST be prefixed by "#".
  1302. *
  1303. * @see http://tools.ietf.org/html/rfc3986#section-4.1
  1304. * @return string
  1305. */
  1306. public function __toString();
  1307. }
  1308. // file: src/Nyholm/Psr7/Factory/HttplugFactory.php
  1309. /**
  1310. * @author Tobias Nyholm <tobias.nyholm@gmail.com>
  1311. * @author Martijn van der Ven <martijn@vanderven.se>
  1312. */
  1313. final class HttplugFactory implements MessageFactory, StreamFactory, UriFactory
  1314. {
  1315. public function createRequest($method, $uri, array $headers = [], $body = null, $protocolVersion = '1.1')
  1316. {
  1317. return new Request($method, $uri, $headers, $body, $protocolVersion);
  1318. }
  1319. public function createResponse($statusCode = 200, $reasonPhrase = null, array $headers = [], $body = null, $version = '1.1')
  1320. {
  1321. return new Response((int) $statusCode, $headers, $body, $version, $reasonPhrase);
  1322. }
  1323. public function createStream($body = null)
  1324. {
  1325. return Stream::create($body ?? '');
  1326. }
  1327. public function createUri($uri = ''): UriInterface
  1328. {
  1329. if ($uri instanceof UriInterface) {
  1330. return $uri;
  1331. }
  1332. return new Uri($uri);
  1333. }
  1334. }
  1335. // file: src/Nyholm/Psr7/Factory/Psr17Factory.php
  1336. /**
  1337. * @author Tobias Nyholm <tobias.nyholm@gmail.com>
  1338. * @author Martijn van der Ven <martijn@vanderven.se>
  1339. */
  1340. final class Psr17Factory implements RequestFactoryInterface, ResponseFactoryInterface, ServerRequestFactoryInterface, StreamFactoryInterface, UploadedFileFactoryInterface, UriFactoryInterface
  1341. {
  1342. public function createRequest(string $method, $uri): RequestInterface
  1343. {
  1344. return new Request($method, $uri);
  1345. }
  1346. public function createResponse(int $code = 200, string $reasonPhrase = ''): ResponseInterface
  1347. {
  1348. return new Response($code, [], null, '1.1', $reasonPhrase);
  1349. }
  1350. public function createStream(string $content = ''): StreamInterface
  1351. {
  1352. return Stream::create($content);
  1353. }
  1354. public function createStreamFromFile(string $filename, string $mode = 'r'): StreamInterface
  1355. {
  1356. $resource = @\fopen($filename, $mode);
  1357. if (false === $resource) {
  1358. if ('' === $mode || false === \in_array($mode[0], ['r', 'w', 'a', 'x', 'c'])) {
  1359. throw new \InvalidArgumentException('The mode ' . $mode . ' is invalid.');
  1360. }
  1361. throw new \RuntimeException('The file ' . $filename . ' cannot be opened.');
  1362. }
  1363. return Stream::create($resource);
  1364. }
  1365. public function createStreamFromResource($resource): StreamInterface
  1366. {
  1367. return Stream::create($resource);
  1368. }
  1369. public function createUploadedFile(StreamInterface $stream, int $size = null, int $error = \UPLOAD_ERR_OK, string $clientFilename = null, string $clientMediaType = null): UploadedFileInterface
  1370. {
  1371. if (null === $size) {
  1372. $size = $stream->getSize();
  1373. }
  1374. return new UploadedFile($stream, $size, $error, $clientFilename, $clientMediaType);
  1375. }
  1376. public function createUri(string $uri = ''): UriInterface
  1377. {
  1378. return new Uri($uri);
  1379. }
  1380. public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface
  1381. {
  1382. return new ServerRequest($method, $uri, [], null, '1.1', $serverParams);
  1383. }
  1384. }
  1385. // file: src/Nyholm/Psr7/MessageTrait.php
  1386. /**
  1387. * Trait implementing functionality common to requests and responses.
  1388. *
  1389. * @author Michael Dowling and contributors to guzzlehttp/psr7
  1390. * @author Tobias Nyholm <tobias.nyholm@gmail.com>
  1391. * @author Martijn van der Ven <martijn@vanderven.se>
  1392. *
  1393. * @internal should not be used outside of Nyholm/Psr7 as it does not fall under our BC promise
  1394. */
  1395. trait MessageTrait
  1396. {
  1397. /** @var array Map of all registered headers, as original name => array of values */
  1398. private $headers = [];
  1399. /** @var array Map of lowercase header name => original name at registration */
  1400. private $headerNames = [];
  1401. /** @var string */
  1402. private $protocol = '1.1';
  1403. /** @var StreamInterface|null */
  1404. private $stream;
  1405. public function getProtocolVersion(): string
  1406. {
  1407. return $this->protocol;
  1408. }
  1409. public function withProtocolVersion($version): self
  1410. {
  1411. if ($this->protocol === $version) {
  1412. return $this;
  1413. }
  1414. $new = clone $this;
  1415. $new->protocol = $version;
  1416. return $new;
  1417. }
  1418. public function getHeaders(): array
  1419. {
  1420. return $this->headers;
  1421. }
  1422. public function hasHeader($header): bool
  1423. {
  1424. return isset($this->headerNames[\strtolower($header)]);
  1425. }
  1426. public function getHeader($header): array
  1427. {
  1428. $header = \strtolower($header);
  1429. if (!isset($this->headerNames[$header])) {
  1430. return [];
  1431. }
  1432. $header = $this->headerNames[$header];
  1433. return $this->headers[$header];
  1434. }
  1435. public function getHeaderLine($header): string
  1436. {
  1437. return \implode(', ', $this->getHeader($header));
  1438. }
  1439. public function withHeader($header, $value): self
  1440. {
  1441. $value = $this->validateAndTrimHeader($header, $value);
  1442. $normalized = \strtolower($header);
  1443. $new = clone $this;
  1444. if (isset($new->headerNames[$normalized])) {
  1445. unset($new->headers[$new->headerNames[$normalized]]);
  1446. }
  1447. $new->headerNames[$normalized] = $header;
  1448. $new->headers[$header] = $value;
  1449. return $new;
  1450. }
  1451. public function withAddedHeader($header, $value): self
  1452. {
  1453. if (!\is_string($header) || '' === $header) {
  1454. throw new \InvalidArgumentException('Header name must be an RFC 7230 compatible string.');
  1455. }
  1456. $new = clone $this;
  1457. $new->setHeaders([$header => $value]);
  1458. return $new;
  1459. }
  1460. public function withoutHeader($header): self
  1461. {
  1462. $normalized = \strtolower($header);
  1463. if (!isset($this->headerNames[$normalized])) {
  1464. return $this;
  1465. }
  1466. $header = $this->headerNames[$normalized];
  1467. $new = clone $this;
  1468. unset($new->headers[$header], $new->headerNames[$normalized]);
  1469. return $new;
  1470. }
  1471. public function getBody(): StreamInterface
  1472. {
  1473. if (null === $this->stream) {
  1474. $this->stream = Stream::create('');
  1475. }
  1476. return $this->stream;
  1477. }
  1478. public function withBody(StreamInterface $body): self
  1479. {
  1480. if ($body === $this->stream) {
  1481. return $this;
  1482. }
  1483. $new = clone $this;
  1484. $new->stream = $body;
  1485. return $new;
  1486. }
  1487. private function setHeaders(array $headers): void
  1488. {
  1489. foreach ($headers as $header => $value) {
  1490. $value = $this->validateAndTrimHeader($header, $value);
  1491. $normalized = \strtolower($header);
  1492. if (isset($this->headerNames[$normalized])) {
  1493. $header = $this->headerNames[$normalized];
  1494. $this->headers[$header] = \array_merge($this->headers[$header], $value);
  1495. } else {
  1496. $this->headerNames[$normalized] = $header;
  1497. $this->headers[$header] = $value;
  1498. }
  1499. }
  1500. }
  1501. /**
  1502. * Make sure the header complies with RFC 7230.
  1503. *
  1504. * Header names must be a non-empty string consisting of token characters.
  1505. *
  1506. * Header values must be strings consisting of visible characters with all optional
  1507. * leading and trailing whitespace stripped. This method will always strip such
  1508. * optional whitespace. Note that the method does not allow folding whitespace within
  1509. * the values as this was deprecated for almost all instances by the RFC.
  1510. *
  1511. * header-field = field-name ":" OWS field-value OWS
  1512. * field-name = 1*( "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." / "^"
  1513. * / "_" / "`" / "|" / "~" / %x30-39 / ( %x41-5A / %x61-7A ) )
  1514. * OWS = *( SP / HTAB )
  1515. * field-value = *( ( %x21-7E / %x80-FF ) [ 1*( SP / HTAB ) ( %x21-7E / %x80-FF ) ] )
  1516. *
  1517. * @see https://tools.ietf.org/html/rfc7230#section-3.2.4
  1518. */
  1519. private function validateAndTrimHeader($header, $values): array
  1520. {
  1521. if (!\is_string($header) || 1 !== \preg_match("@^[!#$%&'*+.^_`|~0-9A-Za-z-]+$@", $header)) {
  1522. throw new \InvalidArgumentException('Header name must be an RFC 7230 compatible string.');
  1523. }
  1524. if (!\is_array($values)) {
  1525. if ((!\is_numeric($values) && !\is_string($values)) || 1 !== \preg_match("@^[ \t\x21-\x7E\x80-\xFF]*$@", (string) $values)) {
  1526. throw new \InvalidArgumentException('Header values must be RFC 7230 compatible strings.');
  1527. }
  1528. return [\trim((string) $values, " \t")];
  1529. }
  1530. if (empty($values)) {
  1531. throw new \InvalidArgumentException('Header values must be a string or an array of strings, empty array given.');
  1532. }
  1533. $returnValues = [];
  1534. foreach ($values as $v) {
  1535. if ((!\is_numeric($v) && !\is_string($v)) || 1 !== \preg_match("@^[ \t\x21-\x7E\x80-\xFF]*$@", (string) $v)) {
  1536. throw new \InvalidArgumentException('Header values must be RFC 7230 compatible strings.');
  1537. }
  1538. $returnValues[] = \trim((string) $v, " \t");
  1539. }
  1540. return $returnValues;
  1541. }
  1542. }
  1543. // file: src/Nyholm/Psr7/Request.php
  1544. /**
  1545. * @author Tobias Nyholm <tobias.nyholm@gmail.com>
  1546. * @author Martijn van der Ven <martijn@vanderven.se>
  1547. */
  1548. final class Request implements RequestInterface
  1549. {
  1550. use MessageTrait;
  1551. use RequestTrait;
  1552. /**
  1553. * @param string $method HTTP method
  1554. * @param string|UriInterface $uri URI
  1555. * @param array $headers Request headers
  1556. * @param string|resource|StreamInterface|null $body Request body
  1557. * @param string $version Protocol version
  1558. */
  1559. public function __construct(string $method, $uri, array $headers = [], $body = null, string $version = '1.1')
  1560. {
  1561. if (!($uri instanceof UriInterface)) {
  1562. $uri = new Uri((string) $uri);
  1563. }
  1564. $this->method = $method;
  1565. $this->uri = $uri;
  1566. $this->setHeaders($headers);
  1567. $this->protocol = $version;
  1568. if (!$this->hasHeader('Host')) {
  1569. $this->updateHostFromUri();
  1570. }
  1571. if ('' !== $body && null !== $body) {
  1572. $this->stream = Stream::create($body);
  1573. }
  1574. }
  1575. }
  1576. // file: src/Nyholm/Psr7/RequestTrait.php
  1577. /**
  1578. * @author Michael Dowling and contributors to guzzlehttp/psr7
  1579. * @author Tobias Nyholm <tobias.nyholm@gmail.com>
  1580. * @author Martijn van der Ven <martijn@vanderven.se>
  1581. *
  1582. * @internal should not be used outside of Nyholm/Psr7 as it does not fall under our BC promise
  1583. */
  1584. trait RequestTrait
  1585. {
  1586. /** @var string */
  1587. private $method;
  1588. /** @var string|null */
  1589. private $requestTarget;
  1590. /** @var UriInterface|null */
  1591. private $uri;
  1592. public function getRequestTarget(): string
  1593. {
  1594. if (null !== $this->requestTarget) {
  1595. return $this->requestTarget;
  1596. }
  1597. if ('' === $target = $this->uri->getPath()) {
  1598. $target = '/';
  1599. }
  1600. if ('' !== $this->uri->getQuery()) {
  1601. $target .= '?' . $this->uri->getQuery();
  1602. }
  1603. return $target;
  1604. }
  1605. public function withRequestTarget($requestTarget): self
  1606. {
  1607. if (\preg_match('#\s#', $requestTarget)) {
  1608. throw new \InvalidArgumentException('Invalid request target provided; cannot contain whitespace');
  1609. }
  1610. $new = clone $this;
  1611. $new->requestTarget = $requestTarget;
  1612. return $new;
  1613. }
  1614. public function getMethod(): string
  1615. {
  1616. return $this->method;
  1617. }
  1618. public function withMethod($method): self
  1619. {
  1620. if (!\is_string($method)) {
  1621. throw new \InvalidArgumentException('Method must be a string');
  1622. }
  1623. $new = clone $this;
  1624. $new->method = $method;
  1625. return $new;
  1626. }
  1627. public function getUri(): UriInterface
  1628. {
  1629. return $this->uri;
  1630. }
  1631. public function withUri(UriInterface $uri, $preserveHost = false): self
  1632. {
  1633. if ($uri === $this->uri) {
  1634. return $this;
  1635. }
  1636. $new = clone $this;
  1637. $new->uri = $uri;
  1638. if (!$preserveHost || !$this->hasHeader('Host')) {
  1639. $new->updateHostFromUri();
  1640. }
  1641. return $new;
  1642. }
  1643. private function updateHostFromUri(): void
  1644. {
  1645. if ('' === $host = $this->uri->getHost()) {
  1646. return;
  1647. }
  1648. if (null !== ($port = $this->uri->getPort())) {
  1649. $host .= ':' . $port;
  1650. }
  1651. if (isset($this->headerNames['host'])) {
  1652. $header = $this->headerNames['host'];
  1653. } else {
  1654. $this->headerNames['host'] = $header = 'Host';
  1655. }
  1656. $this->headers = [$header => [$host]] + $this->headers;
  1657. }
  1658. }
  1659. // file: src/Nyholm/Psr7/Response.php
  1660. /**
  1661. * @author Michael Dowling and contributors to guzzlehttp/psr7
  1662. * @author Tobias Nyholm <tobias.nyholm@gmail.com>
  1663. * @author Martijn van der Ven <martijn@vanderven.se>
  1664. */
  1665. final class Response implements ResponseInterface
  1666. {
  1667. use MessageTrait;
  1668. /** @var array Map of standard HTTP status code/reason phrases */
  1669. private const PHRASES = [
  1670. 100 => 'Continue', 101 => 'Switching Protocols', 102 => 'Processing',
  1671. 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',
  1672. 300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 306 => 'Switch Proxy', 307 => 'Temporary Redirect',
  1673. 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',
  1674. 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',
  1675. ];
  1676. /** @var string */
  1677. private $reasonPhrase = '';
  1678. /** @var int */
  1679. private $statusCode;
  1680. /**
  1681. * @param int $status Status code
  1682. * @param array $headers Response headers
  1683. * @param string|resource|StreamInterface|null $body Response body
  1684. * @param string $version Protocol version
  1685. * @param string|null $reason Reason phrase (when empty a default will be used based on the status code)
  1686. */
  1687. public function __construct(int $status = 200, array $headers = [], $body = null, string $version = '1.1', string $reason = null)
  1688. {
  1689. if ('' !== $body && null !== $body) {
  1690. $this->stream = Stream::create($body);
  1691. }
  1692. $this->statusCode = $status;
  1693. $this->setHeaders($headers);
  1694. if (null === $reason && isset(self::PHRASES[$this->statusCode])) {
  1695. $this->reasonPhrase = self::PHRASES[$status];
  1696. } else {
  1697. $this->reasonPhrase = $reason;
  1698. }
  1699. $this->protocol = $version;
  1700. }
  1701. public function getStatusCode(): int
  1702. {
  1703. return $this->statusCode;
  1704. }
  1705. public function getReasonPhrase(): string
  1706. {
  1707. return $this->reasonPhrase;
  1708. }
  1709. public function withStatus($code, $reasonPhrase = ''): self
  1710. {
  1711. if (!\is_int($code) && !\is_string($code)) {
  1712. throw new \InvalidArgumentException('Status code has to be an integer');
  1713. }
  1714. $code = (int) $code;
  1715. if ($code < 100 || $code > 599) {
  1716. throw new \InvalidArgumentException('Status code has to be an integer between 100 and 599');
  1717. }
  1718. $new = clone $this;
  1719. $new->statusCode = $code;
  1720. if ((null === $reasonPhrase || '' === $reasonPhrase) && isset(self::PHRASES[$new->statusCode])) {
  1721. $reasonPhrase = self::PHRASES[$new->statusCode];
  1722. }
  1723. $new->reasonPhrase = $reasonPhrase;
  1724. return $new;
  1725. }
  1726. }
  1727. // file: src/Nyholm/Psr7/ServerRequest.php
  1728. /**
  1729. * @author Michael Dowling and contributors to guzzlehttp/psr7
  1730. * @author Tobias Nyholm <tobias.nyholm@gmail.com>
  1731. * @author Martijn van der Ven <martijn@vanderven.se>
  1732. */
  1733. final class ServerRequest implements ServerRequestInterface
  1734. {
  1735. use MessageTrait;
  1736. use RequestTrait;
  1737. /** @var array */
  1738. private $attributes = [];
  1739. /** @var array */
  1740. private $cookieParams = [];
  1741. /** @var array|object|null */
  1742. private $parsedBody;
  1743. /** @var array */
  1744. private $queryParams = [];
  1745. /** @var array */
  1746. private $serverParams;
  1747. /** @var UploadedFileInterface[] */
  1748. private $uploadedFiles = [];
  1749. /**
  1750. * @param string $method HTTP method
  1751. * @param string|UriInterface $uri URI
  1752. * @param array $headers Request headers
  1753. * @param string|resource|StreamInterface|null $body Request body
  1754. * @param string $version Protocol version
  1755. * @param array $serverParams Typically the $_SERVER superglobal
  1756. */
  1757. public function __construct(string $method, $uri, array $headers = [], $body = null, string $version = '1.1', array $serverParams = [])
  1758. {
  1759. $this->serverParams = $serverParams;
  1760. if (!($uri instanceof UriInterface)) {
  1761. $uri = new Uri((string) $uri);
  1762. }
  1763. $this->method = $method;
  1764. $this->uri = $uri;
  1765. $this->setHeaders($headers);
  1766. $this->protocol = $version;
  1767. if (!$this->hasHeader('Host')) {
  1768. $this->updateHostFromUri();
  1769. }
  1770. if ('' !== $body && null !== $body) {
  1771. $this->stream = Stream::create($body);
  1772. }
  1773. }
  1774. public function getServerParams(): array
  1775. {
  1776. return $this->serverParams;
  1777. }
  1778. public function getUploadedFiles(): array
  1779. {
  1780. return $this->uploadedFiles;
  1781. }
  1782. public function withUploadedFiles(array $uploadedFiles)
  1783. {
  1784. $new = clone $this;
  1785. $new->uploadedFiles = $uploadedFiles;
  1786. return $new;
  1787. }
  1788. public function getCookieParams(): array
  1789. {
  1790. return $this->cookieParams;
  1791. }
  1792. public function withCookieParams(array $cookies)
  1793. {
  1794. $new = clone $this;
  1795. $new->cookieParams = $cookies;
  1796. return $new;
  1797. }
  1798. public function getQueryParams(): array
  1799. {
  1800. return $this->queryParams;
  1801. }
  1802. public function withQueryParams(array $query)
  1803. {
  1804. $new = clone $this;
  1805. $new->queryParams = $query;
  1806. return $new;
  1807. }
  1808. public function getParsedBody()
  1809. {
  1810. return $this->parsedBody;
  1811. }
  1812. public function withParsedBody($data)
  1813. {
  1814. if (!\is_array($data) && !\is_object($data) && null !== $data) {
  1815. throw new \InvalidArgumentException('First parameter to withParsedBody MUST be object, array or null');
  1816. }
  1817. $new = clone $this;
  1818. $new->parsedBody = $data;
  1819. return $new;
  1820. }
  1821. public function getAttributes(): array
  1822. {
  1823. return $this->attributes;
  1824. }
  1825. public function getAttribute($attribute, $default = null)
  1826. {
  1827. if (false === \array_key_exists($attribute, $this->attributes)) {
  1828. return $default;
  1829. }
  1830. return $this->attributes[$attribute];
  1831. }
  1832. public function withAttribute($attribute, $value): self
  1833. {
  1834. $new = clone $this;
  1835. $new->attributes[$attribute] = $value;
  1836. return $new;
  1837. }
  1838. public function withoutAttribute($attribute): self
  1839. {
  1840. if (false === \array_key_exists($attribute, $this->attributes)) {
  1841. return $this;
  1842. }
  1843. $new = clone $this;
  1844. unset($new->attributes[$attribute]);
  1845. return $new;
  1846. }
  1847. }
  1848. // file: src/Nyholm/Psr7/Stream.php
  1849. /**
  1850. * @author Michael Dowling and contributors to guzzlehttp/psr7
  1851. * @author Tobias Nyholm <tobias.nyholm@gmail.com>
  1852. * @author Martijn van der Ven <martijn@vanderven.se>
  1853. */
  1854. final class Stream implements StreamInterface
  1855. {
  1856. /** @var resource|null A resource reference */
  1857. private $stream;
  1858. /** @var bool */
  1859. private $seekable;
  1860. /** @var bool */
  1861. private $readable;
  1862. /** @var bool */
  1863. private $writable;
  1864. /** @var array|mixed|void|null */
  1865. private $uri;
  1866. /** @var int|null */
  1867. private $size;
  1868. /** @var array Hash of readable and writable stream types */
  1869. private const READ_WRITE_HASH = [
  1870. 'read' => [
  1871. 'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true,
  1872. 'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true,
  1873. 'c+b' => true, 'rt' => true, 'w+t' => true, 'r+t' => true,
  1874. 'x+t' => true, 'c+t' => true, 'a+' => true,
  1875. ],
  1876. 'write' => [
  1877. 'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true,
  1878. 'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true,
  1879. 'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true,
  1880. 'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true,
  1881. ],
  1882. ];
  1883. private function __construct()
  1884. {
  1885. }
  1886. /**
  1887. * Creates a new PSR-7 stream.
  1888. *
  1889. * @param string|resource|StreamInterface $body
  1890. *
  1891. * @return StreamInterface
  1892. *
  1893. * @throws \InvalidArgumentException
  1894. */
  1895. public static function create($body = ''): StreamInterface
  1896. {
  1897. if ($body instanceof StreamInterface) {
  1898. return $body;
  1899. }
  1900. if (\is_string($body)) {
  1901. $resource = \fopen('php://temp', 'rw+');
  1902. \fwrite($resource, $body);
  1903. $body = $resource;
  1904. }
  1905. if (\is_resource($body)) {
  1906. $new = new self();
  1907. $new->stream = $body;
  1908. $meta = \stream_get_meta_data($new->stream);
  1909. $new->seekable = $meta['seekable'];
  1910. $new->readable = isset(self::READ_WRITE_HASH['read'][$meta['mode']]);
  1911. $new->writable = isset(self::READ_WRITE_HASH['write'][$meta['mode']]);
  1912. $new->uri = $new->getMetadata('uri');
  1913. return $new;
  1914. }
  1915. throw new \InvalidArgumentException('First argument to Stream::create() must be a string, resource or StreamInterface.');
  1916. }
  1917. /**
  1918. * Closes the stream when the destructed.
  1919. */
  1920. public function __destruct()
  1921. {
  1922. $this->close();
  1923. }
  1924. public function __toString(): string
  1925. {
  1926. try {
  1927. if ($this->isSeekable()) {
  1928. $this->seek(0);
  1929. }
  1930. return $this->getContents();
  1931. } catch (\Exception $e) {
  1932. return '';
  1933. }
  1934. }
  1935. public function close(): void
  1936. {
  1937. if (isset($this->stream)) {
  1938. if (\is_resource($this->stream)) {
  1939. \fclose($this->stream);
  1940. }
  1941. $this->detach();
  1942. }
  1943. }
  1944. public function detach()
  1945. {
  1946. if (!isset($this->stream)) {
  1947. return null;
  1948. }
  1949. $result = $this->stream;
  1950. unset($this->stream);
  1951. $this->size = $this->uri = null;
  1952. $this->readable = $this->writable = $this->seekable = false;
  1953. return $result;
  1954. }
  1955. public function getSize(): ?int
  1956. {
  1957. if (null !== $this->size) {
  1958. return $this->size;
  1959. }
  1960. if (!isset($this->stream)) {
  1961. return null;
  1962. }
  1963. if ($this->uri) {
  1964. \clearstatcache(true, $this->uri);
  1965. }
  1966. $stats = \fstat($this->stream);
  1967. if (isset($stats['size'])) {
  1968. $this->size = $stats['size'];
  1969. return $this->size;
  1970. }
  1971. return null;
  1972. }
  1973. public function tell(): int
  1974. {
  1975. if (false === $result = \ftell($this->stream)) {
  1976. throw new \RuntimeException('Unable to determine stream position');
  1977. }
  1978. return $result;
  1979. }
  1980. public function eof(): bool
  1981. {
  1982. return !$this->stream || \feof($this->stream);
  1983. }
  1984. public function isSeekable(): bool
  1985. {
  1986. return $this->seekable;
  1987. }
  1988. public function seek($offset, $whence = \SEEK_SET): void
  1989. {
  1990. if (!$this->seekable) {
  1991. throw new \RuntimeException('Stream is not seekable');
  1992. }
  1993. if (-1 === \fseek($this->stream, $offset, $whence)) {
  1994. throw new \RuntimeException('Unable to seek to stream position ' . $offset . ' with whence ' . \var_export($whence, true));
  1995. }
  1996. }
  1997. public function rewind(): void
  1998. {
  1999. $this->seek(0);
  2000. }
  2001. public function isWritable(): bool
  2002. {
  2003. return $this->writable;
  2004. }
  2005. public function write($string): int
  2006. {
  2007. if (!$this->writable) {
  2008. throw new \RuntimeException('Cannot write to a non-writable stream');
  2009. }
  2010. $this->size = null;
  2011. if (false === $result = \fwrite($this->stream, $string)) {
  2012. throw new \RuntimeException('Unable to write to stream');
  2013. }
  2014. return $result;
  2015. }
  2016. public function isReadable(): bool
  2017. {
  2018. return $this->readable;
  2019. }
  2020. public function read($length): string
  2021. {
  2022. if (!$this->readable) {
  2023. throw new \RuntimeException('Cannot read from non-readable stream');
  2024. }
  2025. return \fread($this->stream, $length);
  2026. }
  2027. public function getContents(): string
  2028. {
  2029. if (!isset($this->stream)) {
  2030. throw new \RuntimeException('Unable to read stream contents');
  2031. }
  2032. if (false === $contents = \stream_get_contents($this->stream)) {
  2033. throw new \RuntimeException('Unable to read stream contents');
  2034. }
  2035. return $contents;
  2036. }
  2037. public function getMetadata($key = null)
  2038. {
  2039. if (!isset($this->stream)) {
  2040. return $key ? null : [];
  2041. }
  2042. $meta = \stream_get_meta_data($this->stream);
  2043. if (null === $key) {
  2044. return $meta;
  2045. }
  2046. return $meta[$key] ?? null;
  2047. }
  2048. }
  2049. // file: src/Nyholm/Psr7/UploadedFile.php
  2050. /**
  2051. * @author Michael Dowling and contributors to guzzlehttp/psr7
  2052. * @author Tobias Nyholm <tobias.nyholm@gmail.com>
  2053. * @author Martijn van der Ven <martijn@vanderven.se>
  2054. */
  2055. final class UploadedFile implements UploadedFileInterface
  2056. {
  2057. /** @var array */
  2058. private const ERRORS = [
  2059. \UPLOAD_ERR_OK => 1,
  2060. \UPLOAD_ERR_INI_SIZE => 1,
  2061. \UPLOAD_ERR_FORM_SIZE => 1,
  2062. \UPLOAD_ERR_PARTIAL => 1,
  2063. \UPLOAD_ERR_NO_FILE => 1,
  2064. \UPLOAD_ERR_NO_TMP_DIR => 1,
  2065. \UPLOAD_ERR_CANT_WRITE => 1,
  2066. \UPLOAD_ERR_EXTENSION => 1,
  2067. ];
  2068. /** @var string */
  2069. private $clientFilename;
  2070. /** @var string */
  2071. private $clientMediaType;
  2072. /** @var int */
  2073. private $error;
  2074. /** @var string|null */
  2075. private $file;
  2076. /** @var bool */
  2077. private $moved = false;
  2078. /** @var int */
  2079. private $size;
  2080. /** @var StreamInterface|null */
  2081. private $stream;
  2082. /**
  2083. * @param StreamInterface|string|resource $streamOrFile
  2084. * @param int $size
  2085. * @param int $errorStatus
  2086. * @param string|null $clientFilename
  2087. * @param string|null $clientMediaType
  2088. */
  2089. public function __construct($streamOrFile, $size, $errorStatus, $clientFilename = null, $clientMediaType = null)
  2090. {
  2091. if (false === \is_int($errorStatus) || !isset(self::ERRORS[$errorStatus])) {
  2092. throw new \InvalidArgumentException('Upload file error status must be an integer value and one of the "UPLOAD_ERR_*" constants.');
  2093. }
  2094. if (false === \is_int($size)) {
  2095. throw new \InvalidArgumentException('Upload file size must be an integer');
  2096. }
  2097. if (null !== $clientFilename && !\is_string($clientFilename)) {
  2098. throw new \InvalidArgumentException('Upload file client filename must be a string or null');
  2099. }
  2100. if (null !== $clientMediaType && !\is_string($clientMediaType)) {
  2101. throw new \InvalidArgumentException('Upload file client media type must be a string or null');
  2102. }
  2103. $this->error = $errorStatus;
  2104. $this->size = $size;
  2105. $this->clientFilename = $clientFilename;
  2106. $this->clientMediaType = $clientMediaType;
  2107. if (\UPLOAD_ERR_OK === $this->error) {
  2108. if (\is_string($streamOrFile)) {
  2109. $this->file = $streamOrFile;
  2110. } elseif (\is_resource($streamOrFile)) {
  2111. $this->stream = Stream::create($streamOrFile);
  2112. } elseif ($streamOrFile instanceof StreamInterface) {
  2113. $this->stream = $streamOrFile;
  2114. } else {
  2115. throw new \InvalidArgumentException('Invalid stream or file provided for UploadedFile');
  2116. }
  2117. }
  2118. }
  2119. /**
  2120. * @throws \RuntimeException if is moved or not ok
  2121. */
  2122. private function validateActive(): void
  2123. {
  2124. if (\UPLOAD_ERR_OK !== $this->error) {
  2125. throw new \RuntimeException('Cannot retrieve stream due to upload error');
  2126. }
  2127. if ($this->moved) {
  2128. throw new \RuntimeException('Cannot retrieve stream after it has already been moved');
  2129. }
  2130. }
  2131. public function getStream(): StreamInterface
  2132. {
  2133. $this->validateActive();
  2134. if ($this->stream instanceof StreamInterface) {
  2135. return $this->stream;
  2136. }
  2137. $resource = \fopen($this->file, 'r');
  2138. return Stream::create($resource);
  2139. }
  2140. public function moveTo($targetPath): void
  2141. {
  2142. $this->validateActive();
  2143. if (!\is_string($targetPath) || '' === $targetPath) {
  2144. throw new \InvalidArgumentException('Invalid path provided for move operation; must be a non-empty string');
  2145. }
  2146. if (null !== $this->file) {
  2147. $this->moved = 'cli' === \PHP_SAPI ? \rename($this->file, $targetPath) : \move_uploaded_file($this->file, $targetPath);
  2148. } else {
  2149. $stream = $this->getStream();
  2150. if ($stream->isSeekable()) {
  2151. $stream->rewind();
  2152. }
  2153. $dest = Stream::create(\fopen($targetPath, 'w'));
  2154. while (!$stream->eof()) {
  2155. if (!$dest->write($stream->read(1048576))) {
  2156. break;
  2157. }
  2158. }
  2159. $this->moved = true;
  2160. }
  2161. if (false === $this->moved) {
  2162. throw new \RuntimeException(\sprintf('Uploaded file could not be moved to %s', $targetPath));
  2163. }
  2164. }
  2165. public function getSize(): int
  2166. {
  2167. return $this->size;
  2168. }
  2169. public function getError(): int
  2170. {
  2171. return $this->error;
  2172. }
  2173. public function getClientFilename(): ?string
  2174. {
  2175. return $this->clientFilename;
  2176. }
  2177. public function getClientMediaType(): ?string
  2178. {
  2179. return $this->clientMediaType;
  2180. }
  2181. }
  2182. // file: src/Nyholm/Psr7/Uri.php
  2183. /**
  2184. * PSR-7 URI implementation.
  2185. *
  2186. * @author Michael Dowling
  2187. * @author Tobias Schultze
  2188. * @author Matthew Weier O'Phinney
  2189. * @author Tobias Nyholm <tobias.nyholm@gmail.com>
  2190. * @author Martijn van der Ven <martijn@vanderven.se>
  2191. */
  2192. final class Uri implements UriInterface
  2193. {
  2194. private const SCHEMES = ['http' => 80, 'https' => 443];
  2195. private const CHAR_UNRESERVED = 'a-zA-Z0-9_\-\.~';
  2196. private const CHAR_SUB_DELIMS = '!\$&\'\(\)\*\+,;=';
  2197. /** @var string Uri scheme. */
  2198. private $scheme = '';
  2199. /** @var string Uri user info. */
  2200. private $userInfo = '';
  2201. /** @var string Uri host. */
  2202. private $host = '';
  2203. /** @var int|null Uri port. */
  2204. private $port;
  2205. /** @var string Uri path. */
  2206. private $path = '';
  2207. /** @var string Uri query string. */
  2208. private $query = '';
  2209. /** @var string Uri fragment. */
  2210. private $fragment = '';
  2211. public function __construct(string $uri = '')
  2212. {
  2213. if ('' !== $uri) {
  2214. if (false === $parts = \parse_url($uri)) {
  2215. throw new \InvalidArgumentException("Unable to parse URI: $uri");
  2216. }
  2217. $this->scheme = isset($parts['scheme']) ? \strtolower($parts['scheme']) : '';
  2218. $this->userInfo = $parts['user'] ?? '';
  2219. $this->host = isset($parts['host']) ? \strtolower($parts['host']) : '';
  2220. $this->port = isset($parts['port']) ? $this->filterPort($parts['port']) : null;
  2221. $this->path = isset($parts['path']) ? $this->filterPath($parts['path']) : '';
  2222. $this->query = isset($parts['query']) ? $this->filterQueryAndFragment($parts['query']) : '';
  2223. $this->fragment = isset($parts['fragment']) ? $this->filterQueryAndFragment($parts['fragment']) : '';
  2224. if (isset($parts['pass'])) {
  2225. $this->userInfo .= ':' . $parts['pass'];
  2226. }
  2227. }
  2228. }
  2229. public function __toString(): string
  2230. {
  2231. return self::createUriString($this->scheme, $this->getAuthority(), $this->path, $this->query, $this->fragment);
  2232. }
  2233. public function getScheme(): string
  2234. {
  2235. return $this->scheme;
  2236. }
  2237. public function getAuthority(): string
  2238. {
  2239. if ('' === $this->host) {
  2240. return '';
  2241. }
  2242. $authority = $this->host;
  2243. if ('' !== $this->userInfo) {
  2244. $authority = $this->userInfo . '@' . $authority;
  2245. }
  2246. if (null !== $this->port) {
  2247. $authority .= ':' . $this->port;
  2248. }
  2249. return $authority;
  2250. }
  2251. public function getUserInfo(): string
  2252. {
  2253. return $this->userInfo;
  2254. }
  2255. public function getHost(): string
  2256. {
  2257. return $this->host;
  2258. }
  2259. public function getPort(): ?int
  2260. {
  2261. return $this->port;
  2262. }
  2263. public function getPath(): string
  2264. {
  2265. return $this->path;
  2266. }
  2267. public function getQuery(): string
  2268. {
  2269. return $this->query;
  2270. }
  2271. public function getFragment(): string
  2272. {
  2273. return $this->fragment;
  2274. }
  2275. public function withScheme($scheme): self
  2276. {
  2277. if (!\is_string($scheme)) {
  2278. throw new \InvalidArgumentException('Scheme must be a string');
  2279. }
  2280. if ($this->scheme === $scheme = \strtolower($scheme)) {
  2281. return $this;
  2282. }
  2283. $new = clone $this;
  2284. $new->scheme = $scheme;
  2285. $new->port = $new->filterPort($new->port);
  2286. return $new;
  2287. }
  2288. public function withUserInfo($user, $password = null): self
  2289. {
  2290. $info = $user;
  2291. if (null !== $password && '' !== $password) {
  2292. $info .= ':' . $password;
  2293. }
  2294. if ($this->userInfo === $info) {
  2295. return $this;
  2296. }
  2297. $new = clone $this;
  2298. $new->userInfo = $info;
  2299. return $new;
  2300. }
  2301. public function withHost($host): self
  2302. {
  2303. if (!\is_string($host)) {
  2304. throw new \InvalidArgumentException('Host must be a string');
  2305. }
  2306. if ($this->host === $host = \strtolower($host)) {
  2307. return $this;
  2308. }
  2309. $new = clone $this;
  2310. $new->host = $host;
  2311. return $new;
  2312. }
  2313. public function withPort($port): self
  2314. {
  2315. if ($this->port === $port = $this->filterPort($port)) {
  2316. return $this;
  2317. }
  2318. $new = clone $this;
  2319. $new->port = $port;
  2320. return $new;
  2321. }
  2322. public function withPath($path): self
  2323. {
  2324. if ($this->path === $path = $this->filterPath($path)) {
  2325. return $this;
  2326. }
  2327. $new = clone $this;
  2328. $new->path = $path;
  2329. return $new;
  2330. }
  2331. public function withQuery($query): self
  2332. {
  2333. if ($this->query === $query = $this->filterQueryAndFragment($query)) {
  2334. return $this;
  2335. }
  2336. $new = clone $this;
  2337. $new->query = $query;
  2338. return $new;
  2339. }
  2340. public function withFragment($fragment): self
  2341. {
  2342. if ($this->fragment === $fragment = $this->filterQueryAndFragment($fragment)) {
  2343. return $this;
  2344. }
  2345. $new = clone $this;
  2346. $new->fragment = $fragment;
  2347. return $new;
  2348. }
  2349. /**
  2350. * Create a URI string from its various parts.
  2351. */
  2352. private static function createUriString(string $scheme, string $authority, string $path, string $query, string $fragment): string
  2353. {
  2354. $uri = '';
  2355. if ('' !== $scheme) {
  2356. $uri .= $scheme . ':';
  2357. }
  2358. if ('' !== $authority) {
  2359. $uri .= '//' . $authority;
  2360. }
  2361. if ('' !== $path) {
  2362. if ('/' !== $path[0]) {
  2363. if ('' !== $authority) {
  2364. $path = '/' . $path;
  2365. }
  2366. } elseif (isset($path[1]) && '/' === $path[1]) {
  2367. if ('' === $authority) {
  2368. $path = '/' . \ltrim($path, '/');
  2369. }
  2370. }
  2371. $uri .= $path;
  2372. }
  2373. if ('' !== $query) {
  2374. $uri .= '?' . $query;
  2375. }
  2376. if ('' !== $fragment) {
  2377. $uri .= '#' . $fragment;
  2378. }
  2379. return $uri;
  2380. }
  2381. /**
  2382. * Is a given port non-standard for the current scheme?
  2383. */
  2384. private static function isNonStandardPort(string $scheme, int $port): bool
  2385. {
  2386. return !isset(self::SCHEMES[$scheme]) || $port !== self::SCHEMES[$scheme];
  2387. }
  2388. private function filterPort($port): ?int
  2389. {
  2390. if (null === $port) {
  2391. return null;
  2392. }
  2393. $port = (int) $port;
  2394. if (1 > $port || 0xffff < $port) {
  2395. throw new \InvalidArgumentException(\sprintf('Invalid port: %d. Must be between 1 and 65535', $port));
  2396. }
  2397. return self::isNonStandardPort($this->scheme, $port) ? $port : null;
  2398. }
  2399. private function filterPath($path): string
  2400. {
  2401. if (!\is_string($path)) {
  2402. throw new \InvalidArgumentException('Path must be a string');
  2403. }
  2404. return \preg_replace_callback('/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:@\/]++|%(?![A-Fa-f0-9]{2}))/', [__CLASS__, 'rawurlencodeMatchZero'], $path);
  2405. }
  2406. private function filterQueryAndFragment($str): string
  2407. {
  2408. if (!\is_string($str)) {
  2409. throw new \InvalidArgumentException('Query and fragment must be a string');
  2410. }
  2411. return \preg_replace_callback('/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/', [__CLASS__, 'rawurlencodeMatchZero'], $str);
  2412. }
  2413. private static function rawurlencodeMatchZero(array $match): string
  2414. {
  2415. return \rawurlencode($match[0]);
  2416. }
  2417. }
  2418. // file: src/Nyholm/Psr7Server/ServerRequestCreator.php
  2419. /**
  2420. * @author Tobias Nyholm <tobias.nyholm@gmail.com>
  2421. * @author Martijn van der Ven <martijn@vanderven.se>
  2422. */
  2423. final class ServerRequestCreator implements ServerRequestCreatorInterface
  2424. {
  2425. private $serverRequestFactory;
  2426. private $uriFactory;
  2427. private $uploadedFileFactory;
  2428. private $streamFactory;
  2429. public function __construct(
  2430. ServerRequestFactoryInterface $serverRequestFactory,
  2431. UriFactoryInterface $uriFactory,
  2432. UploadedFileFactoryInterface $uploadedFileFactory,
  2433. StreamFactoryInterface $streamFactory
  2434. ) {
  2435. $this->serverRequestFactory = $serverRequestFactory;
  2436. $this->uriFactory = $uriFactory;
  2437. $this->uploadedFileFactory = $uploadedFileFactory;
  2438. $this->streamFactory = $streamFactory;
  2439. }
  2440. /**
  2441. * {@inheritdoc}
  2442. */
  2443. public function fromGlobals(): ServerRequestInterface
  2444. {
  2445. $server = $_SERVER;
  2446. if (false === isset($server['REQUEST_METHOD'])) {
  2447. $server['REQUEST_METHOD'] = 'GET';
  2448. }
  2449. $headers = \function_exists('getallheaders') ? getallheaders() : static::getHeadersFromServer($_SERVER);
  2450. return $this->fromArrays($server, $headers, $_COOKIE, $_GET, $_POST, $_FILES, \fopen('php://input', 'r') ?: null);
  2451. }
  2452. /**
  2453. * {@inheritdoc}
  2454. */
  2455. public function fromArrays(array $server, array $headers = [], array $cookie = [], array $get = [], array $post = [], array $files = [], $body = null): ServerRequestInterface
  2456. {
  2457. $method = $this->getMethodFromEnv($server);
  2458. $uri = $this->getUriFromEnvWithHTTP($server);
  2459. $protocol = isset($server['SERVER_PROTOCOL']) ? \str_replace('HTTP/', '', $server['SERVER_PROTOCOL']) : '1.1';
  2460. $serverRequest = $this->serverRequestFactory->createServerRequest($method, $uri, $server);
  2461. foreach ($headers as $name => $value) {
  2462. $serverRequest = $serverRequest->withAddedHeader($name, $value);
  2463. }
  2464. $serverRequest = $serverRequest
  2465. ->withProtocolVersion($protocol)
  2466. ->withCookieParams($cookie)
  2467. ->withQueryParams($get)
  2468. ->withParsedBody($post)
  2469. ->withUploadedFiles($this->normalizeFiles($files));
  2470. if (null === $body) {
  2471. return $serverRequest;
  2472. }
  2473. if (\is_resource($body)) {
  2474. $body = $this->streamFactory->createStreamFromResource($body);
  2475. } elseif (\is_string($body)) {
  2476. $body = $this->streamFactory->createStream($body);
  2477. } elseif (!$body instanceof StreamInterface) {
  2478. throw new \InvalidArgumentException('The $body parameter to ServerRequestCreator::fromArrays must be string, resource or StreamInterface');
  2479. }
  2480. return $serverRequest->withBody($body);
  2481. }
  2482. /**
  2483. * Implementation from Zend\Diactoros\marshalHeadersFromSapi().
  2484. */
  2485. public static function getHeadersFromServer(array $server): array
  2486. {
  2487. $headers = [];
  2488. foreach ($server as $key => $value) {
  2489. if (0 === \strpos($key, 'REDIRECT_')) {
  2490. $key = \substr($key, 9);
  2491. if (\array_key_exists($key, $server)) {
  2492. continue;
  2493. }
  2494. }
  2495. if ($value && 0 === \strpos($key, 'HTTP_')) {
  2496. $name = \strtr(\strtolower(\substr($key, 5)), '_', '-');
  2497. $headers[$name] = $value;
  2498. continue;
  2499. }
  2500. if ($value && 0 === \strpos($key, 'CONTENT_')) {
  2501. $name = 'content-'.\strtolower(\substr($key, 8));
  2502. $headers[$name] = $value;
  2503. continue;
  2504. }
  2505. }
  2506. return $headers;
  2507. }
  2508. private function getMethodFromEnv(array $environment): string
  2509. {
  2510. if (false === isset($environment['REQUEST_METHOD'])) {
  2511. throw new \InvalidArgumentException('Cannot determine HTTP method');
  2512. }
  2513. return $environment['REQUEST_METHOD'];
  2514. }
  2515. private function getUriFromEnvWithHTTP(array $environment): UriInterface
  2516. {
  2517. $uri = $this->createUriFromArray($environment);
  2518. if (empty($uri->getScheme())) {
  2519. $uri = $uri->withScheme('http');
  2520. }
  2521. return $uri;
  2522. }
  2523. /**
  2524. * Return an UploadedFile instance array.
  2525. *
  2526. * @param array $files A array which respect $_FILES structure
  2527. *
  2528. * @return UploadedFileInterface[]
  2529. *
  2530. * @throws \InvalidArgumentException for unrecognized values
  2531. */
  2532. private function normalizeFiles(array $files): array
  2533. {
  2534. $normalized = [];
  2535. foreach ($files as $key => $value) {
  2536. if ($value instanceof UploadedFileInterface) {
  2537. $normalized[$key] = $value;
  2538. } elseif (\is_array($value) && isset($value['tmp_name'])) {
  2539. $normalized[$key] = $this->createUploadedFileFromSpec($value);
  2540. } elseif (\is_array($value)) {
  2541. $normalized[$key] = $this->normalizeFiles($value);
  2542. } else {
  2543. throw new \InvalidArgumentException('Invalid value in files specification');
  2544. }
  2545. }
  2546. return $normalized;
  2547. }
  2548. /**
  2549. * Create and return an UploadedFile instance from a $_FILES specification.
  2550. *
  2551. * If the specification represents an array of values, this method will
  2552. * delegate to normalizeNestedFileSpec() and return that return value.
  2553. *
  2554. * @param array $value $_FILES struct
  2555. *
  2556. * @return array|UploadedFileInterface
  2557. */
  2558. private function createUploadedFileFromSpec(array $value)
  2559. {
  2560. if (\is_array($value['tmp_name'])) {
  2561. return $this->normalizeNestedFileSpec($value);
  2562. }
  2563. try {
  2564. $stream = $this->streamFactory->createStreamFromFile($value['tmp_name']);
  2565. } catch (\RuntimeException $e) {
  2566. $stream = $this->streamFactory->createStream();
  2567. }
  2568. return $this->uploadedFileFactory->createUploadedFile(
  2569. $stream,
  2570. (int) $value['size'],
  2571. (int) $value['error'],
  2572. $value['name'],
  2573. $value['type']
  2574. );
  2575. }
  2576. /**
  2577. * Normalize an array of file specifications.
  2578. *
  2579. * Loops through all nested files and returns a normalized array of
  2580. * UploadedFileInterface instances.
  2581. *
  2582. * @param array $files
  2583. *
  2584. * @return UploadedFileInterface[]
  2585. */
  2586. private function normalizeNestedFileSpec(array $files = []): array
  2587. {
  2588. $normalizedFiles = [];
  2589. foreach (\array_keys($files['tmp_name']) as $key) {
  2590. $spec = [
  2591. 'tmp_name' => $files['tmp_name'][$key],
  2592. 'size' => $files['size'][$key],
  2593. 'error' => $files['error'][$key],
  2594. 'name' => $files['name'][$key],
  2595. 'type' => $files['type'][$key],
  2596. ];
  2597. $normalizedFiles[$key] = $this->createUploadedFileFromSpec($spec);
  2598. }
  2599. return $normalizedFiles;
  2600. }
  2601. /**
  2602. * Create a new uri from server variable.
  2603. *
  2604. * @param array $server typically $_SERVER or similar structure
  2605. */
  2606. private function createUriFromArray(array $server): UriInterface
  2607. {
  2608. $uri = $this->uriFactory->createUri('');
  2609. if (isset($server['REQUEST_SCHEME'])) {
  2610. $uri = $uri->withScheme($server['REQUEST_SCHEME']);
  2611. } elseif (isset($server['HTTPS'])) {
  2612. $uri = $uri->withScheme('on' === $server['HTTPS'] ? 'https' : 'http');
  2613. }
  2614. if (isset($server['SERVER_PORT'])) {
  2615. $uri = $uri->withPort($server['SERVER_PORT']);
  2616. }
  2617. if (isset($server['HTTP_HOST'])) {
  2618. if (1 === \preg_match('/^(.+)\:(\d+)$/', $server['HTTP_HOST'], $matches)) {
  2619. $uri = $uri->withHost($matches[1])->withPort($matches[2]);
  2620. } else {
  2621. $uri = $uri->withHost($server['HTTP_HOST']);
  2622. }
  2623. } elseif (isset($server['SERVER_NAME'])) {
  2624. $uri = $uri->withHost($server['SERVER_NAME']);
  2625. }
  2626. if (isset($server['REQUEST_URI'])) {
  2627. $uri = $uri->withPath(\current(\explode('?', $server['REQUEST_URI'])));
  2628. }
  2629. if (isset($server['QUERY_STRING'])) {
  2630. $uri = $uri->withQuery($server['QUERY_STRING']);
  2631. }
  2632. return $uri;
  2633. }
  2634. }
  2635. // file: src/Nyholm/Psr7Server/ServerRequestCreatorInterface.php
  2636. /**
  2637. * @author Tobias Nyholm <tobias.nyholm@gmail.com>
  2638. * @author Martijn van der Ven <martijn@vanderven.se>
  2639. */
  2640. interface ServerRequestCreatorInterface
  2641. {
  2642. /**
  2643. * Create a new server request from the current environment variables.
  2644. * Defaults to a GET request to minimise the risk of an \InvalidArgumentException.
  2645. * Includes the current request headers as supplied by the server through `getallheaders()`.
  2646. * If `getallheaders()` is unavailable on the current server it will fallback to its own `getHeadersFromServer()` method.
  2647. * Defaults to php://input for the request body.
  2648. *
  2649. * @throws \InvalidArgumentException if no valid method or URI can be determined
  2650. */
  2651. public function fromGlobals(): ServerRequestInterface;
  2652. /**
  2653. * Create a new server request from a set of arrays.
  2654. *
  2655. * @param array $server typically $_SERVER or similar structure
  2656. * @param array $headers typically the output of getallheaders() or similar structure
  2657. * @param array $cookie typically $_COOKIE or similar structure
  2658. * @param array $get typically $_GET or similar structure
  2659. * @param array $post typically $_POST or similar structure
  2660. * @param array $files typically $_FILES or similar structure
  2661. * @param StreamInterface|resource|string|null $body Typically stdIn
  2662. *
  2663. * @throws \InvalidArgumentException if no valid method or URI can be determined
  2664. */
  2665. public function fromArrays(
  2666. array $server,
  2667. array $headers = [],
  2668. array $cookie = [],
  2669. array $get = [],
  2670. array $post = [],
  2671. array $files = [],
  2672. $body = null
  2673. ): ServerRequestInterface;
  2674. /**
  2675. * Get parsed headers from ($_SERVER) array.
  2676. *
  2677. * @param array $server typically $_SERVER or similar structure
  2678. *
  2679. * @return array
  2680. */
  2681. public static function getHeadersFromServer(array $server): array;
  2682. }
  2683. // file: src/Tqdev/PhpCrudApi/Cache/Cache.php
  2684. interface Cache
  2685. {
  2686. public function set(String $key, String $value, int $ttl = 0): bool;
  2687. public function get(String $key): String;
  2688. public function clear(): bool;
  2689. }
  2690. // file: src/Tqdev/PhpCrudApi/Cache/CacheFactory.php
  2691. class CacheFactory
  2692. {
  2693. const PREFIX = 'phpcrudapi-%s-%s-%s-';
  2694. private static function getPrefix(Config $config): String
  2695. {
  2696. $driver = $config->getDriver();
  2697. $database = $config->getDatabase();
  2698. $filehash = substr(md5(__FILE__), 0, 8);
  2699. return sprintf(self::PREFIX, $driver, $database, $filehash);
  2700. }
  2701. public static function create(Config $config): Cache
  2702. {
  2703. switch ($config->getCacheType()) {
  2704. case 'TempFile':
  2705. $cache = new TempFileCache(self::getPrefix($config), $config->getCachePath());
  2706. break;
  2707. case 'Redis':
  2708. $cache = new RedisCache(self::getPrefix($config), $config->getCachePath());
  2709. break;
  2710. case 'Memcache':
  2711. $cache = new MemcacheCache(self::getPrefix($config), $config->getCachePath());
  2712. break;
  2713. case 'Memcached':
  2714. $cache = new MemcachedCache(self::getPrefix($config), $config->getCachePath());
  2715. break;
  2716. default:
  2717. $cache = new NoCache();
  2718. }
  2719. return $cache;
  2720. }
  2721. }
  2722. // file: src/Tqdev/PhpCrudApi/Cache/MemcacheCache.php
  2723. class MemcacheCache implements Cache
  2724. {
  2725. protected $prefix;
  2726. protected $memcache;
  2727. public function __construct(String $prefix, String $config)
  2728. {
  2729. $this->prefix = $prefix;
  2730. if ($config == '') {
  2731. $address = 'localhost';
  2732. $port = 11211;
  2733. } elseif (strpos($config, ':') === false) {
  2734. $address = $config;
  2735. $port = 11211;
  2736. } else {
  2737. list($address, $port) = explode(':', $config);
  2738. }
  2739. $this->memcache = $this->create();
  2740. $this->memcache->addServer($address, $port);
  2741. }
  2742. protected function create() /*: \Memcache*/
  2743. {
  2744. return new \Memcache();
  2745. }
  2746. public function set(String $key, String $value, int $ttl = 0): bool
  2747. {
  2748. return $this->memcache->set($this->prefix . $key, $value, 0, $ttl);
  2749. }
  2750. public function get(String $key): String
  2751. {
  2752. return $this->memcache->get($this->prefix . $key) ?: '';
  2753. }
  2754. public function clear(): bool
  2755. {
  2756. return $this->memcache->flush();
  2757. }
  2758. }
  2759. // file: src/Tqdev/PhpCrudApi/Cache/MemcachedCache.php
  2760. class MemcachedCache extends MemcacheCache
  2761. {
  2762. protected function create() /*: \Memcached*/
  2763. {
  2764. return new \Memcached();
  2765. }
  2766. public function set(String $key, String $value, int $ttl = 0): bool
  2767. {
  2768. return $this->memcache->set($this->prefix . $key, $value, $ttl);
  2769. }
  2770. }
  2771. // file: src/Tqdev/PhpCrudApi/Cache/NoCache.php
  2772. class NoCache implements Cache
  2773. {
  2774. public function __construct()
  2775. {
  2776. }
  2777. public function set(String $key, String $value, int $ttl = 0): bool
  2778. {
  2779. return true;
  2780. }
  2781. public function get(String $key): String
  2782. {
  2783. return '';
  2784. }
  2785. public function clear(): bool
  2786. {
  2787. return true;
  2788. }
  2789. }
  2790. // file: src/Tqdev/PhpCrudApi/Cache/RedisCache.php
  2791. class RedisCache implements Cache
  2792. {
  2793. protected $prefix;
  2794. protected $redis;
  2795. public function __construct(String $prefix, String $config)
  2796. {
  2797. $this->prefix = $prefix;
  2798. if ($config == '') {
  2799. $config = '127.0.0.1';
  2800. }
  2801. $params = explode(':', $config, 6);
  2802. if (isset($params[3])) {
  2803. $params[3] = null;
  2804. }
  2805. $this->redis = new \Redis();
  2806. call_user_func_array(array($this->redis, 'pconnect'), $params);
  2807. }
  2808. public function set(String $key, String $value, int $ttl = 0): bool
  2809. {
  2810. return $this->redis->set($this->prefix . $key, $value, $ttl);
  2811. }
  2812. public function get(String $key): String
  2813. {
  2814. return $this->redis->get($this->prefix . $key) ?: '';
  2815. }
  2816. public function clear(): bool
  2817. {
  2818. return $this->redis->flushDb();
  2819. }
  2820. }
  2821. // file: src/Tqdev/PhpCrudApi/Cache/TempFileCache.php
  2822. class TempFileCache implements Cache
  2823. {
  2824. const SUFFIX = 'cache';
  2825. private $path;
  2826. private $segments;
  2827. public function __construct(String $prefix, String $config)
  2828. {
  2829. $this->segments = [];
  2830. $s = DIRECTORY_SEPARATOR;
  2831. $ps = PATH_SEPARATOR;
  2832. if ($config == '') {
  2833. $this->path = sys_get_temp_dir() . $s . $prefix . self::SUFFIX;
  2834. } elseif (strpos($config, $ps) === false) {
  2835. $this->path = $config;
  2836. } else {
  2837. list($path, $segments) = explode($ps, $config);
  2838. $this->path = $path;
  2839. $this->segments = explode(',', $segments);
  2840. }
  2841. if (file_exists($this->path) && is_dir($this->path)) {
  2842. $this->clean($this->path, array_filter($this->segments), strlen(md5('')), false);
  2843. }
  2844. }
  2845. private function getFileName(String $key): String
  2846. {
  2847. $s = DIRECTORY_SEPARATOR;
  2848. $md5 = md5($key);
  2849. $filename = rtrim($this->path, $s) . $s;
  2850. $i = 0;
  2851. foreach ($this->segments as $segment) {
  2852. $filename .= substr($md5, $i, $segment) . $s;
  2853. $i += $segment;
  2854. }
  2855. $filename .= substr($md5, $i);
  2856. return $filename;
  2857. }
  2858. public function set(String $key, String $value, int $ttl = 0): bool
  2859. {
  2860. $filename = $this->getFileName($key);
  2861. $dirname = dirname($filename);
  2862. if (!file_exists($dirname)) {
  2863. if (!mkdir($dirname, 0755, true)) {
  2864. return false;
  2865. }
  2866. }
  2867. $string = $ttl . '|' . $value;
  2868. return $this->filePutContents($filename, $string) !== false;
  2869. }
  2870. private function filePutContents($filename, $string)
  2871. {
  2872. return file_put_contents($filename, $string, LOCK_EX);
  2873. }
  2874. private function fileGetContents($filename)
  2875. {
  2876. $file = fopen($filename, 'rb');
  2877. if ($file === false) {
  2878. return false;
  2879. }
  2880. $lock = flock($file, LOCK_SH);
  2881. if (!$lock) {
  2882. fclose($file);
  2883. return false;
  2884. }
  2885. $string = '';
  2886. while (!feof($file)) {
  2887. $string .= fread($file, 8192);
  2888. }
  2889. flock($file, LOCK_UN);
  2890. fclose($file);
  2891. return $string;
  2892. }
  2893. private function getString($filename): String
  2894. {
  2895. $data = $this->fileGetContents($filename);
  2896. if ($data === false) {
  2897. return '';
  2898. }
  2899. list($ttl, $string) = explode('|', $data, 2);
  2900. if ($ttl > 0 && time() - filemtime($filename) > $ttl) {
  2901. return '';
  2902. }
  2903. return $string;
  2904. }
  2905. public function get(String $key): String
  2906. {
  2907. $filename = $this->getFileName($key);
  2908. if (!file_exists($filename)) {
  2909. return '';
  2910. }
  2911. $string = $this->getString($filename);
  2912. if ($string == null) {
  2913. return '';
  2914. }
  2915. return $string;
  2916. }
  2917. private function clean(String $path, array $segments, int $len, bool $all) /*: void*/
  2918. {
  2919. $entries = scandir($path);
  2920. foreach ($entries as $entry) {
  2921. if ($entry === '.' || $entry === '..') {
  2922. continue;
  2923. }
  2924. $filename = $path . DIRECTORY_SEPARATOR . $entry;
  2925. if (count($segments) == 0) {
  2926. if (strlen($entry) != $len) {
  2927. continue;
  2928. }
  2929. if (is_file($filename)) {
  2930. if ($all || $this->getString($filename) == null) {
  2931. unlink($filename);
  2932. }
  2933. }
  2934. } else {
  2935. if (strlen($entry) != $segments[0]) {
  2936. continue;
  2937. }
  2938. if (is_dir($filename)) {
  2939. $this->clean($filename, array_slice($segments, 1), $len - $segments[0], $all);
  2940. rmdir($filename);
  2941. }
  2942. }
  2943. }
  2944. }
  2945. public function clear(): bool
  2946. {
  2947. if (!file_exists($this->path) || !is_dir($this->path)) {
  2948. return false;
  2949. }
  2950. $this->clean($this->path, array_filter($this->segments), strlen(md5('')), true);
  2951. return true;
  2952. }
  2953. }
  2954. // file: src/Tqdev/PhpCrudApi/Column/Reflection/ReflectedColumn.php
  2955. class ReflectedColumn implements \JsonSerializable
  2956. {
  2957. const DEFAULT_LENGTH = 255;
  2958. const DEFAULT_PRECISION = 19;
  2959. const DEFAULT_SCALE = 4;
  2960. private $name;
  2961. private $type;
  2962. private $length;
  2963. private $precision;
  2964. private $scale;
  2965. private $nullable;
  2966. private $pk;
  2967. private $fk;
  2968. public function __construct(String $name, String $type, int $length, int $precision, int $scale, bool $nullable, bool $pk, String $fk)
  2969. {
  2970. $this->name = $name;
  2971. $this->type = $type;
  2972. $this->length = $length;
  2973. $this->precision = $precision;
  2974. $this->scale = $scale;
  2975. $this->nullable = $nullable;
  2976. $this->pk = $pk;
  2977. $this->fk = $fk;
  2978. $this->sanitize();
  2979. }
  2980. public static function fromReflection(GenericReflection $reflection, array $columnResult): ReflectedColumn
  2981. {
  2982. $name = $columnResult['COLUMN_NAME'];
  2983. $length = (int) $columnResult['CHARACTER_MAXIMUM_LENGTH'];
  2984. $type = $reflection->toJdbcType($columnResult['DATA_TYPE'], $length);
  2985. $precision = (int) $columnResult['NUMERIC_PRECISION'];
  2986. $scale = (int) $columnResult['NUMERIC_SCALE'];
  2987. $nullable = in_array(strtoupper($columnResult['IS_NULLABLE']), ['TRUE', 'YES', 'T', 'Y', '1']);
  2988. $pk = false;
  2989. $fk = '';
  2990. return new ReflectedColumn($name, $type, $length, $precision, $scale, $nullable, $pk, $fk);
  2991. }
  2992. public static function fromJson( /* object */$json): ReflectedColumn
  2993. {
  2994. $name = $json->name;
  2995. $type = $json->type;
  2996. $length = isset($json->length) ? $json->length : 0;
  2997. $precision = isset($json->precision) ? $json->precision : 0;
  2998. $scale = isset($json->scale) ? $json->scale : 0;
  2999. $nullable = isset($json->nullable) ? $json->nullable : false;
  3000. $pk = isset($json->pk) ? $json->pk : false;
  3001. $fk = isset($json->fk) ? $json->fk : '';
  3002. return new ReflectedColumn($name, $type, $length, $precision, $scale, $nullable, $pk, $fk);
  3003. }
  3004. private function sanitize()
  3005. {
  3006. $this->length = $this->hasLength() ? $this->getLength() : 0;
  3007. $this->precision = $this->hasPrecision() ? $this->getPrecision() : 0;
  3008. $this->scale = $this->hasScale() ? $this->getScale() : 0;
  3009. }
  3010. public function getName(): String
  3011. {
  3012. return $this->name;
  3013. }
  3014. public function getNullable(): bool
  3015. {
  3016. return $this->nullable;
  3017. }
  3018. public function getType(): String
  3019. {
  3020. return $this->type;
  3021. }
  3022. public function getLength(): int
  3023. {
  3024. return $this->length ?: self::DEFAULT_LENGTH;
  3025. }
  3026. public function getPrecision(): int
  3027. {
  3028. return $this->precision ?: self::DEFAULT_PRECISION;
  3029. }
  3030. public function getScale(): int
  3031. {
  3032. return $this->scale ?: self::DEFAULT_SCALE;
  3033. }
  3034. public function hasLength(): bool
  3035. {
  3036. return in_array($this->type, ['varchar', 'varbinary']);
  3037. }
  3038. public function hasPrecision(): bool
  3039. {
  3040. return $this->type == 'decimal';
  3041. }
  3042. public function hasScale(): bool
  3043. {
  3044. return $this->type == 'decimal';
  3045. }
  3046. public function isBinary(): bool
  3047. {
  3048. return in_array($this->type, ['blob', 'varbinary']);
  3049. }
  3050. public function isBoolean(): bool
  3051. {
  3052. return $this->type == 'boolean';
  3053. }
  3054. public function isGeometry(): bool
  3055. {
  3056. return $this->type == 'geometry';
  3057. }
  3058. public function isInteger(): bool
  3059. {
  3060. return in_array($this->type, ['integer', 'bigint', 'smallint', 'tinyint']);
  3061. }
  3062. public function setPk($value) /*: void*/
  3063. {
  3064. $this->pk = $value;
  3065. }
  3066. public function getPk(): bool
  3067. {
  3068. return $this->pk;
  3069. }
  3070. public function setFk($value) /*: void*/
  3071. {
  3072. $this->fk = $value;
  3073. }
  3074. public function getFk(): String
  3075. {
  3076. return $this->fk;
  3077. }
  3078. public function serialize()
  3079. {
  3080. return [
  3081. 'name' => $this->name,
  3082. 'type' => $this->type,
  3083. 'length' => $this->length,
  3084. 'precision' => $this->precision,
  3085. 'scale' => $this->scale,
  3086. 'nullable' => $this->nullable,
  3087. 'pk' => $this->pk,
  3088. 'fk' => $this->fk,
  3089. ];
  3090. }
  3091. public function jsonSerialize()
  3092. {
  3093. return array_filter($this->serialize());
  3094. }
  3095. }
  3096. // file: src/Tqdev/PhpCrudApi/Column/Reflection/ReflectedDatabase.php
  3097. class ReflectedDatabase implements \JsonSerializable
  3098. {
  3099. private $tableTypes;
  3100. public function __construct(array $tableTypes)
  3101. {
  3102. $this->tableTypes = $tableTypes;
  3103. }
  3104. public static function fromReflection(GenericReflection $reflection): ReflectedDatabase
  3105. {
  3106. $tableTypes = [];
  3107. foreach ($reflection->getTables() as $table) {
  3108. $tableName = $table['TABLE_NAME'];
  3109. $tableType = $table['TABLE_TYPE'];
  3110. if (in_array($tableName, $reflection->getIgnoredTables())) {
  3111. continue;
  3112. }
  3113. $tableTypes[$tableName] = $tableType;
  3114. }
  3115. return new ReflectedDatabase($tableTypes);
  3116. }
  3117. public static function fromJson( /* object */$json): ReflectedDatabase
  3118. {
  3119. $tableTypes = (array) $json->tables;
  3120. return new ReflectedDatabase($tableTypes);
  3121. }
  3122. public function hasTable(String $tableName): bool
  3123. {
  3124. return isset($this->tableTypes[$tableName]);
  3125. }
  3126. public function getType(String $tableName): String
  3127. {
  3128. return isset($this->tableTypes[$tableName]) ? $this->tableTypes[$tableName] : '';
  3129. }
  3130. public function getTableNames(): array
  3131. {
  3132. return array_keys($this->tableTypes);
  3133. }
  3134. public function removeTable(String $tableName): bool
  3135. {
  3136. if (!isset($this->tableTypes[$tableName])) {
  3137. return false;
  3138. }
  3139. unset($this->tableTypes[$tableName]);
  3140. return true;
  3141. }
  3142. public function serialize()
  3143. {
  3144. return [
  3145. 'tables' => $this->tableTypes,
  3146. ];
  3147. }
  3148. public function jsonSerialize()
  3149. {
  3150. return $this->serialize();
  3151. }
  3152. }
  3153. // file: src/Tqdev/PhpCrudApi/Column/Reflection/ReflectedTable.php
  3154. class ReflectedTable implements \JsonSerializable
  3155. {
  3156. private $name;
  3157. private $type;
  3158. private $columns;
  3159. private $pk;
  3160. private $fks;
  3161. public function __construct(String $name, String $type, array $columns)
  3162. {
  3163. $this->name = $name;
  3164. $this->type = $type;
  3165. $this->columns = [];
  3166. foreach ($columns as $column) {
  3167. $columnName = $column->getName();
  3168. $this->columns[$columnName] = $column;
  3169. }
  3170. $this->pk = null;
  3171. foreach ($columns as $column) {
  3172. if ($column->getPk() == true) {
  3173. $this->pk = $column;
  3174. }
  3175. }
  3176. $this->fks = [];
  3177. foreach ($columns as $column) {
  3178. $columnName = $column->getName();
  3179. $referencedTableName = $column->getFk();
  3180. if ($referencedTableName != '') {
  3181. $this->fks[$columnName] = $referencedTableName;
  3182. }
  3183. }
  3184. }
  3185. public static function fromReflection(GenericReflection $reflection, String $name, String $type): ReflectedTable
  3186. {
  3187. $columns = [];
  3188. foreach ($reflection->getTableColumns($name, $type) as $tableColumn) {
  3189. $column = ReflectedColumn::fromReflection($reflection, $tableColumn);
  3190. $columns[$column->getName()] = $column;
  3191. }
  3192. $columnNames = $reflection->getTablePrimaryKeys($name);
  3193. if (count($columnNames) == 1) {
  3194. $columnName = $columnNames[0];
  3195. if (isset($columns[$columnName])) {
  3196. $pk = $columns[$columnName];
  3197. $pk->setPk(true);
  3198. }
  3199. }
  3200. $fks = $reflection->getTableForeignKeys($name);
  3201. foreach ($fks as $columnName => $table) {
  3202. $columns[$columnName]->setFk($table);
  3203. }
  3204. return new ReflectedTable($name, $type, array_values($columns));
  3205. }
  3206. public static function fromJson( /* object */$json): ReflectedTable
  3207. {
  3208. $name = $json->name;
  3209. $type = $json->type;
  3210. $columns = [];
  3211. if (isset($json->columns) && is_array($json->columns)) {
  3212. foreach ($json->columns as $column) {
  3213. $columns[] = ReflectedColumn::fromJson($column);
  3214. }
  3215. }
  3216. return new ReflectedTable($name, $type, $columns);
  3217. }
  3218. public function hasColumn(String $columnName): bool
  3219. {
  3220. return isset($this->columns[$columnName]);
  3221. }
  3222. public function hasPk(): bool
  3223. {
  3224. return $this->pk != null;
  3225. }
  3226. public function getPk() /*: ?ReflectedColumn */
  3227. {
  3228. return $this->pk;
  3229. }
  3230. public function getName(): String
  3231. {
  3232. return $this->name;
  3233. }
  3234. public function getType(): String
  3235. {
  3236. return $this->type;
  3237. }
  3238. public function getColumnNames(): array
  3239. {
  3240. return array_keys($this->columns);
  3241. }
  3242. public function getColumn($columnName): ReflectedColumn
  3243. {
  3244. return $this->columns[$columnName];
  3245. }
  3246. public function getFksTo(String $tableName): array
  3247. {
  3248. $columns = array();
  3249. foreach ($this->fks as $columnName => $referencedTableName) {
  3250. if ($tableName == $referencedTableName) {
  3251. $columns[] = $this->columns[$columnName];
  3252. }
  3253. }
  3254. return $columns;
  3255. }
  3256. public function removeColumn(String $columnName): bool
  3257. {
  3258. if (!isset($this->columns[$columnName])) {
  3259. return false;
  3260. }
  3261. unset($this->columns[$columnName]);
  3262. return true;
  3263. }
  3264. public function serialize()
  3265. {
  3266. return [
  3267. 'name' => $this->name,
  3268. 'type' => $this->type,
  3269. 'columns' => array_values($this->columns),
  3270. ];
  3271. }
  3272. public function jsonSerialize()
  3273. {
  3274. return $this->serialize();
  3275. }
  3276. }
  3277. // file: src/Tqdev/PhpCrudApi/Column/DefinitionService.php
  3278. class DefinitionService
  3279. {
  3280. private $db;
  3281. private $reflection;
  3282. public function __construct(GenericDB $db, ReflectionService $reflection)
  3283. {
  3284. $this->db = $db;
  3285. $this->reflection = $reflection;
  3286. }
  3287. public function updateTable(String $tableName, /* object */ $changes): bool
  3288. {
  3289. $table = $this->reflection->getTable($tableName);
  3290. $newTable = ReflectedTable::fromJson((object) array_merge((array) $table->jsonSerialize(), (array) $changes));
  3291. if ($table->getName() != $newTable->getName()) {
  3292. if (!$this->db->definition()->renameTable($table->getName(), $newTable->getName())) {
  3293. return false;
  3294. }
  3295. }
  3296. return true;
  3297. }
  3298. public function updateColumn(String $tableName, String $columnName, /* object */ $changes): bool
  3299. {
  3300. $table = $this->reflection->getTable($tableName);
  3301. $column = $table->getColumn($columnName);
  3302. $newColumn = ReflectedColumn::fromJson((object) array_merge((array) $column->jsonSerialize(), (array) $changes));
  3303. if ($newColumn->getPk() != $column->getPk() && $table->hasPk()) {
  3304. $oldColumn = $table->getPk();
  3305. if ($oldColumn->getName() != $columnName) {
  3306. $oldColumn->setPk(false);
  3307. if (!$this->db->definition()->removeColumnPrimaryKey($table->getName(), $oldColumn->getName(), $oldColumn)) {
  3308. return false;
  3309. }
  3310. }
  3311. }
  3312. $newColumn = ReflectedColumn::fromJson((object) array_merge((array) $column->jsonSerialize(), ['pk' => false, 'fk' => false]));
  3313. if ($newColumn->getPk() != $column->getPk() && !$newColumn->getPk()) {
  3314. if (!$this->db->definition()->removeColumnPrimaryKey($table->getName(), $column->getName(), $newColumn)) {
  3315. return false;
  3316. }
  3317. }
  3318. if ($newColumn->getFk() != $column->getFk() && !$newColumn->getFk()) {
  3319. if (!$this->db->definition()->removeColumnForeignKey($table->getName(), $column->getName(), $newColumn)) {
  3320. return false;
  3321. }
  3322. }
  3323. $newColumn = ReflectedColumn::fromJson((object) array_merge((array) $column->jsonSerialize(), (array) $changes));
  3324. $newColumn->setPk(false);
  3325. $newColumn->setFk('');
  3326. if ($newColumn->getName() != $column->getName()) {
  3327. if (!$this->db->definition()->renameColumn($table->getName(), $column->getName(), $newColumn)) {
  3328. return false;
  3329. }
  3330. }
  3331. if ($newColumn->getType() != $column->getType() ||
  3332. $newColumn->getLength() != $column->getLength() ||
  3333. $newColumn->getPrecision() != $column->getPrecision() ||
  3334. $newColumn->getScale() != $column->getScale()
  3335. ) {
  3336. if (!$this->db->definition()->retypeColumn($table->getName(), $newColumn->getName(), $newColumn)) {
  3337. return false;
  3338. }
  3339. }
  3340. if ($newColumn->getNullable() != $column->getNullable()) {
  3341. if (!$this->db->definition()->setColumnNullable($table->getName(), $newColumn->getName(), $newColumn)) {
  3342. return false;
  3343. }
  3344. }
  3345. $newColumn = ReflectedColumn::fromJson((object) array_merge((array) $column->jsonSerialize(), (array) $changes));
  3346. if ($newColumn->getFk()) {
  3347. if (!$this->db->definition()->addColumnForeignKey($table->getName(), $newColumn->getName(), $newColumn)) {
  3348. return false;
  3349. }
  3350. }
  3351. if ($newColumn->getPk()) {
  3352. if (!$this->db->definition()->addColumnPrimaryKey($table->getName(), $newColumn->getName(), $newColumn)) {
  3353. return false;
  3354. }
  3355. }
  3356. return true;
  3357. }
  3358. public function addTable( /* object */$definition)
  3359. {
  3360. $newTable = ReflectedTable::fromJson($definition);
  3361. if (!$this->db->definition()->addTable($newTable)) {
  3362. return false;
  3363. }
  3364. return true;
  3365. }
  3366. public function addColumn(String $tableName, /* object */ $definition)
  3367. {
  3368. $newColumn = ReflectedColumn::fromJson($definition);
  3369. if (!$this->db->definition()->addColumn($tableName, $newColumn)) {
  3370. return false;
  3371. }
  3372. if ($newColumn->getFk()) {
  3373. if (!$this->db->definition()->addColumnForeignKey($tableName, $newColumn->getName(), $newColumn)) {
  3374. return false;
  3375. }
  3376. }
  3377. if ($newColumn->getPk()) {
  3378. if (!$this->db->definition()->addColumnPrimaryKey($tableName, $newColumn->getName(), $newColumn)) {
  3379. return false;
  3380. }
  3381. }
  3382. return true;
  3383. }
  3384. public function removeTable(String $tableName)
  3385. {
  3386. if (!$this->db->definition()->removeTable($tableName)) {
  3387. return false;
  3388. }
  3389. return true;
  3390. }
  3391. public function removeColumn(String $tableName, String $columnName)
  3392. {
  3393. $table = $this->reflection->getTable($tableName);
  3394. $newColumn = $table->getColumn($columnName);
  3395. if ($newColumn->getPk()) {
  3396. $newColumn->setPk(false);
  3397. if (!$this->db->definition()->removeColumnPrimaryKey($table->getName(), $newColumn->getName(), $newColumn)) {
  3398. return false;
  3399. }
  3400. }
  3401. if ($newColumn->getFk()) {
  3402. $newColumn->setFk("");
  3403. if (!$this->db->definition()->removeColumnForeignKey($tableName, $columnName, $newColumn)) {
  3404. return false;
  3405. }
  3406. }
  3407. if (!$this->db->definition()->removeColumn($tableName, $columnName)) {
  3408. return false;
  3409. }
  3410. return true;
  3411. }
  3412. }
  3413. // file: src/Tqdev/PhpCrudApi/Column/ReflectionService.php
  3414. class ReflectionService
  3415. {
  3416. private $db;
  3417. private $cache;
  3418. private $ttl;
  3419. private $database;
  3420. private $tables;
  3421. public function __construct(GenericDB $db, Cache $cache, int $ttl)
  3422. {
  3423. $this->db = $db;
  3424. $this->cache = $cache;
  3425. $this->ttl = $ttl;
  3426. $this->database = $this->loadDatabase(true);
  3427. $this->tables = [];
  3428. }
  3429. private function loadDatabase(bool $useCache): ReflectedDatabase
  3430. {
  3431. $data = $useCache ? $this->cache->get('ReflectedDatabase') : '';
  3432. if ($data != '') {
  3433. $database = ReflectedDatabase::fromJson(json_decode(gzuncompress($data)));
  3434. } else {
  3435. $database = ReflectedDatabase::fromReflection($this->db->reflection());
  3436. $data = gzcompress(json_encode($database, JSON_UNESCAPED_UNICODE));
  3437. $this->cache->set('ReflectedDatabase', $data, $this->ttl);
  3438. }
  3439. return $database;
  3440. }
  3441. private function loadTable(String $tableName, bool $useCache): ReflectedTable
  3442. {
  3443. $data = $useCache ? $this->cache->get("ReflectedTable($tableName)") : '';
  3444. if ($data != '') {
  3445. $table = ReflectedTable::fromJson(json_decode(gzuncompress($data)));
  3446. } else {
  3447. $tableType = $this->database->getType($tableName);
  3448. $table = ReflectedTable::fromReflection($this->db->reflection(), $tableName, $tableType);
  3449. $data = gzcompress(json_encode($table, JSON_UNESCAPED_UNICODE));
  3450. $this->cache->set("ReflectedTable($tableName)", $data, $this->ttl);
  3451. }
  3452. return $table;
  3453. }
  3454. public function refreshTables()
  3455. {
  3456. $this->database = $this->loadDatabase(false);
  3457. }
  3458. public function refreshTable(String $tableName)
  3459. {
  3460. $this->tables[$tableName] = $this->loadTable($tableName, false);
  3461. }
  3462. public function hasTable(String $tableName): bool
  3463. {
  3464. return $this->database->hasTable($tableName);
  3465. }
  3466. public function getType(String $tableName): String
  3467. {
  3468. return $this->database->getType($tableName);
  3469. }
  3470. public function getTable(String $tableName): ReflectedTable
  3471. {
  3472. if (!isset($this->tables[$tableName])) {
  3473. $this->tables[$tableName] = $this->loadTable($tableName, true);
  3474. }
  3475. return $this->tables[$tableName];
  3476. }
  3477. public function getTableNames(): array
  3478. {
  3479. return $this->database->getTableNames();
  3480. }
  3481. public function getDatabaseName(): String
  3482. {
  3483. return $this->database->getName();
  3484. }
  3485. public function removeTable(String $tableName): bool
  3486. {
  3487. unset($this->tables[$tableName]);
  3488. return $this->database->removeTable($tableName);
  3489. }
  3490. }
  3491. // file: src/Tqdev/PhpCrudApi/Controller/CacheController.php
  3492. class CacheController
  3493. {
  3494. private $cache;
  3495. private $responder;
  3496. public function __construct(Router $router, Responder $responder, Cache $cache)
  3497. {
  3498. $router->register('GET', '/cache/clear', array($this, 'clear'));
  3499. $this->cache = $cache;
  3500. $this->responder = $responder;
  3501. }
  3502. public function clear(ServerRequestInterface $request): Response
  3503. {
  3504. return $this->responder->success($this->cache->clear());
  3505. }
  3506. }
  3507. // file: src/Tqdev/PhpCrudApi/Controller/ColumnController.php
  3508. class ColumnController
  3509. {
  3510. private $responder;
  3511. private $reflection;
  3512. private $definition;
  3513. private $utils;
  3514. public function __construct(Router $router, Responder $responder, ReflectionService $reflection, DefinitionService $definition)
  3515. {
  3516. $router->register('GET', '/columns', array($this, 'getDatabase'));
  3517. $router->register('GET', '/columns/*', array($this, 'getTable'));
  3518. $router->register('GET', '/columns/*/*', array($this, 'getColumn'));
  3519. $router->register('PUT', '/columns/*', array($this, 'updateTable'));
  3520. $router->register('PUT', '/columns/*/*', array($this, 'updateColumn'));
  3521. $router->register('POST', '/columns', array($this, 'addTable'));
  3522. $router->register('POST', '/columns/*', array($this, 'addColumn'));
  3523. $router->register('DELETE', '/columns/*', array($this, 'removeTable'));
  3524. $router->register('DELETE', '/columns/*/*', array($this, 'removeColumn'));
  3525. $this->responder = $responder;
  3526. $this->reflection = $reflection;
  3527. $this->definition = $definition;
  3528. $this->utils = new RequestUtils($reflection);
  3529. }
  3530. public function getDatabase(ServerRequestInterface $request): Response
  3531. {
  3532. $tables = [];
  3533. foreach ($this->reflection->getTableNames() as $table) {
  3534. $tables[] = $this->reflection->getTable($table);
  3535. }
  3536. $database = ['tables' => $tables];
  3537. return $this->responder->success($database);
  3538. }
  3539. public function getTable(ServerRequestInterface $request): Response
  3540. {
  3541. $tableName = $this->utils->getPathSegment($request, 2);
  3542. if (!$this->reflection->hasTable($tableName)) {
  3543. return $this->responder->error(ErrorCode::TABLE_NOT_FOUND, $tableName);
  3544. }
  3545. $table = $this->reflection->getTable($tableName);
  3546. return $this->responder->success($table);
  3547. }
  3548. public function getColumn(ServerRequestInterface $request): Response
  3549. {
  3550. $tableName = $this->utils->getPathSegment($request, 2);
  3551. $columnName = $this->utils->getPathSegment($request, 3);
  3552. if (!$this->reflection->hasTable($tableName)) {
  3553. return $this->responder->error(ErrorCode::TABLE_NOT_FOUND, $tableName);
  3554. }
  3555. $table = $this->reflection->getTable($tableName);
  3556. if (!$table->hasColumn($columnName)) {
  3557. return $this->responder->error(ErrorCode::COLUMN_NOT_FOUND, $columnName);
  3558. }
  3559. $column = $table->getColumn($columnName);
  3560. return $this->responder->success($column);
  3561. }
  3562. public function updateTable(ServerRequestInterface $request): Response
  3563. {
  3564. $tableName = $this->utils->getPathSegment($request, 2);
  3565. if (!$this->reflection->hasTable($tableName)) {
  3566. return $this->responder->error(ErrorCode::TABLE_NOT_FOUND, $tableName);
  3567. }
  3568. $success = $this->definition->updateTable($tableName, $request->getBody());
  3569. if ($success) {
  3570. $this->reflection->refreshTables();
  3571. }
  3572. return $this->responder->success($success);
  3573. }
  3574. public function updateColumn(ServerRequestInterface $request): Response
  3575. {
  3576. $tableName = $this->utils->getPathSegment($request, 2);
  3577. $columnName = $this->utils->getPathSegment($request, 3);
  3578. if (!$this->reflection->hasTable($tableName)) {
  3579. return $this->responder->error(ErrorCode::TABLE_NOT_FOUND, $tableName);
  3580. }
  3581. $table = $this->reflection->getTable($tableName);
  3582. if (!$table->hasColumn($columnName)) {
  3583. return $this->responder->error(ErrorCode::COLUMN_NOT_FOUND, $columnName);
  3584. }
  3585. $success = $this->definition->updateColumn($tableName, $columnName, $request->getBody());
  3586. if ($success) {
  3587. $this->reflection->refreshTable($tableName);
  3588. }
  3589. return $this->responder->success($success);
  3590. }
  3591. public function addTable(ServerRequestInterface $request): Response
  3592. {
  3593. $tableName = $request->getBody()->name;
  3594. if ($this->reflection->hasTable($tableName)) {
  3595. return $this->responder->error(ErrorCode::TABLE_ALREADY_EXISTS, $tableName);
  3596. }
  3597. $success = $this->definition->addTable($request->getBody());
  3598. if ($success) {
  3599. $this->reflection->refreshTables();
  3600. }
  3601. return $this->responder->success($success);
  3602. }
  3603. public function addColumn(ServerRequestInterface $request): Response
  3604. {
  3605. $tableName = $this->utils->getPathSegment($request, 2);
  3606. if (!$this->reflection->hasTable($tableName)) {
  3607. return $this->responder->error(ErrorCode::TABLE_NOT_FOUND, $tableName);
  3608. }
  3609. $columnName = $request->getBody()->name;
  3610. $table = $this->reflection->getTable($tableName);
  3611. if ($table->hasColumn($columnName)) {
  3612. return $this->responder->error(ErrorCode::COLUMN_ALREADY_EXISTS, $columnName);
  3613. }
  3614. $success = $this->definition->addColumn($tableName, $request->getBody());
  3615. if ($success) {
  3616. $this->reflection->refreshTable($tableName);
  3617. }
  3618. return $this->responder->success($success);
  3619. }
  3620. public function removeTable(ServerRequestInterface $request): Response
  3621. {
  3622. $tableName = $this->utils->getPathSegment($request, 2);
  3623. if (!$this->reflection->hasTable($tableName)) {
  3624. return $this->responder->error(ErrorCode::TABLE_NOT_FOUND, $tableName);
  3625. }
  3626. $success = $this->definition->removeTable($tableName);
  3627. if ($success) {
  3628. $this->reflection->refreshTables();
  3629. }
  3630. return $this->responder->success($success);
  3631. }
  3632. public function removeColumn(ServerRequestInterface $request): Response
  3633. {
  3634. $tableName = $this->utils->getPathSegment($request, 2);
  3635. $columnName = $this->utils->getPathSegment($request, 3);
  3636. if (!$this->reflection->hasTable($tableName)) {
  3637. return $this->responder->error(ErrorCode::TABLE_NOT_FOUND, $tableName);
  3638. }
  3639. $table = $this->reflection->getTable($tableName);
  3640. if (!$table->hasColumn($columnName)) {
  3641. return $this->responder->error(ErrorCode::COLUMN_NOT_FOUND, $columnName);
  3642. }
  3643. $success = $this->definition->removeColumn($tableName, $columnName);
  3644. if ($success) {
  3645. $this->reflection->refreshTable($tableName);
  3646. }
  3647. return $this->responder->success($success);
  3648. }
  3649. }
  3650. // file: src/Tqdev/PhpCrudApi/Controller/OpenApiController.php
  3651. class OpenApiController
  3652. {
  3653. private $openApi;
  3654. private $responder;
  3655. public function __construct(Router $router, Responder $responder, OpenApiService $openApi)
  3656. {
  3657. $router->register('GET', '/openapi', array($this, 'openapi'));
  3658. $this->openApi = $openApi;
  3659. $this->responder = $responder;
  3660. }
  3661. public function openapi(ServerRequestInterface $request): Response
  3662. {
  3663. return $this->responder->success($this->openApi->get());
  3664. }
  3665. }
  3666. // file: src/Tqdev/PhpCrudApi/Controller/RecordController.php
  3667. class RecordController
  3668. {
  3669. private $service;
  3670. private $responder;
  3671. public function __construct(Router $router, Responder $responder, ReflectionService $reflection, RecordService $service)
  3672. {
  3673. $router->register('GET', '/records/*', array($this, '_list'));
  3674. $router->register('POST', '/records/*', array($this, 'create'));
  3675. $router->register('GET', '/records/*/*', array($this, 'read'));
  3676. $router->register('PUT', '/records/*/*', array($this, 'update'));
  3677. $router->register('DELETE', '/records/*/*', array($this, 'delete'));
  3678. $router->register('PATCH', '/records/*/*', array($this, 'increment'));
  3679. $this->service = $service;
  3680. $this->responder = $responder;
  3681. $this->utils = new RequestUtils($reflection);
  3682. }
  3683. public function _list(ServerRequestInterface $request): Response
  3684. {
  3685. $table = $this->utils->getPathSegment($request, 2);
  3686. $params = $this->utils->getParams($request);
  3687. if (!$this->service->hasTable($table)) {
  3688. return $this->responder->error(ErrorCode::TABLE_NOT_FOUND, $table);
  3689. }
  3690. return $this->responder->success($this->service->_list($table, $params));
  3691. }
  3692. public function read(ServerRequestInterface $request): Response
  3693. {
  3694. $table = $this->utils->getPathSegment($request, 2);
  3695. if (!$this->service->hasTable($table)) {
  3696. return $this->responder->error(ErrorCode::TABLE_NOT_FOUND, $table);
  3697. }
  3698. if ($this->service->getType($table) != 'table') {
  3699. return $this->responder->error(ErrorCode::OPERATION_NOT_SUPPORTED, __FUNCTION__);
  3700. }
  3701. $id = $this->utils->getPathSegment($request, 3);
  3702. $params = $this->utils->getParams($request);
  3703. if (strpos($id, ',') !== false) {
  3704. $ids = explode(',', $id);
  3705. $result = [];
  3706. for ($i = 0; $i < count($ids); $i++) {
  3707. array_push($result, $this->service->read($table, $ids[$i], $params));
  3708. }
  3709. return $this->responder->success($result);
  3710. } else {
  3711. $response = $this->service->read($table, $id, $params);
  3712. if ($response === null) {
  3713. return $this->responder->error(ErrorCode::RECORD_NOT_FOUND, $id);
  3714. }
  3715. return $this->responder->success($response);
  3716. }
  3717. }
  3718. public function create(ServerRequestInterface $request): Response
  3719. {
  3720. $table = $this->utils->getPathSegment($request, 2);
  3721. if (!$this->service->hasTable($table)) {
  3722. return $this->responder->error(ErrorCode::TABLE_NOT_FOUND, $table);
  3723. }
  3724. if ($this->service->getType($table) != 'table') {
  3725. return $this->responder->error(ErrorCode::OPERATION_NOT_SUPPORTED, __FUNCTION__);
  3726. }
  3727. $record = $request->getBody();
  3728. if ($record === null) {
  3729. return $this->responder->error(ErrorCode::HTTP_MESSAGE_NOT_READABLE, '');
  3730. }
  3731. $params = $this->utils->getParams($request);
  3732. if (is_array($record)) {
  3733. $result = array();
  3734. foreach ($record as $r) {
  3735. $result[] = $this->service->create($table, $r, $params);
  3736. }
  3737. return $this->responder->success($result);
  3738. } else {
  3739. return $this->responder->success($this->service->create($table, $record, $params));
  3740. }
  3741. }
  3742. public function update(ServerRequestInterface $request): Response
  3743. {
  3744. $table = $this->utils->getPathSegment($request, 2);
  3745. if (!$this->service->hasTable($table)) {
  3746. return $this->responder->error(ErrorCode::TABLE_NOT_FOUND, $table);
  3747. }
  3748. if ($this->service->getType($table) != 'table') {
  3749. return $this->responder->error(ErrorCode::OPERATION_NOT_SUPPORTED, __FUNCTION__);
  3750. }
  3751. $id = $this->utils->getPathSegment($request, 3);
  3752. $params = $this->utils->getParams($request);
  3753. $record = $request->getBody();
  3754. if ($record === null) {
  3755. return $this->responder->error(ErrorCode::HTTP_MESSAGE_NOT_READABLE, '');
  3756. }
  3757. $ids = explode(',', $id);
  3758. if (is_array($record)) {
  3759. if (count($ids) != count($record)) {
  3760. return $this->responder->error(ErrorCode::ARGUMENT_COUNT_MISMATCH, $id);
  3761. }
  3762. $result = array();
  3763. for ($i = 0; $i < count($ids); $i++) {
  3764. $result[] = $this->service->update($table, $ids[$i], $record[$i], $params);
  3765. }
  3766. return $this->responder->success($result);
  3767. } else {
  3768. if (count($ids) != 1) {
  3769. return $this->responder->error(ErrorCode::ARGUMENT_COUNT_MISMATCH, $id);
  3770. }
  3771. return $this->responder->success($this->service->update($table, $id, $record, $params));
  3772. }
  3773. }
  3774. public function delete(ServerRequestInterface $request): Response
  3775. {
  3776. $table = $this->utils->getPathSegment($request, 2);
  3777. if (!$this->service->hasTable($table)) {
  3778. return $this->responder->error(ErrorCode::TABLE_NOT_FOUND, $table);
  3779. }
  3780. if ($this->service->getType($table) != 'table') {
  3781. return $this->responder->error(ErrorCode::OPERATION_NOT_SUPPORTED, __FUNCTION__);
  3782. }
  3783. $id = $this->utils->getPathSegment($request, 3);
  3784. $params = $this->utils->getParams($request);
  3785. $ids = explode(',', $id);
  3786. if (count($ids) > 1) {
  3787. $result = array();
  3788. for ($i = 0; $i < count($ids); $i++) {
  3789. $result[] = $this->service->delete($table, $ids[$i], $params);
  3790. }
  3791. return $this->responder->success($result);
  3792. } else {
  3793. return $this->responder->success($this->service->delete($table, $id, $params));
  3794. }
  3795. }
  3796. public function increment(ServerRequestInterface $request): Response
  3797. {
  3798. $table = $this->utils->getPathSegment($request, 2);
  3799. if (!$this->service->hasTable($table)) {
  3800. return $this->responder->error(ErrorCode::TABLE_NOT_FOUND, $table);
  3801. }
  3802. if ($this->service->getType($table) != 'table') {
  3803. return $this->responder->error(ErrorCode::OPERATION_NOT_SUPPORTED, __FUNCTION__);
  3804. }
  3805. $id = $this->utils->getPathSegment($request, 3);
  3806. $record = $request->getBody();
  3807. if ($record === null) {
  3808. return $this->responder->error(ErrorCode::HTTP_MESSAGE_NOT_READABLE, '');
  3809. }
  3810. $params = $this->utils->getParams($request);
  3811. $ids = explode(',', $id);
  3812. if (is_array($record)) {
  3813. if (count($ids) != count($record)) {
  3814. return $this->responder->error(ErrorCode::ARGUMENT_COUNT_MISMATCH, $id);
  3815. }
  3816. $result = array();
  3817. for ($i = 0; $i < count($ids); $i++) {
  3818. $result[] = $this->service->increment($table, $ids[$i], $record[$i], $params);
  3819. }
  3820. return $this->responder->success($result);
  3821. } else {
  3822. if (count($ids) != 1) {
  3823. return $this->responder->error(ErrorCode::ARGUMENT_COUNT_MISMATCH, $id);
  3824. }
  3825. return $this->responder->success($this->service->increment($table, $id, $record, $params));
  3826. }
  3827. }
  3828. }
  3829. // file: src/Tqdev/PhpCrudApi/Controller/Responder.php
  3830. class Responder
  3831. {
  3832. public function error(int $error, String $argument, $details = null): Response
  3833. {
  3834. $errorCode = new ErrorCode($error);
  3835. $status = $errorCode->getStatus();
  3836. $document = new ErrorDocument($errorCode, $argument, $details);
  3837. return new Response($status, $document);
  3838. }
  3839. public function success($result): Response
  3840. {
  3841. return new Response(Response::OK, $result);
  3842. }
  3843. }
  3844. // file: src/Tqdev/PhpCrudApi/Database/ColumnConverter.php
  3845. class ColumnConverter
  3846. {
  3847. private $driver;
  3848. public function __construct(String $driver)
  3849. {
  3850. $this->driver = $driver;
  3851. }
  3852. public function convertColumnValue(ReflectedColumn $column): String
  3853. {
  3854. if ($column->isBinary()) {
  3855. switch ($this->driver) {
  3856. case 'mysql':
  3857. return "FROM_BASE64(?)";
  3858. case 'pgsql':
  3859. return "decode(?, 'base64')";
  3860. case 'sqlsrv':
  3861. return "CONVERT(XML, ?).value('.','varbinary(max)')";
  3862. }
  3863. }
  3864. if ($column->isGeometry()) {
  3865. switch ($this->driver) {
  3866. case 'mysql':
  3867. case 'pgsql':
  3868. return "ST_GeomFromText(?)";
  3869. case 'sqlsrv':
  3870. return "geometry::STGeomFromText(?,0)";
  3871. }
  3872. }
  3873. return '?';
  3874. }
  3875. public function convertColumnName(ReflectedColumn $column, $value): String
  3876. {
  3877. if ($column->isBinary()) {
  3878. switch ($this->driver) {
  3879. case 'mysql':
  3880. return "TO_BASE64($value) as $value";
  3881. case 'pgsql':
  3882. return "encode($value::bytea, 'base64') as $value";
  3883. case 'sqlsrv':
  3884. return "CASE WHEN $value IS NULL THEN NULL ELSE (SELECT CAST($value as varbinary(max)) FOR XML PATH(''), BINARY BASE64) END as $value";
  3885. }
  3886. }
  3887. if ($column->isGeometry()) {
  3888. switch ($this->driver) {
  3889. case 'mysql':
  3890. case 'pgsql':
  3891. return "ST_AsText($value) as $value";
  3892. case 'sqlsrv':
  3893. return "REPLACE($value.STAsText(),' (','(') as $value";
  3894. }
  3895. }
  3896. return $value;
  3897. }
  3898. }
  3899. // file: src/Tqdev/PhpCrudApi/Database/ColumnsBuilder.php
  3900. class ColumnsBuilder
  3901. {
  3902. private $driver;
  3903. private $converter;
  3904. public function __construct(String $driver)
  3905. {
  3906. $this->driver = $driver;
  3907. $this->converter = new ColumnConverter($driver);
  3908. }
  3909. public function getOffsetLimit(int $offset, int $limit): String
  3910. {
  3911. if ($limit < 0 || $offset < 0) {
  3912. return '';
  3913. }
  3914. switch ($this->driver) {
  3915. case 'mysql':return " LIMIT $offset, $limit";
  3916. case 'pgsql':return " LIMIT $limit OFFSET $offset";
  3917. case 'sqlsrv':return " OFFSET $offset ROWS FETCH NEXT $limit ROWS ONLY";
  3918. }
  3919. }
  3920. private function quoteColumnName(ReflectedColumn $column): String
  3921. {
  3922. return '"' . $column->getName() . '"';
  3923. }
  3924. public function getOrderBy(ReflectedTable $table, array $columnOrdering): String
  3925. {
  3926. if (count($columnOrdering)==0) {
  3927. return '';
  3928. }
  3929. $results = array();
  3930. foreach ($columnOrdering as $i => list($columnName, $ordering)) {
  3931. $column = $table->getColumn($columnName);
  3932. $quotedColumnName = $this->quoteColumnName($column);
  3933. $results[] = $quotedColumnName . ' ' . $ordering;
  3934. }
  3935. return ' ORDER BY '.implode(',', $results);
  3936. }
  3937. public function getSelect(ReflectedTable $table, array $columnNames): String
  3938. {
  3939. $results = array();
  3940. foreach ($columnNames as $columnName) {
  3941. $column = $table->getColumn($columnName);
  3942. $quotedColumnName = $this->quoteColumnName($column);
  3943. $quotedColumnName = $this->converter->convertColumnName($column, $quotedColumnName);
  3944. $results[] = $quotedColumnName;
  3945. }
  3946. return implode(',', $results);
  3947. }
  3948. public function getInsert(ReflectedTable $table, array $columnValues): String
  3949. {
  3950. $columns = array();
  3951. $values = array();
  3952. foreach ($columnValues as $columnName => $columnValue) {
  3953. $column = $table->getColumn($columnName);
  3954. $quotedColumnName = $this->quoteColumnName($column);
  3955. $columns[] = $quotedColumnName;
  3956. $columnValue = $this->converter->convertColumnValue($column);
  3957. $values[] = $columnValue;
  3958. }
  3959. $columnsSql = '(' . implode(',', $columns) . ')';
  3960. $valuesSql = '(' . implode(',', $values) . ')';
  3961. $outputColumn = $this->quoteColumnName($table->getPk());
  3962. switch ($this->driver) {
  3963. case 'mysql':return "$columnsSql VALUES $valuesSql";
  3964. case 'pgsql':return "$columnsSql VALUES $valuesSql RETURNING $outputColumn";
  3965. case 'sqlsrv':return "$columnsSql OUTPUT INSERTED.$outputColumn VALUES $valuesSql";
  3966. }
  3967. }
  3968. public function getUpdate(ReflectedTable $table, array $columnValues): String
  3969. {
  3970. $results = array();
  3971. foreach ($columnValues as $columnName => $columnValue) {
  3972. $column = $table->getColumn($columnName);
  3973. $quotedColumnName = $this->quoteColumnName($column);
  3974. $columnValue = $this->converter->convertColumnValue($column);
  3975. $results[] = $quotedColumnName . '=' . $columnValue;
  3976. }
  3977. return implode(',', $results);
  3978. }
  3979. public function getIncrement(ReflectedTable $table, array $columnValues): String
  3980. {
  3981. $results = array();
  3982. foreach ($columnValues as $columnName => $columnValue) {
  3983. if (!is_numeric($columnValue)) {
  3984. continue;
  3985. }
  3986. $column = $table->getColumn($columnName);
  3987. $quotedColumnName = $this->quoteColumnName($column);
  3988. $columnValue = $this->converter->convertColumnValue($column);
  3989. $results[] = $quotedColumnName . '=' . $quotedColumnName . '+' . $columnValue;
  3990. }
  3991. return implode(',', $results);
  3992. }
  3993. }
  3994. // file: src/Tqdev/PhpCrudApi/Database/ConditionsBuilder.php
  3995. class ConditionsBuilder
  3996. {
  3997. private $driver;
  3998. public function __construct(String $driver)
  3999. {
  4000. $this->driver = $driver;
  4001. }
  4002. private function getConditionSql(Condition $condition, array &$arguments): String
  4003. {
  4004. if ($condition instanceof AndCondition) {
  4005. return $this->getAndConditionSql($condition, $arguments);
  4006. }
  4007. if ($condition instanceof OrCondition) {
  4008. return $this->getOrConditionSql($condition, $arguments);
  4009. }
  4010. if ($condition instanceof NotCondition) {
  4011. return $this->getNotConditionSql($condition, $arguments);
  4012. }
  4013. if ($condition instanceof SpatialCondition) {
  4014. return $this->getSpatialConditionSql($condition, $arguments);
  4015. }
  4016. if ($condition instanceof ColumnCondition) {
  4017. return $this->getColumnConditionSql($condition, $arguments);
  4018. }
  4019. throw new \Exception('Unknown Condition: ' . get_class($condition));
  4020. }
  4021. private function getAndConditionSql(AndCondition $and, array &$arguments): String
  4022. {
  4023. $parts = [];
  4024. foreach ($and->getConditions() as $condition) {
  4025. $parts[] = $this->getConditionSql($condition, $arguments);
  4026. }
  4027. return '(' . implode(' AND ', $parts) . ')';
  4028. }
  4029. private function getOrConditionSql(OrCondition $or, array &$arguments): String
  4030. {
  4031. $parts = [];
  4032. foreach ($or->getConditions() as $condition) {
  4033. $parts[] = $this->getConditionSql($condition, $arguments);
  4034. }
  4035. return '(' . implode(' OR ', $parts) . ')';
  4036. }
  4037. private function getNotConditionSql(NotCondition $not, array &$arguments): String
  4038. {
  4039. $condition = $not->getCondition();
  4040. return '(NOT ' . $this->getConditionSql($condition, $arguments) . ')';
  4041. }
  4042. private function quoteColumnName(ReflectedColumn $column): String
  4043. {
  4044. return '"' . $column->getName() . '"';
  4045. }
  4046. private function escapeLikeValue(String $value): String
  4047. {
  4048. return addcslashes($value, '%_');
  4049. }
  4050. private function getColumnConditionSql(ColumnCondition $condition, array &$arguments): String
  4051. {
  4052. $column = $this->quoteColumnName($condition->getColumn());
  4053. $operator = $condition->getOperator();
  4054. $value = $condition->getValue();
  4055. switch ($operator) {
  4056. case 'cs':
  4057. $sql = "$column LIKE ?";
  4058. $arguments[] = '%' . $this->escapeLikeValue($value) . '%';
  4059. break;
  4060. case 'sw':
  4061. $sql = "$column LIKE ?";
  4062. $arguments[] = $this->escapeLikeValue($value) . '%';
  4063. break;
  4064. case 'ew':
  4065. $sql = "$column LIKE ?";
  4066. $arguments[] = '%' . $this->escapeLikeValue($value);
  4067. break;
  4068. case 'eq':
  4069. $sql = "$column = ?";
  4070. $arguments[] = $value;
  4071. break;
  4072. case 'lt':
  4073. $sql = "$column < ?";
  4074. $arguments[] = $value;
  4075. break;
  4076. case 'le':
  4077. $sql = "$column <= ?";
  4078. $arguments[] = $value;
  4079. break;
  4080. case 'ge':
  4081. $sql = "$column >= ?";
  4082. $arguments[] = $value;
  4083. break;
  4084. case 'gt':
  4085. $sql = "$column > ?";
  4086. $arguments[] = $value;
  4087. break;
  4088. case 'bt':
  4089. $parts = explode(',', $value, 2);
  4090. $count = count($parts);
  4091. if ($count == 2) {
  4092. $sql = "($column >= ? AND $column <= ?)";
  4093. $arguments[] = $parts[0];
  4094. $arguments[] = $parts[1];
  4095. } else {
  4096. $sql = "FALSE";
  4097. }
  4098. break;
  4099. case 'in':
  4100. $parts = explode(',', $value);
  4101. $count = count($parts);
  4102. if ($count > 0) {
  4103. $qmarks = implode(',', str_split(str_repeat('?', $count)));
  4104. $sql = "$column IN ($qmarks)";
  4105. for ($i = 0; $i < $count; $i++) {
  4106. $arguments[] = $parts[$i];
  4107. }
  4108. } else {
  4109. $sql = "FALSE";
  4110. }
  4111. break;
  4112. case 'is':
  4113. $sql = "$column IS NULL";
  4114. break;
  4115. }
  4116. return $sql;
  4117. }
  4118. private function getSpatialFunctionName(String $operator): String
  4119. {
  4120. switch ($operator) {
  4121. case 'co':return 'ST_Contains';
  4122. case 'cr':return 'ST_Crosses';
  4123. case 'di':return 'ST_Disjoint';
  4124. case 'eq':return 'ST_Equals';
  4125. case 'in':return 'ST_Intersects';
  4126. case 'ov':return 'ST_Overlaps';
  4127. case 'to':return 'ST_Touches';
  4128. case 'wi':return 'ST_Within';
  4129. case 'ic':return 'ST_IsClosed';
  4130. case 'is':return 'ST_IsSimple';
  4131. case 'iv':return 'ST_IsValid';
  4132. }
  4133. }
  4134. private function hasSpatialArgument(String $operator): bool
  4135. {
  4136. return in_array($operator, ['ic', 'is', 'iv']) ? false : true;
  4137. }
  4138. private function getSpatialFunctionCall(String $functionName, String $column, bool $hasArgument): String
  4139. {
  4140. switch ($this->driver) {
  4141. case 'mysql':
  4142. case 'pgsql':
  4143. $argument = $hasArgument ? 'ST_GeomFromText(?)' : '';
  4144. return "$functionName($column, $argument)=TRUE";
  4145. case 'sqlsrv':
  4146. $functionName = str_replace('ST_', 'ST', $functionName);
  4147. $argument = $hasArgument ? 'geometry::STGeomFromText(?,0)' : '';
  4148. return "$column.$functionName($argument)=1";
  4149. }
  4150. }
  4151. private function getSpatialConditionSql(ColumnCondition $condition, array &$arguments): String
  4152. {
  4153. $column = $this->quoteColumnName($condition->getColumn());
  4154. $operator = $condition->getOperator();
  4155. $value = $condition->getValue();
  4156. $functionName = $this->getSpatialFunctionName($operator);
  4157. $hasArgument = $this->hasSpatialArgument($operator);
  4158. $sql = $this->getSpatialFunctionCall($functionName, $column, $hasArgument);
  4159. if ($hasArgument) {
  4160. $arguments[] = $value;
  4161. }
  4162. return $sql;
  4163. }
  4164. public function getWhereClause(Condition $condition, array &$arguments): String
  4165. {
  4166. if ($condition instanceof NoCondition) {
  4167. return '';
  4168. }
  4169. return ' WHERE ' . $this->getConditionSql($condition, $arguments);
  4170. }
  4171. }
  4172. // file: src/Tqdev/PhpCrudApi/Database/DataConverter.php
  4173. class DataConverter
  4174. {
  4175. private $driver;
  4176. public function __construct(String $driver)
  4177. {
  4178. $this->driver = $driver;
  4179. }
  4180. private function convertRecordValue($conversion, $value)
  4181. {
  4182. switch ($conversion) {
  4183. case 'boolean':
  4184. return $value ? true : false;
  4185. case 'integer':
  4186. return (int) $value;
  4187. }
  4188. return $value;
  4189. }
  4190. private function getRecordValueConversion(ReflectedColumn $column): String
  4191. {
  4192. if (in_array($this->driver, ['mysql', 'sqlsrv']) && $column->isBoolean()) {
  4193. return 'boolean';
  4194. }
  4195. if ($this->driver == 'sqlsrv' && $column->getType() == 'bigint') {
  4196. return 'integer';
  4197. }
  4198. return 'none';
  4199. }
  4200. public function convertRecords(ReflectedTable $table, array $columnNames, array &$records) /*: void*/
  4201. {
  4202. foreach ($columnNames as $columnName) {
  4203. $column = $table->getColumn($columnName);
  4204. $conversion = $this->getRecordValueConversion($column);
  4205. if ($conversion != 'none') {
  4206. foreach ($records as $i => $record) {
  4207. $value = $records[$i][$columnName];
  4208. if ($value === null) {
  4209. continue;
  4210. }
  4211. $records[$i][$columnName] = $this->convertRecordValue($conversion, $value);
  4212. }
  4213. }
  4214. }
  4215. }
  4216. private function convertInputValue($conversion, $value)
  4217. {
  4218. switch ($conversion) {
  4219. case 'base64url_to_base64':
  4220. return str_pad(strtr($value, '-_', '+/'), ceil(strlen($value) / 4) * 4, '=', STR_PAD_RIGHT);
  4221. }
  4222. return $value;
  4223. }
  4224. private function getInputValueConversion(ReflectedColumn $column): String
  4225. {
  4226. if ($column->isBinary()) {
  4227. return 'base64url_to_base64';
  4228. }
  4229. return 'none';
  4230. }
  4231. public function convertColumnValues(ReflectedTable $table, array &$columnValues) /*: void*/
  4232. {
  4233. $columnNames = array_keys($columnValues);
  4234. foreach ($columnNames as $columnName) {
  4235. $column = $table->getColumn($columnName);
  4236. $conversion = $this->getInputValueConversion($column);
  4237. if ($conversion != 'none') {
  4238. $value = $columnValues[$columnName];
  4239. if ($value !== null) {
  4240. $columnValues[$columnName] = $this->convertInputValue($conversion, $value);
  4241. }
  4242. }
  4243. }
  4244. }
  4245. }
  4246. // file: src/Tqdev/PhpCrudApi/Database/GenericDB.php
  4247. class GenericDB
  4248. {
  4249. private $driver;
  4250. private $database;
  4251. private $pdo;
  4252. private $reflection;
  4253. private $definition;
  4254. private $conditions;
  4255. private $columns;
  4256. private $converter;
  4257. private function getDsn(String $address, String $port = null, String $database = null): String
  4258. {
  4259. switch ($this->driver) {
  4260. case 'mysql':return "$this->driver:host=$address;port=$port;dbname=$database;charset=utf8mb4";
  4261. case 'pgsql':return "$this->driver:host=$address port=$port dbname=$database options='--client_encoding=UTF8'";
  4262. case 'sqlsrv':return "$this->driver:Server=$address,$port;Database=$database";
  4263. }
  4264. }
  4265. private function getCommands(): array
  4266. {
  4267. switch ($this->driver) {
  4268. case 'mysql':return [
  4269. 'SET SESSION sql_warnings=1;',
  4270. 'SET NAMES utf8mb4;',
  4271. 'SET SESSION sql_mode = "ANSI,TRADITIONAL";',
  4272. ];
  4273. case 'pgsql':return [
  4274. "SET NAMES 'UTF8';",
  4275. ];
  4276. case 'sqlsrv':return [
  4277. ];
  4278. }
  4279. }
  4280. private function getOptions(): array
  4281. {
  4282. $options = array(
  4283. \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
  4284. \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
  4285. );
  4286. switch ($this->driver) {
  4287. case 'mysql':return $options + [
  4288. \PDO::ATTR_EMULATE_PREPARES => false,
  4289. \PDO::MYSQL_ATTR_FOUND_ROWS => true,
  4290. \PDO::ATTR_PERSISTENT => true,
  4291. ];
  4292. case 'pgsql':return $options + [
  4293. \PDO::ATTR_EMULATE_PREPARES => false,
  4294. \PDO::ATTR_PERSISTENT => true,
  4295. ];
  4296. case 'sqlsrv':return $options + [
  4297. \PDO::SQLSRV_ATTR_DIRECT_QUERY => false,
  4298. \PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE => true,
  4299. ];
  4300. }
  4301. }
  4302. public function __construct(String $driver, String $address, String $port = null, String $database = null, String $username = null, String $password = null)
  4303. {
  4304. $this->driver = $driver;
  4305. $this->database = $database;
  4306. $dsn = $this->getDsn($address, $port, $database);
  4307. $options = $this->getOptions();
  4308. $this->pdo = new \PDO($dsn, $username, $password, $options);
  4309. $commands = $this->getCommands();
  4310. foreach ($commands as $command) {
  4311. $this->pdo->query($command);
  4312. }
  4313. $this->reflection = new GenericReflection($this->pdo, $driver, $database);
  4314. $this->definition = new GenericDefinition($this->pdo, $driver, $database);
  4315. $this->conditions = new ConditionsBuilder($driver);
  4316. $this->columns = new ColumnsBuilder($driver);
  4317. $this->converter = new DataConverter($driver);
  4318. }
  4319. public function pdo(): \PDO
  4320. {
  4321. return $this->pdo;
  4322. }
  4323. public function reflection(): GenericReflection
  4324. {
  4325. return $this->reflection;
  4326. }
  4327. public function definition(): GenericDefinition
  4328. {
  4329. return $this->definition;
  4330. }
  4331. private function addMiddlewareConditions(String $tableName, Condition $condition): Condition
  4332. {
  4333. $condition1 = VariableStore::get("authorization.conditions.$tableName");
  4334. if ($condition1) {
  4335. $condition = $condition->_and($condition1);
  4336. }
  4337. $condition2 = VariableStore::get("multiTenancy.conditions.$tableName");
  4338. if ($condition2) {
  4339. $condition = $condition->_and($condition2);
  4340. }
  4341. return $condition;
  4342. }
  4343. public function createSingle(ReflectedTable $table, array $columnValues) /*: ?String*/
  4344. {
  4345. $this->converter->convertColumnValues($table, $columnValues);
  4346. $insertColumns = $this->columns->getInsert($table, $columnValues);
  4347. $tableName = $table->getName();
  4348. $pkName = $table->getPk()->getName();
  4349. $parameters = array_values($columnValues);
  4350. $sql = 'INSERT INTO "' . $tableName . '" ' . $insertColumns;
  4351. $stmt = $this->query($sql, $parameters);
  4352. if (isset($columnValues[$pkName])) {
  4353. return $columnValues[$pkName];
  4354. }
  4355. switch ($this->driver) {
  4356. case 'mysql':
  4357. $stmt = $this->query('SELECT LAST_INSERT_ID()', []);
  4358. break;
  4359. }
  4360. $pkValue = $stmt->fetchColumn(0);
  4361. if ($this->driver == 'sqlsrv' && $table->getPk()->getType() == 'bigint') {
  4362. return (int) $pkValue;
  4363. }
  4364. return $pkValue;
  4365. }
  4366. public function selectSingle(ReflectedTable $table, array $columnNames, String $id) /*: ?array*/
  4367. {
  4368. $selectColumns = $this->columns->getSelect($table, $columnNames);
  4369. $tableName = $table->getName();
  4370. $condition = new ColumnCondition($table->getPk(), 'eq', $id);
  4371. $condition = $this->addMiddlewareConditions($tableName, $condition);
  4372. $parameters = array();
  4373. $whereClause = $this->conditions->getWhereClause($condition, $parameters);
  4374. $sql = 'SELECT ' . $selectColumns . ' FROM "' . $tableName . '" ' . $whereClause;
  4375. $stmt = $this->query($sql, $parameters);
  4376. $record = $stmt->fetch() ?: null;
  4377. if ($record === null) {
  4378. return null;
  4379. }
  4380. $records = array($record);
  4381. $this->converter->convertRecords($table, $columnNames, $records);
  4382. return $records[0];
  4383. }
  4384. public function selectMultiple(ReflectedTable $table, array $columnNames, array $ids): array
  4385. {
  4386. if (count($ids) == 0) {
  4387. return [];
  4388. }
  4389. $selectColumns = $this->columns->getSelect($table, $columnNames);
  4390. $tableName = $table->getName();
  4391. $condition = new ColumnCondition($table->getPk(), 'in', implode(',', $ids));
  4392. $condition = $this->addMiddlewareConditions($tableName, $condition);
  4393. $parameters = array();
  4394. $whereClause = $this->conditions->getWhereClause($condition, $parameters);
  4395. $sql = 'SELECT ' . $selectColumns . ' FROM "' . $tableName . '" ' . $whereClause;
  4396. $stmt = $this->query($sql, $parameters);
  4397. $records = $stmt->fetchAll();
  4398. $this->converter->convertRecords($table, $columnNames, $records);
  4399. return $records;
  4400. }
  4401. public function selectCount(ReflectedTable $table, Condition $condition): int
  4402. {
  4403. $tableName = $table->getName();
  4404. $condition = $this->addMiddlewareConditions($tableName, $condition);
  4405. $parameters = array();
  4406. $whereClause = $this->conditions->getWhereClause($condition, $parameters);
  4407. $sql = 'SELECT COUNT(*) FROM "' . $tableName . '"' . $whereClause;
  4408. $stmt = $this->query($sql, $parameters);
  4409. return $stmt->fetchColumn(0);
  4410. }
  4411. public function selectAll(ReflectedTable $table, array $columnNames, Condition $condition, array $columnOrdering, int $offset, int $limit): array
  4412. {
  4413. if ($limit == 0) {
  4414. return array();
  4415. }
  4416. $selectColumns = $this->columns->getSelect($table, $columnNames);
  4417. $tableName = $table->getName();
  4418. $condition = $this->addMiddlewareConditions($tableName, $condition);
  4419. $parameters = array();
  4420. $whereClause = $this->conditions->getWhereClause($condition, $parameters);
  4421. $orderBy = $this->columns->getOrderBy($table, $columnOrdering);
  4422. $offsetLimit = $this->columns->getOffsetLimit($offset, $limit);
  4423. $sql = 'SELECT ' . $selectColumns . ' FROM "' . $tableName . '"' . $whereClause . $orderBy . $offsetLimit;
  4424. $stmt = $this->query($sql, $parameters);
  4425. $records = $stmt->fetchAll();
  4426. $this->converter->convertRecords($table, $columnNames, $records);
  4427. return $records;
  4428. }
  4429. public function updateSingle(ReflectedTable $table, array $columnValues, String $id)
  4430. {
  4431. if (count($columnValues) == 0) {
  4432. return 0;
  4433. }
  4434. $this->converter->convertColumnValues($table, $columnValues);
  4435. $updateColumns = $this->columns->getUpdate($table, $columnValues);
  4436. $tableName = $table->getName();
  4437. $condition = new ColumnCondition($table->getPk(), 'eq', $id);
  4438. $condition = $this->addMiddlewareConditions($tableName, $condition);
  4439. $parameters = array_values($columnValues);
  4440. $whereClause = $this->conditions->getWhereClause($condition, $parameters);
  4441. $sql = 'UPDATE "' . $tableName . '" SET ' . $updateColumns . $whereClause;
  4442. $stmt = $this->query($sql, $parameters);
  4443. return $stmt->rowCount();
  4444. }
  4445. public function deleteSingle(ReflectedTable $table, String $id)
  4446. {
  4447. $tableName = $table->getName();
  4448. $condition = new ColumnCondition($table->getPk(), 'eq', $id);
  4449. $condition = $this->addMiddlewareConditions($tableName, $condition);
  4450. $parameters = array();
  4451. $whereClause = $this->conditions->getWhereClause($condition, $parameters);
  4452. $sql = 'DELETE FROM "' . $tableName . '" ' . $whereClause;
  4453. $stmt = $this->query($sql, $parameters);
  4454. return $stmt->rowCount();
  4455. }
  4456. public function incrementSingle(ReflectedTable $table, array $columnValues, String $id)
  4457. {
  4458. if (count($columnValues) == 0) {
  4459. return 0;
  4460. }
  4461. $this->converter->convertColumnValues($table, $columnValues);
  4462. $updateColumns = $this->columns->getIncrement($table, $columnValues);
  4463. $tableName = $table->getName();
  4464. $condition = new ColumnCondition($table->getPk(), 'eq', $id);
  4465. $condition = $this->addMiddlewareConditions($tableName, $condition);
  4466. $parameters = array_values($columnValues);
  4467. $whereClause = $this->conditions->getWhereClause($condition, $parameters);
  4468. $sql = 'UPDATE "' . $tableName . '" SET ' . $updateColumns . $whereClause;
  4469. $stmt = $this->query($sql, $parameters);
  4470. return $stmt->rowCount();
  4471. }
  4472. private function query(String $sql, array $parameters): \PDOStatement
  4473. {
  4474. $stmt = $this->pdo->prepare($sql);
  4475. $stmt->execute($parameters);
  4476. return $stmt;
  4477. }
  4478. }
  4479. // file: src/Tqdev/PhpCrudApi/Database/GenericDefinition.php
  4480. class GenericDefinition
  4481. {
  4482. private $pdo;
  4483. private $driver;
  4484. private $database;
  4485. private $typeConverter;
  4486. private $reflection;
  4487. public function __construct(\PDO $pdo, String $driver, String $database)
  4488. {
  4489. $this->pdo = $pdo;
  4490. $this->driver = $driver;
  4491. $this->database = $database;
  4492. $this->typeConverter = new TypeConverter($driver);
  4493. $this->reflection = new GenericReflection($pdo, $driver, $database);
  4494. }
  4495. private function quote(String $identifier): String
  4496. {
  4497. return '"' . str_replace('"', '', $identifier) . '"';
  4498. }
  4499. public function getColumnType(ReflectedColumn $column, bool $update): String
  4500. {
  4501. if ($this->driver == 'pgsql' && !$update && $column->getPk() && $this->canAutoIncrement($column)) {
  4502. return 'serial';
  4503. }
  4504. $type = $this->typeConverter->fromJdbc($column->getType());
  4505. if ($column->hasPrecision() && $column->hasScale()) {
  4506. $size = '(' . $column->getPrecision() . ',' . $column->getScale() . ')';
  4507. } else if ($column->hasPrecision()) {
  4508. $size = '(' . $column->getPrecision() . ')';
  4509. } else if ($column->hasLength()) {
  4510. $size = '(' . $column->getLength() . ')';
  4511. } else {
  4512. $size = '';
  4513. }
  4514. $null = $this->getColumnNullType($column, $update);
  4515. $auto = $this->getColumnAutoIncrement($column, $update);
  4516. return $type . $size . $null . $auto;
  4517. }
  4518. private function getPrimaryKey(String $tableName): String
  4519. {
  4520. $pks = $this->reflection->getTablePrimaryKeys($tableName);
  4521. if (count($pks) == 1) {
  4522. return $pks[0];
  4523. }
  4524. return "";
  4525. }
  4526. private function canAutoIncrement(ReflectedColumn $column): bool
  4527. {
  4528. return in_array($column->getType(), ['integer', 'bigint']);
  4529. }
  4530. private function getColumnAutoIncrement(ReflectedColumn $column, bool $update): String
  4531. {
  4532. if (!$this->canAutoIncrement($column)) {
  4533. return '';
  4534. }
  4535. switch ($this->driver) {
  4536. case 'mysql':
  4537. return $column->getPk() ? ' AUTO_INCREMENT' : '';
  4538. case 'pgsql':
  4539. case 'sqlsrv':
  4540. return '';
  4541. }
  4542. }
  4543. private function getColumnNullType(ReflectedColumn $column, bool $update): String
  4544. {
  4545. if ($this->driver == 'pgsql' && $update) {
  4546. return '';
  4547. }
  4548. return $column->getNullable() ? ' NULL' : ' NOT NULL';
  4549. }
  4550. private function getTableRenameSQL(String $tableName, String $newTableName): String
  4551. {
  4552. $p1 = $this->quote($tableName);
  4553. $p2 = $this->quote($newTableName);
  4554. switch ($this->driver) {
  4555. case 'mysql':
  4556. return "RENAME TABLE $p1 TO $p2";
  4557. case 'pgsql':
  4558. return "ALTER TABLE $p1 RENAME TO $p2";
  4559. case 'sqlsrv':
  4560. return "EXEC sp_rename $p1, $p2";
  4561. }
  4562. }
  4563. private function getColumnRenameSQL(String $tableName, String $columnName, ReflectedColumn $newColumn): String
  4564. {
  4565. $p1 = $this->quote($tableName);
  4566. $p2 = $this->quote($columnName);
  4567. $p3 = $this->quote($newColumn->getName());
  4568. switch ($this->driver) {
  4569. case 'mysql':
  4570. $p4 = $this->getColumnType($newColumn, true);
  4571. return "ALTER TABLE $p1 CHANGE $p2 $p3 $p4";
  4572. case 'pgsql':
  4573. return "ALTER TABLE $p1 RENAME COLUMN $p2 TO $p3";
  4574. case 'sqlsrv':
  4575. $p4 = $this->quote($tableName . '.' . $columnName);
  4576. return "EXEC sp_rename $p4, $p3, 'COLUMN'";
  4577. }
  4578. }
  4579. private function getColumnRetypeSQL(String $tableName, String $columnName, ReflectedColumn $newColumn): String
  4580. {
  4581. $p1 = $this->quote($tableName);
  4582. $p2 = $this->quote($columnName);
  4583. $p3 = $this->quote($newColumn->getName());
  4584. $p4 = $this->getColumnType($newColumn, true);
  4585. switch ($this->driver) {
  4586. case 'mysql':
  4587. return "ALTER TABLE $p1 CHANGE $p2 $p3 $p4";
  4588. case 'pgsql':
  4589. return "ALTER TABLE $p1 ALTER COLUMN $p3 TYPE $p4";
  4590. case 'sqlsrv':
  4591. return "ALTER TABLE $p1 ALTER COLUMN $p3 $p4";
  4592. }
  4593. }
  4594. private function getSetColumnNullableSQL(String $tableName, String $columnName, ReflectedColumn $newColumn): String
  4595. {
  4596. $p1 = $this->quote($tableName);
  4597. $p2 = $this->quote($columnName);
  4598. $p3 = $this->quote($newColumn->getName());
  4599. $p4 = $this->getColumnType($newColumn, true);
  4600. switch ($this->driver) {
  4601. case 'mysql':
  4602. return "ALTER TABLE $p1 CHANGE $p2 $p3 $p4";
  4603. case 'pgsql':
  4604. $p5 = $newColumn->getNullable() ? 'DROP NOT NULL' : 'SET NOT NULL';
  4605. return "ALTER TABLE $p1 ALTER COLUMN $p2 $p5";
  4606. case 'sqlsrv':
  4607. return "ALTER TABLE $p1 ALTER COLUMN $p2 $p4";
  4608. }
  4609. }
  4610. private function getSetColumnPkConstraintSQL(String $tableName, String $columnName, ReflectedColumn $newColumn): String
  4611. {
  4612. $p1 = $this->quote($tableName);
  4613. $p2 = $this->quote($columnName);
  4614. $p3 = $this->quote($tableName . '_pkey');
  4615. switch ($this->driver) {
  4616. case 'mysql':
  4617. $p4 = $newColumn->getPk() ? "ADD PRIMARY KEY ($p2)" : 'DROP PRIMARY KEY';
  4618. return "ALTER TABLE $p1 $p4";
  4619. case 'pgsql':
  4620. case 'sqlsrv':
  4621. $p4 = $newColumn->getPk() ? "ADD CONSTRAINT $p3 PRIMARY KEY ($p2)" : "DROP CONSTRAINT $p3";
  4622. return "ALTER TABLE $p1 $p4";
  4623. }
  4624. }
  4625. private function getSetColumnPkSequenceSQL(String $tableName, String $columnName, ReflectedColumn $newColumn): String
  4626. {
  4627. $p1 = $this->quote($tableName);
  4628. $p2 = $this->quote($columnName);
  4629. $p3 = $this->quote($tableName . '_' . $columnName . '_seq');
  4630. switch ($this->driver) {
  4631. case 'mysql':
  4632. return "select 1";
  4633. case 'pgsql':
  4634. return $newColumn->getPk() ? "CREATE SEQUENCE $p3 OWNED BY $p1.$p2" : "DROP SEQUENCE $p3";
  4635. case 'sqlsrv':
  4636. return $newColumn->getPk() ? "CREATE SEQUENCE $p3" : "DROP SEQUENCE $p3";
  4637. }
  4638. }
  4639. private function getSetColumnPkSequenceStartSQL(String $tableName, String $columnName, ReflectedColumn $newColumn): String
  4640. {
  4641. $p1 = $this->quote($tableName);
  4642. $p2 = $this->quote($columnName);
  4643. switch ($this->driver) {
  4644. case 'mysql':
  4645. return "select 1";
  4646. case 'pgsql':
  4647. $p3 = $this->pdo->quote($tableName . '_' . $columnName . '_seq');
  4648. return "SELECT setval($p3, (SELECT max($p2)+1 FROM $p1));";
  4649. case 'sqlsrv':
  4650. $p3 = $this->quote($tableName . '_' . $columnName . '_seq');
  4651. $p4 = $this->pdo->query("SELECT max($p2)+1 FROM $p1")->fetchColumn();
  4652. return "ALTER SEQUENCE $p3 RESTART WITH $p4";
  4653. }
  4654. }
  4655. private function getSetColumnPkDefaultSQL(String $tableName, String $columnName, ReflectedColumn $newColumn): String
  4656. {
  4657. $p1 = $this->quote($tableName);
  4658. $p2 = $this->quote($columnName);
  4659. switch ($this->driver) {
  4660. case 'mysql':
  4661. $p3 = $this->quote($newColumn->getName());
  4662. $p4 = $this->getColumnType($newColumn, true);
  4663. return "ALTER TABLE $p1 CHANGE $p2 $p3 $p4";
  4664. case 'pgsql':
  4665. if ($newColumn->getPk()) {
  4666. $p3 = $this->pdo->quote($tableName . '_' . $columnName . '_seq');
  4667. $p4 = "SET DEFAULT nextval($p3)";
  4668. } else {
  4669. $p4 = 'DROP DEFAULT';
  4670. }
  4671. return "ALTER TABLE $p1 ALTER COLUMN $p2 $p4";
  4672. case 'sqlsrv':
  4673. $p3 = $this->quote($tableName . '_' . $columnName . '_seq');
  4674. $p4 = $this->quote($tableName . '_' . $columnName . '_def');
  4675. if ($newColumn->getPk()) {
  4676. return "ALTER TABLE $p1 ADD CONSTRAINT $p4 DEFAULT NEXT VALUE FOR $p3 FOR $p2";
  4677. } else {
  4678. return "ALTER TABLE $p1 DROP CONSTRAINT $p4";
  4679. }
  4680. }
  4681. }
  4682. private function getAddColumnFkConstraintSQL(String $tableName, String $columnName, ReflectedColumn $newColumn): String
  4683. {
  4684. $p1 = $this->quote($tableName);
  4685. $p2 = $this->quote($columnName);
  4686. $p3 = $this->quote($tableName . '_' . $columnName . '_fkey');
  4687. $p4 = $this->quote($newColumn->getFk());
  4688. $p5 = $this->quote($this->getPrimaryKey($newColumn->getFk()));
  4689. return "ALTER TABLE $p1 ADD CONSTRAINT $p3 FOREIGN KEY ($p2) REFERENCES $p4 ($p5)";
  4690. }
  4691. private function getRemoveColumnFkConstraintSQL(String $tableName, String $columnName, ReflectedColumn $newColumn): String
  4692. {
  4693. $p1 = $this->quote($tableName);
  4694. $p2 = $this->quote($tableName . '_' . $columnName . '_fkey');
  4695. switch ($this->driver) {
  4696. case 'mysql':
  4697. return "ALTER TABLE $p1 DROP FOREIGN KEY $p2";
  4698. case 'pgsql':
  4699. case 'sqlsrv':
  4700. return "ALTER TABLE $p1 DROP CONSTRAINT $p2";
  4701. }
  4702. }
  4703. private function getAddTableSQL(ReflectedTable $newTable): String
  4704. {
  4705. $tableName = $newTable->getName();
  4706. $p1 = $this->quote($tableName);
  4707. $fields = [];
  4708. $constraints = [];
  4709. foreach ($newTable->getColumnNames() as $columnName) {
  4710. $pkColumn = $this->getPrimaryKey($tableName);
  4711. $newColumn = $newTable->getColumn($columnName);
  4712. $f1 = $this->quote($columnName);
  4713. $f2 = $this->getColumnType($newColumn, false);
  4714. $f3 = $this->quote($tableName . '_' . $columnName . '_fkey');
  4715. $f4 = $this->quote($newColumn->getFk());
  4716. $f5 = $this->quote($this->getPrimaryKey($newColumn->getFk()));
  4717. $f6 = $this->quote($tableName . '_' . $pkColumn . '_pkey');
  4718. $fields[] = "$f1 $f2";
  4719. if ($newColumn->getPk()) {
  4720. $constraints[] = "CONSTRAINT $f6 PRIMARY KEY ($f1)";
  4721. }
  4722. if ($newColumn->getFk()) {
  4723. $constraints[] = "CONSTRAINT $f3 FOREIGN KEY ($f1) REFERENCES $f4 ($f5)";
  4724. }
  4725. }
  4726. $p2 = implode(',', array_merge($fields, $constraints));
  4727. return "CREATE TABLE $p1 ($p2);";
  4728. }
  4729. private function getAddColumnSQL(String $tableName, ReflectedColumn $newColumn): String
  4730. {
  4731. $p1 = $this->quote($tableName);
  4732. $p2 = $this->quote($newColumn->getName());
  4733. $p3 = $this->getColumnType($newColumn, false);
  4734. switch ($this->driver) {
  4735. case 'mysql':
  4736. case 'pgsql':
  4737. return "ALTER TABLE $p1 ADD COLUMN $p2 $p3";
  4738. case 'sqlsrv':
  4739. return "ALTER TABLE $p1 ADD $p2 $p3";
  4740. }
  4741. }
  4742. private function getRemoveTableSQL(String $tableName): String
  4743. {
  4744. $p1 = $this->quote($tableName);
  4745. switch ($this->driver) {
  4746. case 'mysql':
  4747. case 'pgsql':
  4748. return "DROP TABLE $p1 CASCADE;";
  4749. case 'sqlsrv':
  4750. return "DROP TABLE $p1;";
  4751. }
  4752. }
  4753. private function getRemoveColumnSQL(String $tableName, String $columnName): String
  4754. {
  4755. $p1 = $this->quote($tableName);
  4756. $p2 = $this->quote($columnName);
  4757. switch ($this->driver) {
  4758. case 'mysql':
  4759. case 'pgsql':
  4760. return "ALTER TABLE $p1 DROP COLUMN $p2 CASCADE;";
  4761. case 'sqlsrv':
  4762. return "ALTER TABLE $p1 DROP COLUMN $p2;";
  4763. }
  4764. }
  4765. public function renameTable(String $tableName, String $newTableName)
  4766. {
  4767. $sql = $this->getTableRenameSQL($tableName, $newTableName);
  4768. return $this->query($sql);
  4769. }
  4770. public function renameColumn(String $tableName, String $columnName, ReflectedColumn $newColumn)
  4771. {
  4772. $sql = $this->getColumnRenameSQL($tableName, $columnName, $newColumn);
  4773. return $this->query($sql);
  4774. }
  4775. public function retypeColumn(String $tableName, String $columnName, ReflectedColumn $newColumn)
  4776. {
  4777. $sql = $this->getColumnRetypeSQL($tableName, $columnName, $newColumn);
  4778. return $this->query($sql);
  4779. }
  4780. public function setColumnNullable(String $tableName, String $columnName, ReflectedColumn $newColumn)
  4781. {
  4782. $sql = $this->getSetColumnNullableSQL($tableName, $columnName, $newColumn);
  4783. return $this->query($sql);
  4784. }
  4785. public function addColumnPrimaryKey(String $tableName, String $columnName, ReflectedColumn $newColumn)
  4786. {
  4787. $sql = $this->getSetColumnPkConstraintSQL($tableName, $columnName, $newColumn);
  4788. $this->query($sql);
  4789. if ($this->canAutoIncrement($newColumn)) {
  4790. $sql = $this->getSetColumnPkSequenceSQL($tableName, $columnName, $newColumn);
  4791. $this->query($sql);
  4792. $sql = $this->getSetColumnPkSequenceStartSQL($tableName, $columnName, $newColumn);
  4793. $this->query($sql);
  4794. $sql = $this->getSetColumnPkDefaultSQL($tableName, $columnName, $newColumn);
  4795. $this->query($sql);
  4796. }
  4797. return true;
  4798. }
  4799. public function removeColumnPrimaryKey(String $tableName, String $columnName, ReflectedColumn $newColumn)
  4800. {
  4801. if ($this->canAutoIncrement($newColumn)) {
  4802. $sql = $this->getSetColumnPkDefaultSQL($tableName, $columnName, $newColumn);
  4803. $this->query($sql);
  4804. $sql = $this->getSetColumnPkSequenceSQL($tableName, $columnName, $newColumn);
  4805. $this->query($sql);
  4806. }
  4807. $sql = $this->getSetColumnPkConstraintSQL($tableName, $columnName, $newColumn);
  4808. $this->query($sql);
  4809. return true;
  4810. }
  4811. public function addColumnForeignKey(String $tableName, String $columnName, ReflectedColumn $newColumn)
  4812. {
  4813. $sql = $this->getAddColumnFkConstraintSQL($tableName, $columnName, $newColumn);
  4814. return $this->query($sql);
  4815. }
  4816. public function removeColumnForeignKey(String $tableName, String $columnName, ReflectedColumn $newColumn)
  4817. {
  4818. $sql = $this->getRemoveColumnFkConstraintSQL($tableName, $columnName, $newColumn);
  4819. return $this->query($sql);
  4820. }
  4821. public function addTable(ReflectedTable $newTable)
  4822. {
  4823. $sql = $this->getAddTableSQL($newTable);
  4824. return $this->query($sql);
  4825. }
  4826. public function addColumn(String $tableName, ReflectedColumn $newColumn)
  4827. {
  4828. $sql = $this->getAddColumnSQL($tableName, $newColumn);
  4829. return $this->query($sql);
  4830. }
  4831. public function removeTable(String $tableName)
  4832. {
  4833. $sql = $this->getRemoveTableSQL($tableName);
  4834. return $this->query($sql);
  4835. }
  4836. public function removeColumn(String $tableName, String $columnName)
  4837. {
  4838. $sql = $this->getRemoveColumnSQL($tableName, $columnName);
  4839. return $this->query($sql);
  4840. }
  4841. private function query(String $sql): bool
  4842. {
  4843. $stmt = $this->pdo->prepare($sql);
  4844. return $stmt->execute();
  4845. }
  4846. }
  4847. // file: src/Tqdev/PhpCrudApi/Database/GenericReflection.php
  4848. class GenericReflection
  4849. {
  4850. private $pdo;
  4851. private $driver;
  4852. private $database;
  4853. private $typeConverter;
  4854. public function __construct(\PDO $pdo, String $driver, String $database)
  4855. {
  4856. $this->pdo = $pdo;
  4857. $this->driver = $driver;
  4858. $this->database = $database;
  4859. $this->typeConverter = new TypeConverter($driver);
  4860. }
  4861. public function getIgnoredTables(): array
  4862. {
  4863. switch ($this->driver) {
  4864. case 'mysql':return [];
  4865. case 'pgsql':return ['spatial_ref_sys', 'raster_columns', 'raster_overviews', 'geography_columns', 'geometry_columns'];
  4866. case 'sqlsrv':return [];
  4867. }
  4868. }
  4869. private function getTablesSQL(): String
  4870. {
  4871. switch ($this->driver) {
  4872. case 'mysql':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"';
  4873. case 'pgsql':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";';
  4874. case 'sqlsrv':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"';
  4875. }
  4876. }
  4877. private function getTableColumnsSQL(): String
  4878. {
  4879. switch ($this->driver) {
  4880. case 'mysql':return 'SELECT "COLUMN_NAME", "IS_NULLABLE", "DATA_TYPE", "CHARACTER_MAXIMUM_LENGTH", "NUMERIC_PRECISION", "NUMERIC_SCALE" FROM "INFORMATION_SCHEMA"."COLUMNS" WHERE "TABLE_NAME" = ? AND "TABLE_SCHEMA" = ?';
  4881. case 'pgsql':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" 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;';
  4882. case 'sqlsrv':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" 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 \'\' <> ?';
  4883. }
  4884. }
  4885. private function getTablePrimaryKeysSQL(): String
  4886. {
  4887. switch ($this->driver) {
  4888. case 'mysql':return 'SELECT "COLUMN_NAME" FROM "INFORMATION_SCHEMA"."KEY_COLUMN_USAGE" WHERE "CONSTRAINT_NAME" = \'PRIMARY\' AND "TABLE_NAME" = ? AND "TABLE_SCHEMA" = ?';
  4889. case 'pgsql':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\'';
  4890. case 'sqlsrv':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 \'\' <> ?';
  4891. }
  4892. }
  4893. private function getTableForeignKeysSQL(): String
  4894. {
  4895. switch ($this->driver) {
  4896. case 'mysql':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" = ?';
  4897. case 'pgsql':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\'';
  4898. case 'sqlsrv':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 \'\' <> ?';
  4899. }
  4900. }
  4901. public function getDatabaseName(): String
  4902. {
  4903. return $this->database;
  4904. }
  4905. public function getTables(): array
  4906. {
  4907. $sql = $this->getTablesSQL();
  4908. $results = $this->query($sql, [$this->database]);
  4909. foreach ($results as &$result) {
  4910. switch ($this->driver) {
  4911. case 'mysql':
  4912. $map = ['BASE TABLE' => 'table', 'VIEW' => 'view'];
  4913. $result['TABLE_TYPE'] = $map[$result['TABLE_TYPE']];
  4914. break;
  4915. case 'pgsql':
  4916. $map = ['r' => 'table', 'v' => 'view'];
  4917. $result['TABLE_TYPE'] = $map[$result['TABLE_TYPE']];
  4918. break;
  4919. case 'sqlsrv':
  4920. $map = ['U' => 'table', 'V' => 'view'];
  4921. $result['TABLE_TYPE'] = $map[trim($result['TABLE_TYPE'])];
  4922. break;
  4923. }
  4924. }
  4925. return $results;
  4926. }
  4927. public function getTableColumns(String $tableName, String $type): array
  4928. {
  4929. $sql = $this->getTableColumnsSQL();
  4930. $results = $this->query($sql, [$tableName, $this->database]);
  4931. if ($type == 'view') {
  4932. foreach ($results as &$result) {
  4933. $result['IS_NULLABLE'] = false;
  4934. }
  4935. }
  4936. return $results;
  4937. }
  4938. public function getTablePrimaryKeys(String $tableName): array
  4939. {
  4940. $sql = $this->getTablePrimaryKeysSQL();
  4941. $results = $this->query($sql, [$tableName, $this->database]);
  4942. $primaryKeys = [];
  4943. foreach ($results as $result) {
  4944. $primaryKeys[] = $result['COLUMN_NAME'];
  4945. }
  4946. return $primaryKeys;
  4947. }
  4948. public function getTableForeignKeys(String $tableName): array
  4949. {
  4950. $sql = $this->getTableForeignKeysSQL();
  4951. $results = $this->query($sql, [$tableName, $this->database]);
  4952. $foreignKeys = [];
  4953. foreach ($results as $result) {
  4954. $foreignKeys[$result['COLUMN_NAME']] = $result['REFERENCED_TABLE_NAME'];
  4955. }
  4956. return $foreignKeys;
  4957. }
  4958. public function toJdbcType(String $type, int $size): String
  4959. {
  4960. return $this->typeConverter->toJdbc($type, $size);
  4961. }
  4962. private function query(String $sql, array $parameters): array
  4963. {
  4964. $stmt = $this->pdo->prepare($sql);
  4965. $stmt->execute($parameters);
  4966. return $stmt->fetchAll();
  4967. }
  4968. }
  4969. // file: src/Tqdev/PhpCrudApi/Database/TypeConverter.php
  4970. class TypeConverter
  4971. {
  4972. private $driver;
  4973. public function __construct(String $driver)
  4974. {
  4975. $this->driver = $driver;
  4976. }
  4977. private $fromJdbc = [
  4978. 'mysql' => [
  4979. 'clob' => 'longtext',
  4980. 'boolean' => 'bit',
  4981. 'blob' => 'longblob',
  4982. 'timestamp' => 'datetime',
  4983. ],
  4984. 'pgsql' => [
  4985. 'clob' => 'text',
  4986. 'blob' => 'bytea',
  4987. ],
  4988. 'sqlsrv' => [
  4989. 'boolean' => 'bit',
  4990. 'varchar' => 'nvarchar',
  4991. 'clob' => 'ntext',
  4992. 'blob' => 'image',
  4993. ],
  4994. ];
  4995. private $toJdbc = [
  4996. 'simplified' => [
  4997. 'char' => 'varchar',
  4998. 'longvarchar' => 'clob',
  4999. 'nchar' => 'varchar',
  5000. 'nvarchar' => 'varchar',
  5001. 'longnvarchar' => 'clob',
  5002. 'binary' => 'varbinary',
  5003. 'longvarbinary' => 'blob',
  5004. 'tinyint' => 'integer',
  5005. 'smallint' => 'integer',
  5006. 'real' => 'float',
  5007. 'numeric' => 'decimal',
  5008. 'time_with_timezone' => 'time',
  5009. 'timestamp_with_timezone' => 'timestamp',
  5010. ],
  5011. 'mysql' => [
  5012. 'bit' => 'boolean',
  5013. 'tinyblob' => 'blob',
  5014. 'mediumblob' => 'blob',
  5015. 'longblob' => 'blob',
  5016. 'tinytext' => 'clob',
  5017. 'mediumtext' => 'clob',
  5018. 'longtext' => 'clob',
  5019. 'text' => 'clob',
  5020. 'mediumint' => 'integer',
  5021. 'int' => 'integer',
  5022. 'polygon' => 'geometry',
  5023. 'point' => 'geometry',
  5024. 'datetime' => 'timestamp',
  5025. 'year' => 'integer',
  5026. 'enum' => 'varchar',
  5027. 'json' => 'clob',
  5028. ],
  5029. 'pgsql' => [
  5030. 'bigserial' => 'bigint',
  5031. 'bit varying' => 'bit',
  5032. 'box' => 'geometry',
  5033. 'bytea' => 'blob',
  5034. 'character varying' => 'varchar',
  5035. 'character' => 'char',
  5036. 'cidr' => 'varchar',
  5037. 'circle' => 'geometry',
  5038. 'double precision' => 'double',
  5039. 'inet' => 'integer',
  5040. 'json' => 'clob',
  5041. 'jsonb' => 'clob',
  5042. 'line' => 'geometry',
  5043. 'lseg' => 'geometry',
  5044. 'macaddr' => 'varchar',
  5045. 'money' => 'decimal',
  5046. 'path' => 'geometry',
  5047. 'point' => 'geometry',
  5048. 'polygon' => 'geometry',
  5049. 'real' => 'float',
  5050. 'serial' => 'integer',
  5051. 'text' => 'clob',
  5052. 'time without time zone' => 'time',
  5053. 'time with time zone' => 'time_with_timezone',
  5054. 'timestamp without time zone' => 'timestamp',
  5055. 'timestamp with time zone' => 'timestamp_with_timezone',
  5056. 'uuid' => 'char',
  5057. 'xml' => 'clob',
  5058. ],
  5059. 'sqlsrv' => [
  5060. 'varbinary(0)' => 'blob',
  5061. 'bit' => 'boolean',
  5062. 'datetime' => 'timestamp',
  5063. 'datetime2' => 'timestamp',
  5064. 'float' => 'double',
  5065. 'image' => 'blob',
  5066. 'int' => 'integer',
  5067. 'money' => 'decimal',
  5068. 'ntext' => 'clob',
  5069. 'smalldatetime' => 'timestamp',
  5070. 'smallmoney' => 'decimal',
  5071. 'text' => 'clob',
  5072. 'timestamp' => 'binary',
  5073. 'udt' => 'varbinary',
  5074. 'uniqueidentifier' => 'char',
  5075. 'xml' => 'clob',
  5076. ],
  5077. ];
  5078. private $valid = [
  5079. 'bigint' => true,
  5080. 'binary' => true,
  5081. 'bit' => true,
  5082. 'blob' => true,
  5083. 'boolean' => true,
  5084. 'char' => true,
  5085. 'clob' => true,
  5086. 'date' => true,
  5087. 'decimal' => true,
  5088. 'distinct' => true,
  5089. 'double' => true,
  5090. 'float' => true,
  5091. 'integer' => true,
  5092. 'longnvarchar' => true,
  5093. 'longvarbinary' => true,
  5094. 'longvarchar' => true,
  5095. 'nchar' => true,
  5096. 'nclob' => true,
  5097. 'numeric' => true,
  5098. 'nvarchar' => true,
  5099. 'real' => true,
  5100. 'smallint' => true,
  5101. 'time' => true,
  5102. 'time_with_timezone' => true,
  5103. 'timestamp' => true,
  5104. 'timestamp_with_timezone' => true,
  5105. 'tinyint' => true,
  5106. 'varbinary' => true,
  5107. 'varchar' => true,
  5108. 'geometry' => true,
  5109. ];
  5110. public function toJdbc(String $type, int $size): String
  5111. {
  5112. $jdbcType = strtolower($type);
  5113. if (isset($this->toJdbc[$this->driver]["$jdbcType($size)"])) {
  5114. $jdbcType = $this->toJdbc[$this->driver]["$jdbcType($size)"];
  5115. }
  5116. if (isset($this->toJdbc[$this->driver][$jdbcType])) {
  5117. $jdbcType = $this->toJdbc[$this->driver][$jdbcType];
  5118. }
  5119. if (isset($this->toJdbc['simplified'][$jdbcType])) {
  5120. $jdbcType = $this->toJdbc['simplified'][$jdbcType];
  5121. }
  5122. if (!isset($this->valid[$jdbcType])) {
  5123. throw new \Exception("Unsupported type '$jdbcType' for driver '$this->driver'");
  5124. }
  5125. return $jdbcType;
  5126. }
  5127. public function fromJdbc(String $type): String
  5128. {
  5129. $jdbcType = strtolower($type);
  5130. if (isset($this->fromJdbc[$this->driver][$jdbcType])) {
  5131. $jdbcType = $this->fromJdbc[$this->driver][$jdbcType];
  5132. }
  5133. return $jdbcType;
  5134. }
  5135. }
  5136. // file: src/Tqdev/PhpCrudApi/Middleware/Base/Handler.php
  5137. interface Handler
  5138. {
  5139. public function handle(ServerRequestInterface $request): Response;
  5140. }
  5141. // file: src/Tqdev/PhpCrudApi/Middleware/Base/Middleware.php
  5142. abstract class Middleware implements Handler
  5143. {
  5144. protected $next;
  5145. protected $responder;
  5146. private $properties;
  5147. public function __construct(Router $router, Responder $responder, array $properties)
  5148. {
  5149. $router->load($this);
  5150. $this->responder = $responder;
  5151. $this->properties = $properties;
  5152. }
  5153. public function setNext(Handler $handler) /*: void*/
  5154. {
  5155. $this->next = $handler;
  5156. }
  5157. protected function getArrayProperty(String $key, String $default): array
  5158. {
  5159. return array_filter(array_map('trim', explode(',', $this->getProperty($key, $default))));
  5160. }
  5161. protected function getProperty(String $key, $default)
  5162. {
  5163. return isset($this->properties[$key]) ? $this->properties[$key] : $default;
  5164. }
  5165. }
  5166. // file: src/Tqdev/PhpCrudApi/Middleware/Communication/VariableStore.php
  5167. class VariableStore
  5168. {
  5169. static $values = array();
  5170. public static function get(String $key)
  5171. {
  5172. if (isset(self::$values[$key])) {
  5173. return self::$values[$key];
  5174. }
  5175. return null;
  5176. }
  5177. public static function set(String $key, /* object */ $value)
  5178. {
  5179. self::$values[$key] = $value;
  5180. }
  5181. }
  5182. // file: src/Tqdev/PhpCrudApi/Middleware/Router/Router.php
  5183. interface Router extends Handler
  5184. {
  5185. public function register(String $method, String $path, array $handler);
  5186. public function load(Middleware $middleware);
  5187. public function route(ServerRequestInterface $request): Response;
  5188. }
  5189. // file: src/Tqdev/PhpCrudApi/Middleware/Router/SimpleRouter.php
  5190. class SimpleRouter implements Router
  5191. {
  5192. private $responder;
  5193. private $cache;
  5194. private $ttl;
  5195. private $debug;
  5196. private $registration;
  5197. private $routes;
  5198. private $routeHandlers;
  5199. private $middlewares;
  5200. public function __construct(Responder $responder, Cache $cache, int $ttl, bool $debug)
  5201. {
  5202. $this->responder = $responder;
  5203. $this->cache = $cache;
  5204. $this->ttl = $ttl;
  5205. $this->debug = $debug;
  5206. $this->registration = true;
  5207. $this->routes = $this->loadPathTree();
  5208. $this->routeHandlers = [];
  5209. $this->middlewares = array();
  5210. }
  5211. private function loadPathTree(): PathTree
  5212. {
  5213. $data = $this->cache->get('PathTree');
  5214. if ($data != '') {
  5215. $tree = PathTree::fromJson(json_decode(gzuncompress($data)));
  5216. $this->registration = false;
  5217. } else {
  5218. $tree = new PathTree();
  5219. }
  5220. return $tree;
  5221. }
  5222. public function register(String $method, String $path, array $handler)
  5223. {
  5224. $routeNumber = count($this->routeHandlers);
  5225. $this->routeHandlers[$routeNumber] = $handler;
  5226. if ($this->registration) {
  5227. $parts = explode('/', trim($path, '/'));
  5228. array_unshift($parts, $method);
  5229. $this->routes->put($parts, $routeNumber);
  5230. }
  5231. }
  5232. public function load(Middleware $middleware) /*: void*/
  5233. {
  5234. if (count($this->middlewares) > 0) {
  5235. $next = $this->middlewares[0];
  5236. } else {
  5237. $next = $this;
  5238. }
  5239. $middleware->setNext($next);
  5240. array_unshift($this->middlewares, $middleware);
  5241. }
  5242. public function route(ServerRequestInterface $request): Response
  5243. {
  5244. if ($this->registration) {
  5245. $data = gzcompress(json_encode($this->routes, JSON_UNESCAPED_UNICODE));
  5246. $this->cache->set('PathTree', $data, $this->ttl);
  5247. }
  5248. $obj = $this;
  5249. if (count($this->middlewares) > 0) {
  5250. $obj = $this->middlewares[0];
  5251. }
  5252. return $obj->handle($request);
  5253. }
  5254. private function getRouteNumbers(ServerRequestInterface $request): array
  5255. {
  5256. $method = strtoupper($request->getMethod());
  5257. $path = explode('/', trim($request->getRequestTarget(), '/'));
  5258. array_unshift($path, $method);
  5259. return $this->routes->match($path);
  5260. }
  5261. public function handle(ServerRequestInterface $request): Response
  5262. {
  5263. $routeNumbers = $this->getRouteNumbers($request);
  5264. if (count($routeNumbers) == 0) {
  5265. return $this->responder->error(ErrorCode::ROUTE_NOT_FOUND, $request->getRequestTarget());
  5266. }
  5267. try {
  5268. $response = call_user_func($this->routeHandlers[$routeNumbers[0]], $request);
  5269. } catch (\PDOException $e) {
  5270. if (strpos(strtolower($e->getMessage()), 'duplicate') !== false) {
  5271. $response = $this->responder->error(ErrorCode::DUPLICATE_KEY_EXCEPTION, '');
  5272. } elseif (strpos(strtolower($e->getMessage()), 'default value') !== false) {
  5273. $response = $this->responder->error(ErrorCode::DATA_INTEGRITY_VIOLATION, '');
  5274. } elseif (strpos(strtolower($e->getMessage()), 'allow nulls') !== false) {
  5275. $response = $this->responder->error(ErrorCode::DATA_INTEGRITY_VIOLATION, '');
  5276. } elseif (strpos(strtolower($e->getMessage()), 'constraint') !== false) {
  5277. $response = $this->responder->error(ErrorCode::DATA_INTEGRITY_VIOLATION, '');
  5278. }
  5279. if ($this->debug) {
  5280. $response->addExceptionHeaders($e);
  5281. }
  5282. }
  5283. return $response;
  5284. }
  5285. }
  5286. // file: src/Tqdev/PhpCrudApi/Middleware/AjaxOnlyMiddleware.php
  5287. class AjaxOnlyMiddleware extends Middleware
  5288. {
  5289. public function handle(ServerRequestInterface $request): Response
  5290. {
  5291. $method = $request->getMethod();
  5292. $excludeMethods = $this->getArrayProperty('excludeMethods', 'OPTIONS,GET');
  5293. if (!in_array($method, $excludeMethods)) {
  5294. $headerName = $this->getProperty('headerName', 'X-Requested-With');
  5295. $headerValue = $this->getProperty('headerValue', 'XMLHttpRequest');
  5296. if ($headerValue != $request->getHeader($headerName)) {
  5297. return $this->responder->error(ErrorCode::ONLY_AJAX_REQUESTS_ALLOWED, $method);
  5298. }
  5299. }
  5300. return $this->next->handle($request);
  5301. }
  5302. }
  5303. // file: src/Tqdev/PhpCrudApi/Middleware/AuthorizationMiddleware.php
  5304. class AuthorizationMiddleware extends Middleware
  5305. {
  5306. private $reflection;
  5307. private $utils;
  5308. public function __construct(Router $router, Responder $responder, array $properties, ReflectionService $reflection)
  5309. {
  5310. parent::__construct($router, $responder, $properties);
  5311. $this->reflection = $reflection;
  5312. $this->utils = new RequestUtils($reflection);
  5313. }
  5314. private function handleColumns(String $operation, String $tableName) /*: void*/
  5315. {
  5316. $columnHandler = $this->getProperty('columnHandler', '');
  5317. if ($columnHandler) {
  5318. $table = $this->reflection->getTable($tableName);
  5319. foreach ($table->getColumnNames() as $columnName) {
  5320. $allowed = call_user_func($columnHandler, $operation, $tableName, $columnName);
  5321. if (!$allowed) {
  5322. $table->removeColumn($columnName);
  5323. }
  5324. }
  5325. }
  5326. }
  5327. private function handleTable(String $operation, String $tableName) /*: void*/
  5328. {
  5329. if (!$this->reflection->hasTable($tableName)) {
  5330. return;
  5331. }
  5332. $tableHandler = $this->getProperty('tableHandler', '');
  5333. if ($tableHandler) {
  5334. $allowed = call_user_func($tableHandler, $operation, $tableName);
  5335. if (!$allowed) {
  5336. $this->reflection->removeTable($tableName);
  5337. } else {
  5338. $this->handleColumns($operation, $tableName);
  5339. }
  5340. }
  5341. }
  5342. private function handleRecords(String $operation, String $tableName) /*: void*/
  5343. {
  5344. if (!$this->reflection->hasTable($tableName)) {
  5345. return;
  5346. }
  5347. $recordHandler = $this->getProperty('recordHandler', '');
  5348. if ($recordHandler) {
  5349. $query = call_user_func($recordHandler, $operation, $tableName);
  5350. $filters = new FilterInfo();
  5351. $table = $this->reflection->getTable($tableName);
  5352. $query = str_replace('][]=', ']=', str_replace('=', '[]=', $query));
  5353. parse_str($query, $params);
  5354. $condition = $filters->getCombinedConditions($table, $params);
  5355. VariableStore::set("authorization.conditions.$tableName", $condition);
  5356. }
  5357. }
  5358. public function handle(ServerRequestInterface $request): Response
  5359. {
  5360. $path = $this->utils->getPathSegment($request, 1);
  5361. $operation = $this->utils->getOperation($request);
  5362. $tableNames = $this->utils->getTableNames($request);
  5363. foreach ($tableNames as $tableName) {
  5364. $this->handleTable($operation, $tableName);
  5365. if ($path == 'records') {
  5366. $this->handleRecords($operation, $tableName);
  5367. }
  5368. }
  5369. if ($path == 'openapi') {
  5370. VariableStore::set('authorization.tableHandler', $this->getProperty('tableHandler', ''));
  5371. VariableStore::set('authorization.columnHandler', $this->getProperty('columnHandler', ''));
  5372. }
  5373. return $this->next->handle($request);
  5374. }
  5375. }
  5376. // file: src/Tqdev/PhpCrudApi/Middleware/BasicAuthMiddleware.php
  5377. class BasicAuthMiddleware extends Middleware
  5378. {
  5379. private function hasCorrectPassword(String $username, String $password, array &$passwords): bool
  5380. {
  5381. $hash = isset($passwords[$username]) ? $passwords[$username] : false;
  5382. if ($hash && password_verify($password, $hash)) {
  5383. if (password_needs_rehash($hash, PASSWORD_DEFAULT)) {
  5384. $passwords[$username] = password_hash($password, PASSWORD_DEFAULT);
  5385. }
  5386. return true;
  5387. }
  5388. return false;
  5389. }
  5390. private function getValidUsername(String $username, String $password, String $passwordFile): String
  5391. {
  5392. $passwords = $this->readPasswords($passwordFile);
  5393. $valid = $this->hasCorrectPassword($username, $password, $passwords);
  5394. $this->writePasswords($passwordFile, $passwords);
  5395. return $valid ? $username : '';
  5396. }
  5397. private function readPasswords(String $passwordFile): array
  5398. {
  5399. $passwords = [];
  5400. $passwordLines = file($passwordFile);
  5401. foreach ($passwordLines as $passwordLine) {
  5402. if (strpos($passwordLine, ':') !== false) {
  5403. list($username, $hash) = explode(':', trim($passwordLine), 2);
  5404. if (strlen($hash) > 0 && $hash[0] != '$') {
  5405. $hash = password_hash($hash, PASSWORD_DEFAULT);
  5406. }
  5407. $passwords[$username] = $hash;
  5408. }
  5409. }
  5410. return $passwords;
  5411. }
  5412. private function writePasswords(String $passwordFile, array $passwords): bool
  5413. {
  5414. $success = false;
  5415. $passwordFileContents = '';
  5416. foreach ($passwords as $username => $hash) {
  5417. $passwordFileContents .= "$username:$hash\n";
  5418. }
  5419. if (file_get_contents($passwordFile) != $passwordFileContents) {
  5420. $success = file_put_contents($passwordFile, $passwordFileContents) !== false;
  5421. }
  5422. return $success;
  5423. }
  5424. private function getAuthorizationCredentials(ServerRequestInterface $request): String
  5425. {
  5426. if (isset($_SERVER['PHP_AUTH_USER'])) {
  5427. return $_SERVER['PHP_AUTH_USER'] . ':' . $_SERVER['PHP_AUTH_PW'];
  5428. }
  5429. $parts = explode(' ', trim($request->getHeader('Authorization')), 2);
  5430. if (count($parts) != 2) {
  5431. return '';
  5432. }
  5433. if ($parts[0] != 'Basic') {
  5434. return '';
  5435. }
  5436. return base64_decode(strtr($parts[1], '-_', '+/'));
  5437. }
  5438. public function handle(ServerRequestInterface
  5439. $request): Response {
  5440. if (session_status() == PHP_SESSION_NONE) {
  5441. session_start();
  5442. }
  5443. $credentials = $this->getAuthorizationCredentials($request);
  5444. if ($credentials) {
  5445. list($username, $password) = array('', '');
  5446. if (strpos($credentials, ':') !== false) {
  5447. list($username, $password) = explode(':', $credentials, 2);
  5448. }
  5449. $passwordFile = $this->getProperty('passwordFile', '.htpasswd');
  5450. $validUser = $this->getValidUsername($username, $password, $passwordFile);
  5451. $_SESSION['username'] = $validUser;
  5452. if (!$validUser) {
  5453. return $this->responder->error(ErrorCode::AUTHENTICATION_FAILED, $username);
  5454. }
  5455. if (!headers_sent()) {
  5456. session_regenerate_id();
  5457. }
  5458. }
  5459. if (!isset($_SESSION['username']) || !$_SESSION['username']) {
  5460. $authenticationMode = $this->getProperty('mode', 'required');
  5461. if ($authenticationMode == 'required') {
  5462. $response = $this->responder->error(ErrorCode::AUTHENTICATION_REQUIRED, '');
  5463. $realm = $this->getProperty('realm', 'Username and password required');
  5464. $response->addHeader('WWW-Authenticate', "Basic realm=\"$realm\"");
  5465. return $response;
  5466. }
  5467. }
  5468. return $this->next->handle($request);
  5469. }
  5470. }
  5471. // file: src/Tqdev/PhpCrudApi/Middleware/CorsMiddleware.php
  5472. class CorsMiddleware extends Middleware
  5473. {
  5474. private function isOriginAllowed(String $origin, String $allowedOrigins): bool
  5475. {
  5476. $found = false;
  5477. foreach (explode(',', $allowedOrigins) as $allowedOrigin) {
  5478. $hostname = preg_quote(strtolower(trim($allowedOrigin)));
  5479. $regex = '/^' . str_replace('\*', '.*', $hostname) . '$/';
  5480. if (preg_match($regex, $origin)) {
  5481. $found = true;
  5482. break;
  5483. }
  5484. }
  5485. return $found;
  5486. }
  5487. public function handle(ServerRequestInterface $request): Response
  5488. {
  5489. $method = $request->getMethod();
  5490. $origin = count($request->getHeader('Origin')) ? $request->getHeader('Origin')[0] : '';
  5491. $allowedOrigins = $this->getProperty('allowedOrigins', '*');
  5492. if ($origin && !$this->isOriginAllowed($origin, $allowedOrigins)) {
  5493. $response = $this->responder->error(ErrorCode::ORIGIN_FORBIDDEN, $origin);
  5494. } elseif ($method == 'OPTIONS') {
  5495. $response = new Response(Response::OK, '');
  5496. $allowHeaders = $this->getProperty('allowHeaders', 'Content-Type, X-XSRF-TOKEN, X-Authorization');
  5497. if ($allowHeaders) {
  5498. $response->addHeader('Access-Control-Allow-Headers', $allowHeaders);
  5499. }
  5500. $allowMethods = $this->getProperty('allowMethods', 'OPTIONS, GET, PUT, POST, DELETE, PATCH');
  5501. if ($allowMethods) {
  5502. $response->addHeader('Access-Control-Allow-Methods', $allowMethods);
  5503. }
  5504. $allowCredentials = $this->getProperty('allowCredentials', 'true');
  5505. if ($allowCredentials) {
  5506. $response->addHeader('Access-Control-Allow-Credentials', $allowCredentials);
  5507. }
  5508. $maxAge = $this->getProperty('maxAge', '1728000');
  5509. if ($maxAge) {
  5510. $response->addHeader('Access-Control-Max-Age', $maxAge);
  5511. }
  5512. $exposeHeaders = $this->getProperty('exposeHeaders', '');
  5513. if ($exposeHeaders) {
  5514. $response->addHeader('Access-Control-Expose-Headers', $exposeHeaders);
  5515. }
  5516. } else {
  5517. $response = $this->next->handle($request);
  5518. }
  5519. if ($origin) {
  5520. $allowCredentials = $this->getProperty('allowCredentials', 'true');
  5521. if ($allowCredentials) {
  5522. $response->addHeader('Access-Control-Allow-Credentials', $allowCredentials);
  5523. }
  5524. $response->addHeader('Access-Control-Allow-Origin', $origin);
  5525. }
  5526. return $response;
  5527. }
  5528. }
  5529. // file: src/Tqdev/PhpCrudApi/Middleware/CustomizationMiddleware.php
  5530. class CustomizationMiddleware extends Middleware
  5531. {
  5532. private $reflection;
  5533. public function __construct(Router $router, Responder $responder, array $properties, ReflectionService $reflection)
  5534. {
  5535. parent::__construct($router, $responder, $properties);
  5536. $this->reflection = $reflection;
  5537. $this->utils = new RequestUtils($reflection);
  5538. }
  5539. public function handle(ServerRequestInterface $request): Response
  5540. {
  5541. $operation = $this->utils->getOperation($request);
  5542. $tableName = $this->utils->getPathSegment($request, 2);
  5543. $beforeHandler = $this->getProperty('beforeHandler', '');
  5544. $environment = (object) array();
  5545. if ($beforeHandler !== '') {
  5546. call_user_func($beforeHandler, $operation, $tableName, $request, $environment);
  5547. }
  5548. $response = $this->next->handle($request);
  5549. $afterHandler = $this->getProperty('afterHandler', '');
  5550. if ($afterHandler !== '') {
  5551. call_user_func($afterHandler, $operation, $tableName, $response, $environment);
  5552. }
  5553. return $response;
  5554. }
  5555. }
  5556. // file: src/Tqdev/PhpCrudApi/Middleware/FirewallMiddleware.php
  5557. class FirewallMiddleware extends Middleware
  5558. {
  5559. private function ipMatch(String $ip, String $cidr): bool
  5560. {
  5561. if (strpos($cidr, '/') !== false) {
  5562. list($subnet, $mask) = explode('/', trim($cidr));
  5563. if ((ip2long($ip) & ~((1 << (32 - $mask)) - 1)) == ip2long($subnet)) {
  5564. return true;
  5565. }
  5566. } else {
  5567. if (ip2long($ip) == ip2long($cidr)) {
  5568. return true;
  5569. }
  5570. }
  5571. return false;
  5572. }
  5573. private function isIpAllowed(String $ipAddress, String $allowedIpAddresses): bool
  5574. {
  5575. foreach (explode(',', $allowedIpAddresses) as $allowedIp) {
  5576. if ($this->ipMatch($ipAddress, $allowedIp)) {
  5577. return true;
  5578. }
  5579. }
  5580. return false;
  5581. }
  5582. public function handle(ServerRequestInterface $request): Response
  5583. {
  5584. $reverseProxy = $this->getProperty('reverseProxy', '');
  5585. if ($reverseProxy) {
  5586. $ipAddress = array_pop(explode(',', $request->getHeader('X-Forwarded-For')));
  5587. } elseif (isset($_SERVER['REMOTE_ADDR'])) {
  5588. $ipAddress = $_SERVER['REMOTE_ADDR'];
  5589. } else {
  5590. $ipAddress = '127.0.0.1';
  5591. }
  5592. $allowedIpAddresses = $this->getProperty('allowedIpAddresses', '');
  5593. if (!$this->isIpAllowed($ipAddress, $allowedIpAddresses)) {
  5594. $response = $this->responder->error(ErrorCode::TEMPORARY_OR_PERMANENTLY_BLOCKED, '');
  5595. } else {
  5596. $response = $this->next->handle($request);
  5597. }
  5598. return $response;
  5599. }
  5600. }
  5601. // file: src/Tqdev/PhpCrudApi/Middleware/IpAddressMiddleware.php
  5602. class IpAddressMiddleware extends Middleware
  5603. {
  5604. private $reflection;
  5605. public function __construct(Router $router, Responder $responder, array $properties, ReflectionService $reflection)
  5606. {
  5607. parent::__construct($router, $responder, $properties);
  5608. $this->reflection = $reflection;
  5609. $this->utils = new RequestUtils($reflection);
  5610. }
  5611. private function callHandler($record, String $operation, ReflectedTable $table) /*: object */
  5612. {
  5613. $context = (array) $record;
  5614. $columnNames = $this->getProperty('columns', '');
  5615. if ($columnNames) {
  5616. foreach (explode(',', $columnNames) as $columnName) {
  5617. if ($table->hasColumn($columnName)) {
  5618. if ($operation == 'create') {
  5619. $context[$columnName] = $_SERVER['REMOTE_ADDR'];
  5620. } else {
  5621. unset($context[$columnName]);
  5622. }
  5623. }
  5624. }
  5625. }
  5626. return (object) $context;
  5627. }
  5628. public function handle(ServerRequestInterface $request): Response
  5629. {
  5630. $operation = $this->utils->getOperation($request);
  5631. if (in_array($operation, ['create', 'update', 'increment'])) {
  5632. $tableNames = $this->getProperty('tables', '');
  5633. $tableName = $this->utils->getPathSegment($request, 2);
  5634. if (!$tableNames || in_array($tableName, explode(',', $tableNames))) {
  5635. if ($this->reflection->hasTable($tableName)) {
  5636. $record = $request->getBody();
  5637. if ($record !== null) {
  5638. $table = $this->reflection->getTable($tableName);
  5639. if (is_array($record)) {
  5640. foreach ($record as &$r) {
  5641. $r = $this->callHandler($r, $operation, $table);
  5642. }
  5643. } else {
  5644. $record = $this->callHandler($record, $operation, $table);
  5645. }
  5646. $request->setBody($record);
  5647. }
  5648. }
  5649. }
  5650. }
  5651. return $this->next->handle($request);
  5652. }
  5653. }
  5654. // file: src/Tqdev/PhpCrudApi/Middleware/JoinLimitsMiddleware.php
  5655. class JoinLimitsMiddleware extends Middleware
  5656. {
  5657. private $reflection;
  5658. public function __construct(Router $router, Responder $responder, array $properties, ReflectionService $reflection)
  5659. {
  5660. parent::__construct($router, $responder, $properties);
  5661. $this->reflection = $reflection;
  5662. $this->utils = new RequestUtils($reflection);
  5663. }
  5664. public function handle(ServerRequestInterface $request): Response
  5665. {
  5666. $operation = $this->utils->getOperation($request);
  5667. $params = $this->utils->getParams($request);
  5668. if (in_array($operation, ['read', 'list']) && isset($params['join'])) {
  5669. $maxDepth = (int) $this->getProperty('depth', '3');
  5670. $maxTables = (int) $this->getProperty('tables', '10');
  5671. $maxRecords = (int) $this->getProperty('records', '1000');
  5672. $tableCount = 0;
  5673. $joinPaths = array();
  5674. for ($i = 0; $i < count($params['join']); $i++) {
  5675. $joinPath = array();
  5676. $tables = explode(',', $params['join'][$i]);
  5677. for ($depth = 0; $depth < min($maxDepth, count($tables)); $depth++) {
  5678. array_push($joinPath, $tables[$depth]);
  5679. $tableCount += 1;
  5680. if ($tableCount == $maxTables) {
  5681. break;
  5682. }
  5683. }
  5684. array_push($joinPaths, implode(',', $joinPath));
  5685. if ($tableCount == $maxTables) {
  5686. break;
  5687. }
  5688. }
  5689. $params['join'] = $joinPaths;
  5690. $request->setParams($params);
  5691. VariableStore::set("joinLimits.maxRecords", $maxRecords);
  5692. }
  5693. return $this->next->handle($request);
  5694. }
  5695. }
  5696. // file: src/Tqdev/PhpCrudApi/Middleware/JwtAuthMiddleware.php
  5697. class JwtAuthMiddleware extends Middleware
  5698. {
  5699. private function getVerifiedClaims(String $token, int $time, int $leeway, int $ttl, String $secret, array $requirements): array
  5700. {
  5701. $algorithms = array(
  5702. 'HS256' => 'sha256',
  5703. 'HS384' => 'sha384',
  5704. 'HS512' => 'sha512',
  5705. 'RS256' => 'sha256',
  5706. 'RS384' => 'sha384',
  5707. 'RS512' => 'sha512',
  5708. );
  5709. $token = explode('.', $token);
  5710. if (count($token) < 3) {
  5711. return array();
  5712. }
  5713. $header = json_decode(base64_decode(strtr($token[0], '-_', '+/')), true);
  5714. if (!$secret) {
  5715. return array();
  5716. }
  5717. if ($header['typ'] != 'JWT') {
  5718. return array();
  5719. }
  5720. $algorithm = $header['alg'];
  5721. if (!isset($algorithms[$algorithm])) {
  5722. return array();
  5723. }
  5724. if (!empty($requirements['alg']) && !in_array($algorithm, $requirements['alg'])) {
  5725. return array();
  5726. }
  5727. $hmac = $algorithms[$algorithm];
  5728. $signature = base64_decode(strtr($token[2], '-_', '+/'));
  5729. $data = "$token[0].$token[1]";
  5730. switch ($algorithm[0]) {
  5731. case 'H':
  5732. $hash = hash_hmac($hmac, $data, $secret, true);
  5733. if (function_exists('hash_equals')) {
  5734. $equals = hash_equals($signature, $hash);
  5735. } else {
  5736. $equals = $signature == $hash;
  5737. }
  5738. if (!$equals) {
  5739. return array();
  5740. }
  5741. break;
  5742. case 'R':
  5743. $equals = openssl_verify($data, $signature, $secret, $hmac) == 1;
  5744. if (!$equals) {
  5745. return array();
  5746. }
  5747. break;
  5748. }
  5749. $claims = json_decode(base64_decode(strtr($token[1], '-_', '+/')), true);
  5750. if (!$claims) {
  5751. return array();
  5752. }
  5753. foreach ($requirements as $field => $values) {
  5754. if (!empty($values)) {
  5755. if ($field != 'alg') {
  5756. if (!isset($claims[$field]) || !in_array($claims[$field], $values)) {
  5757. return array();
  5758. }
  5759. }
  5760. }
  5761. }
  5762. if (isset($claims['nbf']) && $time + $leeway < $claims['nbf']) {
  5763. return array();
  5764. }
  5765. if (isset($claims['iat']) && $time + $leeway < $claims['iat']) {
  5766. return array();
  5767. }
  5768. if (isset($claims['exp']) && $time - $leeway > $claims['exp']) {
  5769. return array();
  5770. }
  5771. if (isset($claims['iat']) && !isset($claims['exp'])) {
  5772. if ($time - $leeway > $claims['iat'] + $ttl) {
  5773. return array();
  5774. }
  5775. }
  5776. return $claims;
  5777. }
  5778. private function getClaims(String $token): array
  5779. {
  5780. $time = (int) $this->getProperty('time', time());
  5781. $leeway = (int) $this->getProperty('leeway', '5');
  5782. $ttl = (int) $this->getProperty('ttl', '30');
  5783. $secret = $this->getProperty('secret', '');
  5784. $requirements = array(
  5785. 'alg' => $this->getArrayProperty('algorithms', ''),
  5786. 'aud' => $this->getArrayProperty('audiences', ''),
  5787. 'iss' => $this->getArrayProperty('issuers', ''),
  5788. );
  5789. if (!$secret) {
  5790. return array();
  5791. }
  5792. return $this->getVerifiedClaims($token, $time, $leeway, $ttl, $secret, $requirements);
  5793. }
  5794. private function getAuthorizationToken(ServerRequestInterface $request): String
  5795. {
  5796. $header = $this->getProperty('header', 'X-Authorization');
  5797. $parts = explode(' ', trim($request->getHeader($header)), 2);
  5798. if (count($parts) != 2) {
  5799. return '';
  5800. }
  5801. if ($parts[0] != 'Bearer') {
  5802. return '';
  5803. }
  5804. return $parts[1];
  5805. }
  5806. public function handle(ServerRequestInterface $request): Response
  5807. {
  5808. if (session_status() == PHP_SESSION_NONE) {
  5809. session_start();
  5810. }
  5811. $token = $this->getAuthorizationToken($request);
  5812. if ($token) {
  5813. $claims = $this->getClaims($token);
  5814. $_SESSION['claims'] = $claims;
  5815. if (empty($claims)) {
  5816. return $this->responder->error(ErrorCode::AUTHENTICATION_FAILED, 'JWT');
  5817. }
  5818. if (!headers_sent()) {
  5819. session_regenerate_id();
  5820. }
  5821. }
  5822. if (empty($_SESSION['claims'])) {
  5823. $authenticationMode = $this->getProperty('mode', 'required');
  5824. if ($authenticationMode == 'required') {
  5825. return $this->responder->error(ErrorCode::AUTHENTICATION_REQUIRED, '');
  5826. }
  5827. }
  5828. return $this->next->handle($request);
  5829. }
  5830. }
  5831. // file: src/Tqdev/PhpCrudApi/Middleware/MultiTenancyMiddleware.php
  5832. class MultiTenancyMiddleware extends Middleware
  5833. {
  5834. private $reflection;
  5835. public function __construct(Router $router, Responder $responder, array $properties, ReflectionService $reflection)
  5836. {
  5837. parent::__construct($router, $responder, $properties);
  5838. $this->reflection = $reflection;
  5839. $this->utils = new RequestUtils($reflection);
  5840. }
  5841. private function getCondition(String $tableName, array $pairs): Condition
  5842. {
  5843. $condition = new NoCondition();
  5844. $table = $this->reflection->getTable($tableName);
  5845. foreach ($pairs as $k => $v) {
  5846. $condition = $condition->_and(new ColumnCondition($table->getColumn($k), 'eq', $v));
  5847. }
  5848. return $condition;
  5849. }
  5850. private function getPairs($handler, String $operation, String $tableName): array
  5851. {
  5852. $result = array();
  5853. $pairs = call_user_func($handler, $operation, $tableName);
  5854. $table = $this->reflection->getTable($tableName);
  5855. foreach ($pairs as $k => $v) {
  5856. if ($table->hasColumn($k)) {
  5857. $result[$k] = $v;
  5858. }
  5859. }
  5860. return $result;
  5861. }
  5862. private function handleRecord(ServerRequestInterface $request, String $operation, array $pairs) /*: void*/
  5863. {
  5864. $record = $request->getBody();
  5865. if ($record === null) {
  5866. return;
  5867. }
  5868. $multi = is_array($record);
  5869. $records = $multi ? $record : [$record];
  5870. foreach ($records as &$record) {
  5871. foreach ($pairs as $column => $value) {
  5872. if ($operation == 'create') {
  5873. $record->$column = $value;
  5874. } else {
  5875. if (isset($record->$column)) {
  5876. unset($record->$column);
  5877. }
  5878. }
  5879. }
  5880. }
  5881. $request->setBody($multi ? $records : $records[0]);
  5882. }
  5883. public function handle(ServerRequestInterface $request): Response
  5884. {
  5885. $handler = $this->getProperty('handler', '');
  5886. if ($handler !== '') {
  5887. $path = $this->utils->getPathSegment($request, 1);
  5888. if ($path == 'records') {
  5889. $operation = $this->utils->getOperation($request);
  5890. $tableNames = $this->utils->getTableNames($request);
  5891. foreach ($tableNames as $i => $tableName) {
  5892. if (!$this->reflection->hasTable($tableName)) {
  5893. continue;
  5894. }
  5895. $pairs = $this->getPairs($handler, $operation, $tableName);
  5896. if ($i == 0) {
  5897. if (in_array($operation, ['create', 'update', 'increment'])) {
  5898. $this->handleRecord($request, $operation, $pairs);
  5899. }
  5900. }
  5901. $condition = $this->getCondition($tableName, $pairs);
  5902. VariableStore::set("multiTenancy.conditions.$tableName", $condition);
  5903. }
  5904. }
  5905. }
  5906. return $this->next->handle($request);
  5907. }
  5908. }
  5909. // file: src/Tqdev/PhpCrudApi/Middleware/PageLimitsMiddleware.php
  5910. class PageLimitsMiddleware extends Middleware
  5911. {
  5912. private $reflection;
  5913. public function __construct(Router $router, Responder $responder, array $properties, ReflectionService $reflection)
  5914. {
  5915. parent::__construct($router, $responder, $properties);
  5916. $this->reflection = $reflection;
  5917. $this->utils = new RequestUtils($reflection);
  5918. }
  5919. public function handle(Request $request): Response
  5920. {
  5921. $operation = $this->utils->getOperation($request);
  5922. if ($operation == 'list') {
  5923. $params = $this->utils->getParams($request);
  5924. $maxPage = (int) $this->getProperty('pages', '100');
  5925. if (isset($params['page']) && $params['page'] && $maxPage > 0) {
  5926. if (strpos($params['page'][0], ',') === false) {
  5927. $page = $params['page'][0];
  5928. } else {
  5929. list($page, $size) = explode(',', $params['page'][0], 2);
  5930. }
  5931. if ($page > $maxPage) {
  5932. return $this->responder->error(ErrorCode::PAGINATION_FORBIDDEN, '');
  5933. }
  5934. }
  5935. $maxSize = (int) $this->getProperty('records', '1000');
  5936. if (!isset($params['size']) || !$params['size'] && $maxSize > 0) {
  5937. $params['size'] = array($maxSize);
  5938. } else {
  5939. $params['size'] = array(min($params['size'][0], $maxSize));
  5940. }
  5941. $request->setParams($params);
  5942. }
  5943. return $this->next->handle($request);
  5944. }
  5945. }
  5946. // file: src/Tqdev/PhpCrudApi/Middleware/SanitationMiddleware.php
  5947. class SanitationMiddleware extends Middleware
  5948. {
  5949. private $reflection;
  5950. public function __construct(Router $router, Responder $responder, array $properties, ReflectionService $reflection)
  5951. {
  5952. parent::__construct($router, $responder, $properties);
  5953. $this->reflection = $reflection;
  5954. $this->utils = new RequestUtils($reflection);
  5955. }
  5956. private function callHandler($handler, $record, String $operation, ReflectedTable $table) /*: object */
  5957. {
  5958. $context = (array) $record;
  5959. $tableName = $table->getName();
  5960. foreach ($context as $columnName => &$value) {
  5961. if ($table->hasColumn($columnName)) {
  5962. $column = $table->getColumn($columnName);
  5963. $value = call_user_func($handler, $operation, $tableName, $column->serialize(), $value);
  5964. }
  5965. }
  5966. return (object) $context;
  5967. }
  5968. public function handle(ServerRequestInterface $request): Response
  5969. {
  5970. $operation = $this->utils->getOperation($request);
  5971. if (in_array($operation, ['create', 'update', 'increment'])) {
  5972. $tableName = $this->utils->getPathSegment($request, 2);
  5973. if ($this->reflection->hasTable($tableName)) {
  5974. $record = $request->getBody();
  5975. if ($record !== null) {
  5976. $handler = $this->getProperty('handler', '');
  5977. if ($handler !== '') {
  5978. $table = $this->reflection->getTable($tableName);
  5979. if (is_array($record)) {
  5980. foreach ($record as &$r) {
  5981. $r = $this->callHandler($handler, $r, $operation, $table);
  5982. }
  5983. } else {
  5984. $record = $this->callHandler($handler, $record, $operation, $table);
  5985. }
  5986. $request->setBody($record);
  5987. }
  5988. }
  5989. }
  5990. }
  5991. return $this->next->handle($request);
  5992. }
  5993. }
  5994. // file: src/Tqdev/PhpCrudApi/Middleware/ValidationMiddleware.php
  5995. class ValidationMiddleware extends Middleware
  5996. {
  5997. private $reflection;
  5998. public function __construct(Router $router, Responder $responder, array $properties, ReflectionService $reflection)
  5999. {
  6000. parent::__construct($router, $responder, $properties);
  6001. $this->reflection = $reflection;
  6002. $this->utils = new RequestUtils($reflection);
  6003. }
  6004. private function callHandler($handler, $record, String $operation, ReflectedTable $table) /*: Response?*/
  6005. {
  6006. $context = (array) $record;
  6007. $details = array();
  6008. $tableName = $table->getName();
  6009. foreach ($context as $columnName => $value) {
  6010. if ($table->hasColumn($columnName)) {
  6011. $column = $table->getColumn($columnName);
  6012. $valid = call_user_func($handler, $operation, $tableName, $column->serialize(), $value, $context);
  6013. if ($valid !== true && $valid !== '') {
  6014. $details[$columnName] = $valid;
  6015. }
  6016. }
  6017. }
  6018. if (count($details) > 0) {
  6019. return $this->responder->error(ErrorCode::INPUT_VALIDATION_FAILED, $tableName, $details);
  6020. }
  6021. return null;
  6022. }
  6023. public function handle(ServerRequestInterface $request): Response
  6024. {
  6025. $operation = $this->utils->getOperation($request);
  6026. if (in_array($operation, ['create', 'update', 'increment'])) {
  6027. $tableName = $this->utils->getPathSegment($request, 2);
  6028. if ($this->reflection->hasTable($tableName)) {
  6029. $record = $request->getBody();
  6030. if ($record !== null) {
  6031. $handler = $this->getProperty('handler', '');
  6032. if ($handler !== '') {
  6033. $table = $this->reflection->getTable($tableName);
  6034. if (is_array($record)) {
  6035. foreach ($record as $r) {
  6036. $response = $this->callHandler($handler, $r, $operation, $table);
  6037. if ($response !== null) {
  6038. return $response;
  6039. }
  6040. }
  6041. } else {
  6042. $response = $this->callHandler($handler, $record, $operation, $table);
  6043. if ($response !== null) {
  6044. return $response;
  6045. }
  6046. }
  6047. }
  6048. }
  6049. }
  6050. }
  6051. return $this->next->handle($request);
  6052. }
  6053. }
  6054. // file: src/Tqdev/PhpCrudApi/Middleware/XsrfMiddleware.php
  6055. class XsrfMiddleware extends Middleware
  6056. {
  6057. private function getToken(): String
  6058. {
  6059. $cookieName = $this->getProperty('cookieName', 'XSRF-TOKEN');
  6060. if (isset($_COOKIE[$cookieName])) {
  6061. $token = $_COOKIE[$cookieName];
  6062. } else {
  6063. $secure = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on';
  6064. $token = bin2hex(random_bytes(8));
  6065. if (!headers_sent()) {
  6066. setcookie($cookieName, $token, 0, '', '', $secure);
  6067. }
  6068. }
  6069. return $token;
  6070. }
  6071. public function handle(ServerRequestInterface $request): Response
  6072. {
  6073. $token = $this->getToken();
  6074. $method = $request->getMethod();
  6075. $excludeMethods = $this->getArrayProperty('excludeMethods', 'OPTIONS,GET');
  6076. if (!in_array($method, $excludeMethods)) {
  6077. $headerName = $this->getProperty('headerName', 'X-XSRF-TOKEN');
  6078. if ($token != $request->getHeader($headerName)) {
  6079. return $this->responder->error(ErrorCode::BAD_OR_MISSING_XSRF_TOKEN, '');
  6080. }
  6081. }
  6082. return $this->next->handle($request);
  6083. }
  6084. }
  6085. // file: src/Tqdev/PhpCrudApi/OpenApi/OpenApiBuilder.php
  6086. class OpenApiBuilder
  6087. {
  6088. private $openapi;
  6089. private $reflection;
  6090. private $operations = [
  6091. 'list' => 'get',
  6092. 'create' => 'post',
  6093. 'read' => 'get',
  6094. 'update' => 'put',
  6095. 'delete' => 'delete',
  6096. 'increment' => 'patch',
  6097. ];
  6098. private $types = [
  6099. 'integer' => ['type' => 'integer', 'format' => 'int32'],
  6100. 'bigint' => ['type' => 'integer', 'format' => 'int64'],
  6101. 'varchar' => ['type' => 'string'],
  6102. 'clob' => ['type' => 'string'],
  6103. 'varbinary' => ['type' => 'string', 'format' => 'byte'],
  6104. 'blob' => ['type' => 'string', 'format' => 'byte'],
  6105. 'decimal' => ['type' => 'string'],
  6106. 'float' => ['type' => 'number', 'format' => 'float'],
  6107. 'double' => ['type' => 'number', 'format' => 'double'],
  6108. 'date' => ['type' => 'string', 'format' => 'date'],
  6109. 'time' => ['type' => 'string', 'format' => 'date-time'],
  6110. 'timestamp' => ['type' => 'string', 'format' => 'date-time'],
  6111. 'geometry' => ['type' => 'string'],
  6112. 'boolean' => ['type' => 'boolean'],
  6113. ];
  6114. public function __construct(ReflectionService $reflection, $base)
  6115. {
  6116. $this->reflection = $reflection;
  6117. $this->openapi = new OpenApiDefinition($base);
  6118. }
  6119. private function getServerUrl(): String
  6120. {
  6121. $protocol = @$_SERVER['HTTP_X_FORWARDED_PROTO'] ?: @$_SERVER['REQUEST_SCHEME'] ?: ((isset($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] == "on") ? "https" : "http");
  6122. $port = @intval($_SERVER['HTTP_X_FORWARDED_PORT']) ?: @intval($_SERVER["SERVER_PORT"]) ?: (($protocol === 'https') ? 443 : 80);
  6123. $host = @explode(":", $_SERVER['HTTP_HOST'])[0] ?: @$_SERVER['SERVER_NAME'] ?: @$_SERVER['SERVER_ADDR'];
  6124. $port = ($protocol === 'https' && $port === 443) || ($protocol === 'http' && $port === 80) ? '' : ':' . $port;
  6125. $path = @trim(substr($_SERVER['REQUEST_URI'], 0, strpos($_SERVER['REQUEST_URI'], '/openapi')), '/');
  6126. return sprintf('%s://%s%s/%s', $protocol, $host, $port, $path);
  6127. }
  6128. private function getAllTableReferences(): array
  6129. {
  6130. $tableReferences = array();
  6131. foreach ($this->reflection->getTableNames() as $tableName) {
  6132. $table = $this->reflection->getTable($tableName);
  6133. foreach ($table->getColumnNames() as $columnName) {
  6134. $column = $table->getColumn($columnName);
  6135. $referencedTableName = $column->getFk();
  6136. if ($referencedTableName) {
  6137. if (!isset($tableReferences[$referencedTableName])) {
  6138. $tableReferences[$referencedTableName] = array();
  6139. }
  6140. $tableReferences[$referencedTableName][] = "$tableName.$columnName";
  6141. }
  6142. }
  6143. }
  6144. return $tableReferences;
  6145. }
  6146. public function build(): OpenApiDefinition
  6147. {
  6148. $this->openapi->set("openapi", "3.0.0");
  6149. if (!$this->openapi->has("servers") && isset($_SERVER['REQUEST_URI'])) {
  6150. $this->openapi->set("servers|0|url", $this->getServerUrl());
  6151. }
  6152. $tableNames = $this->reflection->getTableNames();
  6153. foreach ($tableNames as $tableName) {
  6154. $this->setPath($tableName);
  6155. }
  6156. $this->openapi->set("components|responses|pk_integer|description", "inserted primary key value (integer)");
  6157. $this->openapi->set("components|responses|pk_integer|content|application/json|schema|type", "integer");
  6158. $this->openapi->set("components|responses|pk_integer|content|application/json|schema|format", "int64");
  6159. $this->openapi->set("components|responses|pk_string|description", "inserted primary key value (string)");
  6160. $this->openapi->set("components|responses|pk_string|content|application/json|schema|type", "string");
  6161. $this->openapi->set("components|responses|pk_string|content|application/json|schema|format", "uuid");
  6162. $this->openapi->set("components|responses|rows_affected|description", "number of rows affected (integer)");
  6163. $this->openapi->set("components|responses|rows_affected|content|application/json|schema|type", "integer");
  6164. $this->openapi->set("components|responses|rows_affected|content|application/json|schema|format", "int64");
  6165. $tableReferences = $this->getAllTableReferences();
  6166. foreach ($tableNames as $tableName) {
  6167. $references = isset($tableReferences[$tableName]) ? $tableReferences[$tableName] : array();
  6168. $this->setComponentSchema($tableName, $references);
  6169. $this->setComponentResponse($tableName);
  6170. $this->setComponentRequestBody($tableName);
  6171. }
  6172. $this->setComponentParameters();
  6173. foreach ($tableNames as $index => $tableName) {
  6174. $this->setTag($index, $tableName);
  6175. }
  6176. return $this->openapi;
  6177. }
  6178. private function isOperationOnTableAllowed(String $operation, String $tableName): bool
  6179. {
  6180. $tableHandler = VariableStore::get('authorization.tableHandler');
  6181. if (!$tableHandler) {
  6182. return true;
  6183. }
  6184. return (bool) call_user_func($tableHandler, $operation, $tableName);
  6185. }
  6186. private function isOperationOnColumnAllowed(String $operation, String $tableName, String $columnName): bool
  6187. {
  6188. $columnHandler = VariableStore::get('authorization.columnHandler');
  6189. if (!$columnHandler) {
  6190. return true;
  6191. }
  6192. return (bool) call_user_func($columnHandler, $operation, $tableName, $columnName);
  6193. }
  6194. private function setPath(String $tableName) /*: void*/
  6195. {
  6196. $table = $this->reflection->getTable($tableName);
  6197. $type = $table->getType();
  6198. $pk = $table->getPk();
  6199. $pkName = $pk ? $pk->getName() : '';
  6200. foreach ($this->operations as $operation => $method) {
  6201. if (!$pkName && $operation != 'list') {
  6202. continue;
  6203. }
  6204. if ($type != 'table' && $operation != 'list') {
  6205. continue;
  6206. }
  6207. if (!$this->isOperationOnTableAllowed($operation, $tableName)) {
  6208. continue;
  6209. }
  6210. $parameters = [];
  6211. if (in_array($operation, ['list', 'create'])) {
  6212. $path = sprintf('/records/%s', $tableName);
  6213. if ($operation == 'list') {
  6214. $parameters = ['filter', 'include', 'exclude', 'order', 'size', 'page', 'join'];
  6215. }
  6216. } else {
  6217. $path = sprintf('/records/%s/{%s}', $tableName, $pkName);
  6218. if ($operation == 'read') {
  6219. $parameters = ['pk', 'include', 'exclude', 'join'];
  6220. } else {
  6221. $parameters = ['pk'];
  6222. }
  6223. }
  6224. foreach ($parameters as $p => $parameter) {
  6225. $this->openapi->set("paths|$path|$method|parameters|$p|\$ref", "#/components/parameters/$parameter");
  6226. }
  6227. if (in_array($operation, ['create', 'update', 'increment'])) {
  6228. $this->openapi->set("paths|$path|$method|requestBody|\$ref", "#/components/requestBodies/$operation-" . urlencode($tableName));
  6229. }
  6230. $this->openapi->set("paths|$path|$method|tags|0", "$tableName");
  6231. $this->openapi->set("paths|$path|$method|description", "$operation $tableName");
  6232. switch ($operation) {
  6233. case 'list':
  6234. $this->openapi->set("paths|$path|$method|responses|200|\$ref", "#/components/responses/$operation-" . urlencode($tableName));
  6235. break;
  6236. case 'create':
  6237. if ($pk->getType() == 'integer') {
  6238. $this->openapi->set("paths|$path|$method|responses|200|\$ref", "#/components/responses/pk_integer");
  6239. } else {
  6240. $this->openapi->set("paths|$path|$method|responses|200|\$ref", "#/components/responses/pk_string");
  6241. }
  6242. break;
  6243. case 'read':
  6244. $this->openapi->set("paths|$path|$method|responses|200|\$ref", "#/components/responses/$operation-" . urlencode($tableName));
  6245. break;
  6246. case 'update':
  6247. case 'delete':
  6248. case 'increment':
  6249. $this->openapi->set("paths|$path|$method|responses|200|\$ref", "#/components/responses/rows_affected");
  6250. break;
  6251. }
  6252. }
  6253. }
  6254. private function setComponentSchema(String $tableName, array $references) /*: void*/
  6255. {
  6256. $table = $this->reflection->getTable($tableName);
  6257. $type = $table->getType();
  6258. $pk = $table->getPk();
  6259. $pkName = $pk ? $pk->getName() : '';
  6260. foreach ($this->operations as $operation => $method) {
  6261. if (!$pkName && $operation != 'list') {
  6262. continue;
  6263. }
  6264. if ($type != 'table' && $operation != 'list') {
  6265. continue;
  6266. }
  6267. if ($operation == 'delete') {
  6268. continue;
  6269. }
  6270. if (!$this->isOperationOnTableAllowed($operation, $tableName)) {
  6271. continue;
  6272. }
  6273. if ($operation == 'list') {
  6274. $this->openapi->set("components|schemas|$operation-$tableName|type", "object");
  6275. $this->openapi->set("components|schemas|$operation-$tableName|properties|results|type", "integer");
  6276. $this->openapi->set("components|schemas|$operation-$tableName|properties|results|format", "int64");
  6277. $this->openapi->set("components|schemas|$operation-$tableName|properties|records|type", "array");
  6278. $prefix = "components|schemas|$operation-$tableName|properties|records|items";
  6279. } else {
  6280. $prefix = "components|schemas|$operation-$tableName";
  6281. }
  6282. $this->openapi->set("$prefix|type", "object");
  6283. foreach ($table->getColumnNames() as $columnName) {
  6284. if (!$this->isOperationOnColumnAllowed($operation, $tableName, $columnName)) {
  6285. continue;
  6286. }
  6287. $column = $table->getColumn($columnName);
  6288. $properties = $this->types[$column->getType()];
  6289. foreach ($properties as $key => $value) {
  6290. $this->openapi->set("$prefix|properties|$columnName|$key", $value);
  6291. }
  6292. if ($column->getPk()) {
  6293. $this->openapi->set("$prefix|properties|$columnName|x-primary-key", true);
  6294. $this->openapi->set("$prefix|properties|$columnName|x-referenced", $references);
  6295. }
  6296. $fk = $column->getFk();
  6297. if ($fk) {
  6298. $this->openapi->set("$prefix|properties|$columnName|x-references", $fk);
  6299. }
  6300. }
  6301. }
  6302. }
  6303. private function setComponentResponse(String $tableName) /*: void*/
  6304. {
  6305. $table = $this->reflection->getTable($tableName);
  6306. $type = $table->getType();
  6307. $pk = $table->getPk();
  6308. $pkName = $pk ? $pk->getName() : '';
  6309. foreach (['list', 'read'] as $operation) {
  6310. if (!$pkName && $operation != 'list') {
  6311. continue;
  6312. }
  6313. if ($type != 'table' && $operation != 'list') {
  6314. continue;
  6315. }
  6316. if (!$this->isOperationOnTableAllowed($operation, $tableName)) {
  6317. continue;
  6318. }
  6319. if ($operation == 'list') {
  6320. $this->openapi->set("components|responses|$operation-$tableName|description", "list of $tableName records");
  6321. } else {
  6322. $this->openapi->set("components|responses|$operation-$tableName|description", "single $tableName record");
  6323. }
  6324. $this->openapi->set("components|responses|$operation-$tableName|content|application/json|schema|\$ref", "#/components/schemas/$operation-" . urlencode($tableName));
  6325. }
  6326. }
  6327. private function setComponentRequestBody(String $tableName) /*: void*/
  6328. {
  6329. $table = $this->reflection->getTable($tableName);
  6330. $type = $table->getType();
  6331. $pk = $table->getPk();
  6332. $pkName = $pk ? $pk->getName() : '';
  6333. if ($pkName && $type == 'table') {
  6334. foreach (['create', 'update', 'increment'] as $operation) {
  6335. if (!$this->isOperationOnTableAllowed($operation, $tableName)) {
  6336. continue;
  6337. }
  6338. $this->openapi->set("components|requestBodies|$operation-$tableName|description", "single $tableName record");
  6339. $this->openapi->set("components|requestBodies|$operation-$tableName|content|application/json|schema|\$ref", "#/components/schemas/$operation-" . urlencode($tableName));
  6340. }
  6341. }
  6342. }
  6343. private function setComponentParameters() /*: void*/
  6344. {
  6345. $this->openapi->set("components|parameters|pk|name", "id");
  6346. $this->openapi->set("components|parameters|pk|in", "path");
  6347. $this->openapi->set("components|parameters|pk|schema|type", "string");
  6348. $this->openapi->set("components|parameters|pk|description", "primary key value");
  6349. $this->openapi->set("components|parameters|pk|required", true);
  6350. $this->openapi->set("components|parameters|filter|name", "filter");
  6351. $this->openapi->set("components|parameters|filter|in", "query");
  6352. $this->openapi->set("components|parameters|filter|schema|type", "array");
  6353. $this->openapi->set("components|parameters|filter|schema|items|type", "string");
  6354. $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");
  6355. $this->openapi->set("components|parameters|filter|required", false);
  6356. $this->openapi->set("components|parameters|include|name", "include");
  6357. $this->openapi->set("components|parameters|include|in", "query");
  6358. $this->openapi->set("components|parameters|include|schema|type", "string");
  6359. $this->openapi->set("components|parameters|include|description", "Columns you want to include in the output (comma separated). Example: posts.*,categories.name");
  6360. $this->openapi->set("components|parameters|include|required", false);
  6361. $this->openapi->set("components|parameters|exclude|name", "exclude");
  6362. $this->openapi->set("components|parameters|exclude|in", "query");
  6363. $this->openapi->set("components|parameters|exclude|schema|type", "string");
  6364. $this->openapi->set("components|parameters|exclude|description", "Columns you want to exclude from the output (comma separated). Example: posts.content");
  6365. $this->openapi->set("components|parameters|exclude|required", false);
  6366. $this->openapi->set("components|parameters|order|name", "order");
  6367. $this->openapi->set("components|parameters|order|in", "query");
  6368. $this->openapi->set("components|parameters|order|schema|type", "array");
  6369. $this->openapi->set("components|parameters|order|schema|items|type", "string");
  6370. $this->openapi->set("components|parameters|order|description", "Column you want to sort on and the sort direction (comma separated). Example: id,desc");
  6371. $this->openapi->set("components|parameters|order|required", false);
  6372. $this->openapi->set("components|parameters|size|name", "size");
  6373. $this->openapi->set("components|parameters|size|in", "query");
  6374. $this->openapi->set("components|parameters|size|schema|type", "string");
  6375. $this->openapi->set("components|parameters|size|description", "Maximum number of results (for top lists). Example: 10");
  6376. $this->openapi->set("components|parameters|size|required", false);
  6377. $this->openapi->set("components|parameters|page|name", "page");
  6378. $this->openapi->set("components|parameters|page|in", "query");
  6379. $this->openapi->set("components|parameters|page|schema|type", "string");
  6380. $this->openapi->set("components|parameters|page|description", "Page number and page size (comma separated). Example: 1,10");
  6381. $this->openapi->set("components|parameters|page|required", false);
  6382. $this->openapi->set("components|parameters|join|name", "join");
  6383. $this->openapi->set("components|parameters|join|in", "query");
  6384. $this->openapi->set("components|parameters|join|schema|type", "array");
  6385. $this->openapi->set("components|parameters|join|schema|items|type", "string");
  6386. $this->openapi->set("components|parameters|join|description", "Paths (comma separated) to related entities that you want to include. Example: comments,users");
  6387. $this->openapi->set("components|parameters|join|required", false);
  6388. }
  6389. private function setTag(int $index, String $tableName) /*: void*/
  6390. {
  6391. $this->openapi->set("tags|$index|name", "$tableName");
  6392. $this->openapi->set("tags|$index|description", "$tableName operations");
  6393. }
  6394. }
  6395. // file: src/Tqdev/PhpCrudApi/OpenApi/OpenApiDefinition.php
  6396. class OpenApiDefinition implements \JsonSerializable
  6397. {
  6398. private $root;
  6399. public function __construct($base)
  6400. {
  6401. $this->root = $base;
  6402. }
  6403. public function set(String $path, $value) /*: void*/
  6404. {
  6405. $parts = explode('|', trim($path, '|'));
  6406. $current = &$this->root;
  6407. while (count($parts) > 0) {
  6408. $part = array_shift($parts);
  6409. if (!isset($current[$part])) {
  6410. $current[$part] = [];
  6411. }
  6412. $current = &$current[$part];
  6413. }
  6414. $current = $value;
  6415. }
  6416. public function has(String $path): bool
  6417. {
  6418. $parts = explode('|', trim($path, '|'));
  6419. $current = &$this->root;
  6420. while (count($parts) > 0) {
  6421. $part = array_shift($parts);
  6422. if (!isset($current[$part])) {
  6423. return false;
  6424. }
  6425. $current = &$current[$part];
  6426. }
  6427. return true;
  6428. }
  6429. public function jsonSerialize()
  6430. {
  6431. return $this->root;
  6432. }
  6433. }
  6434. // file: src/Tqdev/PhpCrudApi/OpenApi/OpenApiService.php
  6435. class OpenApiService
  6436. {
  6437. private $builder;
  6438. public function __construct(ReflectionService $reflection, array $base)
  6439. {
  6440. $this->builder = new OpenApiBuilder($reflection, $base);
  6441. }
  6442. public function get(): OpenApiDefinition
  6443. {
  6444. return $this->builder->build();
  6445. }
  6446. }
  6447. // file: src/Tqdev/PhpCrudApi/Record/Condition/AndCondition.php
  6448. class AndCondition extends Condition
  6449. {
  6450. private $conditions;
  6451. public function __construct(Condition $condition1, Condition $condition2)
  6452. {
  6453. $this->conditions = [$condition1, $condition2];
  6454. }
  6455. public function _and(Condition $condition): Condition
  6456. {
  6457. if ($condition instanceof NoCondition) {
  6458. return $this;
  6459. }
  6460. $this->conditions[] = $condition;
  6461. return $this;
  6462. }
  6463. public function getConditions(): array
  6464. {
  6465. return $this->conditions;
  6466. }
  6467. public static function fromArray(array $conditions): Condition
  6468. {
  6469. $condition = new NoCondition();
  6470. foreach ($conditions as $c) {
  6471. $condition = $condition->_and($c);
  6472. }
  6473. return $condition;
  6474. }
  6475. }
  6476. // file: src/Tqdev/PhpCrudApi/Record/Condition/ColumnCondition.php
  6477. class ColumnCondition extends Condition
  6478. {
  6479. private $column;
  6480. private $operator;
  6481. private $value;
  6482. public function __construct(ReflectedColumn $column, String $operator, String $value)
  6483. {
  6484. $this->column = $column;
  6485. $this->operator = $operator;
  6486. $this->value = $value;
  6487. }
  6488. public function getColumn(): ReflectedColumn
  6489. {
  6490. return $this->column;
  6491. }
  6492. public function getOperator(): String
  6493. {
  6494. return $this->operator;
  6495. }
  6496. public function getValue(): String
  6497. {
  6498. return $this->value;
  6499. }
  6500. }
  6501. // file: src/Tqdev/PhpCrudApi/Record/Condition/Condition.php
  6502. abstract class Condition
  6503. {
  6504. public function _and(Condition $condition): Condition
  6505. {
  6506. if ($condition instanceof NoCondition) {
  6507. return $this;
  6508. }
  6509. return new AndCondition($this, $condition);
  6510. }
  6511. public function _or(Condition $condition): Condition
  6512. {
  6513. if ($condition instanceof NoCondition) {
  6514. return $this;
  6515. }
  6516. return new OrCondition($this, $condition);
  6517. }
  6518. public function _not(): Condition
  6519. {
  6520. return new NotCondition($this);
  6521. }
  6522. public static function fromString(ReflectedTable $table, String $value): Condition
  6523. {
  6524. $condition = new NoCondition();
  6525. $parts = explode(',', $value, 3);
  6526. if (count($parts) < 2) {
  6527. return $condition;
  6528. }
  6529. if (count($parts) < 3) {
  6530. $parts[2] = '';
  6531. }
  6532. $field = $table->getColumn($parts[0]);
  6533. $command = $parts[1];
  6534. $negate = false;
  6535. $spatial = false;
  6536. if (strlen($command) > 2) {
  6537. if (substr($command, 0, 1) == 'n') {
  6538. $negate = true;
  6539. $command = substr($command, 1);
  6540. }
  6541. if (substr($command, 0, 1) == 's') {
  6542. $spatial = true;
  6543. $command = substr($command, 1);
  6544. }
  6545. }
  6546. if ($spatial) {
  6547. if (in_array($command, ['co', 'cr', 'di', 'eq', 'in', 'ov', 'to', 'wi', 'ic', 'is', 'iv'])) {
  6548. $condition = new SpatialCondition($field, $command, $parts[2]);
  6549. }
  6550. } else {
  6551. if (in_array($command, ['cs', 'sw', 'ew', 'eq', 'lt', 'le', 'ge', 'gt', 'bt', 'in', 'is'])) {
  6552. $condition = new ColumnCondition($field, $command, $parts[2]);
  6553. }
  6554. }
  6555. if ($negate) {
  6556. $condition = $condition->_not();
  6557. }
  6558. return $condition;
  6559. }
  6560. }
  6561. // file: src/Tqdev/PhpCrudApi/Record/Condition/NoCondition.php
  6562. class NoCondition extends Condition
  6563. {
  6564. public function _and(Condition $condition): Condition
  6565. {
  6566. return $condition;
  6567. }
  6568. public function _or(Condition $condition): Condition
  6569. {
  6570. return $condition;
  6571. }
  6572. public function _not(): Condition
  6573. {
  6574. return $this;
  6575. }
  6576. }
  6577. // file: src/Tqdev/PhpCrudApi/Record/Condition/NotCondition.php
  6578. class NotCondition extends Condition
  6579. {
  6580. private $condition;
  6581. public function __construct(Condition $condition)
  6582. {
  6583. $this->condition = $condition;
  6584. }
  6585. public function getCondition(): Condition
  6586. {
  6587. return $this->condition;
  6588. }
  6589. }
  6590. // file: src/Tqdev/PhpCrudApi/Record/Condition/OrCondition.php
  6591. class OrCondition extends Condition
  6592. {
  6593. private $conditions;
  6594. public function __construct(Condition $condition1, Condition $condition2)
  6595. {
  6596. $this->conditions = [$condition1, $condition2];
  6597. }
  6598. public function _or(Condition $condition): Condition
  6599. {
  6600. if ($condition instanceof NoCondition) {
  6601. return $this;
  6602. }
  6603. $this->conditions[] = $condition;
  6604. return $this;
  6605. }
  6606. public function getConditions(): array
  6607. {
  6608. return $this->conditions;
  6609. }
  6610. public static function fromArray(array $conditions): Condition
  6611. {
  6612. $condition = new NoCondition();
  6613. foreach ($conditions as $c) {
  6614. $condition = $condition->_or($c);
  6615. }
  6616. return $condition;
  6617. }
  6618. }
  6619. // file: src/Tqdev/PhpCrudApi/Record/Condition/SpatialCondition.php
  6620. class SpatialCondition extends ColumnCondition
  6621. {
  6622. }
  6623. // file: src/Tqdev/PhpCrudApi/Record/Document/ErrorDocument.php
  6624. class ErrorDocument implements \JsonSerializable
  6625. {
  6626. public $code;
  6627. public $message;
  6628. public $details;
  6629. public function __construct(ErrorCode $errorCode, String $argument, $details)
  6630. {
  6631. $this->code = $errorCode->getCode();
  6632. $this->message = $errorCode->getMessage($argument);
  6633. $this->details = $details;
  6634. }
  6635. public function getCode(): int
  6636. {
  6637. return $this->code;
  6638. }
  6639. public function getMessage(): String
  6640. {
  6641. return $this->message;
  6642. }
  6643. public function serialize()
  6644. {
  6645. return [
  6646. 'code' => $this->code,
  6647. 'message' => $this->message,
  6648. 'details' => $this->details,
  6649. ];
  6650. }
  6651. public function jsonSerialize()
  6652. {
  6653. return array_filter($this->serialize());
  6654. }
  6655. }
  6656. // file: src/Tqdev/PhpCrudApi/Record/Document/ListDocument.php
  6657. class ListDocument implements \JsonSerializable
  6658. {
  6659. private $records;
  6660. private $results;
  6661. public function __construct(array $records, int $results)
  6662. {
  6663. $this->records = $records;
  6664. $this->results = $results;
  6665. }
  6666. public function getRecords(): array
  6667. {
  6668. return $this->records;
  6669. }
  6670. public function getResults(): int
  6671. {
  6672. return $this->results;
  6673. }
  6674. public function serialize()
  6675. {
  6676. return [
  6677. 'records' => $this->records,
  6678. 'results' => $this->results,
  6679. ];
  6680. }
  6681. public function jsonSerialize()
  6682. {
  6683. return array_filter($this->serialize(), function ($v) {
  6684. return $v !== 0;
  6685. });
  6686. }
  6687. }
  6688. // file: src/Tqdev/PhpCrudApi/Record/ColumnIncluder.php
  6689. class ColumnIncluder
  6690. {
  6691. private function isMandatory(String $tableName, String $columnName, array $params): bool
  6692. {
  6693. return isset($params['mandatory']) && in_array($tableName . "." . $columnName, $params['mandatory']);
  6694. }
  6695. private function select(String $tableName, bool $primaryTable, array $params, String $paramName,
  6696. array $columnNames, bool $include): array{
  6697. if (!isset($params[$paramName])) {
  6698. return $columnNames;
  6699. }
  6700. $columns = array();
  6701. foreach (explode(',', $params[$paramName][0]) as $columnName) {
  6702. $columns[$columnName] = true;
  6703. }
  6704. $result = array();
  6705. foreach ($columnNames as $columnName) {
  6706. $match = isset($columns['*.*']);
  6707. if (!$match) {
  6708. $match = isset($columns[$tableName . '.*']) || isset($columns[$tableName . '.' . $columnName]);
  6709. }
  6710. if ($primaryTable && !$match) {
  6711. $match = isset($columns['*']) || isset($columns[$columnName]);
  6712. }
  6713. if ($match) {
  6714. if ($include || $this->isMandatory($tableName, $columnName, $params)) {
  6715. $result[] = $columnName;
  6716. }
  6717. } else {
  6718. if (!$include || $this->isMandatory($tableName, $columnName, $params)) {
  6719. $result[] = $columnName;
  6720. }
  6721. }
  6722. }
  6723. return $result;
  6724. }
  6725. public function getNames(ReflectedTable $table, bool $primaryTable, array $params): array
  6726. {
  6727. $tableName = $table->getName();
  6728. $results = $table->getColumnNames();
  6729. $results = $this->select($tableName, $primaryTable, $params, 'include', $results, true);
  6730. $results = $this->select($tableName, $primaryTable, $params, 'exclude', $results, false);
  6731. return $results;
  6732. }
  6733. public function getValues(ReflectedTable $table, bool $primaryTable, /* object */ $record, array $params): array
  6734. {
  6735. $results = array();
  6736. $columnNames = $this->getNames($table, $primaryTable, $params);
  6737. foreach ($columnNames as $columnName) {
  6738. if (property_exists($record, $columnName)) {
  6739. $results[$columnName] = $record->$columnName;
  6740. }
  6741. }
  6742. return $results;
  6743. }
  6744. }
  6745. // file: src/Tqdev/PhpCrudApi/Record/ErrorCode.php
  6746. class ErrorCode
  6747. {
  6748. private $code;
  6749. private $message;
  6750. private $status;
  6751. const ERROR_NOT_FOUND = 9999;
  6752. const ROUTE_NOT_FOUND = 1000;
  6753. const TABLE_NOT_FOUND = 1001;
  6754. const ARGUMENT_COUNT_MISMATCH = 1002;
  6755. const RECORD_NOT_FOUND = 1003;
  6756. const ORIGIN_FORBIDDEN = 1004;
  6757. const COLUMN_NOT_FOUND = 1005;
  6758. const TABLE_ALREADY_EXISTS = 1006;
  6759. const COLUMN_ALREADY_EXISTS = 1007;
  6760. const HTTP_MESSAGE_NOT_READABLE = 1008;
  6761. const DUPLICATE_KEY_EXCEPTION = 1009;
  6762. const DATA_INTEGRITY_VIOLATION = 1010;
  6763. const AUTHENTICATION_REQUIRED = 1011;
  6764. const AUTHENTICATION_FAILED = 1012;
  6765. const INPUT_VALIDATION_FAILED = 1013;
  6766. const OPERATION_FORBIDDEN = 1014;
  6767. const OPERATION_NOT_SUPPORTED = 1015;
  6768. const TEMPORARY_OR_PERMANENTLY_BLOCKED = 1016;
  6769. const BAD_OR_MISSING_XSRF_TOKEN = 1017;
  6770. const ONLY_AJAX_REQUESTS_ALLOWED = 1018;
  6771. const PAGINATION_FORBIDDEN = 1019;
  6772. private $values = [
  6773. 9999 => ["%s", Response::INTERNAL_SERVER_ERROR],
  6774. 1000 => ["Route '%s' not found", Response::NOT_FOUND],
  6775. 1001 => ["Table '%s' not found", Response::NOT_FOUND],
  6776. 1002 => ["Argument count mismatch in '%s'", Response::UNPROCESSABLE_ENTITY],
  6777. 1003 => ["Record '%s' not found", Response::NOT_FOUND],
  6778. 1004 => ["Origin '%s' is forbidden", Response::FORBIDDEN],
  6779. 1005 => ["Column '%s' not found", Response::NOT_FOUND],
  6780. 1006 => ["Table '%s' already exists", Response::CONFLICT],
  6781. 1007 => ["Column '%s' already exists", Response::CONFLICT],
  6782. 1008 => ["Cannot read HTTP message", Response::UNPROCESSABLE_ENTITY],
  6783. 1009 => ["Duplicate key exception", Response::CONFLICT],
  6784. 1010 => ["Data integrity violation", Response::CONFLICT],
  6785. 1011 => ["Authentication required", Response::UNAUTHORIZED],
  6786. 1012 => ["Authentication failed for '%s'", Response::FORBIDDEN],
  6787. 1013 => ["Input validation failed for '%s'", Response::UNPROCESSABLE_ENTITY],
  6788. 1014 => ["Operation forbidden", Response::FORBIDDEN],
  6789. 1015 => ["Operation '%s' not supported", Response::METHOD_NOT_ALLOWED],
  6790. 1016 => ["Temporary or permanently blocked", Response::FORBIDDEN],
  6791. 1017 => ["Bad or missing XSRF token", Response::FORBIDDEN],
  6792. 1018 => ["Only AJAX requests allowed for '%s'", Response::FORBIDDEN],
  6793. 1019 => ["Pagination forbidden", Response::FORBIDDEN],
  6794. ];
  6795. public function __construct(int $code)
  6796. {
  6797. if (!isset($this->values[$code])) {
  6798. $code = 9999;
  6799. }
  6800. $this->code = $code;
  6801. $this->message = $this->values[$code][0];
  6802. $this->status = $this->values[$code][1];
  6803. }
  6804. public function getCode(): int
  6805. {
  6806. return $this->code;
  6807. }
  6808. public function getMessage(String $argument): String
  6809. {
  6810. return sprintf($this->message, $argument);
  6811. }
  6812. public function getStatus(): int
  6813. {
  6814. return $this->status;
  6815. }
  6816. }
  6817. // file: src/Tqdev/PhpCrudApi/Record/FilterInfo.php
  6818. class FilterInfo
  6819. {
  6820. private function addConditionFromFilterPath(PathTree $conditions, array $path, ReflectedTable $table, array $params)
  6821. {
  6822. $key = 'filter' . implode('', $path);
  6823. if (isset($params[$key])) {
  6824. foreach ($params[$key] as $filter) {
  6825. $condition = Condition::fromString($table, $filter);
  6826. if (($condition instanceof NoCondition) == false) {
  6827. $conditions->put($path, $condition);
  6828. }
  6829. }
  6830. }
  6831. }
  6832. private function getConditionsAsPathTree(ReflectedTable $table, array $params): PathTree
  6833. {
  6834. $conditions = new PathTree();
  6835. $this->addConditionFromFilterPath($conditions, [], $table, $params);
  6836. for ($n = ord('0'); $n <= ord('9'); $n++) {
  6837. $this->addConditionFromFilterPath($conditions, [chr($n)], $table, $params);
  6838. for ($l = ord('a'); $l <= ord('f'); $l++) {
  6839. $this->addConditionFromFilterPath($conditions, [chr($n), chr($l)], $table, $params);
  6840. }
  6841. }
  6842. return $conditions;
  6843. }
  6844. private function combinePathTreeOfConditions(PathTree $tree): Condition
  6845. {
  6846. $andConditions = $tree->getValues();
  6847. $and = AndCondition::fromArray($andConditions);
  6848. $orConditions = [];
  6849. foreach ($tree->getKeys() as $p) {
  6850. $orConditions[] = $this->combinePathTreeOfConditions($tree->get($p));
  6851. }
  6852. $or = OrCondition::fromArray($orConditions);
  6853. return $and->_and($or);
  6854. }
  6855. public function getCombinedConditions(ReflectedTable $table, array $params): Condition
  6856. {
  6857. return $this->combinePathTreeOfConditions($this->getConditionsAsPathTree($table, $params));
  6858. }
  6859. }
  6860. // file: src/Tqdev/PhpCrudApi/Record/HabtmValues.php
  6861. class HabtmValues
  6862. {
  6863. public $pkValues;
  6864. public $fkValues;
  6865. public function __construct(array $pkValues, array $fkValues)
  6866. {
  6867. $this->pkValues = $pkValues;
  6868. $this->fkValues = $fkValues;
  6869. }
  6870. }
  6871. // file: src/Tqdev/PhpCrudApi/Record/OrderingInfo.php
  6872. class OrderingInfo
  6873. {
  6874. public function getColumnOrdering(ReflectedTable $table, array $params): array
  6875. {
  6876. $fields = array();
  6877. if (isset($params['order'])) {
  6878. foreach ($params['order'] as $order) {
  6879. $parts = explode(',', $order, 3);
  6880. $columnName = $parts[0];
  6881. if (!$table->hasColumn($columnName)) {
  6882. continue;
  6883. }
  6884. $ascending = 'ASC';
  6885. if (count($parts) > 1) {
  6886. if (substr(strtoupper($parts[1]), 0, 4) == "DESC") {
  6887. $ascending = 'DESC';
  6888. }
  6889. }
  6890. $fields[] = [$columnName, $ascending];
  6891. }
  6892. }
  6893. if (count($fields) == 0) {
  6894. return $this->getDefaultColumnOrdering($table);
  6895. }
  6896. return $fields;
  6897. }
  6898. public function getDefaultColumnOrdering(ReflectedTable $table): array
  6899. {
  6900. $fields = array();
  6901. $pk = $table->getPk();
  6902. if ($pk) {
  6903. $fields[] = [$pk->getName(), 'ASC'];
  6904. } else {
  6905. foreach ($table->getColumnNames() as $columnName) {
  6906. $fields[] = [$columnName, 'ASC'];
  6907. }
  6908. }
  6909. return $fields;
  6910. }
  6911. }
  6912. // file: src/Tqdev/PhpCrudApi/Record/PaginationInfo.php
  6913. class PaginationInfo
  6914. {
  6915. public $DEFAULT_PAGE_SIZE = 20;
  6916. public function hasPage(array $params): bool
  6917. {
  6918. return isset($params['page']);
  6919. }
  6920. public function getPageOffset(array $params): int
  6921. {
  6922. $offset = 0;
  6923. $pageSize = $this->getPageSize($params);
  6924. if (isset($params['page'])) {
  6925. foreach ($params['page'] as $page) {
  6926. $parts = explode(',', $page, 2);
  6927. $page = intval($parts[0]) - 1;
  6928. $offset = $page * $pageSize;
  6929. }
  6930. }
  6931. return $offset;
  6932. }
  6933. private function getPageSize(array $params): int
  6934. {
  6935. $pageSize = $this->DEFAULT_PAGE_SIZE;
  6936. if (isset($params['page'])) {
  6937. foreach ($params['page'] as $page) {
  6938. $parts = explode(',', $page, 2);
  6939. if (count($parts) > 1) {
  6940. $pageSize = intval($parts[1]);
  6941. }
  6942. }
  6943. }
  6944. return $pageSize;
  6945. }
  6946. public function getResultSize(array $params): int
  6947. {
  6948. $numberOfRows = -1;
  6949. if (isset($params['size'])) {
  6950. foreach ($params['size'] as $size) {
  6951. $numberOfRows = intval($size);
  6952. }
  6953. }
  6954. return $numberOfRows;
  6955. }
  6956. public function getPageLimit(array $params): int
  6957. {
  6958. $pageLimit = -1;
  6959. if ($this->hasPage($params)) {
  6960. $pageLimit = $this->getPageSize($params);
  6961. }
  6962. $resultSize = $this->getResultSize($params);
  6963. if ($resultSize >= 0) {
  6964. if ($pageLimit >= 0) {
  6965. $pageLimit = min($pageLimit, $resultSize);
  6966. } else {
  6967. $pageLimit = $resultSize;
  6968. }
  6969. }
  6970. return $pageLimit;
  6971. }
  6972. }
  6973. // file: src/Tqdev/PhpCrudApi/Record/PathTree.php
  6974. class PathTree implements \JsonSerializable
  6975. {
  6976. const WILDCARD = '*';
  6977. private $tree;
  6978. public function __construct( /* object */&$tree = null)
  6979. {
  6980. if (!$tree) {
  6981. $tree = $this->newTree();
  6982. }
  6983. $this->tree = &$tree;
  6984. }
  6985. public function newTree()
  6986. {
  6987. return (object) ['values' => [], 'branches' => (object) []];
  6988. }
  6989. public function getKeys(): array
  6990. {
  6991. $branches = (array) $this->tree->branches;
  6992. return array_keys($branches);
  6993. }
  6994. public function getValues(): array
  6995. {
  6996. return $this->tree->values;
  6997. }
  6998. public function get(String $key): PathTree
  6999. {
  7000. if (!isset($this->tree->branches->$key)) {
  7001. return null;
  7002. }
  7003. return new PathTree($this->tree->branches->$key);
  7004. }
  7005. public function put(array $path, $value)
  7006. {
  7007. $tree = &$this->tree;
  7008. foreach ($path as $key) {
  7009. if (!isset($tree->branches->$key)) {
  7010. $tree->branches->$key = $this->newTree();
  7011. }
  7012. $tree = &$tree->branches->$key;
  7013. }
  7014. $tree->values[] = $value;
  7015. }
  7016. public function match(array $path): array
  7017. {
  7018. $star = self::WILDCARD;
  7019. $tree = &$this->tree;
  7020. foreach ($path as $key) {
  7021. if (isset($tree->branches->$key)) {
  7022. $tree = &$tree->branches->$key;
  7023. } else if (isset($tree->branches->$star)) {
  7024. $tree = &$tree->branches->$star;
  7025. } else {
  7026. return [];
  7027. }
  7028. }
  7029. return $tree->values;
  7030. }
  7031. public static function fromJson( /* object */$tree): PathTree
  7032. {
  7033. return new PathTree($tree);
  7034. }
  7035. public function jsonSerialize()
  7036. {
  7037. return $this->tree;
  7038. }
  7039. }
  7040. // file: src/Tqdev/PhpCrudApi/Record/RecordService.php
  7041. class RecordService
  7042. {
  7043. private $db;
  7044. private $reflection;
  7045. private $columns;
  7046. private $joiner;
  7047. private $filters;
  7048. private $ordering;
  7049. private $pagination;
  7050. public function __construct(GenericDB $db, ReflectionService $reflection)
  7051. {
  7052. $this->db = $db;
  7053. $this->reflection = $reflection;
  7054. $this->columns = new ColumnIncluder();
  7055. $this->joiner = new RelationJoiner($reflection, $this->columns);
  7056. $this->filters = new FilterInfo();
  7057. $this->ordering = new OrderingInfo();
  7058. $this->pagination = new PaginationInfo();
  7059. }
  7060. private function sanitizeRecord(String $tableName, /* object */ $record, String $id)
  7061. {
  7062. $keyset = array_keys((array) $record);
  7063. foreach ($keyset as $key) {
  7064. if (!$this->reflection->getTable($tableName)->hasColumn($key)) {
  7065. unset($record->$key);
  7066. }
  7067. }
  7068. if ($id != '') {
  7069. $pk = $this->reflection->getTable($tableName)->getPk();
  7070. foreach ($this->reflection->getTable($tableName)->getColumnNames() as $key) {
  7071. $field = $this->reflection->getTable($tableName)->getColumn($key);
  7072. if ($field->getName() == $pk->getName()) {
  7073. unset($record->$key);
  7074. }
  7075. }
  7076. }
  7077. }
  7078. public function hasTable(String $table): bool
  7079. {
  7080. return $this->reflection->hasTable($table);
  7081. }
  7082. public function getType(String $table): String
  7083. {
  7084. return $this->reflection->getType($table);
  7085. }
  7086. public function create(String $tableName, /* object */ $record, array $params)
  7087. {
  7088. $this->sanitizeRecord($tableName, $record, '');
  7089. $table = $this->reflection->getTable($tableName);
  7090. $columnValues = $this->columns->getValues($table, true, $record, $params);
  7091. return $this->db->createSingle($table, $columnValues);
  7092. }
  7093. public function read(String $tableName, String $id, array $params) /*: ?object*/
  7094. {
  7095. $table = $this->reflection->getTable($tableName);
  7096. $this->joiner->addMandatoryColumns($table, $params);
  7097. $columnNames = $this->columns->getNames($table, true, $params);
  7098. $record = $this->db->selectSingle($table, $columnNames, $id);
  7099. if ($record == null) {
  7100. return null;
  7101. }
  7102. $records = array($record);
  7103. $this->joiner->addJoins($table, $records, $params, $this->db);
  7104. return $records[0];
  7105. }
  7106. public function update(String $tableName, String $id, /* object */ $record, array $params)
  7107. {
  7108. $this->sanitizeRecord($tableName, $record, $id);
  7109. $table = $this->reflection->getTable($tableName);
  7110. $columnValues = $this->columns->getValues($table, true, $record, $params);
  7111. return $this->db->updateSingle($table, $columnValues, $id);
  7112. }
  7113. public function delete(String $tableName, String $id, array $params)
  7114. {
  7115. $table = $this->reflection->getTable($tableName);
  7116. return $this->db->deleteSingle($table, $id);
  7117. }
  7118. public function increment(String $tableName, String $id, /* object */ $record, array $params)
  7119. {
  7120. $this->sanitizeRecord($tableName, $record, $id);
  7121. $table = $this->reflection->getTable($tableName);
  7122. $columnValues = $this->columns->getValues($table, true, $record, $params);
  7123. return $this->db->incrementSingle($table, $columnValues, $id);
  7124. }
  7125. public function _list(String $tableName, array $params): ListDocument
  7126. {
  7127. $table = $this->reflection->getTable($tableName);
  7128. $this->joiner->addMandatoryColumns($table, $params);
  7129. $columnNames = $this->columns->getNames($table, true, $params);
  7130. $condition = $this->filters->getCombinedConditions($table, $params);
  7131. $columnOrdering = $this->ordering->getColumnOrdering($table, $params);
  7132. if (!$this->pagination->hasPage($params)) {
  7133. $offset = 0;
  7134. $limit = $this->pagination->getPageLimit($params);
  7135. $count = 0;
  7136. } else {
  7137. $offset = $this->pagination->getPageOffset($params);
  7138. $limit = $this->pagination->getPageLimit($params);
  7139. $count = $this->db->selectCount($table, $condition);
  7140. }
  7141. $records = $this->db->selectAll($table, $columnNames, $condition, $columnOrdering, $offset, $limit);
  7142. $this->joiner->addJoins($table, $records, $params, $this->db);
  7143. return new ListDocument($records, $count);
  7144. }
  7145. }
  7146. // file: src/Tqdev/PhpCrudApi/Record/RelationJoiner.php
  7147. class RelationJoiner
  7148. {
  7149. private $reflection;
  7150. private $columns;
  7151. public function __construct(ReflectionService $reflection, ColumnIncluder $columns)
  7152. {
  7153. $this->reflection = $reflection;
  7154. $this->ordering = new OrderingInfo();
  7155. $this->columns = $columns;
  7156. }
  7157. public function addMandatoryColumns(ReflectedTable $table, array &$params) /*: void*/
  7158. {
  7159. if (!isset($params['join']) || !isset($params['include'])) {
  7160. return;
  7161. }
  7162. $params['mandatory'] = array();
  7163. foreach ($params['join'] as $tableNames) {
  7164. $t1 = $table;
  7165. foreach (explode(',', $tableNames) as $tableName) {
  7166. if (!$this->reflection->hasTable($tableName)) {
  7167. continue;
  7168. }
  7169. $t2 = $this->reflection->getTable($tableName);
  7170. $fks1 = $t1->getFksTo($t2->getName());
  7171. $t3 = $this->hasAndBelongsToMany($t1, $t2);
  7172. if ($t3 != null || count($fks1) > 0) {
  7173. $params['mandatory'][] = $t2->getName() . '.' . $t2->getPk()->getName();
  7174. }
  7175. foreach ($fks1 as $fk) {
  7176. $params['mandatory'][] = $t1->getName() . '.' . $fk->getName();
  7177. }
  7178. $fks2 = $t2->getFksTo($t1->getName());
  7179. if ($t3 != null || count($fks2) > 0) {
  7180. $params['mandatory'][] = $t1->getName() . '.' . $t1->getPk()->getName();
  7181. }
  7182. foreach ($fks2 as $fk) {
  7183. $params['mandatory'][] = $t2->getName() . '.' . $fk->getName();
  7184. }
  7185. $t1 = $t2;
  7186. }
  7187. }
  7188. }
  7189. private function getJoinsAsPathTree(array $params): PathTree
  7190. {
  7191. $joins = new PathTree();
  7192. if (isset($params['join'])) {
  7193. foreach ($params['join'] as $tableNames) {
  7194. $path = array();
  7195. foreach (explode(',', $tableNames) as $tableName) {
  7196. $t = $this->reflection->getTable($tableName);
  7197. if ($t != null) {
  7198. $path[] = $t->getName();
  7199. }
  7200. }
  7201. $joins->put($path, true);
  7202. }
  7203. }
  7204. return $joins;
  7205. }
  7206. public function addJoins(ReflectedTable $table, array &$records, array $params, GenericDB $db) /*: void*/
  7207. {
  7208. $joins = $this->getJoinsAsPathTree($params);
  7209. $this->addJoinsForTables($table, $joins, $records, $params, $db);
  7210. }
  7211. private function hasAndBelongsToMany(ReflectedTable $t1, ReflectedTable $t2) /*: ?ReflectedTable*/
  7212. {
  7213. foreach ($this->reflection->getTableNames() as $tableName) {
  7214. $t3 = $this->reflection->getTable($tableName);
  7215. if (count($t3->getFksTo($t1->getName())) > 0 && count($t3->getFksTo($t2->getName())) > 0) {
  7216. return $t3;
  7217. }
  7218. }
  7219. return null;
  7220. }
  7221. private function addJoinsForTables(ReflectedTable $t1, PathTree $joins, array &$records, array $params, GenericDB $db)
  7222. {
  7223. foreach ($joins->getKeys() as $t2Name) {
  7224. $t2 = $this->reflection->getTable($t2Name);
  7225. $belongsTo = count($t1->getFksTo($t2->getName())) > 0;
  7226. $hasMany = count($t2->getFksTo($t1->getName())) > 0;
  7227. if (!$belongsTo && !$hasMany) {
  7228. $t3 = $this->hasAndBelongsToMany($t1, $t2);
  7229. } else {
  7230. $t3 = null;
  7231. }
  7232. $hasAndBelongsToMany = ($t3 != null);
  7233. $newRecords = array();
  7234. $fkValues = null;
  7235. $pkValues = null;
  7236. $habtmValues = null;
  7237. if ($belongsTo) {
  7238. $fkValues = $this->getFkEmptyValues($t1, $t2, $records);
  7239. $this->addFkRecords($t2, $fkValues, $params, $db, $newRecords);
  7240. }
  7241. if ($hasMany) {
  7242. $pkValues = $this->getPkEmptyValues($t1, $records);
  7243. $this->addPkRecords($t1, $t2, $pkValues, $params, $db, $newRecords);
  7244. }
  7245. if ($hasAndBelongsToMany) {
  7246. $habtmValues = $this->getHabtmEmptyValues($t1, $t2, $t3, $db, $records);
  7247. $this->addFkRecords($t2, $habtmValues->fkValues, $params, $db, $newRecords);
  7248. }
  7249. $this->addJoinsForTables($t2, $joins->get($t2Name), $newRecords, $params, $db);
  7250. if ($fkValues != null) {
  7251. $this->fillFkValues($t2, $newRecords, $fkValues);
  7252. $this->setFkValues($t1, $t2, $records, $fkValues);
  7253. }
  7254. if ($pkValues != null) {
  7255. $this->fillPkValues($t1, $t2, $newRecords, $pkValues);
  7256. $this->setPkValues($t1, $t2, $records, $pkValues);
  7257. }
  7258. if ($habtmValues != null) {
  7259. $this->fillFkValues($t2, $newRecords, $habtmValues->fkValues);
  7260. $this->setHabtmValues($t1, $t2, $records, $habtmValues);
  7261. }
  7262. }
  7263. }
  7264. private function getFkEmptyValues(ReflectedTable $t1, ReflectedTable $t2, array $records): array
  7265. {
  7266. $fkValues = array();
  7267. $fks = $t1->getFksTo($t2->getName());
  7268. foreach ($fks as $fk) {
  7269. $fkName = $fk->getName();
  7270. foreach ($records as $record) {
  7271. if (isset($record[$fkName])) {
  7272. $fkValue = $record[$fkName];
  7273. $fkValues[$fkValue] = null;
  7274. }
  7275. }
  7276. }
  7277. return $fkValues;
  7278. }
  7279. private function addFkRecords(ReflectedTable $t2, array $fkValues, array $params, GenericDB $db, array &$records) /*: void*/
  7280. {
  7281. $columnNames = $this->columns->getNames($t2, false, $params);
  7282. $fkIds = array_keys($fkValues);
  7283. foreach ($db->selectMultiple($t2, $columnNames, $fkIds) as $record) {
  7284. $records[] = $record;
  7285. }
  7286. }
  7287. private function fillFkValues(ReflectedTable $t2, array $fkRecords, array &$fkValues) /*: void*/
  7288. {
  7289. $pkName = $t2->getPk()->getName();
  7290. foreach ($fkRecords as $fkRecord) {
  7291. $pkValue = $fkRecord[$pkName];
  7292. $fkValues[$pkValue] = $fkRecord;
  7293. }
  7294. }
  7295. private function setFkValues(ReflectedTable $t1, ReflectedTable $t2, array &$records, array $fkValues) /*: void*/
  7296. {
  7297. $fks = $t1->getFksTo($t2->getName());
  7298. foreach ($fks as $fk) {
  7299. $fkName = $fk->getName();
  7300. foreach ($records as $i => $record) {
  7301. if (isset($record[$fkName])) {
  7302. $key = $record[$fkName];
  7303. $records[$i][$fkName] = $fkValues[$key];
  7304. }
  7305. }
  7306. }
  7307. }
  7308. private function getPkEmptyValues(ReflectedTable $t1, array $records): array
  7309. {
  7310. $pkValues = array();
  7311. $pkName = $t1->getPk()->getName();
  7312. foreach ($records as $record) {
  7313. $key = $record[$pkName];
  7314. $pkValues[$key] = array();
  7315. }
  7316. return $pkValues;
  7317. }
  7318. private function addPkRecords(ReflectedTable $t1, ReflectedTable $t2, array $pkValues, array $params, GenericDB $db, array &$records) /*: void*/
  7319. {
  7320. $fks = $t2->getFksTo($t1->getName());
  7321. $columnNames = $this->columns->getNames($t2, false, $params);
  7322. $pkValueKeys = implode(',', array_keys($pkValues));
  7323. $conditions = array();
  7324. foreach ($fks as $fk) {
  7325. $conditions[] = new ColumnCondition($fk, 'in', $pkValueKeys);
  7326. }
  7327. $condition = OrCondition::fromArray($conditions);
  7328. $columnOrdering = array();
  7329. $limit = VariableStore::get("joinLimits.maxRecords") ?: -1;
  7330. if ($limit != -1) {
  7331. $columnOrdering = $this->ordering->getDefaultColumnOrdering($t2);
  7332. }
  7333. foreach ($db->selectAll($t2, $columnNames, $condition, $columnOrdering, 0, $limit) as $record) {
  7334. $records[] = $record;
  7335. }
  7336. }
  7337. private function fillPkValues(ReflectedTable $t1, ReflectedTable $t2, array $pkRecords, array &$pkValues) /*: void*/
  7338. {
  7339. $fks = $t2->getFksTo($t1->getName());
  7340. foreach ($fks as $fk) {
  7341. $fkName = $fk->getName();
  7342. foreach ($pkRecords as $pkRecord) {
  7343. $key = $pkRecord[$fkName];
  7344. if (isset($pkValues[$key])) {
  7345. $pkValues[$key][] = $pkRecord;
  7346. }
  7347. }
  7348. }
  7349. }
  7350. private function setPkValues(ReflectedTable $t1, ReflectedTable $t2, array &$records, array $pkValues) /*: void*/
  7351. {
  7352. $pkName = $t1->getPk()->getName();
  7353. $t2Name = $t2->getName();
  7354. foreach ($records as $i => $record) {
  7355. $key = $record[$pkName];
  7356. $records[$i][$t2Name] = $pkValues[$key];
  7357. }
  7358. }
  7359. private function getHabtmEmptyValues(ReflectedTable $t1, ReflectedTable $t2, ReflectedTable $t3, GenericDB $db, array $records): HabtmValues
  7360. {
  7361. $pkValues = $this->getPkEmptyValues($t1, $records);
  7362. $fkValues = array();
  7363. $fk1 = $t3->getFksTo($t1->getName())[0];
  7364. $fk2 = $t3->getFksTo($t2->getName())[0];
  7365. $fk1Name = $fk1->getName();
  7366. $fk2Name = $fk2->getName();
  7367. $columnNames = array($fk1Name, $fk2Name);
  7368. $pkIds = implode(',', array_keys($pkValues));
  7369. $condition = new ColumnCondition($t3->getColumn($fk1Name), 'in', $pkIds);
  7370. $columnOrdering = array();
  7371. $limit = VariableStore::get("joinLimits.maxRecords") ?: -1;
  7372. if ($limit != -1) {
  7373. $columnOrdering = $this->ordering->getDefaultColumnOrdering($t3);
  7374. }
  7375. $records = $db->selectAll($t3, $columnNames, $condition, $columnOrdering, 0, $limit);
  7376. foreach ($records as $record) {
  7377. $val1 = $record[$fk1Name];
  7378. $val2 = $record[$fk2Name];
  7379. $pkValues[$val1][] = $val2;
  7380. $fkValues[$val2] = null;
  7381. }
  7382. return new HabtmValues($pkValues, $fkValues);
  7383. }
  7384. private function setHabtmValues(ReflectedTable $t1, ReflectedTable $t2, array &$records, HabtmValues $habtmValues) /*: void*/
  7385. {
  7386. $pkName = $t1->getPk()->getName();
  7387. $t2Name = $t2->getName();
  7388. foreach ($records as $i => $record) {
  7389. $key = $record[$pkName];
  7390. $val = array();
  7391. $fks = $habtmValues->pkValues[$key];
  7392. foreach ($fks as $fk) {
  7393. $val[] = $habtmValues->fkValues[$fk];
  7394. }
  7395. $records[$i][$t2Name] = $val;
  7396. }
  7397. }
  7398. }
  7399. // file: src/Tqdev/PhpCrudApi/Record/RequestUtils.php
  7400. class RequestUtils
  7401. {
  7402. private $reflection;
  7403. public function __construct(ReflectionService $reflection)
  7404. {
  7405. $this->reflection = $reflection;
  7406. }
  7407. public function getParams(ServerRequestInterface $request): array
  7408. {
  7409. $params = array();
  7410. $query = $request->getUri()->getQuery();
  7411. $query = str_replace('][]=', ']=', str_replace('=', '[]=', $query));
  7412. parse_str($query, $params);
  7413. return $params;
  7414. }
  7415. public function getPathSegment(ServerRequestInterface $request, int $part): String
  7416. {
  7417. $pathSegments = explode('/', rtrim($request->getUri()->getPath(), '/'));
  7418. if ($part < 0 || $part >= count($pathSegments)) {
  7419. return '';
  7420. }
  7421. return $pathSegments[$part];
  7422. }
  7423. public function getOperation(ServerRequestInterface $request): String
  7424. {
  7425. $method = $request->getMethod();
  7426. $path = $this->utils->getPathSegment($request, 1);
  7427. $hasPk = $this->utils->getPathSegment($request, 3) != '';
  7428. switch ($path) {
  7429. case 'openapi':
  7430. return 'document';
  7431. case 'columns':
  7432. return $method == 'get' ? 'reflect' : 'remodel';
  7433. case 'records':
  7434. switch ($method) {
  7435. case 'POST':
  7436. return 'create';
  7437. case 'GET':
  7438. return $hasPk ? 'read' : 'list';
  7439. case 'PUT':
  7440. return 'update';
  7441. case 'DELETE':
  7442. return 'delete';
  7443. case 'PATCH':
  7444. return 'increment';
  7445. }
  7446. }
  7447. return 'unknown';
  7448. }
  7449. private function getJoinTables(String $tableName, array $parameters): array
  7450. {
  7451. $uniqueTableNames = array();
  7452. $uniqueTableNames[$tableName] = true;
  7453. if (isset($parameters['join'])) {
  7454. foreach ($parameters['join'] as $parameter) {
  7455. $tableNames = explode(',', trim($parameter));
  7456. foreach ($tableNames as $tableName) {
  7457. $uniqueTableNames[$tableName] = true;
  7458. }
  7459. }
  7460. }
  7461. return array_keys($uniqueTableNames);
  7462. }
  7463. public function getTableNames(ServerRequestInterface $request): array
  7464. {
  7465. $path = $this->utils->getPathSegment($request, 1);
  7466. $tableName = $this->utils->getPathSegment($request, 2);
  7467. $allTableNames = $this->reflection->getTableNames();
  7468. switch ($path) {
  7469. case 'openapi':
  7470. return $allTableNames;
  7471. case 'columns':
  7472. return $tableName ? [$tableName] : $allTableNames;
  7473. case 'records':
  7474. return $this->getJoinTables($tableName, $this->utils->getParams($request));
  7475. }
  7476. return $allTableNames;
  7477. }
  7478. }
  7479. // file: src/Tqdev/PhpCrudApi/Api.php
  7480. class Api
  7481. {
  7482. private $router;
  7483. private $responder;
  7484. private $debug;
  7485. public function __construct(Config $config)
  7486. {
  7487. $db = new GenericDB(
  7488. $config->getDriver(),
  7489. $config->getAddress(),
  7490. $config->getPort(),
  7491. $config->getDatabase(),
  7492. $config->getUsername(),
  7493. $config->getPassword()
  7494. );
  7495. $cache = CacheFactory::create($config);
  7496. $reflection = new ReflectionService($db, $cache, $config->getCacheTime());
  7497. $responder = new Responder();
  7498. $router = new SimpleRouter($responder, $cache, $config->getCacheTime(), $config->getDebug());
  7499. foreach ($config->getMiddlewares() as $middleware => $properties) {
  7500. switch ($middleware) {
  7501. case 'cors':
  7502. new CorsMiddleware($router, $responder, $properties);
  7503. break;
  7504. case 'firewall':
  7505. new FirewallMiddleware($router, $responder, $properties);
  7506. break;
  7507. case 'basicAuth':
  7508. new BasicAuthMiddleware($router, $responder, $properties);
  7509. break;
  7510. case 'jwtAuth':
  7511. new JwtAuthMiddleware($router, $responder, $properties);
  7512. break;
  7513. case 'validation':
  7514. new ValidationMiddleware($router, $responder, $properties, $reflection);
  7515. break;
  7516. case 'ipAddress':
  7517. new IpAddressMiddleware($router, $responder, $properties, $reflection);
  7518. break;
  7519. case 'sanitation':
  7520. new SanitationMiddleware($router, $responder, $properties, $reflection);
  7521. break;
  7522. case 'multiTenancy':
  7523. new MultiTenancyMiddleware($router, $responder, $properties, $reflection);
  7524. break;
  7525. case 'authorization':
  7526. new AuthorizationMiddleware($router, $responder, $properties, $reflection);
  7527. break;
  7528. case 'xsrf':
  7529. new XsrfMiddleware($router, $responder, $properties);
  7530. break;
  7531. case 'pageLimits':
  7532. new PageLimitsMiddleware($router, $responder, $properties, $reflection);
  7533. break;
  7534. case 'joinLimits':
  7535. new JoinLimitsMiddleware($router, $responder, $properties, $reflection);
  7536. break;
  7537. case 'customization':
  7538. new CustomizationMiddleware($router, $responder, $properties, $reflection);
  7539. break;
  7540. }
  7541. }
  7542. foreach ($config->getControllers() as $controller) {
  7543. switch ($controller) {
  7544. case 'records':
  7545. $records = new RecordService($db, $reflection);
  7546. new RecordController($router, $responder, $reflection, $records);
  7547. break;
  7548. case 'columns':
  7549. $definition = new DefinitionService($db, $reflection);
  7550. new ColumnController($router, $responder, $reflection, $definition);
  7551. break;
  7552. case 'cache':
  7553. new CacheController($router, $responder, $cache);
  7554. break;
  7555. case 'openapi':
  7556. $openApi = new OpenApiService($reflection, $config->getOpenApiBase());
  7557. new OpenApiController($router, $responder, $openApi);
  7558. break;
  7559. }
  7560. }
  7561. $this->router = $router;
  7562. $this->responder = $responder;
  7563. $this->debug = $config->getDebug();
  7564. }
  7565. public function handle(ServerRequestInterface $request): Response
  7566. {
  7567. $response = null;
  7568. try {
  7569. $response = $this->router->route($request);
  7570. } catch (\Throwable $e) {
  7571. $response = $this->responder->error(ErrorCode::ERROR_NOT_FOUND, $e->getMessage());
  7572. if ($this->debug) {
  7573. $response->addExceptionHeaders($e);
  7574. }
  7575. }
  7576. return $response;
  7577. }
  7578. }
  7579. // file: src/Tqdev/PhpCrudApi/Config.php
  7580. class Config
  7581. {
  7582. private $values = [
  7583. 'driver' => null,
  7584. 'address' => 'localhost',
  7585. 'port' => null,
  7586. 'username' => null,
  7587. 'password' => null,
  7588. 'database' => null,
  7589. 'middlewares' => 'cors',
  7590. 'controllers' => 'records,openapi',
  7591. 'cacheType' => 'TempFile',
  7592. 'cachePath' => '',
  7593. 'cacheTime' => 10,
  7594. 'debug' => false,
  7595. 'openApiBase' => '{"info":{"title":"PHP-CRUD-API","version":"1.0.0"}}',
  7596. ];
  7597. private function getDefaultDriver(array $values): String
  7598. {
  7599. if (isset($values['driver'])) {
  7600. return $values['driver'];
  7601. }
  7602. return 'mysql';
  7603. }
  7604. private function getDefaultPort(String $driver): int
  7605. {
  7606. switch ($driver) {
  7607. case 'mysql':return 3306;
  7608. case 'pgsql':return 5432;
  7609. case 'sqlsrv':return 1433;
  7610. }
  7611. }
  7612. private function getDefaultAddress(String $driver): String
  7613. {
  7614. switch ($driver) {
  7615. case 'mysql':return 'localhost';
  7616. case 'pgsql':return 'localhost';
  7617. case 'sqlsrv':return 'localhost';
  7618. }
  7619. }
  7620. private function getDriverDefaults(String $driver): array
  7621. {
  7622. return [
  7623. 'driver' => $driver,
  7624. 'address' => $this->getDefaultAddress($driver),
  7625. 'port' => $this->getDefaultPort($driver),
  7626. ];
  7627. }
  7628. public function __construct(array $values)
  7629. {
  7630. $driver = $this->getDefaultDriver($values);
  7631. $defaults = $this->getDriverDefaults($driver);
  7632. $newValues = array_merge($this->values, $defaults, $values);
  7633. $newValues = $this->parseMiddlewares($newValues);
  7634. $diff = array_diff_key($newValues, $this->values);
  7635. if (!empty($diff)) {
  7636. $key = array_keys($diff)[0];
  7637. throw new \Exception("Config has invalid value '$key'");
  7638. }
  7639. $this->values = $newValues;
  7640. }
  7641. private function parseMiddlewares(array $values): array
  7642. {
  7643. $newValues = array();
  7644. $properties = array();
  7645. $middlewares = array_map('trim', explode(',', $values['middlewares']));
  7646. foreach ($middlewares as $middleware) {
  7647. $properties[$middleware] = [];
  7648. }
  7649. foreach ($values as $key => $value) {
  7650. if (strpos($key, '.') === false) {
  7651. $newValues[$key] = $value;
  7652. } else {
  7653. list($middleware, $key2) = explode('.', $key, 2);
  7654. if (isset($properties[$middleware])) {
  7655. $properties[$middleware][$key2] = $value;
  7656. } else {
  7657. throw new \Exception("Config has invalid value '$key'");
  7658. }
  7659. }
  7660. }
  7661. $newValues['middlewares'] = array_reverse($properties, true);
  7662. return $newValues;
  7663. }
  7664. public function getDriver(): String
  7665. {
  7666. return $this->values['driver'];
  7667. }
  7668. public function getAddress(): String
  7669. {
  7670. return $this->values['address'];
  7671. }
  7672. public function getPort(): int
  7673. {
  7674. return $this->values['port'];
  7675. }
  7676. public function getUsername(): String
  7677. {
  7678. return $this->values['username'];
  7679. }
  7680. public function getPassword(): String
  7681. {
  7682. return $this->values['password'];
  7683. }
  7684. public function getDatabase(): String
  7685. {
  7686. return $this->values['database'];
  7687. }
  7688. public function getMiddlewares(): array
  7689. {
  7690. return $this->values['middlewares'];
  7691. }
  7692. public function getControllers(): array
  7693. {
  7694. return array_map('trim', explode(',', $this->values['controllers']));
  7695. }
  7696. public function getCacheType(): String
  7697. {
  7698. return $this->values['cacheType'];
  7699. }
  7700. public function getCachePath(): String
  7701. {
  7702. return $this->values['cachePath'];
  7703. }
  7704. public function getCacheTime(): int
  7705. {
  7706. return $this->values['cacheTime'];
  7707. }
  7708. public function getDebug(): String
  7709. {
  7710. return $this->values['debug'];
  7711. }
  7712. public function getOpenApiBase(): array
  7713. {
  7714. return json_decode($this->values['openApiBase'], true);
  7715. }
  7716. }
  7717. // file: src/Tqdev/PhpCrudApi/RequestFactory.php
  7718. class RequestFactory
  7719. {
  7720. public static function fromGlobals(): ServerRequestInterface
  7721. {
  7722. $psr17Factory = new Psr17Factory();
  7723. $creator = new ServerRequestCreator($psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory);
  7724. return $creator->fromGlobals();
  7725. }
  7726. public static function fromString(String $request): ServerRequestInterface
  7727. {
  7728. $parts = explode("\n\n", trim($request), 2);
  7729. $lines = explode("\n", $parts[0]);
  7730. $first = explode(' ', trim(array_shift($lines)), 2);
  7731. $method = $first[0];
  7732. $body = $parts[1];
  7733. $url = isset($first[1]) ? $first[1] : '';
  7734. $psr17Factory = new Psr17Factory();
  7735. $serverRequest = $psr17Factory->createServerRequest($method, $url);
  7736. if (isset($parts[1])) {
  7737. $serverRequest = $serverRequest->withBody($body);
  7738. }
  7739. foreach ($lines as $line) {
  7740. list($key, $value) = explode(':', $line, 2);
  7741. $serverRequest = $serverRequest->withAddedHeader($key, $value);
  7742. }
  7743. return $serverRequest;
  7744. }
  7745. }
  7746. // file: src/Tqdev/PhpCrudApi/Response.php
  7747. class Response
  7748. {
  7749. const OK = 200;
  7750. const UNAUTHORIZED = 401;
  7751. const FORBIDDEN = 403;
  7752. const NOT_FOUND = 404;
  7753. const METHOD_NOT_ALLOWED = 405;
  7754. const CONFLICT = 409;
  7755. const UNPROCESSABLE_ENTITY = 422;
  7756. const INTERNAL_SERVER_ERROR = 500;
  7757. private $status;
  7758. private $headers;
  7759. private $body;
  7760. public function __construct(int $status, $body)
  7761. {
  7762. $this->status = $status;
  7763. $this->headers = array();
  7764. $this->parseBody($body);
  7765. }
  7766. private function parseBody($body)
  7767. {
  7768. if ($body === '') {
  7769. $this->body = '';
  7770. } else {
  7771. $data = json_encode($body, JSON_UNESCAPED_UNICODE);
  7772. $this->addHeader('Content-Type', 'application/json');
  7773. $this->addHeader('Content-Length', strlen($data));
  7774. $this->body = $data;
  7775. }
  7776. }
  7777. public function getStatus(): int
  7778. {
  7779. return $this->status;
  7780. }
  7781. public function getBody(): String
  7782. {
  7783. return $this->body;
  7784. }
  7785. public function addHeader(String $key, String $value)
  7786. {
  7787. $this->headers[$key] = $value;
  7788. }
  7789. public function getHeader(String $key): String
  7790. {
  7791. if (isset($this->headers[$key])) {
  7792. return $this->headers[$key];
  7793. }
  7794. return null;
  7795. }
  7796. public function getHeaders(): array
  7797. {
  7798. return $this->headers;
  7799. }
  7800. public function output()
  7801. {
  7802. http_response_code($this->getStatus());
  7803. foreach ($this->headers as $key => $value) {
  7804. header("$key: $value");
  7805. }
  7806. echo $this->getBody();
  7807. }
  7808. public function addExceptionHeaders(\Throwable $e)
  7809. {
  7810. $this->addHeader('X-Exception-Name', get_class($e));
  7811. $this->addHeader('X-Exception-Message', $e->getMessage());
  7812. $this->addHeader('X-Exception-File', $e->getFile() . ':' . $e->getLine());
  7813. }
  7814. public function __toString(): String
  7815. {
  7816. $str = "$this->status\n";
  7817. foreach ($this->headers as $key => $value) {
  7818. $str .= "$key: $value\n";
  7819. }
  7820. if ($this->body !== '') {
  7821. $str .= "\n";
  7822. $str .= "$this->body\n";
  7823. }
  7824. return $str;
  7825. }
  7826. }