Laravel:UpandRunningAFrameworkforBuildingModernPHPApps
MattStauffer
Laravel:UpandRunningbyMattStauffer
Copyright©2017MattStauffer.Allrightsreserved.
PrintedintheUnitedStatesofAmerica.
PublishedbyO’ReillyMedia,Inc.,1005GravensteinHighwayNorth,Sebastopol,CA95472.
O’Reillybooksmaybepurchasedforeducational,business,orsalespromotionaluse.Onlineeditionsarealsoavailableformosttitles(http://oreilly.com/safari).Formoreinformation,contactourcorporate/institutionalsalesdepartment:[email protected].
Editor:AllysonMacDonald
ProductionEditor:ColleenLobner
Copyeditor:RachelHead
Proofreader:KimCofer
Indexer:AngelaHoward
InteriorDesigner:DavidFutato
CoverDesigner:RandyComer
Illustrator:RebeccaDemarest
December2016:FirstEdition
RevisionHistoryfortheFirstEdition2016-11-14:FirstRelease
Seehttp://oreilly.com/catalog/errata.csp?isbn=9781491936085forreleasedetails.
TheO’ReillylogoisaregisteredtrademarkofO’ReillyMedia,Inc.Laravel:UpandRunning,thecoverimageofagemsbok,andrelatedtradedressaretrademarksofO’ReillyMedia,Inc.
Whilethepublisherandtheauthorhaveusedgoodfaitheffortstoensurethattheinformationandinstructionscontainedinthisworkareaccurate,thepublisherandtheauthordisclaimallresponsibilityforerrorsoromissions,includingwithoutlimitationresponsibilityfordamagesresultingfromtheuseoforrelianceonthiswork.Useoftheinformationandinstructionscontainedinthisworkisatyourownrisk.Ifanycodesamplesorothertechnologythisworkcontainsordescribesissubjecttoopensourcelicensesortheintellectualpropertyrightsofothers,itisyourresponsibilitytoensurethatyourusethereofcomplieswithsuchlicensesand/orrights.
978-1-491-93608-5
[LSI]
DedicationThisbookisdedicatedtomygraciousandinspiringwife,Tereva,myjoyfulandcourageousson,Malachi,andmybeautifuldaughter,Mia,whospentthemajorityofthisbook’screationinhermama’sbelly.
Preface
ThestoryofhowIgotstartedwithLaravelisacommonone:IhadwrittenPHPforyears,butIwasonmywayoutthedoor,pursuingthepowerofRailsandothermodernwebframeworks.Railsinparticularhadalivelycommunity,aperfectcombinationofopinionateddefaultsandflexibility,andthepowerofRubyGemstoleverageprepackagedcommoncode.
Somethingkeptmefromjumpingship,andIwasgladforthatwhenIfoundLaravel.ItofferedeverythingIwasdrawntoinRails,butitwasn’tjustaRailsclone;thiswasaninnovativeframeworkwithincredibledocumentation,awelcomingcommunity,andclearinfluencesfrommanylanguagesandframeworks.
SincethatdayI’vebeenabletosharemyjourneyoflearningLaravelthroughbloggingandspeakingatconferences;I’vewrittendozensofappsinLaravelforsideandworkprojects,andI’vemetthousandsofLaraveldevelopersonlineandinperson.Ihaveplentyoftoolsinmytoolkitatourconsultancy,butIamhonestlyhappiestwhenIsitdowninfrontofacommandlineandtypelaravelnewproject.
WhatThisBookIsAboutThisisnotthefirstbookaboutLaravel,anditwon’tbethelast.Idon’tintendforthistobeabookthatcoverseverylineofcodeoreveryimplementationpattern.Idon’twantthistobethesortofbookthatgoesoutofdatewhenanewversionofLaravelisreleased.Instead,itsprimarypurposeistoprovidedeveloperswithahigh-leveloverviewandconcreteexamplestolearnwhattheyneedtogetstarted,asquicklyaspossible.Ratherthanmirroringthedocs,IwanttohelpyouunderstandthefoundationalconceptsbehindLaravel.
LaravelisapowerfulandflexiblePHPframework.Ithasathrivingcommunityandawideecosystemoftools,andasaresultit’sgrowinginappealandreach.ThisbookisfordeveloperswhoalreadyknowhowtomakewebsitesandapplicationsandwanttoquicklylearnhowtodosoinLaravel.
Laravel’sdocumentationisthoroughandexcellent.IfyoufindthatIdon’tcoveranyparticulartopicdeeplyenoughforyourliking,Iencourageyoutovisittheonlinedocumentationanddigdeeperintothatparticulartopic.
Ithinkyouwillfindthebookacomfortablebalancebetweenhigh-levelintroductionandconcreteapplication,andbytheendyoushouldfeelcomfortablewritinganentireapplicationinLaravel,fromscratch.And,ifIdidmyjobwell,you’llbeexcitedtotry.
WhoThisBookIsForThisbookassumesknowledgeofbasicobject-orientedprogrammingpractices,PHP(oratleastthegeneralsyntaxofC-familylanguages),andthebasicconceptsoftheModel–View–Controller(MVC)patternandtemplating.Ifyou’venevermadeawebsitebefore,youmayfindyourselfinoveryourhead.Butaslongasyouhavesomeprogrammingexperience,youdon’thavetoknowanythingaboutLaravelbeforeyoureadthisbook—we’llcovereverythingyouneedtoknow,fromthesimplest“Hello,world!”
Laravelcanrunonanyoperatingsystem,buttherewillbesomeBash(shell)commandsinthebookthatareeasiesttorunonLinux/MacOS.WindowsusersmayhaveahardertimewiththesecommandsandwithmodernPHPdevelopment,butifyoufollowtheinstructionstogetHomestead(aLinuxvirtualmachine)running,you’llbeabletorunallofthecommandsfromthere.
HowThisBookIsStructuredThisbookisstructuredinwhatIimaginetobeachronologicalorder:ifyou’rebuildingyourfirstwebappwithLaravel,theearlychapterscoverthefoundationalcomponentsyou’llneedtogetstarted,andthelaterchapterscoverlessfoundationalormoreesotericfeatures.
Eachsectionofthebookcanbereadonitsown,butforsomeonenewtotheframework,I’vetriedtostructurethechapterssothatit’sactuallyveryreasonabletostartfromthebeginningandreaduntiltheend.
Whereapplicable,eachchapterwillendwithtwosections:“Testing”and“TL;DR.”Ifyou’renotfamiliar,TL;DRmeans“toolong;didn’tread.”Thesefinalsectionswillshowyouhowtowritetestsforthefeaturescoveredineachchapterandgiveahigh-leveloverviewofwhatwascovered.
ThebookiswrittenforLaravel5.3,butbecauseLaravel5.1isthelatestLTSrelease,anyfeaturesthatarenewin5.2or5.3willbeidentified.
ConventionsUsedinThisBookThefollowingtypographicalconventionsareusedinthisbook:
ItalicIndicatesnewterms,URLs,emailaddresses,filenames,andfileextensions.
Constantwidth
Usedforprogramlistings,aswellaswithinparagraphstorefertoprogramelementssuchasvariableorfunctionnames,databases,datatypes,environmentvariables,statements,andkeywords.
Constantwidthbold
Showscommandsorothertextthatshouldbetypedliterallybytheuser.
Constantwidthitalic
Showstextthatshouldbereplacedwithuser-suppliedvaluesorbyvaluesdeterminedbycontext.
TIPThiselementsignifiesatiporsuggestion.
NOTEThiselementsignifiesageneralnote.
WARNINGThiselementindicatesawarningorcaution.
O’ReillySafariNOTE
Safari(formerlySafariBooksOnline)ismembership-basedtrainingandreferenceplatformforenterprise,government,educators,andindividuals.
Membershaveaccesstothousandsofbooks,trainingvideos,LearningPaths,interactivetutorials,andcuratedplaylistsfromover250publishers,includingO’ReillyMedia,HarvardBusinessReview,PrenticeHallProfessional,Addison-WesleyProfessional,MicrosoftPress,Sams,Que,PeachpitPress,Adobe,FocalPress,CiscoPress,JohnWiley&Sons,Syngress,MorganKaufmann,IBMRedbooks,Packt,AdobePress,FTPress,Apress,Manning,NewRiders,McGraw-Hill,Jones&Bartlett,andCourseTechnology,amongothers.
Formoreinformation,pleasevisithttp://oreilly.com/safari.
HowtoContactUsPleaseaddresscommentsandquestionsconcerningthisbooktothepublisher:
O’ReillyMedia,Inc.
1005GravensteinHighwayNorth
Sebastopol,CA95472
800-998-9938(intheUnitedStatesorCanada)
707-829-0515(internationalorlocal)
707-829-0104(fax)
Wehaveawebpageforthisbook,wherewelisterrata,examples,andanyadditionalinformation.Youcanaccessthispageathttp://bit.ly/laravel-up-and-running.
Tocommentorasktechnicalquestionsaboutthisbook,[email protected].
Formoreinformationaboutourbooks,courses,conferences,andnews,seeourwebsiteathttp://www.oreilly.com.
FindusonFacebook:http://facebook.com/oreilly
FollowusonTwitter:http://twitter.com/oreillymedia
WatchusonYouTube:http://www.youtube.com/oreillymedia
AcknowledgmentsThisbookwouldnothavehappenedwithoutthegracioussupportofmyamazingwifeTerevaortheunderstanding(“Daddy’swriting,buddy!”)ofmysonMalachi.Andwhileshewasn’texplicitlyawareofit,mydaughterMiawasaroundforalmosttheentirecreationofthebook,sothisbookisdedicatedtothewholefamily.Thereweremany,manylongeveninghoursandweekendStarbuckstripsthattookmeawayfrommyfamily,andIcouldn’tbemoregratefulfortheirsupportandalsotheirpresencejustmakingmylifeawesome.
Additionally,theentireTightenCo.familyhassupportedandencouragedmethroughthewritingofthebook,severalevenediting(KeithDamiani,editorextraordinaire)andhelpingmewithchallengingcodesamples(AdamWathan,KingoftheCollectionPipeline).DanSheetz,mypartnerinTightencrime,hasbeengraciousenoughtowatchmewhileawaymanyaworkhourcrankingonthisbookandwasnothingbutsupportiveandencouraging;andDaveHicking,ouroperationsmanager,helpedmearrangemyscheduleandworkresponsibilitiesaroundwritingtime.
TaylorOtwelldeservesthanksandhonorforcreatingLaravel—andthereforecreatingsomanyjobsandhelpingsomanydevelopersloveourlivesthatmuchmore.Hedeservesappreciationforhowhe’sfocusedondeveloperhappinessandhowhardhe’sworkedtohaveempathyfordevelopersandtobuildapositiveandencouragingcommunity.ButIalsowanttothankhimforbeingakind,encouraging,andchallengingfriend.Taylor,you’reaboss.
ThankstoJeffreyWay,whoIstillcontendtobeoneofthebestteachersontheInternet.HeoriginallyintroducedmetoLaravelandstillintroducesmorepeopleeveryday.He’salso,unsurprisingly,afantastichumanbeingwhomI’mgladtocallafriend.
ThankyoutoJessD’Amico,ShawnMcCool,IanLandsman,andTaylorforseeingvalueinmeasaconferencespeakerearlyonandgivingmeaplatformtoteachfrom.ThankstoDayleReesformakingitsoeasyforsomanytolearnLaravelintheearlydays.
ThankstoeverypersonwhoputtheirtimeandeffortintowritingblogpostsaboutLaravel,especiallyearlyon:EricBarnes,ChrisFidao,MattMachuga,JasonLewis,RyanTablada,DriesVints,MaksSurguy,andsomanymore.
AndthankstotheentirecommunityoffriendsonTwitter,IRC,andSlackwho’veinteractedwithmeovertheyears.IwishIcouldnameeveryname,butIwouldmisssomeandthenfeelawfulaboutmissingthem.Youallarebrilliant,andI’mhonoredtogettointeractwithyouonaregularbasis.
ThankstomyO’Reillyeditor,AllyMacDonald,andallofmytechnicaleditors:KeithDamiani,MichaelDyrynda,AdamFairholm,andMylesHyson.
And,ofcourse,thankstotherestofmyfamilyandfriends,whosupportedmedirectlyorindirectlythroughthisprocess—myparentsandsiblings,theGainesvillecommunity,otherbusinessownersandauthors,otherconferencespeakers,andtheinimitableDCB.Ineedto
stopwritingbecausebythetimeIrunoutofspacehereI’llbethankingmyStarbucksbaristas.
Chapter1.WhyLaravel?
Intheearlydaysofthedynamicweb,writingawebapplicationlookedalotdifferentthanitdoestoday.Developersthenwereresponsibleforwritingthecodefornotjusttheuniquebusinesslogicofourapplications,butalsoeachofthecomponentsthataresocommonacrosssites—userauthentication,inputvalidation,databaseaccess,templating,andmore.
Today,programmershavedozensofapplicationdevelopmentframeworksandthousandsofcomponentsandlibrarieseasilyaccessible.It’sacommonrefrainamongprogrammersthat,bythetimeyoulearnoneframework,threenewer(andpurportedlybetter)frameworkshavepoppedupintendingtoreplaceit.
“Justbecauseit’sthere”mightbeavalidjustificationforclimbingamountain,buttherearebetterreasonstochoosetouseaspecificframework—ortouseaframeworkatall.It’sworthaskingthequestion:whyframeworks?Morespecifically,whyLaravel?
WhyUseaFramework?It’seasytoseewhyit’sbeneficialtousetheindividualcomponents,orpackages,thatareavailabletoPHPdevelopers.Withpackages,someoneelseisresponsiblefordevelopingandmaintaininganisolatedpieceofcodethathasawell-definedjob,andintheorythatpersonhasadeeperunderstandingofthissinglecomponentthanyouhavetimetohave.
FrameworkslikeLaravel—andSymfony,Silex,Lumen,andSlim—prepackageacollectionofthird-partycomponentstogetherwithcustomframework“glue”likeconfigurationfiles,serviceproviders,prescribeddirectorystructures,andapplicationbootstraps.So,thebenefitofusingaframeworkingeneralisthatsomeonehasmadedecisionsnotjustaboutindividualcomponentsforyou,butalsoabouthowthosecomponentsshouldfittogether.
“I’llJustBuildItMyself”Let’ssayyoustartanewwebappwithoutthebenefitofaframework.Wheredoyoubegin?Well,itshouldprobablyrouteHTTPrequests,soyounowneedtoevaluatealloftheHTTPrequestandresponselibrariesavailableandpickone.Thenarouter.Oh,andyou’llprobablyneedtosetupsomeformofroutesconfigurationfile.Whatsyntaxshouldituse?Whereshoulditgo?Whataboutcontrollers?Wheredotheylive,andhowaretheyloaded?Well,youprobablyneedadependencyinjectioncontainertoresolvethecontrollersandtheirdependencies.Butwhichone?
Furthermore,whatifyoudotakethetimetoanswerallthosequestionsandsuccessfullycreateyourapplication—what’stheimpactonthenextdeveloper?Whataboutwhenyouhavefoursuchcustom-framework–basedapplications,oradozen,andyouhavetorememberwherethecontrollersliveineach,orwhattheroutingsyntaxis?
ConsistencyandFlexibilityFrameworksaddressthisissuebyprovidingacarefullyconsideredanswertothequestion“Whichcomponentshouldweusehere?”andensuringthattheparticularcomponentschosenworkwelltogether.Additionally,frameworksprovideconventionsthatreducetheamountofcodeadevelopernewtotheprojecthastounderstand—ifyouunderstandhowroutingworksinoneLaravelproject,forexample,youunderstandhowitworksinallLaravelprojects.
Whensomeoneprescribesrollingyourownframeworkforeachnewproject,whatthey’rereallyadvocatingistheabilitytocontrolwhatdoesanddoesn’tgointoyourapplication’sfoundation.Thatmeansthebestframeworkswillnotonlyprovideyouwithasolidfoundation,butalsogiveyouthefreedomtocustomizetoyourheart’scontent.Andthis,asI’llshowyouintherestofthisbook,ispartofwhatmakesLaravelsospecial.
AShortHistoryofWebandPHPFrameworksAnimportantpartofbeingabletoanswerthequestion“WhyLaravel?”isunderstandingLaravel’shistory—andunderstandingwhatcamebeforeit.PriortoLaravel’sriseinpopularity,therewereavarietyofframeworksandothermovementsinPHPandotherwebdevelopmentspaces.
RubyonRailsDavidHeinemeierHanssonreleasedthefirstversionofRubyonRailsin2004,andit’sbeenhardtofindawebapplicationframeworksincethenthathasn’tbeeninfluencedbyRailsinsomeway.
RailspopularizedMVC,RESTfulJSONAPIs,conventionoverconfiguration,ActiveRecord,andmanymoretoolsandconventionsthathadaprofoundinfluenceonthewaywebdevelopersapproachedtheirapplications—especiallywithregardtorapidapplicationdevelopment.
TheInfluxofPHPFrameworksItwascleartomostdevelopersthatRails,andsimilarwebapplicationframeworks,werethewaveofthefuture,andPHPframeworks,includingthoseadmittedlyimitatingRails,startingpoppingupquickly.
CakePHPwasthefirstin2005,anditwassoonfollowedbySymfony,CodeIgniter,ZendFramework,andKohana(aCodeIgniterfork).Yiiarrivedin2008,andAuraandSlimin2010.2011broughtFuelPHPandLaravel,bothofwhichwerenotquiteCodeIgniteroffshoots,butinsteadproposedasalternatives.
SomeoftheseframeworksweremoreRails-y,focusingondatabaseobject-relationalmappers(ORMs),MVCstructures,andothertoolstargetingrapiddevelopment.Others,likeSymfonyandZend,focusedmoreonenterprisedesignpatternsandecommerce.
TheGoodandtheBadofCodeIgniterCakePHPandCodeIgniterwerethetwoearlyPHPframeworksthatweremostopenabouthowmuchtheirinspirationwasdrawnfromRails.CodeIgniterquicklyrosetofameandby2010wasarguablythemostpopularoftheindependentPHPframeworks.
CodeIgniterwassimple,easytouse,andboastedamazingdocumentationandastrongcommunity.Butitsuseofmoderntechnologyandpatternsadvancedslowly,andastheframeworkworldgrewandPHP’stoolingadvanced,CodeIgniterstartedfallingbehindintermsofbothtechnologicaladvancesandout-of-the-boxfeatures.Unlikemanyotherframeworks,CodeIgniterwasmanagedbyacompany,andtheywereslowtocatchupwithPHP5.3’snewerfeatureslikenamespacesandthemovestoGitHubandlaterComposer.Itwasin2010thatTaylorOtwell,Laravel’screator,becamedissatisfiedenoughwithCodeIgniterthathesetofftowritehisownframework.
Laravel1,2,and3ThefirstbetaofLaravel1wasreleasedinJune2011,anditwaswrittencompletelyfromscratch.ItfeaturedacustomORM(Eloquent);closure-basedrouting(inspiredbyRubySinatra);amodulesystemforextension;andhelpersforforms,validation,authentication,andmore.
EarlyLaraveldevelopmentmovedquickly,andLaravel2and3werereleasedinNovember2011andFebruary2012,respectively.Theyintroducedcontrollers,unittesting,acommand-linetool,aninversionofcontrol(IoC)container,Eloquentrelationships,andmigrations.
Laravel4WithLaravel4,Taylorrewrotetheentireframeworkfromthegroundup.BythispointComposer,PHP’snow-ubiquitouspackagemanager,wasshowingsignsofbecominganindustrystandardandTaylorsawthevalueofrewritingtheframeworkasacollectionofcomponents,distributedandbundledtogetherbyComposer.
TaylordevelopedasetofcomponentsunderthecodenameIlluminateand,inMay2013,releasedLaravel4withanentirelynewstructure.Insteadofbundlingthemajorityofitscodeasadownload,LaravelnowpulledinthemajorityofitscomponentsfromSymfony(anotherframeworkthatreleaseditscomponentsforusebyothers)andtheIlluminatecomponentsthroughComposer.
Laravel4alsointroducedqueues,amailcomponent,facades,anddatabaseseeding.AndbecauseLaravelwasnowrelyingonSymfonycomponents,itwasannouncedthatLaravelwouldbemirroring(notexactly,butsoonafter)thesix-monthlyreleasescheduleSymfonyfollows.
Laravel5Laravel4.3wasscheduledtoreleaseinNovember2014,butasdevelopmentprogressed,itbecameclearthatthesignificanceofitschangesmeritedamajorrelease,andLaravel5wasreleasedinFebruary2015.
Laravel5featuredarevampeddirectorystructure,removaloftheformandHTMLhelpers,theintroductionofthecontractinterfaces,aspateofnewviews,Socialiteforsocialmediaauthentication,Elixirforassetcompilation,Schedulertosimplifycron,dotenvforsimplifiedenvironmentmanagement,formrequests,andabrandnewREPL(read–evaluate–printloop).
What’sSoSpecialAboutLaravel?SowhatisitthatsetsLaravelapart?WhyisitworthhavingmorethanonePHPframeworkatanytime?TheyallusecomponentsfromSymfonyanyway,right?Let’stalkabitaboutwhatmakesLaravel“tick.”
ThePhilosophyofLaravelYouonlyneedtoreadthroughtheLaravelmarketingmaterialsandREADMEstostartseeingitsvalues.Tayloruseslight-relatedwordslike“Illuminate”and“Spark.”Andthentherearethese:“Artisans.”“Elegant.”Also,these:“Breathoffreshair.”“Freshstart.”Andfinally:“Rapid.”“Warpspeed.”
Thetwomoststronglycommunicatedvaluesoftheframeworkaretoincreasedeveloperspeedanddeveloperhappiness.Taylorhasdescribedthe“Artisan”languageasintentionallycontrastingagainstmoreutilitarianvalues.Youcanseethegenesisofthissortofthinkinginhis2011questiononStackExchangeinwhichhestated,“SometimesIspendridiculousamountsoftime(hours)agonizingovermakingcodelookpretty”—justforthesakeofabetterexperienceoflookingatthecodeitself.Andhe’softentalkedaboutthevalueofmakingiteasierandquickerfordeveloperstotaketheirideastofruition,gettingridofunnecessarybarrierstocreatinggreatproducts.
Laravelis,atitscore,aboutequippingandenablingdevelopers.Itsgoalistoprovideclear,simple,andbeautifulcodeandfeaturesthathelpdevelopersquicklylearn,start,anddevelop,andwritecodethat’ssimple,clear,andwilllast.
TheconceptoftargetingdevelopersisclearacrossLaravelmaterials.“Happydevelopersmakethebestcode”iswritteninthedocumentation.“Developerhappinessfromdownloadtodeploy”wastheunofficialsloganforawhile.Ofcourse,anytoolorframeworkwillsayitwantsdeveloperstobehappy.Buthavingdeveloperhappinessasaprimaryconcern,ratherthansecondary,hashadahugeimpactonLaravel’sstyleanddecision-makingprogress.Whereotherframeworksmaytargetarchitecturalpurityastheirprimarygoal,orcompatibilitywiththegoalsandvaluesofenterprisedevelopmentteams,Laravel’sprimaryfocusisonservingtheindividualdeveloper.
HowLaravelAchievesDeveloperHappinessJustsayingyouwanttomakedevelopershappyisonething.Doingitisanother,anditrequiresyoutoquestionwhatinaframeworkismostlikelytomakedevelopersunhappyandwhatismostlikelytomakethemhappy.ThereareafewwaysLaraveltriestomakedevelopers’liveseasier.
First,Laravelisarapidapplicationdevelopmentframework.Thatmeansitfocusesonashallow(easy)learningcurveandonminimizingthestepsbetweenstartinganewappandpublishingit.Allofthemostcommontasksinbuildingwebapplications,fromdatabaseinteractionstoauthenticationtoqueuestoemailtocaching,aremadesimplerbythecomponentsLaravelprovides.ButLaravel’scomponentsaren’tjustgreatontheirown;theyprovideaconsistentAPIandpredictablestructuresacrosstheentireframework.Thatmeansthat,whenyou’retryingsomethingnewinLaravel,you’remorethanlikelygoingtoendupsaying,“…anditjustworks.”
Thisdoesn’tendattheframeworkitself,either.Laravelprovidesanentireecosystemoftoolsforbuildingandlaunchingapplications.YouhaveHomesteadandValetforlocaldevelopment,Forgeforservermanagement,andEnvoyerforadvanceddeployment.Andthere’sasuiteofadd-onpackages:Cashierforpaymentsandsubscriptions,EchoforWebSockets,Scoutforsearch,PassportforAPIauthentication,Socialiteforsociallogin,andSparktobootstrapyourSaaS.Laravelistryingtotaketherepetitiveworkoutofdevelopers’jobssotheycandosomethingunique.
Next,Laravelfocuseson“conventionoverconfiguration”—meaningthatifyou’rewillingtouseLaravel’sdefaults,you’llhavetodomuchlessworkthanwithotherframeworksthatrequireyoutodeclareallofyoursettingsevenifyou’reusingtherecommendedconfiguration.ProjectsbuiltonLaraveltakelesstimethanthosebuiltonmostotherPHPframeworks.
Laravelalsofocusesdeeplyonsimplicity.It’spossibletousedependencyinjectionandmockingandtheDataMapperpatternandrepositoriesandCommandQueryResponsibilitySegregationandallsortsofothermorecomplexarchitecturalpatternswithLaravel,ifyouwant.Butwhileotherframeworksmightsuggestusingthosetoolsandstructuresoneveryproject,Laravelanditsdocumentationandcommunityleantowardstartingwiththesimplestpossibleimplementation—aglobalfunctionhere,afacadethere,ActiveRecordoverthere.Thisallowsdeveloperstocreatethesimplestpossibleapplicationtosolvefortheirneeds.
AninterestingsourceofhowLaravelisdifferentisthatitscreatoranditscommunityaremoreconnectedtoandinspiredbyRubyandRailsandfunctionalprogramminglanguagesthanbyJava.There’sastrongcurrentinmodernPHPtoleantowardverbosityandcomplexity,embracingthemoreJava-esqueaspectsofPHP.ButLaraveltendstobeontheotherside,embracingexpressive,dynamic,andsimplecodingpracticesandlanguagefeatures.
TheLaravelCommunityIfthisbookisyourfirstexposuretotheLaravelcommunity,youhavesomethingspecialtolookforwardto.OneofthedistinguishingelementsofLaravel,whichhascontributedtoitsgrowthandsuccess,isthewelcoming,teachingcommunitythatsurroundsit.FromJeffreyWay’sLaracastsvideotutorialstoLaravelNewstoSlackandIRCchannels,fromTwitterfriendstobloggerstotheLaraconconferences,Laravelhasarichandvibrantcommunityfulloffolkswho’vebeenaroundsincedayoneandfolkswhoareontheirowndayone.Andthisisn’tanaccident:
FromtheverybeginningofLaravel,I’vehadthisideathatallpeoplewanttofeelliketheyarepartofsomething.It’sanaturalhumaninstincttowanttobelongandbeacceptedintoagroupofotherlike-mindedpeople.So,byinjectingpersonalityintoawebframeworkandbeingreallyactivewiththecommunity,thattypeoffeelingcangrowinthecommunity.TaylorOtwell,ProductandSupportinterview
TaylorunderstoodfromtheearlydaysofLaravelthatasuccessfulopensourceprojectneededtwothings:gooddocumentationandawelcomingcommunity.AndthosetwothingsarenowhallmarksofLaravel.
HowItWorksUpuntilnow,everythingI’vesharedherehasbeenentirelyabstract.Whataboutthecode,youask?Let’sdigintoasimpleapplication(Example1-1)soyoucanseewhatworkingwithLaravelday-to-dayisactuallylike.
Example1-1.“Hello,World”inroutes/web.php//File:routes/web.php
<?php
Route::get('/',function(){
return'Hello,World!';
});
ThesimplestpossibleactionyoucantakeinaLaravelapplicationistodefinearouteandreturnaresultanytimesomeonevisitsthatroute.IfyouinitializeabrandnewLaravelapplicationonyourmachine,definetherouteinExample1-1,andthenservethesitefromthepublicdirectory,you’llhaveafullyfunctioning“Hello,World”example(seeFigure1-1).
Figure1-1.Returning“Hello,World!”withLaravel
Itlooksverysimilartodothesamewithcontrollers,asyoucanseeinExample1-2.
Example1-2.“Hello,World”withcontrollers//File:routes/web.php
<?php
Route::get('/','WelcomeController@index');
//File:app/Http/Controllers/WelcomeController.php
<?php
namespaceapp\Http\Controllers;
classWelcomeController
{
publicfunctionindex()
{
return'Hello,World!';
}
}
Andifwe’restoringourgreetingsinthedatabase,it’llalsolookprettysimilar(seeExample1-3).
Example1-3.Multigreeting“Hello,World”withdatabaseaccess//File:routes/web.php
<?php
Route::get('/',function(){
returnGreeting::first()->body;
});
//File:app/Greeting.php
<?php
useIlluminate\Database\Eloquent\Model;
classGreetingextendsModel{}
//File:database/migrations/2015_07_19_010000_create_greetings_table.php
<?php
useIlluminate\Database\Migrations\Migration;
useIlluminate\Database\Schema\Blueprint;
classCreateGreetingsTableextendsMigration
{
publicfunctionup()
{
Schema::create('greetings',function(Blueprint$table){
$table->increments('id');
$table->string('body');
$table->timestamps();
});
}
publicfunctiondown()
{
Schema::drop('greetings');
}
}
Example1-3mightbeabitoverwhelming,andifso,justskipoverit.We’lllearnabouteverythingthat’shappeninghereinlaterchapters,butyoucanalreadyseethatwithjustafewlinesofcode,we’vesetupdatabasemigrationsandmodelsandpulledrecordsout.It’sjustthatsimple.
WhyLaravel?So—whyLaravel?
BecauseLaravelhelpsyoubringyourideastorealitywithnowastedcode,usingmoderncodingstandards,surroundedbyavibrantcommunity,withanempoweringecosystemoftools.
Andbecauseyou,deardeveloper,deservetobehappy.
Chapter2.SettingUpaLaravelDevelopmentEnvironment
PartofPHP’ssuccesshasbeenbecauseit’shardtofindawebserverthatcan’tservePHP.However,modernPHPtoolshavestricterrequirementsthanthoseofthepast.ThebestwaytodevelopforLaravelistoensureaconsistentlocalandremoteserverenvironmentforyourcode,andthankfully,theLaravelecosystemhasafewtoolsforthis.
SystemRequirementsEverythingwe’llcoverinthischapterispossiblewithWindowsmachines,butyou’llneeddozensofpagesofcustominstructionsandcaveats.I’llleavethoseinstructionsandcaveatstoactualWindowsusers,sotheexampleshereandintherestofthebookwillfocusonUnix/Linux/MacOSdevelopers.
WhetheryouchoosetoserveyourwebsitebyinstallingPHPandothertoolsonyourlocalmachine,serveyourdevelopmentenvironmentfromavirtualmachineviaVagrant,orrelyonatoollikeMAMP/WAMP/XAMPP,yourdevelopmentenvironmentwillneedtohaveallofthefollowinginstalledinordertoserveLaravelsites:
PHP>=5.6.4forLaravel5.3orPHP>=5.5.9for5.1and5.2
OpenSSLPHPextension
PDOPHPextension
MbstringPHPextension
TokenizerPHPextension
ComposerWhatevermachineyou’redevelopingonwillneedtohaveComposerinstalledglobally.Ifyou’renotfamiliarwithComposer,it’satoolthat’satthefoundationofmostmodernPHPdevelopment.ComposerisadependencymanagerforPHP,muchlikeNPMforNodeorRubyGemsforRuby.You’llneedComposertoinstallLaravel,updateLaravel,andbringinexternaldependencies.
LocalDevelopmentEnvironmentsFormanyprojects,hostingyourdevelopmentenvironmentusingasimplertoolsetwillbeenough.IfyoualreadyhaveMAMPorWAMPorXAMPPinstalledonyoursystem,thatwilllikelybefinetorunLaravel.YoucanalsojustrunLaravelwithPHP’sbuilt-inwebserver,assumingyoursystemPHPistherightversion.
AllyoureallyneedtogetstartedistheabilitytorunPHP.Everythingpastthatisuptoyou.
However,Laravelofferstwotoolsforlocaldevelopment,ValetandHomestead,andwe’llcoverbothbriefly.Ifyou’reunsureofwhichtouse,I’drecommendusingValetandjustskimmingtheHomesteadsection;however,bothtoolsarevaluableandworthunderstanding.
LaravelValetIfyouwanttousePHP’sbuilt-inwebserver,yoursimplestoptionistoserveeverysitefromalocalhostURL.Ifyourunphp-Slocalhost:8000-tpublicfromyourLaravelsite’srootfolder,PHP’sbuilt-inwebserverwillserveyoursiteathttp://localhost:8000/.Youcanalsorunphpartisanserveonceyouhaveyourapplicationsetuptoeasilyspinupanequivalentserver.
Butifyou’reinterestedintyingeachofyoursitestoaspecificdevelopmentdomain,you’llneedtogetcomfortablewithyouroperatingsystem’shostsfileanduseatoollikednsmasq.Let’sinsteadtrysomethingsimpler.
Ifyou’reaMacuser(therearealsounofficialforksforWindowsandLinux),LaravelValettakesawaytheneedtoconnectyourdomainstoyourapplicationfolders.ValetinstallsdnsmasqandaseriesofPHPscriptsthatmakeitpossibletotypelaravelnewmyapp&&openmyapp.devandforittojustwork.You’llneedtoinstallafewtoolsusingHomebrew,whichthedocumentationwillwalkyouthrough,butthestepsfrominitialinstallationtoservingyourappsarefewandsimple.
InstallValet(seethedocsforthelatestinstallationinstruction—it’sunderveryactivedevelopmentatthistimeofwriting),andpointitatoneormoredirectorieswhereyoursiteswilllive.Iranvaletparkfrommy~/Sitesdirectory,whichiswhereIputallofmyunder-developmentapps.Now,youcanjustadd.devtotheendofthedirectorynameandvisititinyourbrowser.
Valetmakesiteasytoserveallfoldersinagivenfolderas“FOLDERNAME.dev”usingvaletpark,toservejustasinglefolderusingvaletlink,toopentheValet-serveddomainforafolderusingvaletopen,toservetheValetsitewithHTTPSusingvaletsecure,andtoopenanngroktunnelsoyoucanshareyoursitewithotherswithvaletshare.
LaravelHomesteadHomesteadisanothertoolyoumightwanttousetosetupyourlocaldevelopmentenvironment.It’saconfigurationtoolthatsitsontopofVagrantandprovidesapre-configuredvirtualmachineimagethatisperfectlysetupforLaraveldevelopment,andmirrorsthemostcommonproductionenvironmentthatmanyLaravelsitesrunon.
SettingupHomesteadIfyouchoosetouseHomestead,it’sgoingtotakeabitmoreworktosetupthansomethinglikeMAMPorValet.Thebenefitsaremyriad,however:configuredcorrectly,yourlocalenvironmentcanbeincrediblyclosetoyourremoteworkingenvironment;youwon’thavetoworryaboutupdatingyourdependenciesonyourlocalmachine;andyoucanlearnallaboutthestructureofUbuntuserversfromthesafetyofyourlocalmachine.
WHATTOOLSDOHOMESTEADOFFER?
YoucanalwaysupgradetheindividualcomponentsofyourHomesteadvirtualmachine,buthereareafewimportanttoolsHomesteadcomeswithbydefault:
Toruntheserverandservethesite,Ubuntu,PHP,andNginx(awebserversimilartoApache).
Fordatabase/storageandqueues,MySQL,Postgres,Redis,Memcached,andbeanstalkd.
Forbuildstepsandothertools,Node.
InstallingHomestead’sdependenciesFirst,you’llneedtodownloadandinstalleitherVirtualBoxorVMWare.VirtualBoxismostcommonbecauseit’sfree.
Next,downloadandinstallVagrant.
Vagrantisconvenientbecauseitmakesiteasyforyoutocreateanewlocalvirtualmachinefromaprecreated“box,”whichisessentiallyatemplateforavirtualmachine.So,thenextstepistorunvagrantboxaddlaravel/homesteadfromyourterminaltodownloadthebox.
InstallingHomesteadNext,let’sactuallyinstallHomestead.YoucaninstallmultipleinstancesofHomestead(perhapshostingadifferentHomesteadboxperproject),butIpreferasingleHomesteadvirtualmachineforallofmyprojects.Ifyouwantoneperproject,you’llwanttoinstallHomesteadinyourprojectdirectory;checktheHomesteaddocumentationonlineforinstructions.Ifyouwantasinglevirtualmachineforallofyourprojects,installHomesteadinyouruser ’shomedirectoryusingthefollowingcommand:
gitclonehttps://github.com/laravel/homestead.git~/Homestead
Now,runtheinitializationscriptfromwhereveryouputtheHomesteaddirectory:
bash~/Homestead/init.sh
ThiswillplaceHomestead’sprimaryconfigurationfile,Homestead.yaml,inanew~/.homesteaddirectory.
ConfiguringHomesteadOpenupHomestead.yamlandconfigureithowyou’dlike.Here’swhatitlookslikeoutofthebox:
ip:"192.168.10.10"
memory:2048
cpus:1
provider:virtualbox
authorize:~/.ssh/id_rsa.pub
keys:
-~/.ssh/id_rsa
folders:
-map:~/Code
to:/home/vagrant/Code
sites:
-map:homestead.app
to:/home/vagrant/Code/Laravel/public
databases:
-homestead
#blackfire:
#-id:foo
#token:bar
#client-id:foo
#client-token:bar
#ports:
#-send:50000
#to:5000
#-send:7777
#to:777
#protocol:udp
You’llneedtotellityourprovider(likelyvirtualbox),pointittoyourpublicSSHkey(bydefault~/.ssh/id_rsa.pub;ifyoudon’thaveone,GitHubhasagreattutorialoncreatingSSHkeys),mapfoldersandsitestotheirlocalmachineequivalents,andprovisionadatabase.
MappingfoldersinHomesteadallowsyoutoeditfilesonyourlocalmachineandhavethosefilesshowupinyourVagrantboxsotheycanbeserved.Forexample,ifyouhavea~/Sitesdirectorywhereyouputallofyourcode,you’dmapthefoldersinHomesteadbyreplacingthefolderssectioninHomestead.yamlwiththefollowing:
folders:
-map:~/Sites
to:/home/vagrant/Sites
You’venowjustcreatedadirectoryinyourHomesteadvirtualmachineat/home/vagrant/Sitesthatwillmirroryourcomputer ’sdirectoryat~/Sites.
TOP-LEVELDOMAINSFORDEVELOPMENTSITESYoucanchooseanyconventionforlocaldevelopmentsites’URLs,but.appand.devarethemostcommon.Homesteadsuggests.app,soifI’mworkingonalocalcopyofsymposiumapp.com,I’lldevelopatsymposiumapp.app.
Now,let’ssetupourfirstexamplewebsite.Let’ssayourlivesiteisgoingtobeprojectName.com.InHomestead.yaml,we’llmapourlocaldevelopmentfoldertoprojectName.app,sowehaveaseparateURLtovisitforlocaldevelopment:
sites:
-map:projectName.app
to:/home/vagrant/Sites/projectName/public
Asyoucansee,we’remappingtheURLprojectName.apptothevirtualmachinedirectory/home/vagrant/Sites/projectName/public,whichisthepublicfolderwithinourLaravelinstall.We’lllearnmoreaboutthatlater.
Finally,you’regoingtoneedtoteachyourlocalmachinethat,whenyoutrytovisitprojectName.app,itshouldlookatyourcomputer ’slocalIPaddresstoresolveit.MacandLinuxusersshouldedit/etc/hosts,andWindowsusersC:\Windows\System32\drivers\etc\hosts.Addalinetothisfilethatlookslikethis:
192.168.10.10projectName.app
Onceyou’veprovisionedHomestead,yoursitewillbeavailabletobrowse(onyourmachine)athttp://projectName.app/.
CreatingdatabasesinHomesteadJustlikeyoucandefineasiteinHomestead.yaml,youcanalsodefineadatabase.Databasesarealotsimpler,becauseyou’reonlytellingtheprovisionertocreateadatabasewiththatname,nothingelse.Wedothisasfollows:
databases:
-projectname
ProvisioningHomesteadThefirsttimeyouactuallyturnonaHomesteadbox,youneedtotellVagranttoinitializeit.NavigatetoyourHomesteaddirectoryandrunvagrantup:
cd~/Homestead
vagrantup
YourHomesteadboxisnowupandrunning;it’smirroringalocalfolder,andit’sservingittoaURLyoucanvisitinanybrowseronyourcomputer.ItalsohascreatedaMySQL
database.Nowthatyouhavethatenvironmentrunning,you’rereadytosetupyourfirstLaravelproject—butfirst,aquicknoteaboutusingHomesteadday-to-day.
UsingHomesteadday-to-dayIt’scommontoleaveyourHomesteadvirtualmachineupandrunningatalltimes,butifyoudon’t,orifyouhaverecentlyrestartedyourcomputer,you’llneedtoknowhowtospintheboxupanddown.
SinceHomesteadisbasedonVagrantcommands,you’lljustusebasicVagrantcommandsformostHomesteadactions.ChangetothedirectorywhereyouinstalledHomestead(usingcd)andthenrunthefollowingcommandsasneeded:
vagrantupspinsuptheHomesteadbox.
vagrantsuspendtakesasnapshotofwheretheboxisandthenshutsitdown;like“hibernating”adesktopmachine.
vagranthaltshutstheentireboxdown;liketurningoffadesktopmachine.
vagrantdestroydeletestheentirebox;likeformattingadesktopmachine.
vagrantprovisionre-runstheprovisionersonthepreexistingbox.
ConnectingtoHomesteaddatabasesfromdesktopapplicationsIfyouuseadesktopapplicationlikeSequelPro,you’lllikelywanttoconnecttoyourHomesteadMySQLdatabasesfromyourhostmachine.Thesesettingswillgetyougoing:
Connectiontype:Standard(non-SSH)
Host:127.0.0.1
Username:homestead
Password:secret
Port:33060
CreatingaNewLaravelProjectTherearetwowaystocreateanewLaravelproject,butbotharerunfromthecommandline.ThefirstoptionistogloballyinstalltheLaravelinstallertool(usingComposer);thesecondistouseComposer ’screate-projectfeature.
YoucanlearnaboutbothoptionsinmoredetailontheInstallationdocumentationpage,butI’drecommendtheLaravelinstallertool.
InstallingLaravelwiththeLaravelInstallerToolIfyouhaveComposerinstalledglobally,installingtheLaravelinstallertoolisassimpleasrunningthefollowingcommand:
composerglobalrequire"laravel/installer=~1.1"
OnceyouhavetheLaravelinstallertoolinstalled,spinningupanewLaravelprojectissimple.Justrunthiscommandfromyourcommandline:
laravelnewprojectName
ThiswillcreateanewsubdirectoryofyourcurrentdirectorynamedprojectNameandinstallabareLaravelprojectinit.
InstallingLaravelwithComposer’screate-projectFeatureComposeralsooffersafeaturecalledcreate-projectforcreatingnewprojectswithaparticularskeleton.TousethistooltocreateanewLaravelproject,issuethefollowingcommand:
composercreate-projectlaravel/laravelprojectName--prefer-dist
Justliketheinstallertool,thiswillcreateasubdirectoryofyourcurrentdirectorynamedprojectNamethatcontainsaskeletonLaravelinstall,readyforyoutodevelop.
Laravel’sDirectoryStructureWhenyouopenupadirectorythatcontainsaskeletonLaravelapplication,you’llseethefollowingfilesanddirectories:
app/
bootstrap/
config/
database/
public/
resources/
routes/
storage/
tests/
vendor/
.env
.env.example
.gitattributes
.gitignore
artisan
composer.json
composer.lock
gulpfile.js
package.json
phpunit.xml
readme.md
server.php
Let’swalkthroughthemonebyonetogetfamiliar.
TheFoldersTherootdirectorycontainsthefollowingfoldersbydefault:
appiswherethebulkofyouractualapplicationwillgo.Models,controllers,routedefinitions,commands,andyourPHPdomaincodeallgoinhere.
bootstrapcontainsthefilesthattheLaravelframeworkusestobooteverytimeitruns.
configiswherealltheconfigurationfileslive.
databaseiswheredatabasemigrationsandseedslive.
publicisthedirectorytheserverpointstowhenit’sservingthewebsite.Thiscontainsindex.php,whichisthefrontcontrollerthatkicksoffthebootstrappingprocessandroutesallrequestsappropriately.It’salsowhereanypublic-facingfileslikeimages,stylesheets,scripts,ordownloadsgo.
resourcesiswherenon-PHPfilesthatareneededforotherscriptslive.Views,languagefiles,and(optionally)Sass/LESSandsourceJavaScriptfileslivehere.
routesiswherealloftheroutedefinitionslive,bothforHTTProutesand“consoleroutes,”orArtisancommands.
storageiswherecaches,logs,andcompiledsystemfileslive.
testsiswhereunitandintegrationtestslive.
vendoriswhereComposerinstallsitsdependencies.It’sGit-ignored(markedtobeexcludedfromyourversioncontrolsystem),asComposerisexpectedtorunasapartofyourdeployprocessonanyremoteservers.
TheLooseFilesTherootdirectoryalsocontainsthefollowingfiles:
.envand.env.examplearethefilesthatdictatetheenvironmentvariables(variablesthatareexpectedtobedifferentineachenvironmentandarethereforenotcommittedtoversioncontrol)..env.exampleisatemplatethateachenvironmentshouldduplicatetocreateitsown.envfile,whichisGit-ignored.
artisanisthefilethatallowsyoutorunArtisancommands(seeChapter7)fromthecommandline.
.gitignoreand.gitattributesareGitconfigurationfiles.
composer.jsonandcomposer.lockaretheconfigurationfilesforComposer;composer.jsonisuser-editableandcomposer.lockisnot.ThesefilessharesomebasicinformationaboutthisprojectandalsodefineitsPHPdependencies.
gulpfile.jsisthe(optional)configurationfileforElixirandGulp.Thisisforcompilingandprocessingyourfrontendassets.
package.jsonislikecomposer.jsonbutforfrontendassets.
phpunit.xmlisaconfigurationfileforPHPUnit,thetoolLaravelusesfortestingoutofthebox.
readme.mdisaMarkdownfilegivingabasicintroductiontoLaravel.
server.phpisabackupserverthattriestoallowless-capableserverstostillpreviewtheLaravelapplication.
ConfigurationThecoresettingsofyourLaravelapplication—databaseconnection,queueandmailsettings,etc.—liveinfilesintheconfigfolder.Eachofthesefilesreturnsanarray,andeachvalueinthearraywillbeaccessiblebyaconfigkeythatiscomprisedofthefilenameandalldescendantkeys,separatedbydots(.)
So,ifyoucreateafileatconfig/services.phpthatlookslikethis:
//config/services.php
return[
'sparkpost'=>[
'secret'=>'abcdefg'
]
];
youwillnowhaveaccesstothatconfigvariableusingconfig('services.sparkpost.secret').
Anyconfigurationvariablesthatshouldbedistinctforeachenvironment(andthereforenotcommittedtosourcecontrol)willinsteadliveinyour.envfiles.Let’ssayyouwanttouseadifferentBugsnagAPIkeyforeachenvironment.You’dsettheconfigfiletopullitfrom.env:
//config/services.php
return[
'bugsnag'=>[
'api_key'=>env('BUGSNAG_API_KEY')
]
];
Thisenv()helperfunctionpullsavaluefromyour.envfilewiththatsamekey.Sonow,addthatkeytoyour.env(settingsforthisenvironment)and.env.example(templateforallenvironments)files:
BUGSNAG_API_KEY=oinfp9813410942
Your.envfilealreadycontainsquiteafewenvironment-specificvariablesneededbytheframework,likewhichmaildriveryou’llbeusingandwhatyourbasicdatabasesettingsare.
UpandRunningYou’renowupandrunningwithabareLaravelinstall.Rungitinit,committhebarefileswithgitadd.andgitcommit,andyou’rereadytostartcoding.That’sit!Andifyou’reusingValet,youcanrunthefollowingcommandsandinstantlyseeyoursiteliveinyourbrowser:
laravelnewmyProject&&cdmyProject&&valetopen
EverytimeIstartanewproject,thesearethestepsItake:
laravelnewmyProject
cdmyProject
gitinit
gitadd.
gitcommit-m"Initialcommit"
Ikeepallofmysitesina~/Sitesfolder,whichIhavesetupasmyprimaryValetdirectory,sointhiscaseI’dinstantlyhavemyProject.devaccessibleinmybrowserwithnoaddedwork.Icanedit.envandpointittoaparticulardatabase,addthatdatabaseinmyMySQLapp,andI’mreadytostartcoding.
LAMBOIperformthethissetofstepssooftenthatIcreatedasimpleglobalComposerpackagetodoitforme.It’scalledLambo,andyoucanlearnmoreaboutitonGitHub.
TestingIneverychapterafterthis,the“Testing”sectionattheendofthechapterwillshowyouhowtowritetestsforthefeatureorfeaturesthatwerecovered.Sincethischapterdoesn’tcoveratestablefeature,let’stalktestsquickly.(TolearnmoreaboutwritingandrunningtestsinLaravel,headovertoChapter12.)
Outofthebox,LaravelbringsinPHPUnitasadependencyandisconfiguredtorunthetestsinanyfileinthetestsdirectorywhosenameendswithTest.php(forexample,tests/UserTest.php).
So,thesimplestwaytowritetestsistocreateafileinthetestsdirectorywithanamethatendswithTest.php.Andtheeasiestwaytorunthemistorun./vendor/bin/phpunitfromthecommandline(intheprojectroot).
Ifanytestsrequiredatabaseaccess,besuretorunyourtestsfromthemachinewhereyourdatabaseishosted—soifyou’rehostingyourdatabaseinVagrant,makesuretosshintoyourVagrantboxtorunyourtestsfromthere.Again,youcanlearnaboutthisandmuchmoreinChapter12.
TL;DRSinceLaravelisaPHPframework,it’sverysimpletoserveitlocally.Laravelalsoprovidestwotoolsformanagingyourlocaldevelopment:asimplertoolcalledValetthatusesyourlocalmachinetoprovideyourdependencies,andapreconfiguredVagrantsetupnamedHomestead.Laravelrelieson,andcanbeinstalledby,Composer,andcomesoutoftheboxwithaseriesoffoldersandfilesthatreflectbothitsconventionsanditsrelationshipwithotheropensourcetools.
Chapter3.RoutingandControllers
Theessentialfunctionofanywebapplicationframeworkistotakerequestsfromauseranddeliverresponses,usuallyviaHTTP(S).Thismeansdefininganapplication’sroutesisthefirstandmostimportantprojecttotacklewhenlearningawebframework;withoutroutes,youhavenoabilitytointeractwiththeenduser.
InthischapterwewillexamineroutesinLaravelandshowhowtodefinethem,howtopointthemtothecodetheyshouldexecute,andhowtouseLaravel’sroutingtoolstohandleadiversearrayofroutingneeds.
RouteDefinitionsInaLaravelapplication,youwilldefineyour“web”routesinroutes/web.phpandyour“API”routesinroutes/api.php.Webroutesarethosethatwillbevisitedbyyourendusers;APIroutesarethoseforyourAPI,ifyouhaveone.Fornow,we’llprimarilyfocusontheroutesinroutes/web.php.
NOTEInprojectsrunningversionsofLaravelpriorto5.3,therewillbeonlyoneroutesfile,locatedatapp/Http/routes.php.
Thesimplestwaytodefinearouteistomatchapath(e.g.,/)withaclosure,asseeninExample3-1.
Example3-1.Basicroutedefinition//routes/web.php
Route::get('/',function(){
return'Hello,World!';
});
WHAT’SACLOSURE?
ClosuresarePHP’sversionofanonymousfunctions.Aclosureisafunctionthatyoucanpassaroundasanobject,assigntoavariable,passasaparametertootherfunctionsandmethods,orevenserialize.
You’venowdefinedthat,ifanyonevisits/(therootofyourdomain),Laravel’sroutershouldruntheclosuredefinedthereandreturntheresult.Notethatwereturnourcontentanddon’techoorprintit.
AQUICKINTRODUCTIONTOMIDDLEWAREYoumightbewondering,“WhyamIreturning‘Hello,World!’insteadofechoingit?”
Therearequiteafewanswers,butthesimplestisthattherearealotofwrappersaroundLaravel’srequestandresponsecycle,includingsomethingcalledmiddleware.Whenyourrouteclosureorcontrollermethodisdone,it’snottimetosendtheoutputtothebrowseryet;returningthecontentallowsittocontinueflowingthroughtheresponsestackandthemiddlewarebeforeitisreturnedbacktotheuser.
Manysimplewebsitescouldbedefinedentirelywithinthewebroutesfile.WithafewsimpleGETroutescombinedwithsometemplatesasillustratedinExample3-2,youcancanserveaclassicwebsiteeasily.
Example3-2.SamplewebsiteRoute::get('/',function(){
returnview('welcome');
});
Route::get('about',function(){
returnview('about');
});
Route::get('products',function(){
returnview('products');
});
Route::get('services',function(){
returnview('services');
});
STATICCALLSIfyouhavemuchexperiencedevelopingPHP,youmightbesurprisedtoseestaticcallsontheRouteclass.Thisisnotactuallyastaticmethodperse,butratherservicelocationusingLaravel’sfacades,whichwe’llcoverinChapter11.
Ifyouprefertoavoidfacades,youcanaccomplishthesesamedefinitionslikethis:
$router->get('/',function(){
return'Hello,World!';
});
HTTPMETHODS
Ifyou’renotfamiliarwiththeideaofHTTPmethods,readoninthischapterformoreinformation,butfornow,justknowthateveryHTTPrequesthasa“verb,”oraction,alongwithit.Laravelallowsyoutodefineyourroutesbasedonwhichverbwasused;themostcommonareGETandPOST,followedbyPUT,DELETE,andPATCH.Eachmethodcommunicatesadifferentthingtotheserver,andtoyourcode,abouttheintentionsofthecaller.
RouteVerbsYoumight’venoticedthatwe’vebeenusingRoute::getinourroutedefinitions.Thismeanswe’retellingLaraveltoonlymatchfortheserouteswhentheHTTPrequestusestheGETaction.Butwhatifit’saformPOST,ormaybesomeJavaScriptsendingPUTorDELETErequests?Thereareafewotheroptionsformethodstocallonaroutedefinition,asillustratedinExample3-3.
Example3-3.RouteverbsRoute::get('/',function(){
return'Hello,World!';
});
Route::post('/',function(){});
Route::put('/',function(){});
Route::delete('/',function(){});
Route::any('/',function(){});
Route::match(['get','post'],'/',function(){});
RouteHandlingAsyou’veprobablyguessed,passingaclosuretotheroutedefinitionisnottheonlywaytoteachithowtoresolvearoute.Closuresarequickandsimple,butthelargeryourapplicationgets,theclumsieritbecomestoputallofyourroutinglogicinonefile.Additionally,applicationsusingrouteclosurescan’ttakeadvantageofLaravel’sroutecaching(moreonthatlater),whichcanshaveuptohundredsofmillisecondsoffofeachrequest.
Theothercommonoptionistopassacontrollernameandmethodasastringinplaceoftheclosure,asinExample3-4.
Example3-4.RoutescallingcontrollermethodsRoute::get('/','WelcomeController@index');
ThisistellingLaraveltopassrequeststothatpathtotheindex()methodoftheApp\Http\Controllers\WelcomeControllercontroller.Thismethodwillbepassedthesameparametersandtreatedthesamewayasaclosureyoumight’vealternativelyputinitsplace.
RouteParametersIftherouteyou’redefininghasparameters—segmentsintheURLstructurethatarevariable—it’ssimpletodefinetheminyourrouteandpassthemtoyourclosure(seeExample3-5).
Example3-5.RouteparametersRoute::get('users/{id}/friends',function($id){
//
});
THENAMINGRELATIONSHIPBETWEENROUTEPARAMETERSANDCLOSURE/CONTROLLER METHODPARAMETERS
AsyoucanseeinExample3-5,it’smostcommontousethesamenamesforyourrouteparameters({id})andthemethodparameterstheyinjectintoyourroutedefinition(function($id)).Butisthisnecessary?
Unlessyou’reusingroute/modelbinding,no.Theonlythingthatdefineswhichrouteparametermatcheswithwhichmethodparameteristheirorder(lefttoright),asyoucanseehere:
Route::get('users/{userId}/comments/{commentId}',function(
$thisIsActuallyTheRouteId,
$thisisReallyTheCommentId
){
//
});
Thathavingbeensaid,justbecauseyoucanmakethemdifferentdoesn’tmeanyoushould.Irecommendkeepingthemthesameforthesakeoffuturedevelopers,whocouldgettrippedupbyinconsistentnaming.
Youcanalsomakeyourrouteparametersoptionalbyincludingaquestionmark(?)aftertheparametername,asillustratedinExample3-6.Inthiscase,youshouldalsoprovideadefaultvaluefortheroute’scorrespondingvariable.
Example3-6.OptionalrouteparametersRoute::get('users/{id?}',function($id='fallbackId'){
//
});
Andyoucanuseregularexpressions(regexes)todefinethatarouteshouldonlymatchifaparametermeetsparticularrequirements,asinExample3-7.
Example3-7.RegularexpressionrouteconstraintsRoute::get('users/{id}',function($id){
//
})->where('id','[0-9]+');
Route::get('users/{username}',function($username){
//
})->where('username','[A-Za-z]+');
Route::get('posts/{id}/{slug}',function($id,$slug){
//
})->where(['id'=>'[0-9]+','slug'=>'[A-Za-z]+']);
Asyou’veprobablyguessed,ifyouvisitapaththatmatchesaroutestring,buttheregexdoesn’tmatchtheparameter,itwon’tbematched.Sinceroutesarematchedtoptobottom,
users/abcwouldskipthefirstclosureinExample3-7,butitwouldbematchedbythesecondclosure,soitwouldgetroutedthere.Ontheotherhand,posts/abc/123wouldn’tmatchanyoftheclosures,soitwouldreturna404NotFounderror.
RouteNamesThesimplestwaytorefertotheserouteselsewhereinyourapplicationisjustbytheirpath.There’saurl()helpertosimplifythatlinkinginyourviews,ifyouneedit;seeExample3-8foranexample.Thehelperwillprefixyourroutewiththefulldomainofyoursite.
Example3-8.URLhelper<ahref="<?phpechourl('/');?>">
//outputs<ahref="http://myapp.com/">
However,Laravelalsoallowsyoutonameeachroute,whichenablesyoutorefertoitwithoutexplicitlyreferencingtheURL.Thisishelpfulbecauseitmeansyoucangivesimplenicknamestocomplexroutes,andalsobecauselinkingthembynamemeansyoudon’thavetorewriteyourfrontendlinksifthepathschange(seeExample3-9).
Example3-9.Definingroutenames//Definingaroutewithnameinroutes/web.php:
Route::get('members/{id}','MembersController@show')->name('members.show');
//Linktherouteinaviewusingtheroute()helper
<ahref="<?phpechoroute('members.show',['id'=>14]);?>">
Thisexampleillustratesafewnewconcepts.First,we’reusingfluentroutedefinitiontoaddthename,bychainingthename()methodaftertheget()method.Thismethodallowsustonametheroute,givingitashortaliastomakeiteasiertoreferenceelsewhere.
DEFININGCUSTOMROUTESINLARAVEL5.1Fluentroutedefinitionsdon’texistinLaravel5.1.You’llneedtoinsteadpassanarraytothesecondparameterofyourroutedefinition;checktheLaraveldocstoseemoreabouthowthisworks.Here’sExample3-9inLaravel5.1:
Route::get('members/{id}',[
'as'=>'members.show',
'uses'=>'MembersController@show'
]);
Inourexample,we’venamedthisroutemembers.show;resourcePlural.actionisacommonconventionwithinLaravelforrouteandviewnames.
ROUTENAMINGCONVENTIONS
Youcannameyourrouteanythingyou’dlike,butthecommonconventionistousethepluraloftheresourcename,thenaperiod,thentheaction.So,herearetheroutesmostcommonforaresourcenamedphoto:
photos.index
photos.create
photos.store
photos.show
photos.edit
photos.update
photos.destroy
Tolearnmoreabouttheseconventions,see“ResourceControllers”.
Wealsointroducedtheroute()helper.Justlikeurl(),it’sintendedtobeusedinviewstosimplifylinkingtoanamedroute.Iftheroutehasnoparameters,youcansimplypasstheroutename:(route('members.index'))andreceivearoutestringhttp://myapp.com/members/index).Ifithasparameters,passtheminasanarrayasthesecondparameterlikewedidinthisexample.
Ingeneral,Irecommendusingroutenamesinsteadofpathstorefertoyourroutes,andthereforeusingtheroute()helperinsteadoftheurl()helper.Sometimesitcangetabitclumsy—forexample,ifyou’reworkingwithmultiplesubdomains—butitprovidesanincrediblelevelofflexibilitytolaterchangetheapplication’sroutingstructurewithoutmajorpenalty.
PASSINGROUTEPARAMETERSTOTHEROUTE( ) HELPER
Whenyourroutehasparameters(e.g.,users/{id}),youneedtodefinethoseparameterswhenyou’reusingtheroute()helpertogeneratealinktotheroute.
Thereareafewdifferentwaystopasstheseparameters.Let’simaginearoutedefinedasusers/{userId}/comments/{commentId}.IftheuserIDis1andthecommentIDis2,let’slookatafewoptionswehaveavailabletous:
Option1:
route('users.comments.show',[1,2])
//http://myapp.com/users/1/comments/2
Option2:
route('users.comments.show',['userId'=>1,'commentId'=>2])
//http://myapp.com/users/1/comments/2
Option3:
route('users.comments.show',['commentId'=>2,'userId'=>1])
//http://myapp.com/users/1/comments/2
Option4:
route('users.comments.show',['userId'=>1,'commentId'=>2,'opt'=>'a'])
//http://myapp.com/users/1/comments/2?opt=a
Asyoucansee,nonkeyedarrayvaluesareassignedinorder;keyedarrayvaluesarematchedwiththerouteparametersmatchingtheirkeys,andanythingleftoverisaddedasaqueryparameter.
RouteGroupsOftenagroupofroutesshareaparticularcharacteristic—acertainauthenticationrequirement,apathprefix,orperhapsacontrollernamespace.Definingthesesharedcharacteristicsagainandagainoneachroutenotonlyseemstediousbutalsocanmuddyuptheshapeofyourroutesfileandobscuresomeofthestructuresofyourapplication.
Routegroupsallowyoutogroupseveralroutestogether,andapplyanysharedconfigurationsettingsoncetotheentiregroup,toreducethisduplication.Additionally,routegroupsarevisualcuestofuturedevelopers(andtoyourownbrain)thattheseroutesaregroupedtogether.
Togrouptwoormoreroutestogether,you“surround”theroutedefinitionswitharoutegroup,asshowninExample3-10.Inreality,you’reactuallypassingaclosuretothegroupdefinition,anddefiningthegroupedrouteswithinthatclosure.
Example3-10.DefiningaroutegroupRoute::group([],function(){
Route::get('hello',function(){
return'Hello';
});
Route::get('world',function(){
return'World';
});
});
Bydefault,aroutegroupdoesn’tactuallydoanything.There’snodifferencebetweenthegroupinExample3-10andseparatingasegmentofyourrouteswithcodecomments.Theemptyarraythat’sthefirstparameter,however,allowsyoutopassavarietyofconfigurationsettingsthatwillapplytotheentireroutegroup.
MiddlewareProbablythemostcommonuseforroutegroupsistoapplymiddlewaretoagroupofroutes.We’lllearnmoreaboutmiddlewareinChapter10,but,amongotherthings,they’rewhatLaravelusesforauthenticatingusersandrestrictingguestusersfromusingcertainpartsofasite.
InExample3-11,we’recreatingaroutegrouparoundthedashboardandaccountviewsandapplyingtheauthmiddlewaretoboth.Inthisexample,itmeansusershavetobeloggedintotheapplicationtoviewthedashboardortheaccountpage.
Example3-11.Restrictingagroupofroutestologged-inusersonlyRoute::group(['middleware'=>'auth'],function(){
Route::get('dashboard',function(){
returnview('dashboard');
});
Route::get('account',function(){
returnview('account');
});
});
APPLYINGMIDDLEWAREINCONTROLLERSOftenit’sclearerandmoredirecttoattachmiddlewaretoyourroutesinthecontrollerinsteadofattheroutedefinition.Youcandothisbycallingthemiddleware()methodintheconstructorofyourcontroller.Thestringyoupasstothemiddleware()methodisthenameofthemiddleware,andyoucanoptionallychainmodifiermethods(only()andexcept())todefinewhichmethodswillreceivethatmiddleware:
classDashboardControllerextendsController
{
publicfunction__construct()
{
$this->middleware('auth');
$this->middleware('admin-auth')
->only('admin');
$this->middleware('team-member')
->except('admin');
}
}
Notethat,ifyou’redoingalotof“only”and“except”customizations,that’softenasignthatyoushouldbreakoutanewcontrollerfortheexceptionalroutes.
PathPrefixesIfyouhaveagroupofroutesthatshareasegmentoftheirpath—forexample,ifyoursite’sAPIisprefixedwith/api—youcanuseroutegroupstosimplifythisstructure(seeExample3-12).
Example3-12.PrefixingagroupofroutesRoute::group(['prefix'=>'api'],function(){
Route::get('/',function(){
//Handlesthepath/api
});
Route::get('users',function(){
//Handlesthepath/api/users
});
});
Notethateachprefixedgroupalsohasa/routethatrepresentstherootoftheprefix—inExample3-12that’s/api.
SubdomainRoutingSubdomainroutingisthesameasrouteprefixing,butit’sscopedbysubdomaininsteadofrouteprefix.Therearetwoprimaryusesforthis.First,youmaywanttopresentdifferentsectionsoftheapplication(orentirelydifferentapplications)todifferentsubdomains.Example3-13showshowyoucanachievethis.
Example3-13.SubdomainroutingRoute::group(['domain'=>'api.myapp.com'],function(){
Route::get('/',function(){
//
});
});
Second,youmightwanttosetpartofthesubdomainasaparameter,asillustratedinExample3-14.Thisismostoftendoneincasesofmultitenancy(thinkSlackorHarvest,whereeachcompanygetsitsownsubdomain,liketighten.slack.co).
Example3-14.ParameterizedsubdomainroutingRoute::group(['domain'=>'{account}.myapp.com'],function(){
Route::get('/',function($account){
//
});
Route::get('users/{id}',function($account,$id){
//
});
});
Notethatanyparametersforthegroupgetpassedintothegroupedroutes’methodsasthefirstparameter(s).
NamespacePrefixesWhenyou’regroupingroutesbysubdomainorrouteprefix,it’slikelytheircontrollershaveasimilarPHPnamespace.IntheAPIexample,alloftheAPIroutes’controllersmightbeunderanAPInamespace.Byusingtheroutegroupnamespaceprefix,asshowninExample3-15,youcanavoidlongcontrollerreferencesingroupslike"API/ControllerA@index"and"API/ControllerB@index".
Example3-15.Routegroupnamespaceprefixes//App\Http\Controllers\ControllerA
Route::get('/','ControllerA@index');
Route::group(['namespace'=>'API'],function(){
//App\Http\Controllers\API\ControllerB
Route::get('api/','ControllerB@index');
});
NamePrefixesTheprefixesdon’tstopthere.It’scommonthatroutenameswillreflecttheinheritancechainofpathelements,sousers/comments/5willbeservedbyaroutenamedusers.comments.show.Inthiscase,it’scommontousearoutegrouparoundalloftheroutesthatarebeneaththeusers.commentsresource.
JustlikewecanprefixURLsegmentsandcontrollernamespaces,wecanalsoprefixstringstotheroutename.Withroutegroupnameprefixes,wecandefinethateveryroutewithinthisgroupshouldhaveagivenstringprefixedtoitsname.Inthiscontext,we’reprefixing"users."toeachroutename,then"comments."(seeExample3-16).
Example3-16.RoutegroupnameprefixesRoute::group(['as'=>'users.','prefix'=>'users'],function(){
Route::group(['as'=>'comments.','prefix'=>'comments'],function(){
//Routenamewillbeusers.comments.show
Route::get('{id}',function(){
//
})->name('show');
});
});
ViewsInafewoftherouteclosureswe’velookedatsofar,we’veseensomethingalongthelinesofreturnview('account').What’sgoingonhere?
Ifyou’renotfamiliarwiththeModel–View–Controller(MVC)pattern,views(ortemplates)arefilesthatdescribewhatsomeparticularoutputshouldlooklike.YoumighthaveviewsforJSONorXMLoremails,butthemostcommonviewsinawebframeworkoutputHTML.
InLaravel,therearetwoformatsofviewyoucanuseoutofthebox:plainPHP,orBladetemplates(seeChapter4).Thedifferenceisinthefilename:about.phpwillberenderedwiththePHPengine,andabout.blade.phpwillberenderedwiththeBladeengine.
THREEWAYSTOLOADAVIEW()Therearethreedifferentwaystoreturnaview.Fornow,justconcernyourselfwithview(),butifyoueverseeView::make(),it’sthesamething,andyoucouldalsoinjecttheIlluminate\View\ViewFactoryifyouprefer.
Onceyou’veloadedaview,youhavetheoptiontosimplyreturnit(asinExample3-17),whichwillworkfineiftheviewdoesn’trelyonanyvariablesfromthecontroller.
Example3-17.Simpleview()usageRoute::get('/',function(){
returnview('home');
});
Thiscodelooksforaviewinresources/views/home.blade.phporresources/views/home.php,andloadsitscontentsandparsesanyinlinePHPorcontrolstructuresuntilyouhavejusttheview’soutput.Onceyoureturnit,it’spassedontotherestoftheresponsestackandeventuallyreturnedtotheuser.
Butwhatifyouneedtopassinvariables?TakealookatExample3-18.
Example3-18.PassingvariablestoviewsRoute::get('tasks',function(){
returnview('tasks.index')
->with('tasks',Task::all());
});
Thisclosureloadstheresources/views/tasks/index.blade.phporresources/views/tasks/index.phpviewandpassesitasinglevariablenamedtasks,whichcontainstheresultoftheTask::all()method.Task::all()isanEloquentdatabasequerywe’lllearnaboutinChapter8.
UsingViewComposerstoShareVariableswithEveryViewSometimesitcanbecomeahassletopassthesamevariablesoverandover.Theremaybeavariablethatyouwantaccessibletoeveryviewinthesite,ortoacertainclassofviewsoracertainincludedsubview—forexample,allviewsrelatedtotasks,ortheheaderpartial.
It’spossibletosharecertainvariableswitheverytemplateorjustcertaintemplates,likeinthefollowingcode:
view()->share('variableName','variableValue');
Tolearnmore,checkout“ViewComposersandServiceInjection”.
ControllersI’vementionedcontrollersafewtimes,butuntilnowmostoftheexampleshaveshownrouteclosures.Ifyou’renotfamiliarwiththeMVCpattern(Figure3-1),controllersareessentiallyclassesthatorganizethelogicofoneormoreroutestogetherinoneplace.Controllerstendtogroupsimilarroutestogether,especiallyifyourapplicationisstructuredalongatraditionallyCRUD-likeformat;inthiscase,acontrollermighthandlealltheactionsthatcanbeperformedonaparticularresource.
Figure3-1.AbasicillustrationofMVC
WHATISCRUD?CRUDstandsforcreate,read,update,delete,whicharethefourprimaryoperationsthatwebapplicationsmostcommonlyprovideonaresource.Forexample,youcancreateanewblogpost,youcanreadthatpost,youcanupdateit,oryoucandeleteit.
Itmaybetemptingtocramalloftheapplication’slogicintothecontrollers,butit’sbettertothinkofcontrollersasthetrafficcopsthatrouteHTTPrequestsaroundyourapplication.Sincethereareotherwaysrequestscancomeintoyourapplication—cronjobs,Artisancommand-linecalls,queuejobs,etc.—it’swisetonotrelyoncontrollersformuchbehavior.Thismeansacontroller ’sprimaryjobistocapturetheintentofanHTTPrequestandpassitontotherestoftheapplication.
So,let’screateacontroller.OneeasywaytodothisiswithanArtisancommand,sofromthecommandlinerunthefollowing:
phpartisanmake:controllerTasksController
ARTISANANDARTISANGENERATORSLaravelcomesbundledwithacommand-linetoolcalledArtisan.Artisancanbeusedtorunmigrations,createusersandotherdatabaserecordsmanually,andperformmanyothermanual,one-timetasks.
Underthemakenamespace,Artisanprovidestoolsforgeneratingskeletonfilesforavarietyofsystemfiles.That’swhatallowsustorunphpartisanmake:controller.
TolearnmoreaboutthisandotherArtisanfeatures,seeChapter7.
ThiswillcreateanewfilenamedTasksController.phpinapp/Http/Controllers,withthecontentsshowninExample3-19.
Example3-19.Defaultgeneratedcontroller<?php
namespaceApp\Http\Controllers;
useIlluminate\Http\Request;
useApp\Http\Requests;
classTasksControllerextendsController
{
}
ModifythisfileasshowninExample3-20,creatinganewpublicmethodcalledhome().We’lljustreturnsometextthere.
Example3-20.Simplecontrollerexample<?php
useApp\Http\Controllers\Controller;
classTasksControllerextendsController
{
publicfunctionhome()
{
return'Hello,World!';
}
}
Thenlikewelearnedbefore,we’llhookuparoutetoit,asshowninExample3-21.
Example3-21.Routeforthesimplecontroller//routes/web.php
<?php
Route::get('/','TasksController@home');
That’sit.Visitthe/routeandyou’llseethewords“Hello,World!”
CONTROLLER NAMESPACING
InExample3-21wereferencedacontrollerwiththefullyqualifiedclassnameofApp\Http\Controllers\TasksController,butweonlyusedtheclassname.Thisisn’tbecausewecansimplyreferencecontrollersbytheirclassname.Rather,wecanignoretheApp\Http\Controllers\whenwereferencecontrollers;bydefault,Laravelisconfiguredtolookforcontrollerswithinthatnamespace.
ThismeansthatifyouhaveacontrollerwiththefullyqualifiedclassnameofApp\Http\Controllers\API\ExercisesController,you’dreferenceitinaroutedefinitionasAPI\ExercisesController.
Themostcommonuseofacontrollermethod,then,willbesomethinglikeExample3-22.
Example3-22.Commoncontrollermethodexample//TasksController.php
...
publicfunctionindex()
{
returnview('tasks.index')
->with('tasks',Task::all());
}
Thiscontrollermethodloadstheresources/views/tasks/index.blade.phporresources/views/tasks/index.phpviewandpassesitasinglevariablenamedtasks,whichcontainstheresultoftheTask::all()Eloquentmethod.
GENERATINGRESOURCECONTROLLERSIfyoueverusedphpartisanmake:controllerinLaravelpriorto5.3,youmightbeexpectingittoautogeneratemethodsforallofthebasicresourcerouteslikecreate()andupdate().YoucanbringthisbehaviorbackinLaravel5.3bypassingthe--resourceflagwhenyoucreatethecontroller:
phpartisanmake:controllerTasksController--resource
GettingUserInputThesecondmostcommonactiontoperforminacontrollermethodistotakeinputfromtheuserandactonit.Thatintroducesafewnewconcepts,solet’stakealookatabitofsamplecodeandwalkthroughthenewpieces.
First,let’sbinditquickly;seeExample3-23.
Example3-23.Bindingbasicformactions//routes/web.php
Route::get('tasks/create','TasksController@create');
Route::post('tasks','TasksController@store');
Noticethatwe’rebindingtheGETactionoftasks/create(whichshowstheform)andthePOSTactionoftasks/(whichiswherewePOSTwhenwe’recreatinganewtask).Wecanassumethecreate()methodinourcontrollerjustshowsaform,solet’slookatthestore()methodinExample3-24.
Example3-24.Commonforminputcontrollermethod//TasksController.php
...
publicfunctionstore()
{
$task=newTask;
$task->title=Input::get('title');
$task->description=Input::get('description');
$task->save();
returnredirect('tasks');
}
ThisexamplemakesuseofEloquentmodelsandtheredirect()functionality,andwe’lltalkaboutthemmorelater,butyoucanseewhatwe’redoinghere:wecreateanewTask,pulldataoutoftheuserinputandsetitonthetask,saveit,andthenredirectbacktothepagethatshowsalltasks.
TherearetwomainwaystogetuserinputfromaPOST:theInputfacade,whichweusedhere,andtheRequestobject,whichwe’lltalkaboutnext.
IMPORTINGFACADESIfyoufollowanyoftheseexamples,whetherincontrollersoranyotherPHPclassthatisnamespaced,youmightfinderrorsshowingthatthefacadecannotbefound.Thisisbecausethey’renotpresentineverynamespace,butratherthey’remadeavailableintherootnamespace.
So,inExample3-24,we’dneedtoimporttheInputfacadeatthetopofthefile.Therearetwowaystodothat:eitherwecanimport\Input,orwecanimportIlluminate\Support\Facades\Input.Forexample:
<?php
namespaceApp\Http\Controllers;
useIlluminate\Support\facades\Input;
classTasksController
{
publicfunctionstore()
{
$task=newTask;
$task->title=Input::get('title');
$task->description=Input::get('description');
$task->save();
returnredirect('tasks');
}
Asyoucansee,wecangetthevalueofanyuser-providedinformation,whetherfromaqueryparameteroraPOSTvalue,usingInput::get('fieldName').Soouruserfilledouttwofieldsonthe“addtask”page:“title”and“description.”WeretrievebothusingtheInputfacade,savethemtothedatabase,andthenreturn.
InjectingDependenciesintoControllersLaravel’sfacadespresentasimpleinterfacetothemostusefulclassesinLaravel’scodebase.Youcangetinformationaboutthecurrentrequestanduserinput,thesession,caches,andmuchmore.
Butifyouprefertoinjectyourdependencies,orifyouwanttouseaservicethatdoesn’thaveafacade,you’llneedtofindsomewaytobringinstancesoftheseclassesintoyourcontroller.
ThisisourfirstexposuretoLaravel’sservicecontainer.Fornow,ifthisisunfamiliar,youcanthinkaboutitasalittlebitofLaravelmagic;or,ifyouwanttoknowmoreabouthowit’sactuallyfunctioning,youcanskipaheadtoChapter11.
Allcontrollermethods(includingtheconstructors)areresolvedoutofLaravel’scontainer,whichmeansanythingyoutypehintthatthecontainerknowshowtoresolvewillbeautomaticallyinjected.
Asaniceexample,whatifyou’dpreferhavinganinstanceoftheRequestobjectinsteadofusingthefacade?JusttypehintIlluminate\Http\Requestinyourmethodparameters,likeinExample3-25.
Example3-25.Controllermethodinjectionviatypehinting//TasksController.php
...
publicfunctionstore(\Illuminate\Http\Request$request)
{
$task=newTask;
$task->title=$request->input('title');
$task->description=$request->input('description');
$task->save();
returnredirect('tasks');
}
So,you’vedefinedaparameterthatmustbepassedintothestore()method.Andsinceyoutypehintedit,andsinceLaravelknowshowtoresolvethatclassname,you’regoingtohavetheRequestobjectreadyforyoutouseinyourmethodwithnoworkonyourpart.Noexplicitbinding,noanythingelse—it’sjustthereasthe$requestvariable.
Bytheway,thisisactuallyhowIandmanyotherLaraveldevelopersprefertogettheuserinput:injectaninstanceoftheRequestandreadtheuserinputfromthere,insteadofrelyingontheInputfacade.
ResourceControllersSometimesnamingthemethodsinyourcontrollerscanbethehardestpartofwritingacontroller.Thankfully,LaravelhassomeconventionsforalloftheroutesofatraditionalREST/CRUDcontroller(calleda“resourcecontroller”inLaravel);additionally,itcomeswithageneratoroutoftheboxandaconvenienceroutedefinitionthatallowsyoutobindanentireresourcecontrolleratonce.
ToseethemethodsthatLaravelexpectsforaresourcecontroller,let’sgenerateanewcontrollerfromthecommandline:
phpartisanmake:controllerMySampleResourceController--resource
Nowopenapp/Http/Controllers/MySampleResourceController.php.You’llseeitcomesprefilledwithquiteafewmethods.Let’swalkoverwhateachrepresents.We’lluseaTaskasanexample.
ThemethodsofLaravel’sresourcecontrollersForeach,youcanseetheHTTPverb,theURL,thecontrollermethodname,andthe“name.”Table3-1showstheHTTPverb,theURL,thecontrollermethodname,andthe“name”foreachofthesedefaultmethods.
Table3-1.ThemethodsofLaravel’sresourcecontrollers
Verb URL Controllermethod Name Description
GET tasks index() tasks.index Showalltasks
GET tasks/create create() tasks.create Showthecreatetaskform
POST tasks store() tasks.store Acceptformsubmissionfromthecreatetaskform
GET tasks/{task} show() tasks.show Showonetask
GET tasks/{task}/edit edit() tasks.edit Editonetask
PUT/PATCH tasks/{task} update() tasks.update Acceptformsubmissionfromtheedittaskform
DELETE tasks/{task} destroy() tasks.destroy Deleteonetask
BindingaresourcecontrollerSo,we’veseenthatthesearetheconventionalroutenamestouseinLaravel,andalsothatit’seasytogeneratearesourcecontrollerwithmethodsforeachofthesedefaultroutes.Thankfully,youdon’thavetogenerateroutesforeachofthesecontrollermethodsbyhand,ifyoudon’twantto.Instead,there’satrickforthat,andit’scalled“resourcecontrollerbinding.”TakealookatExample3-26.
Example3-26.Resourcecontrollerbinding//routes/web.php
Route::resource('tasks','TasksController');
Thiswillautomaticallybindalloftheroutesforthisresourcetotheappropriatemethodnamesonthespecifiedcontroller.It’llalsonametheseroutesappropriately;forexample,theindex()methodonthetasksresourcecontrollerwillbenamedtasks.index.
ARTISANROUTE:LISTIfyoueverfindyourselfinasituationwhereyou’rewonderingwhatroutesyourcurrentapplicationhasavailable,there’satoolforthat:fromthecommandline,runphpartisanroute:listandyou’llgetalistingofalloftheavailableroutes(seeFigure3-2).
Figure3-2.phpartisanroute:listexample
RouteModelBindingOneofthemostcommonroutingpatternsisthatthefirstlineofanycontrollermethodtriestofindtheresourcewiththegivenID,likeinExample3-27.
Example3-27.GettingaresourceforeachrouteRoute::get('conferences/{id}',function($id){
$conference=Conference::findOrFail($id);
});
Laravelprovidesafeaturethatsimplifiesthispatterncalled“routemodelbinding.”Thisallowsyoutodefinethataparticularparametername(e.g.,{conference})willindicatetotherouteresolverthatitshouldlookupanEloquentrecordwiththatIDandthenpassitinastheparameterinsteadofjustpassingtheID.
Therearetwokindsofroutemodelbinding:implicitandcustom(orexplicit).
ImplicitRouteModelBindingThesimplestwaytouseroutemodelbindingistonameyourrouteparametersomethinguniquetothatmodel(e.g.,nameit$conferenceinsteadof$id),thentypehintthatparameterintheclosure/controllermethodandusethesamevariablenamethere.It’seasiertoshowthantodescribe,sotakealookatExample3-28.
Example3-28.UsinganexplicitroutemodelbindingRoute::get('conferences/{conference}',function(Conference$conference){
returnview('conferences.show')->with('conference',$conference);
});
Becausetherouteparameter({conference})isthesameasthemethodparameter($conference),andthemethodparameteristypehintedwithaConferencemodel(Conference$conference),Laravelseesthisasaroutemodelbinding.Everytimethisrouteisvisited,theapplicationwillassumethatwhateverispassedintotheURLinplaceof{conference}isanIDthatshouldbeusedtolookupaConference,andthenthatresultingmodelinstancewillbepassedintoyourclosureorcontrollermethod.
CUSTOMIZINGTHEROUTEKEYFORANELOQUENTMODELAnytimeanEloquentmodelislookedupviaaURLsegment(usuallybecauseofroutemodelbinding),thedefaultcolumnEloquentwilllookitupbyisitsprimarykey(ID).
TochangethecolumnyourEloquentmodelusesforURLlookups,addamethodtoyourmodelnamedgetRouteKeyName():
publicfunctiongetRouteKeyName()
{
return'slug';
}
Now,aURLlikeconferences/{conference}willexpecttogetthesluginsteadoftheID,andwillperformitslookupsaccordingly.
ImplicitroutemodelbindingwasaddedinLaravel5.2,soyouwon’thaveaccesstoitin5.1.
CustomRouteModelBindingTomanuallyconfigureroutemodelbindings,addalineliketheoneinExample3-29totheboot()methodinApp\Providers\RouteServiceProvider.
Example3-29.Addingaroutemodelbindingpublicfunctionboot(Router$router)
{
//Justallowstheparent'sboot()methodtostillrun
parent::boot($router);
//Performthebinding
$router->model('event',Conference::class);
}
You’venowdefinedthatwheneveraroutehasaparameterinitsdefinitionnamed{event},asdemonstratedinExample3-30,therouteresolverwillreturnaninstanceoftheConferenceclasswiththeIDofthatURLparameter.
Example3-30.UsinganexplicitroutemodelbindingRoute::get('events/{event}',function(Conference$event){
returnview('events.show')->with('event',$event);
});
RouteCachingIfyou’relookingtosqueezeeverymillisecondoutofyourloadtime,youmaywanttotakealookatroutecaching.OneofthepiecesofLaravel’sbootstrapthatcantakeanywherefromafewdozentoafewhundredmillisecondsisparsingtheroutes/*files,androutecachingspeedsupthisprocessdramatically.
Tocacheyourroutesfile,youneedtobeusingallcontrollerandresourceroutes(norouteclosures).Ifyourappisn’tusinganyrouteclosures,youcanrunphpartisanroute:cache,Laravelwillserializetheresultsofyourroutes/*files.Ifyouwanttodeletethecache,runphpartisanroute:clear.
Here’sthedrawback:Laravelwillnowmatchroutesagainstthatcachedfileinsteadofyouractualroutes/*files.Youcanmakeendlesschangestothosefiles,andtheywon’ttakeeffectuntilyourunroute:cacheagain.Thismeansyou’llhavetorecacheeverytimeyoumakeachange,whichintroducesalotofpotentialforconfusion.
Here’swhatIwouldrecommendinstead:sinceGitignorestheroutecachefilebydefaultanyway,consideronlyusingroutecachingonyourproductionserver,andrunthephpartisanroute:cachecommandeverytimeyoudeploynewcode(whetherviaaGitpost-deployhook,aForgedeploycommand,orasapartofwhateverotherdeploysystemyouuse).Thiswayyouwon’thaveconfusinglocaldevelopmentissues,butyourremoteenvironmentwillstillbenefitfromroutecaching.
FormMethodSpoofingSometimes,youneedtomanuallydefinewhichHTTPverbaformshouldsendas.HTMLformsonlyallowforGETorPOST,soifyouwantanyothersortofverb,you’llneedtospecifythatyourself.
AnIntroductiontoHTTPVerbsWe’vetalkedabouttheGETandPOSTHTTPverbsalready.Ifyou’renotfamiliarwithHTTPverbs,theothertwomostcommononesarePUTandDELETE,butthere’salsoHEAD,OPTIONS,PATCH,andtwoothersthatareprettymuchneverusedinnormalwebdevelopment,TRACEandCONNECT.
Here’sthequickrundown:GETrequestsaresourceandHEADasksforaheaders-onlyversionoftheGET,POSTcreatesaresource,PUToverwritesaresourceandPATCHmodifiesaresource,DELETEdeletesaresource,andOPTIONSaskstheserverwhichverbsareallowedatthisURL.
HTTPVerbsinLaravelAswe’veshownalready,youcandefinewhichverbsaroutewillmatchintheroutedefinitionusingRoute::get(),Route::post(),Route::any(),orRoute::match().YoucanalsomatchwithRoute::patch(),Route::put(),andRoute::delete().
ButhowdoesonesendarequestotherthanGETwithawebbrowser?First,themethodattributeinanHTMLformdeterminesitsHTTPverb:ifyourformhasamethodof"GET",itwillsubmitviaqueryparametersandaGETmethod;iftheformhasamethodof"POST",itwillsubmitviathepostbodyandaPOSTmethod.
JavaScriptframeworksmakeiteasytosendotherrequests,likeDELETEandPATCH.ButifyoufindyourselfneedingtosubmitHTMLformsinLaravelwithverbsotherthanGETorPOST,you’llneedtouseformmethodspoofing,whichisspoofingtheHTTPmethodinanHTMLform.
HTTPMethodSpoofinginHTMLFormsToinformLaravelthattheformyou’recurrentlysubmittingshouldbetreatedassomethingotherthanPOST,addahiddenvariablenamed_methodwiththevalueofeither"PUT","PATCH",or"DELETE",andLaravelwillmatchandroutethatformsubmissionasifitwereactuallyarequestwiththatverb.
TheforminExample3-31,sinceit’spassingLaravelthemethodof"DELETE",willmatchroutesdefinedwithRoute::delete()butnotthosewithRoute::post().
Example3-31.Formmethodspoofing<formaction="/tasks/5"method="POST">
<inputtype="hidden"name="_method"value="DELETE">
</form>
CSRFProtectionIfyou’vetriedtocreateandsubmitaforminaLaravelapplicationalready—includingtheforminExample3-31—you’velikelyrunintothedreadedTokenMismatchException.
Bydefault,allroutesinLaravelexcept“read-only”routes(thoseusingGET,HEAD,orOPTIONS)areprotectedagainstcross-siterequestforgery(CSRF)attacksbyrequiringatoken,intheformofaninputnamed_token,tobepassedalongwitheachrequest.Thistokenisgeneratedatthestartofeverysession,andeverynon–read-onlyroutecomparesthesubmitted_tokenagainstthesessiontoken.
WHATISCSRF?Across-siterequestforgeryiswhenonewebsitepretendstobeanother.Thegoalisforsomeonetohijackyourusers’accesstoyourwebsite,bysubmittingformsfromtheirwebsitetoyourwebsiteviathelogged-inuser’sbrowser.
ThebestwayaroundCSRFattacksistoprotectallinboundroutes—POST,DELETE,etc.—withatoken,whichLaraveldoesoutofthebox.
Youhavetwooptionsforgettingaroundthis.Thefirst,andpreferred,methodistoaddthe_tokeninputtoeachofyoursubmissions.InHTMLforms,that’ssimple;lookatExample3-32.
Example3-32.CSRFtokens<formaction="/tasks/5"method="POST">
<?phpechocsrf_field();?>
<!--or:-->
<inputtype="hidden"name="_token"value="<?phpechocsrf_token();?>">
</form>
InJavaScriptapplications,it’sabitmorework,butnotmuch.ThemostcommonsolutionforsitesusingJavaScriptframeworksistostorethetokenoneverypageina<meta>taglikethisone:
<metaname="csrf-token"content="<?phpechocsrf_token();?>"id="token">
Storingthetokenina<meta>tagmakesiteasytobindittothecorrectHTTPheader,whichyoucandooncegloballyforallrequestsfromyourJavaScriptframework,likeinExample3-33.
Example3-33.GloballybindingaheaderforCSRF//injQuery:
$.ajaxSetup({
headers:{
'X-CSRF-TOKEN':$('meta[name="csrf-token"]').attr('content')
}
});
//inVue:
Vue.http.interceptors.push((request,next)=>{
request.headers['X-CSRF-TOKEN']=
document.querySelector('#token').getAttribute('content');
next();
});
LaravelwillchecktheX-CSRF-TOKENoneveryrequest,andvalidtokenspassedtherewillmarktheCSRFprotectionassatisfied.
NotethattheVuesyntaxforCSRFinthisexampleisnotnecessaryifyou’reworkingwiththe5.3Vuebootstrap;italreadydoesthisworkforyou.
RedirectsSofartheonlythingswe’vereturnedfromacontrollermethodorroutedefinitionhavebeenviews.Butthereareafewotherstructureswecanreturntogivethebrowserinstructionsonhowtobehave.
First,let’scovertheredirect.Therearetwocommonwaystogeneratearedirect;we’llusetheredirectglobalhelperhere,butyoumaypreferthefacade.BothcreateaninstanceofIlluminate\Http\RedirectResponse,performsomeconveniencemethodsonit,andthenreturnit.Youcanalsodothismanually,butyou’llhavetodoalittlemoreworkyourself.TakealookatExample3-34toseeafewwaysyoucanreturnaredirect.
Example3-34.Differentwaystoreturnaredirect//Usingtheglobalhelpertogeneratearedirectresponse
Route::get('redirect-with-helper',function(){
returnredirect()->to('login');
});
//Usingtheglobalhelpershortcut
Route::get('redirect-with-helper-shortcut',function(){
returnredirect('login');
});
//Usingthefacadetogeneratearedirectresponse
Route::get('redirect-with-facade',function(){
returnRedirect::to('login');
});
Notethattheredirect()helperexposesthesamemethodsastheRedirectfacade,butitalsohasashortcut;ifyoupassparametersdirectlytothehelper,insteadofchainingmethodsafterit,it’sashortcuttotheto()redirectmethod.
redirect()->to()Themethodsignaturefortheto()methodforredirectslookslikethis:
functionto($to=null,$status=302,$headers=[],$secure=null)
$toisavalidinternalpath;$statusistheHTTPstatus(defaultingto302FOUND);$headersallowsyoutodefinewhichHTTPheaderstosendalongwithyourredirect;and$secureallowsyoutooverridethedefaultchoiceofhttpversushttps(whichisnormallysetbasedonyourcurrentrequestURL).Example3-35showsanotherexampleofitsuse.
Example3-35.redirect()->to()Route::get('redirect',function(){
returnredirect()->to('home');
//orsame,usingtheshortcut:
returnredirect('home');
});
redirect()->route()Theroute()methodisthesameastheto()method,butratherthanpointingtoaparticularpath,itpointstoaparticularroutename(seeExample3-36).
Example3-36.redirect()->route()Route::get('redirect',function(){
returnredirect()->route('conferences.index');
});
Notethat,sincesomeroutenamesrequireparameters,itsparameterorderisalittledifferent.route()hasanoptionalsecondparameterfortherouteparameters:
functionroute($to=null,$parameters=[],$status=302,$headers=[])
So,usingitmightlookalittlelikeExample3-37.
Example3-37.redirect()->route()withparametersRoute::get('redirect',function(){
returnredirect()->route('conferences.show',['conference'=>99]);
});
redirect()->back()Becauseofsomeofthebuilt-inconveniencesofLaravel’ssessionimplementation,yourapplicationwillalwayshaveknowledgeofwhattheuser ’spreviouslyvisitedpagewas.Thatopensuptheopportunityforaredirect()->()redirect,whichsimplyredirectstheusertowhateverpageshecamefrom.There’salsoaglobalshortcutforthis:back().
OtherRedirectMethodsTheredirectserviceprovidesothermethodsthatarelesscommonlyused,butstillavailable:
home()redirectstoaroutenamedhome.
refresh()redirectstothesamepagetheuseriscurrentlyon.
away()allowsforredirectingtoanexternalURLwithoutthedefaultURLvalidation.
secure()isliketo()withthesecureparametersetto"true".
action()allowsyoutolinktoacontrollerandmethodlikethis:redirect()->action('MyController@myMethod').
guest()isusedinternallybytheauthsystem(discussedinChapter9);whenauservisitsaroutehe’snotauthenticatedfor,thiscapturesthe“intended”routeandthenredirectstheuser(usuallytoaloginpage).
intended()isalsousedinternallybytheauthsystem;afterasuccessfulauthentication,thisgrabsthe“intended”URLstoredbytheguest()methodandredirectstheuserthere.
redirect()->with()Whenyou’reredirectinguserstodifferentpages,youoftenwanttopasscertaindataalongwiththem.Youcouldmanuallyflashthedatatothesession,butLaravelhassomeconveniencemethodstohelpyouwiththat.
Mostcommonly,youcanpassalongeitheranarrayofkeysandvaluesorasinglekeyandvalueusingwith(),likeinExample3-38.
Example3-38.RedirectwithdataRoute::get('redirect-with-key-value',function(){
returnredirect('dashboard')
->with('error',true);
});
Route::get('redirect-with-array',function(){
returnredirect('dashboard')
->with(['error'=>true,'message'=>'Whoops!']);
});
CHAININGMETHODSONREDIRECTSAswithmanyotherfacades,mostcallstotheRedirectfacadecanacceptfluentmethodchains,likethewith()callsinExample3-38.Learnmoreaboutfluencyin“WhatIsaFluentInterface?”.
YoucanalsousewithInput(),asinExample3-39,toredirectwiththeuser ’sforminputflashed;thisismostcommoninthecaseofavalidationerror,whereyouwanttosendtheuserbacktotheformshejustcamefrom.
Example3-39.RedirectwithforminputRoute::get('form',function(){
returnview('form');
});
Route::post('form',function(){
returnredirect('form')
->withInput()
->with(['error'=>true,'message'=>'Whoops!']);
});
TheeasiestwaytogettheflashedinputthatwaspassedwithwithInput()isusingtheold()helper,whichcanbeusedtogetalloldinput(old())orjustthevalueforaparticularkey(old('username'),withthesecondparameterasthedefaultifthereisnooldvalue).You’llcommonlyseethisinviews,whichallowsthisHTMLtobeusedbothonthe“create”andthe“edit”viewforthisform:
<inputname="username"value="<?=
old('username','Defaultusernameinstructionshere');
?>">
Speakingofvalidation,thereisalsoausefulmethodforpassingerrorsalongwitharedirectresponse:withErrors().Youcanpassitany“provider”oferrors,whichmaybeanerrorstring,anarrayoferrors,or,mostcommonly,aninstanceoftheIlluminateValidator,whichwe’llcoverinChapter10.Example3-40showsanexampleofitsuse.
Example3-40.RedirectwitherrorsRoute::post('form',function(){
$validator=Validator::make($request->all()),$this->validationRules);
if($validator->fails()){
returnredirect('form')
->withErrors($validator)
->withInput();
}
});
withErrors()automaticallysharesan$errorsvariablewiththeviewsofthepageit’sredirectingto,foryoutohandlehoweveryou’dlike.
THEVALIDATE()SHORTCUTINCONTROLLERMETHODSLikehowExample3-40looks?Ifyou’redefiningyourroutesinacontroller,there’sasimpleandpowerfultoolthatcleansupthatcode.Readmorein“validate()intheControllerUsingValidatesRequests”.
AbortingtheRequestAsidefromreturningviewsandredirects,themostcommonwaytoexitarouteistoabort.Thereareafewgloballyavailablemethods(abort(),abort_if(),andabort_unless()),whichoptionallytakeHTTPstatuscodes,amessage,andaheadersarrayasparameters.
AsExample3-41shows,abort_if()andabort_unless()takeafirstparameterthatisevaluatedforitstruthiness,andperformtheabortdependingontheresult.
Example3-41.403ForbiddenabortsRoute::post('something-you-cant-do',function(Illuminate\Http\Request){
abort(403,'Youcannotdothat!');
abort_unless($request->has('magicToken'),403);
abort_if($request->user()->isBanned,403);
});
CustomResponsesThereareafewotheroptionsavailableforustoreturn,solet’sgooverthemostcommonresponsesafterviews,redirects,andaborts.Justlikewithredirects,youcaneitherusetheresponse()helperortheResponsefacadetorunthesemethodson.
response()->make()IfyouwanttocreateanHTTPresponsemanually,justpassyourdataintothefirstparameterofresponse()->make():e.g.,returnresponse()->make('Hello,World!').Onceagain,thesecondparameteristheHTTPstatuscodeandthethirdisyourheaders.
response()->json()and->jsonp()TocreateaJSON-encodedHTTPresponsemanually,passyourJSON-ablecontent(arrays,collections,orwhateverelse)tothejson()method:e.g.,returnresponse()->json(User::all());.It’sjustlikemake(),exceptitjson_encodesyourcontentandsetstheappropriateheaders.
response()->download()and->file()Tosendafilefortheendusertodownload,passeitheranSplFileInfoinstanceorastringfilenametodownload(),withanoptionalsecondparameterofthefilename:e.g.,returnresponse()->download('file501751.pdf','myFile.pdf').
Todisplaythesamefileinthebrowser(ifit’saPDForanimageorsomethingelsethebrowsercanhandle),useresponse()->file()instead,whichtakesthesameparameters.
TestingInsomeothercommunities,theideaofunittestingcontrollermethodsiscommon,butwithinLaravel(andmostofthePHPcommunity),it’smostcommontorelyonapplicationtestingtotestthefunctionalityofroutes.
Forexample,toverifythataPOSTrouteworkscorrectly,wecanwriteatestlikeExample3-42.
Example3-42.WritingasimplePOSTroutetest//AssignmentTest.php
publicfunctiontest_post_creates_new_assignment()
{
$this->post('/assignments',[
'title'=>'Mygreatassignment'
]);
$this->seeInDatabase('assignments',[
'title'=>'Mygreatassignment'
]);
}
Didwedirectlycallthecontrollermethods?No.Butweensuredthatthegoalofthisroute—toreceiveaPOSTandsaveitsimportantinformationtothedatabase—wasmet.
Youcanalsousesimilarsyntaxtovisitarouteandverifythatcertaintextshowsuponthepage,orthatclickingcertainbuttonsdoescertainthings(seeExample3-43).
Example3-43.WritingasimpleGETroutetest//AssignmentTest.php
publicfunctiontest_list_page_shows_all_assignments()
{
$assignment=Assignment::create([
'title'=>'Mygreatassignment'
]);
$this->visit('assignments')
->dee(['Mygreatassignment']);
}
TL;DRLaravel’sroutesaredefinedinroutes/web.phpandroutes/api.php,whereyoucandefinetheexpectedpathforeachroute,whichsegmentsarestaticandwhichareparameters,whichHTTPverbscanaccesstheroute,andhowtoresolveit.Youcanalsoattachmiddlewaretoroutes,groupthem,andgivethemnames.
WhatisreturnedfromtherouteclosureorcontrollermethoddictateshowLaravelrespondstotheuser.Ifit’sastringoraview,it’spresentedtotheuser;ifit’sothersortsofdata,it’sconvertedtoJSONandpresentedtotheuser;andifit’saredirect,itforcesaredirect.
Laravelprovidesaseriesoftoolsandconveniencestosimplifycommonrouting-relatedtasksandstructures.Theseincluderesourcecontrollers,routemodelbinding,andformmethodspoofing.
Chapter4.BladeTemplating
Comparedtomostotherbackendlanguages,PHPactuallyfunctionsrelativelywellasatemplatinglanguage.Butithasitsshortcomings,andit’salsojustuglytobeusing<?phpinlineallovertheplace,soyoucanexpectmostmodernframeworkstoofferatemplatinglanguage.
LaraveloffersacustomtemplatingenginecalledBlade,whichisinspiredby.NET’sRazorengine.Itboastsaconcisesyntax,ashallowlearningcurve,apowerfulandintuitiveinheritancemodel,andeasyextensibility.
ForaquicklookatwhatwritingBladelookslike,checkoutExample4-1.
Example4-1.Bladesamples<h1>{{$group->title}}</h1>
{!!$group->heroImageHtml()!!}
@forelse($usersas$user)
•{{$user->first_name}}{{$user->last_name}}<br>
@empty
Nousersinthisgroup.
@endforelse
Asyoucansee,Bladeintroducesaconventioninwhichitscustomtags,called“directives,”[email protected]’llusedirectivesforallofyourcontrolstructuresandalsoforinheritanceandanycustomfunctionalityyouwanttoadd.
Blade’ssyntaxiscleanandconcise,soatitscoreit’sjustmorepleasantandtidytoworkwiththanthealternatives.Butthemomentyouneedanythingofanycomplexityinyourtemplates—nestedinheritance,complexconditionals,orrecursion—Bladestartstoreallyshine.JustlikethebestLaravelcomponents,ittakescomplexapplicationrequirementsandmakesthemeasyandaccessible.
Additionally,sinceallBladesyntaxiscompiledintonormalPHPcodeandthencached,it’sfastanditallowsyoutousenativePHPinyourBladefilesifyouwant.However,I’drecommmendavoidingusageofPHPifatallpossible—usuallyifyouneedtodoanythingthatyoucan’tdowithBladeoracustomBladedirective,itdoesn’tbelonginthetemplate.
USINGTWIGWITHLARAVELUnlikemanyotherSymfony-basedframeworks,Laraveldoesn’tuseTwigbydefault.Butifyou’rejustinlovewithTwig,there’saTwigBridgepackagethatmakesiteasytouseTwiginsteadofBlade.
EchoingDataAsyoucanseeinExample4-1,{{and}}areusedtowrapsectionsofPHPthatyou’dliketoecho.{{$variable}}issimilarto<?=$variable?>inplainPHP.
It’sdifferentinoneway,however,andyoumight’veguessedthisalready:BladeescapesallechoesbydefaultusingPHP’shtmlentities()toprotectyourusersfrommaliciousscriptinsertion.Thatmeans{{$variable}}isfunctionallyequivalentto<?=htmlentities($variable)?>.Ifyouwanttoechowithouttheescaping,use{!!and!!}instead.
{{AND}}WHENUSINGAFRONTENDTEMPLATINGFRAMEWORK
Youmight’venoticedthattheechosyntaxforBlade({{}})issimilartotheechosyntaxformanyfrontendframeworks.So,howdoesLaravelknowwhenyou’rewritingBladeversusHandlebars?
Bladewillignoreany{{that’[email protected],itwillparsethefirstofthefollowingexamples,butthesecondwillbeechoedoutdirectly:
//ParsedasBlade;thevalueof$bladeVariableisechoedtotheview
{{$bladeVariable}}
//@isremoved,and"{{handlebarsVariable}}"echoedtotheviewdirectly
@{{handlebarsVariable}}
ControlStructuresMostofthecontrolstructuresinBladewillbeveryfamiliar.ManydirectlyechothenameandstructureofthesametaginPHP.
Thereareafewconveniencehelpers,butingeneral,thecontrolstructuresjustlookcleanerthantheywouldinPHP.
ConditionalsFirst,let’stakealookatthecontrolstructuresthatallowforlogic.
@ifBlade’s@if($condition)compilesto<?phpif($condition):?>.@else,@elseif,and@endifalsocompiletotheexactsamesyntaxinPHP.TakealookatExample4-2forsomeexamples.
Example4-2.@if,@else,@elseif,and@endif@if(count($talks)===1)
Thereisonetalkatthistimeperiod.
@elseif(count($talks)===0)
Therearenotalksatthistimeperiod.
@else
Thereare{{count($talks)}}talksatthistimeperiod.
@endif
JustlikewiththenativePHPconditionals,youcanmixandmatchthesehowyouwant.Theydon’thaveanyspeciallogic;there’sliterallyaparserlookingforsomethingwiththeshapeof@if($condition)andreplacingitwiththeappropriatePHPcode.
@unlessand@endunless@unless,ontheotherhand,isanewsyntaxthatdoesn’thaveadirectequivalentinPHP.It’sthedirectinverseof@if.@unless($condition)isthesameas<?phpif(!$condition).SeeitinuseinExample4-3.
Example4-3.@unlessand@endunless@unless($user->hasPaid())
Youcancompleteyourpaymentbyswitchingtothepaymenttab.
@endunless
LoopsNext,let’stakealookattheloops.
@for,@foreach,and@while@for,@foreach,and@whileworkthesameinBladeastheydoinPHP;seeExamples4-4,4-5,and4-6.
Example4-4.@forand@endfor@for($i=0;$i<$talk->slotsCount();$i++)
Thenumberis{{$i}}<br>
@endfor
Example4-5.@foreachand@endforeach@foreach($talksas$talk)
•{{$talk->title}}({{$talk->length}}minutes)<br>
@endforeach
Example4-6.@whileand@endwhile@while($item=array_pop($items))
{{$item->orSomething()}}<br>
@endwhile
@forelse@forelseisa@foreachthatalsoallowsyoutoprograminafallbackiftheobjectyou’reiteratingoverisempty.Wesawitinactionatthestartofthischapter;Example4-7showsanotherexample.
Example4-7.@forelse@forelse($talksas$talk)
•{{$talk->title}}({{$talk->length}}minutes)<br>
@empty
Notalksthisday.
@endforelse
$LOOPWITHIN@FOREACHAND@FORELSEThe@[email protected]’snotavailableinPHPforeachloops:the$loopvariable.Usedwithina@foreachor@forelseloop,thisvariablewillreturnastdClassobjectwiththefollowingproperties:
index
The0-basedindexofthecurrentitemintheloop;0wouldmean“firstitem”
iteration
The1-basedindexofthecurrentitemintheloop;1wouldmean“firstitem”
remaining
Howmanyitemsremainintheloop;ifthecurrentitemisthefirstofthree,thiswillbe2
count
Thecountofitemsintheloop
first
Abooleanindicatingwhetherthisisthefirstitemintheloop
last
Abooleanindicatingwhetherthisisthelastitemintheloop
depth
Howmany“levels”deepthisloopis:1foraloop,2foraloopwithinaloop,etc.
parent
Areferencetothe$loopvariablefortheparentloopitem;ifthisloopiswithinanother@foreachloopotherwise,null
Here’sanexampleofhowtouseit:
<ul>
@foreach($pagesas$page)
<li>{{$loop->iteration}}:{{$page->title}}
@if($page->hasChildren())
<ul>
@foreach($page->children()as$child)
<li>{{$loop->parent->iteration}}.
{{$loop->iteration}}:
{{$child->title}}</li>
@endforeach
</ul>
@endif
</li>
@endforeach
</ul>
orIfyou’reeverunsurewhetheravariableisset,you’reprobablyusedtocheckingisset()onitbeforeechoingit,andechoingsomethingelseifit’snotset.Bladehasaconveniencehelper,or,thatdoesthisforyouandletsyousetadefaultfallback:{{$titleor"Default"}}willechothevalueof$titleifit’sset,or“Default”ifnot.
TemplateInheritanceBladeprovidesastructurefortemplateinheritancethatallowsviewstoextend,modify,andincludeotherviews.
Here’showinheritanceisstructuredwithBlade.
DefiningSectionswith@section/@showand@yieldLet’sstartwithatop-levelBladelayout,likeinExample4-8.Thisisthedefinitionofthegenericpagewrapperthatwe’lllaterplacepage-specificcontentinto.
Example4-8.Bladelayout<!--resources/views/layouts/master.blade.php-->
<html>
<head>
<title>MySite|@yield('title','HomePage')</title>
</head>
<body>
<divclass="container">
@yield('content')
</div>
@section('footerScripts')
<scriptsrc="app.js"></script>
@show
</body>
</html>
ThislooksabitlikeanormalHTMLpage,butyoucanseewe’veyieldedintwoplaces(titleandcontent),andwe’vedefinedasectioninathird(footerScripts).
WehavethreeBladedirectivesherethateachlookalittledifferent:@yield('title','HomePage')alone,@yield('content')withadefineddefault,and@section...@showwithactualcontentinit.
Allthreefunctionessentiallythesame.Allthreearedefiningthatthere’sasectionwithagivenname(whichisthefirstparameter).Allthreearedefiningthatthesectioncanbeextendedlater.Andallthreearedefiningwhattodoifthesectionisn’textended,eitherbyprovidingastringfallback('HomePage'),nofallback(whichwilljustnotshowanythingifit’snotextended),oranentireblockfallback(inthiscase,<scriptsrc="app.js"></script>).
What’sdifferent?Well,clearly,@yield('content')hasnodefaultcontent.Butadditionally,thedefaultcontentin@yield('title')onlywillbeshownifit’sneverextended.Ifitisextended,itschildsectionswillnothaveprogrammaticaccesstothedefaultvalue.@section...@show,ontheotherhand,isbothdefiningadefaultanddoingsoinawaythatitsdefaultcontentswillbeavailabletoitschildren,through@parent.
Onceyouhaveaparentlayoutlikethis,youcanextenditlikeinExample4-9.
Example4-9.ExtendingaBladelayout<!--resources/views/dashboard.blade.php-->
@extends('layouts.master')
@section('title','Dashboard')
@section('content')
Welcometoyourapplicationdashboard!
@endsection
@section('footerScripts')
@parent
<scriptsrc="dashboard.js"></script>
@endsection
@SHOWVERSUS@ENDSECTIONYoumayhavenoticedthatExample4-8uses@section...@show,butExample4-9uses@[email protected]’sthedifference?
Use@showwhenyou’redefiningtheplaceforasection,intheparenttemplate.Use@endsectionwhenyou’redefiningthecontentforatemplateinachildtemplate.
ThischildviewwillactuallyallowustocoverafewnewconceptsinBladeinheritance.
@extendsFirst,with@extends('layouts.master'),wedefinethatthisviewshouldnotberenderedonitsown,butthatitinsteadextendsanotherview.Thatmeansitsroleistodefinethecontentofvarioussections,butnottostandalone.It’salmostmorelikeaseriesofbucketsofcontent,ratherthananHTMLpage.Thislinealsodefinesthattheviewit’sextendinglivesatresources/views/layouts/master.blade.php.
Eachfileshouldonlyextendoneotherfile,andthe@extendscallshouldbethefirstlineofthefile.
@sectionand@endsectionSecond,with@section('title','Dashboard'),weprovideourcontentforthefirstsection,title.Sincethecontentissoshort,insteadofusing@sectionand@endsectionwe’rejustusingashortcut.Thisallowsustopassthecontentinasthesecondparameterof@sectionandthenmoveon.Ifit’sabitdisconcertingtosee@sectionwithout@endsection,youcouldjustusethenormalsyntax.
Third,with@section('content')andon,weusethenormalsyntaxtodefinethecontentsxofthecontentsection.We’lljustthrowalittlegreetinginfornow.Note,however,thatwhenyou’reusing@sectioninachildview,youenditwith@endsection(oritsalias@stop),insteadof@show,whichisreservedfordefiningsectionsinparentviews.
@parentFourth,with@section('footerScripts')andon,weusethenormalsyntaxtodefinethecontentsofthefooterScriptssection.
Butremember,weactuallydefinedthatcontent(or,atleast,its“default”)alreadyinthemasterlayout.Sothistime,wehavetwooptions:wecaneitheroverwritethecontentfromtheparentview,orwecanaddtoit.
Youcanseethatwehavetheoptiontoincludethecontentfromtheparentbyusingthe@parentdirectivewithinthesection.Ifwedidn’t,thecontentofthissectionwouldentirelyoverwriteanythingdefinedintheparentforthissection.
@includeNowthatwe’veestablishedthebasicsofinheritance,thereareafewmoretrickswecanperform.
Whatifwe’reinaviewandwanttopullinanotherview?Maybewehaveacall-to-action“Signup”buttonthatwewanttore-usearoundthesite.Andmaybewewanttocustomizeitsbuttontexteverytimeweuseit.TakealookatExample4-10.
Example4-10.Includingviewpartialswith@include<!--resources/views/home.blade.php-->
<divclass="content"data-page-name="{{$pageName}}">
<p>Here'swhyyoushouldsignupforourapp:<strong>It'sGreat.</strong></p>
@include('sign-up-button',['text'=>'Seejusthowgreatitis'])
</div>
<!--resources/views/sign-up-button.blade.php-->
<aclass="buttonbutton--callout"data-page-name="{{$pageName}}">
<iclass="exclamation-icon"></i>{{$text}}
</a>
@includepullsinthepartialand,optionally,passesdataintoit.Notethatnotonlycanyouexplicitlypassdatatoanincludeviathesecondparameterof@include,butyoucanalsoreferenceanyvariableswithintheincludedfilethatareavailabletotheincludingview($pageName,inthisexample).Onceagain,youcandowhateveryouwant,butIwouldrecommendyouconsideralwaysexplicitlypassingeveryvariablethatyouintendtouse,justforclarity.
@eachYoucanprobablyimaginesomecircumstancesinwhichyou’dneedtoloopoveranarrayorcollectionand@includeapartialforeachitem.There’sadirectiveforthat:@each.
Let’ssaywehaveasidebarcomposedofmodules,andwewanttoincludemultiplemodules,eachwithadifferenttitle.TakealookatExample4-11.
Example4-11.Usingviewpartialsinaloopwith@each<!--resources/views/sidebar.blade.php-->
<divclass="sidebar">
@each('partials.module',$modules,'module','partials.empty-module')
</div>
<!--resources/views/partials/module.blade.php-->
<divclass="sidebar-module">
<h1>{{$module->title}}</h1>
</div>
<!--resources/views/partials/empty-module.blade.php-->
<divclass="sidebar-module">
Nomodules:(
</div>
Considerthat@eachsyntax.Thefirstparameteristhenameoftheviewpartial.Thesecondisthearrayorcollectiontoiterateover.Thethirdisthevariablenamethateachitem(inthiscase,eachelementinthe$modulesarray)willbepassedtotheviewas.Andtheoptionalfourthparameteristheviewtoshowifthearrayorcollectionisempty(or,optionally,youcanpassastringinherethatwillbeusedasyourtemplate).
ViewComposersandServiceInjectionAswecoveredinChapter3,it’ssimpletopassdatatoourviewsfromtheroutedefinition(seeExample4-12).
Example4-12.ReminderonhowtopassdatatoviewsRoute::get('passing-data-to-views',function(){
returnview('dashboard')
->with('key','value');
});
Therearetimes,however,whenyouwillfindyourselfpassingthesamedataoverandovertomultipleviews.Or,youmightfindyourselfusingaheaderpartialorsomethingsimilarthatrequiressomedata;willyounowhavetopassthatdatainfromeveryroutedefinitionthatmighteverloadthatheaderpartial?
BindingDatatoViewsUsingViewComposersThankfully,there’sasimplerway.Thesolutioniscalledaviewcomposer,anditallowsyoutodefinethatanytimeaparticularviewloads,itshouldhavecertaindatapassedtoit—withouttheroutedefinitionhavingtopassthatdatainexplicitly.
Let’ssayyouhaveasidebaroneverypage,whichisdefinedinapartialnamedpartials.sidebar(resources/views/partials/sidebar.blade.php)andthenincludedoneverypage.Thissidebarshowsalistofthelastsevenpoststhatwerepublishedonyoursite.Ifit’soneverypage,everyroutedefinitionwouldnormallyhavetograbthatlistandpassitin,likeinExample4-13.
Example4-13.PassingsidebardatainfromeveryrouteRoute::get('home',function(){
returnview('home')
->with('posts',Post::recent());
});
Route::get('about',function(){
returnview('about')
->with('posts',Post::recent());
});
Thatcouldgetannoyingquickly.Instead,we’regoingtouseviewcomposersto“share”thatvariablewithaprescribedsetofviews.Wecandothisafewways,solet’sstartsimpleandmoveup.
SharingavariablegloballyFirst,thesimplestoption:justglobally“share”avariablewitheveryviewinyourapplicationlikeinExample4-14.
Example4-14.Sharingavariableglobally//Someserviceprovider
publicfunctionboot()
{
...
view()->share('posts',Post::recent());
}
Ifyouwanttouseview()->share(),thebestplacewouldbetheboot()methodofaserviceprovidersothatthebindingrunsoneverypageload.YoucancreateacustomViewComposerServiceProvider(seeChapter11formoreaboutserviceproviders),butfornowjustputitinApp\Providers\AppServiceProviderintheboot()method.
Usingview()->share()makesthevariableaccessibletoeveryviewintheentireapplication,however,soitmightbeoverkill.
Closure-basedviewcomposersThenextoptionistouseaclosure-basedviewcomposertosharevariableswithasingleview,likeinExample4-15.
Example4-15.Creatingaclosure-basedviewcomposerview()->composer('partials.sidebar',function($view){
$view->with('posts',Post::recent());
});
Asyoucansee,we’vedefinedthenameoftheviewwewantitsharedwithinthefirstparameter(partials.sidebar)andthenpassedaclosuretothesecondparameter;intheclosure,we’veused$view->with()toshareavariable,butnowonlywithaspecificview.
VIEWCOMPOSERSFORMULTIPLEVIEWSAnywhereaviewcomposerisbindingtoaparticularview(likeinExample4-15,whichbindstopartials.sidebar),youcanpassanarrayofviewnamesinsteadtobindtomultipleviews.
Youcanalsouseanasteriskintheviewpath,asinpartials.*,tasks.*,orjust*:
view()->composer(
['partials.header','partials.footer'],
function(){
$view->with('posts',Post::recent());
}
);
view()->composer('partials.*',function(){
$view->with('posts',Post::recent());
});
Class-basedviewcomposersFinally,themostflexiblebutalsomostcomplexoptionistocreateadedicatedclassforyourviewcomposer.
First,let’screatetheviewcomposerclass.There’snoformallydefinedplaceforviewcomposerstolive,butthedocsrecommendApp\Http\ViewComposers.So,let’screateApp\Http\ViewComposers\RecentPostsComposerlikeinExample4-16.
Example4-16.Aviewcomposer<?php
namespaceApp\Http\ViewComposers;
useApp\Post;
useIlluminate\Contracts\View\View;
classRecentPostsComposer
{
private$posts;
publicfunction__construct(Post$posts)
{
$this->posts=$posts;
}
publicfunctioncompose(View$view)
{
$view->with('posts',$this->posts->recent());
}
}
Asyoucansee,we’reinjectingthePostmodel(typehintedconstructorparametersofviewcomposerswillbeautomaticallyinjected;seeChapter11formoreonthecontaineranddependencyinjection).Notethatwecouldskiptheprivate$postsandtheconstructorinjectionandjustusePost::recent()inthecompose()methodifwewanted.Then,whenthiscomposeriscalled,itrunsthecompose()method,inwhichwebindthepostsvariabletotheresultofrunningtherecent()method.
Liketheothermethodsofsharingvariables,thisviewcomposerneedstohaveabindingsomewhere.Again,you’dlikelycreateacustomViewComposerServiceProvider,butfornow,asseeninExample4-17,we’lljustputitintheboot()methodofApp\Providers\AppServiceProvider.
Example4-17.RegisteringaviewcomposerinAppServiceProvider//AppServiceProvider
publicfunctionboot()
{
...
view()->composer(
'partials.sidebar',
\App\Http\ViewComposers\RecentPostsComposer::class
);
}
Notethatthisbindingisthesameasaclosure-basedviewcomposer,butinsteadofpassingaclosure,we’repassingtheclassnameofourviewcomposer.Now,everytimeBladerendersthepartials.sidebarview,it’llautomaticallyrunourproviderandpasstheviewapostsvariablesettotheresultsoftherecent()methodonourPostmodel.
BladeServiceInjectionTherearethreeprimarytypesofdatawe’remostlikelytoinjectintoaview:collectionsofdatatoiterateover,singleobjectsthatwe’redisplayingonthepage,andservicesthatgeneratedataorviews.
Withaservice,thepatternwillmostlikelylooklikeExample4-18,whereweinjectaninstanceofouranalyticsserviceintotheroutedefinitionbytypehintingitintheroute’smethodsignature,andthenpassitintotheview.
Example4-18.InjectingservicesintoaviewviatheroutedefinitionconstructorRoute::get('backend/sales',function(AnalyticsService$analytics){
returnview('backend.sales-graphs')
->with('analytics',$analytics);
});
Justaswithviewcomposers,Blade’sserviceinjectionoffersaconvenientshortcuttoreduceduplicationinyourroutedefinitions.Normally,thecontentofaviewusingouranalyticsservicemightlooklikeExample4-19.
Example4-19.Usinganinjectednavigationserviceinaview<divclass="finances-display">
{{$analytics->getBalance()}}/{{$analytics->getBudget()}}
</div>
Bladeserviceinjectionmakesiteasytoinjectaninstanceofaclassoutsideofthecontainerdirectlyfromtheview,likeinExample4-20.
Example4-20.Injectingaservicedirectlyintoaview@inject('analytics','App\Services\Analytics')
<divclass="finances-display">
{{$analytics->getBalance()}}/{{$analytics->getBudget()}}
</div>
Asyoucansee,this@injectdirectivehasactuallymadean$analyticsvariableavailable,whichwe’reusinglaterinourview.
Thefirstparameterof@injectisthenameofthevariableyou’reinjecting,andthesecondparameteristheclassorinterfacethatyouwanttoinjectaninstanceof.ThisisresolvedjustlikewhenyoutypehintadependencyinaconstructorelsewhereinLaravel;ifyou’reunfamiliarwithhowthatworks,gotoChapter11tolearnmore.
Justlikeviewcomposers,Bladeserviceinjectionmakesiteasytomakecertaindataorfunctionalityavailabletoeveryinstanceofaview,withouthavingtoinjectitviatheroutedefinitioneverytime.
CustomBladeDirectivesAllofthebuilt-insyntaxofBladethatwe’vecoveredsofar—@if,@unless,andsoon—arecalleddirectives.EachBladedirectiveisamappingbetweenapattern(e.g.,@if($condition))andaPHPoutput(e.g.,<?phpif($condition):?>).
Directivesaren’tjustforthecore;youcanactuallycreateyourown.Youmightthinkdirectivesaregoodformakinglittleshortcutstobiggerpiecesofcode—forexample,using@button('buttonName')andhavingitexpandtoalargersetofbuttonHTML.Thisisn’taterribleidea,butforsimplecodeexpansionlikethisyoumightbebetteroffincludingaviewpartial.
I’vefoundcustomdirectivestobethemostusefulwhentheysimplifysomeformofrepeatedlogic.Saywe’retiredofhavingtowrapourcodewith@if(auth()->guest())(tocheckifauserisloggedinornot)[email protected],itmightbeworthhavingacustomserviceprovidertoregisterthese,butfornowlet’sjustputitintheboot()methodofApp\Providers\AppServiceProvider.TakealookatExample4-21toseewhatthisbindingwilllooklike.
Example4-21.BindingacustomBladedirective//AppServiceProvider
publicfunctionboot()
{
Blade::directive('ifGuest',function(){
return"<?phpif(auth()->guest()):?>";
});
}
We’venowregisteredacustomdirective,@ifGuest,whichwillbereplacedwiththePHPcode<?phpif(auth()->guest()):?>.
Thismightfeelstrange.You’rewritingastringthatwillbereturnedandthenexecutedasPHP.Butwhatthismeansisthatyoucannowtakethecomplex,orugly,orunclear,orrepetitiveaspectsofyourPHPtemplatingcodeandhidethembehindclear,simple,andexpressivesyntax.
CUSTOMDIRECTIVERESULTCACHINGYoumightbetemptedtodosomelogictomakeyourcustomdirectivefasterbyperforminganoperationinthebindingandthenembeddingtheresultwithinthereturnedstring:
Blade::directive('ifGuest',function(){
//Antipattern!Donotcopy.
$ifGuest=auth()->guest();
return"<?phpif({$ifGuest}):?>";
});
Theproblemwiththisideaisthatitassumesthisdirectivewillbere-createdoneverypageload.However,Bladecachesaggressively,soyou’regoingtofindyourselfinabadspotifyoutrythis.
ParametersinCustomBladeDirectivesWhatifyouwanttocheckaconditioninyourcustomlogic?CheckoutExample4-22.
Example4-22.CreatingaBladedirectivewithparameters//Binding
Blade::directive('newlinesToBr',function($expression){
return"<?phpechonl2br({$expression});?>";
});
//Inuse
<p>@newlinesToBr($message->body)</p>
The$expressionparameterreceivedbytheclosurerepresentswhatever ’swithintheparentheses.Asyoucansee,wethengenerateavalidPHPcodesnippetandreturnit.
$EXPRESSIONPARAMETERSCOPINGBEFORELARAVEL5.3BeforeLaravel5.3,the$expressionparameteralsoincludedtheparenthesesthemselves.So,inExample4-22,$expression(whichis$message->bodyinLaravel5.3andlater)wouldhaveinsteadbeen($message->body),andwewould’vehadtowrite<?phpechonl2br{$expression};?>.
Ifyoufindyourselfconstantlywritingthesameconditionallogicoverandover,youshouldconsideraBladedirective.
Example:UsingCustomBladeDirectivesforaMultitenantAppSo,let’simaginewe’rebuildinganapplicationthatsupportsmultitenancy,whichmeansusersmightbevisitingthesitefromwww.myapp.com,client1.myapp.com,client2.myapp.com,orelsewhere.
SupposewehavewrittenaclasstoencapsulatesomeofourmultitenancylogicandnameditContext.Thisclasswillcaptureinformationandlogicaboutthecontextofthecurrentvisit,suchaswhotheauthenticateduserisandwhethertheuserisvisitingthepublicwebsiteoraclientsubdomain.
We’llprobablyfrequentlyresolvethatContextclassinourviewsandperformconditionalsonit,likeinExample4-23.Theapp('context')isashortcuttogetaninstanceofaclassfromthecontainer,whichwe’lllearnmoreaboutinChapter11.
Example4-23.ConditionalsoncontextwithoutacustomBladedirective@if(app('context')->isPublic())
©CopyrightMyAppLLC
@else
©Copyright{{app('context')->client->name}}
@endif
Whatifwecouldsimplify@if(app('context')->isPublic())tojust@ifPublic?Let’sdoit.CheckoutExample4-24.
Example4-24.ConditionalsoncontextwithacustomBladedirective//Binding
Blade::directive('ifPublic',function(){
return"<?phpif(app('context')->isPublic()):?>";
});
//Inuse
@ifPublic
©CopyrightMyAppLLC
@else
©Copyright{{app('context')->client->name}}
@endif
Sincethisresolvestoasimpleifstatement,wecanstillrelyonthenative@[email protected],wecouldalsocreateacustom@elseIfClientdirective,oraseparate@ifClientdirective,orreallywhateverelsewewant.
TestingThemostcommonmethodoftestingviewsisthroughapplicationtesting,meaningthatyou’reactuallycallingtheroutethatdisplaystheviewsandensuringtheviewshavecertaincontent(seeExample4-25).Youcanalsoclickbuttonsorsubmitformsandensurethatyouareredirectedtoacertainpage,orthatyouseeacertainerror.(You’lllearnmoreabouttestinginChapter12.)
Example4-25.Testingthataviewdisplayscertaincontent//EventsTest.php
publicfunctiontest_list_page_shows_all_events()
{
$event1=factory(Event::class)->create();
$event2=factory(Event::class)->create();
$this->visit('events')
->see($event1->title)
->see($event2->title);
}
Youcanalsotestthatacertainviewhasbeenpassedaparticularsetofdata,which,ifitaccomplishesyourtestinggoals,islessfragilethancheckingforcertaintextonthepage.Example4-26demonstratesthisapproach.
Example4-26.Testingthataviewwaspassedcertaincontent//EventsTest.php
publicfunctiontest_list_page_shows_all_events()
{
$event1=factory(Event::class)->create();
$event2=factory(Event::class)->create();
$this->visit('events');
$this->assertViewHas('events',Event::all());
$this->assertViewHasAll([
'events'=>Event::all(),
'title'=>'EventsPage'
]);
$this->assertViewMissing('dogs');
}
In5.3,wegainedtheabilitytopassaclosureto$assertViewHas(),meaningwecancustomizehowwewanttocheckmorecomplexdatastructures.Example4-27illustrateshowwemightusethis.
Example4-27.PassingaclosuretoassertViewHas()//EventsTest.php
publicfunctiontest_list_page_shows_all_events()
{
$event1=factory(Event::class)->create();
$this->visit('events/'.$event1->id);
$this->assertViewHas('event',function($event)use($event1){
return$event->id===$event1->id;
});
}
TL;DRBladeisLaravel’stemplatingengine.Itsprimaryfocusisaclear,concise,andexpressivesyntaxwithpowerfulinheritanceandextensibility.Its“safeecho”bracketsare{{and}},itsunprotectedechobracketsare{!!and!!},andithasaseriesofcustomtagscalleddirectivesthatallbeginwith@(@ifand@unless,forexample).
Youcandefineaparenttemplateandleave“holes”initforcontentusing@yieldand@section/@show.Youcanthenteachitschildviewstoextenditusing@extends('parent.view.name'),anddefinetheirsectionsusing@section/@endsection.Youuse@parenttoreferencethecontentoftheblock’sparent.
Viewcomposersmakeiteasytodefinethat,everytimeaparticularvieworsubviewloads,itshouldhavecertaininformationavailabletoit.Andserviceinjectionallowstheviewitselftorequestdatastraightfromtheapplicationcontainer.
Chapter5.FrontendComponents
LaravelisprimarilyaPHPframework,butitalsohasaseriesofcomponentsfocusedongeneratingfrontendcode.Someofthese,likepaginationandmessagebags,arePHPhelpersthattargetthefrontend,butLaravelalsoprovidesaGulp-basedbuildsystemcalledElixirandsomeconventionsaroundnon-PHPassets.
SinceElixirisatthecoreofthenon-PHPfrontendcomponents,let’sstartthere.
ElixirElixir(nottobeconfusedwiththefunctionalprogramminglanguage)isabuildtoolthatprovidesasimpleuserinterfaceandaseriesofconventionsontopofGulp.Elixir ’scorefeatureissimplifyingthemostcommonGulptasksbymeansofacleanerAPIandaseriesofnamingandapplicationstructureconventions.
AQUICKINTRODUCTIONTOGULP
GulpisaJavaScripttooldesignedforcompilingstaticassetsandcoordinatingotherstepsofyourbuildprocess.
GulpissimilartoGrunt,Rake,ormake—itallowsyoutodefineanaction(calleda“task”inGulp)orseriesofactionstotakeeverytimeyoubuildyourapplication.ThiswillcommonlyincluderunningaCSSpreprocessorlikeSassorLESS,copyingfiles,concatenatingandminifyingJavaScript,andmuchmore.
Gulp,andthereforeElixir,isbasedontheideaofstreams.Mosttaskswillbeginbyloadingsomefilesintothestreambuffer,andthenthetaskwillapplytransformationstothecontent—preprocessit,minifyit,andthenmaybesavethecontenttoanewfile.
Atitscore,ElixirisjustatoolinyourGulptoolbox.Thereisn’tevensuchathingasanElixirfile;you’lldefineyourElixirtasksinyourgulpfile.js.ButtheylookalotdifferentfromvanillaGulptasks,andyou’llhavetodoalotlessworktogetthemrunningoutofthebox.
Let’slookatacommonexample:runningSasstopreprocessyourCSSstyles.InanormalGulpenvironment,thatmightlookalittlebitlikeExample5-1.
Example5-1.CompilingaSassfileinGulpvargulp=require('gulp'),
sass=require('gulp-ruby-sass'),
autoprefixer=require('gulp-autoprefixer'),
rename=require('gulp-rename'),
notify=require('gulp-notify'),
livereload=require('gulp-livereload'),
lr=require('tiny-lr'),
server=lr();
gulp.task('sass',function(){
returngulp.src('resources/assets/sass/app.scss')
.pipe(sass({
style:'compressed',
sourcemap:true
}))
.pipe(autoprefixer('last2version','ie9','ios6'))
.pipe(gulp.dest('public/css'))
.pipe(rename({suffix:'.min'}))
.pipe(livereload(server))
.pipe(notify({
title:"Karani",
message:"Stylestaskcomplete."
}));
});
Now,I’veseenworse.Itreadswell,andit’sclearwhat’sgoingon.Butthere’salothappeningthatyou’lljustpullintoeverysiteyouevermake.Itcangetconfusingandrepetitive.
Let’strythatsametaskinElixir(Example5-2).
Example5-2.CompilingaSassfileinElixir
varelixir=require('laravel-elixir');
elixir(function(mix){
mix.sass('app.scss');
});
That’sit.Thatcoversallthebasics—preprocessing,notification,folderstructure,autoprefixing,andmuchmore.
ES6INELIXIR6Elixir6,whichcameoutwithLaravel5.3,changedalotofthesyntaxtouseES6,thelatestversionofJavaScript.Here’swhatExample5-2lookslikeinElixir6:
constelixir=require('laravel-elixir');
elixir(mix=>{
mix.sass('app.scss')
});
Don’tworry;thisdoesexactlythesamething.
ElixirFolderStructureMuchofElixir ’ssimplicitycomesfromtheassumeddirectorystructure.Whymakethedecisionfreshineverynewapplicationaboutwherethesourceandcompiledassetslive?JuststickwithElixir ’sconvention,andyouwon’thavetothinkaboutiteveragain.
EverynewLaravelappcomeswitharesourcesfolderwithanassetssubfolder,whichiswhereElixirwillexpectyourfrontendassetstolive.YourSasswillliveinresources/assets/sass,oryourLESSinresources/assets/less,andyourJavaScriptwillliveinresources/assets/js.Thesewillexporttopublic/cssandpublic/js.
Butifyou’reinterestedinchangingthestructure,youcanalwayschangethesourceandpublicpathsbychangingtheappropriateproperties(assetsPathandpublicPath)ontheelixir.configobject.
RunningElixirSinceElixirrunsonGulp,you’llneedtosetupafewtoolsbeforeusingit:
1. First,you’llneedNode.jsinstalled.VisittheNodewebsitetolearnhowtogetitrunning.
2. Next,you’llneedtoinstallGulpgloballyonyourmachine.Justrunnpminstall--globalgulp-clifromtheterminalanywhereonyourmachine.OnceNodeandGulpareinstalled,youwillneverhavetorunthosecommandsagain.Nowyou’rereadytoinstallthisproject’sdependencies.
3. Opentheprojectrootinyourterminal,andrunnpminstalltoinstalltherequiredpackages(LaravelshipswithanElixir-readypackage.jsonfiletodirectNPM).
You’renowsetup!YoucanrungulptorunGulp/Elixironce,gulpwatchtolistenforrelevantfilechangesandruninresponse,orgulpscriptsorgulpstylestojustrunthescriptorstyletasks.
WhatDoesElixirProvide?We’vealreadycoveredthatElixircanpreprocessyourCSSusingSassorLESS.Itcanconcatenatefiles,minifythem,renamethem,andcopythem,anditcancopyentiredirectoriesorindividualfiles.
ElixircanalsoprocessES6/ES2015JavaScriptandrunWebpack,Rollup,and/orAutoprefixeronyourcode.Notonlythat,butmostofthemoderncodingstandardsforJavaScriptandCSSarecoveredoneveryscriptorstyle,outofthebox.
Elixircanalsorunyourtests.There’samethodforPHPUnitandoneforPHPSpec;bothlistentochangestoyourtestfilesandrerunyourtestsuiteeverytimeyoumakeanychanges.
TheElixirdocumentationcoversalloftheseoptionsandmore,butwe’llcoverafewspecificusecasesinthefollowingsections.
The--productionflagBydefault,Elixirdoesn’tminifyallthefilesit’sgenerating.Butifyouwanttorunthebuildscriptsin“production”mode,withallminificationenabled,youcanjustaddthe--productionflag:
$gulp--production
PassingmultiplefilesMostoftheElixirmethodsthatnormallyacceptasinglefile(e.g.,mix.sass('app.scss'))canalsotakeanarrayoffiles,likeinExample5-3.
Example5-3.CompilingmultiplefileswithElixirconstelixir=require('laravel-elixir');
elixir(mix=>{
mix.sass([
'app.scss',
'public.scss'
]);
});
SourcemapsBydefault,Elixirgeneratessourcemapsforyourfiles—you’llseethemasa.{filename}.mapfilenexttoeachgeneratedfile.
Ifyou’renotfamiliarwithsourcemaps,theyworkwithanysortofpreprocessortoteachyourbrowser ’swebinspectorwhichfilesgeneratedthecompiledsourceyou’reinspecting.
Withoutsourcemaps,ifyouuseyourbrowser ’sdevelopmenttoolstoinspectaparticularCSSruleorJavaScriptaction,you’lljustseeabigmessofcompiledcode.Withsourcemaps,yourbrowsercanpinpointtheexactlineofthesourcefile,whetheritbeSassorJavaScriptorwhateverelse,thatgeneratedtheruleyou’reinspecting.
Ifyoudon’twantsourcemaps,youcanalwayschangetheconfigurationbeforeyourelixirblocklikeinExample5-4.
Example5-4.DisablingsourcemapsinElixirconstelixir=require('laravel-elixir');
elixir.config.sourcemaps=false;
elixir(mix=>{
mix.sass('app.scss');
});
PreprocessorlessCSSIfyoudon’twanttodealwithapreprocessor,there’sacommandforthat—itwillgraballofyourCSSfiles,concatenatethem,andoutputthemtothepublic/cssdirectory,justasiftheyhadbeenrunthroughapreprocessor.Ifyoudon’tspecifyanouputfilename,it’llendupinall.css.Thereareafewoptions,whichyoucanseeinExample5-5.
Example5-5.CombiningstylesheetswithElixirconstelixir=require('laravel-elixir');
elixir(mix=>{
//Combinesallfilesfromresources/assets/cssandsubfolders
mix.styles();
//Combinesfilesfromresources/assets/css
mix.styles([
'normalize.css',
'app.css'
]);
//Combinesallstylesfromotherdirectory
mix.stylesIn('resources/some/other/css/directory');
//Combinesgivenstylesfromresources/assets/css
//andoutputstoacustomdirectory
mix.styles([
'normalize.css',
'app.css'
],'public/other/css/output.css');
//Combinesgivenstylesfromcustomdirectory
//andoutputstoacustomdirectory
mix.styles([
'normalize.css',
'app.css'
],'public/other/css/output.css','resources/some/other/css/directory');
});
ConcatenatingJavaScriptTheoptionsavailableforworkingwithnormalJavaScriptfilesareverysimilartothoseavailablefornormalCSSfiles.TakealookatExample5-6.Likewithstyles(),anycommandsnotprovidedwithanoutputfilenamewilloutputtopublic/js/all.js.
Example5-6.CombiningJavaScriptfileswithElixirconstelixir=require('laravel-elixir');
elixir(mix=>{
//Combinesfilesfromresources/assets/js
mix.scripts([
'jquery.js',
'app.js'
]);
//Combinesallscriptsfromotherdirectory
mix.scriptsIn('resources/some/other/js/directory');
//Combinesgivenscriptsfromresources/assets/js
//andoutputstoacustomdirectory
mix.scripts([
'jquery.js',
'app.js'
],'public/other/js/output.js');
//Combinesgivenscriptsfromcustomdirectory
//andoutputstoacustomdirectory
mix.scripts([
'jquery.js',
'app.js'
],'public/other/js/output.js','resources/some/other/js/directory');
});
ProcessingJavaScriptIfyouwanttoprocessyourJavaScript—forexample,tocompileyourES6codeintoplainJavaScript—ElixirmakesiteasytouseeitherWebpackorRollupforthispurpose(seeExample5-7).
Example5-7.ProcessingJavaScriptfilesinElixirwithWebpackorRollupelixir(function(mix){
mix.webpack('app.js');
//or
mix.rollup('app.js');
});
Thesescriptslookfortheprovidedfilenameinresources/assets/jsandoutputtopublic/js/all.js.
YoucanusemorecomplicatedaspectsofWebpack’sfeaturesetbycreatingawebpack.config.jsfileinyourprojectroot.
COMPILINGJAVASCRIPTINELIXIR5PriortoLaravel5.3/Elixir6,you’llwanttocompileyourJavaScriptusingmix.browserify('app.js').
VersioningMostofthetipsfromSteveSouders’EvenFasterWebSites(O’Reilly)havemadetheirwayintooureverydaydevelopmentpractices.Wemovescriptstothefooter,reducethenumberofHTTPrequests,andmore,oftenwithoutevenrealizingwherethoseideasoriginated.
OneofSteve’stipsisstillveryrarelyimplemented,though,andthatissettingaverylongcachelifeonassets(scripts,styles,andimages).Doingthismeanstherewillbefewerrequeststoyourservertogetthelatestversionofyourassets.Butitalsomeansthatusersareextremelylikelytohaveacachedversionofyourassets,whichwillmakethingsgetoutdated,andthereforebreak,quickly.
Thesolutiontothisisversioning.Appendauniquehashtoeachasset’sfilenameeverytimeyourunyourbuildscript,andthenthatuniquefilewillbecachedindefinitely—oratleastuntilthenextbuild.
What’stheproblem?Well,firstyouneedtogettheuniquehashesgeneratedandappendedtoyourfilenames.Butyoualsowillneedtoupdateyourviewsoneverybuildtoreferencethenewfilenames.
Asyoucanprobablyguess,Elixirhandlesthatforyou,andit’sincrediblysimple.Therearetwocomponents:theversioningtaskinElixir,andtheelixir()PHPhelper.First,youcanversionyourassetsbyrunningmix.version()likeinExample5-8.
Example5-8.mix.versionconstelixir=require('laravel-elixir');
elixir(mix=>{
mix.version('public/css/all.css');
});
Thiswillgenerateaversionofthespecifiedfilewithauniquehashappendedtoitinthepublic/builddirectory—somethinglikepublic/build/css/all-84fa1258.css.
Next,usethePHPelixir()helperinyourviewstorefertothatfilelikeinExample5-9.
Example5-9.Usingtheelixir()helperinviews<linkrel="stylesheet"href="{{elixir("css/all.css")}}">
//willoutputsomethinglike:
<linkrel="stylesheet"href="/build/css/all-84fa1258.css">
HOWDOESELIXIR VERSIONINGWORKBEHINDTHESCENES?
Elixirusesgulp-rev,whichtakescareofappendingthehashestothefilenames,andalsogeneratesafilenamedpublic/build/rev-manifest.json.Thisstorestheinformationtheelixir()helperneedstofindthegeneratedfile.Here’swhatasamplerev-manifest.jsonlookslike:
{
"css/all.css":"css/all-7f592e49.css"
}
TestsWithElixirit’seasytorunyourPHPUnitorPHPSpectestseverytimeyourtestfileschange.
Youhavetwooptions,mix.phpUnit()andmix.phpSpec(),andeachwillruntherespectiveframeworksdirectlyfromthevendorfolder,soyouwon’thavetodoanythingtomakethemwork.
IfyouaddoneofthesemethodstoyourGulpfile,however,you’llfindtheyonlyrunonce,evenifyou’reusinggulpwatch.Howdoyougetthemtorespondtochangesinyourtestsfolder?
There’saseparateGulpcommandforthat:gulptdd.ThisgrabsjustthetestcommandsoutofyourGulpfile,whetherphpUnit()orphpSpec(),listenstotheappropriatefolder,andrerunsthetestsuitewheneveranyfileschange.
ElixirextensionsElixirdoesn’tjustprovideasimplesyntaxforitsownprebuilttasks;italsomakesiteasytodefineyourown.
Let’ssayyouwanttosavetexttoalogfileatcertainpoints.That’sashellcommand,whichisecho"message">>file.log.Normallywe’ddefinethisasaGulptask,usingshell('echo"message">>file.log'),likeinExample5-10.
Example5-10.UsingaGulptaskinElixir//Definethetask
gulp.task("log",function(){
varmessage="Somethinghappened";
gulp.src("").pipe(shell('echo"'+message+'">>file.log'));
});
elixir(mix=>{
//UsethetaskinElixir
mix.task('log');
//Bindthetasktoruneverytimecertainfilesarechanged
mix.task('log','resources/somefiles/to/watch/**/*')
});
However,ifwewantalittlemorecontrol—forexample,ifwewanttobeabletoactuallypassinthemessage,whichisreallysortofvitaltomakethisparticulartaskwork—wecan
createanElixirextensionlikeinExample5-11.
Example5-11.CreatinganElixirextension//Eitheringulpfile.js,orinanexternalfileandrequiredingulpfile.js
vargulp=require("gulp"),
shell=require("gulp-shell"),
elixir=require("laravel-elixir");
elixir.extend("log",function(message){
newTask('log',function(){
returngulp.src('').pipe(shell('echo"'+message+'">>file.log'));
})
.watch('./resources/some/files/**/*');
});
Aswithanycomponent,wehaven’tcoveredeverythingthereistolearnaboutElixir,buthopefullyyou’velearnedenoughtogetyourunningwithit.Wanttolearnmore?Checkoutthedocs.
PaginationForsomethingthatissocommonacrosswebapplications,paginationstillcanbewildlycomplicatedtoimplement.Thankfully,Laravelhasabuilt-inconceptofpagination,andit’salsohookedintoEloquentresultsandtherouterbydefault.
ABRIEFINTRODUCTIONTOELOQUENT
We’llbecoveringEloquent,databaseaccess,andLaravel’squerybuilderindepthinChapter8,buttherewillbeafewreferencesbetweennowandthenthatwillmakeabasicunderstandinguseful.
EloquentisLaravel’sActiveRecorddatabaseobject-relationalmapper(ORM),whichmakesiteasytorelateaPostclass(model)tothepostsdatabasetable,andgetallrecordswithacalllikePost::all().
ThequerybuilderisthetoolthatmakesitpossibletomakecallslikePost::where('active',true)->get()orevenDB::table('users')->all().You’rebuildingaquerybychainingmethodsoneafteranother.
PaginatingDatabaseResultsThemostcommonplaceyou’llseepaginationiswhenyouaredisplayingtheresultsofadatabasequeryandtherearetoomanyresultsforasinglepage.Eloquentandthequerybuilderbothreadthepagequeryparameterfromthecurrentpagerequestanduseittoprovideapaginate()methodonanyresultsets;thesingleparameteryoushouldpasspaginate()ishowmanyresultsyouwantperpage.TakealookatExample5-12toseehowthisworks.
Example5-12.Paginatingaquerybuilderresponse//PostsController
publicfunctionindex()
{
returnview('posts.index',['posts'=>DB::table('posts')->paginate(20)]);
}
Example5-12definesthatthisrouteshouldreturn20postsperpage,andwilldefinewhich“page”ofresultsthecurrentuserisonbasedontheURL’spagequeryparameter,ifithasone.Eloquentmodelsallhavethesamepaginate()method.
Whenyoudisplaytheresultsinyourview,yourcollectionwillnowhavealinks()methodonit(orrender()forLaravel5.1)thatwilloutputthepaginationcontrols,withbootstrapclassnamesassignedtothembydefault(seeExample5-13).
Example5-13.Renderingpaginationlinksinatemplate//posts/index.blade.php
<table>
@foreach($postsas$post)
<tr><td>{{$post->title}}</td></tr>
@endforeach
</table>
{{$posts->links()}}
//Bydefaut,$posts->links()willoutputsomethinglikethis:
<ulclass="pagination">
<liclass="disabled"><span>«</span></li>
<liclass="active"><span>1</span></li>
<li><ahref="http://myapp.com/posts?page=2">2</a></li>
<li><ahref="http://myapp.com/posts?page=3">3</a></li>
<li><ahref="http://myapp.com/posts?page=2"rel="next">»</a></li>
</ul>
ManuallyCreatingPaginatorsIfyou’renotworkingwithEloquentorthequerybuilder,orifyou’reworkingwithacomplexquery(e.g.,thoseusinggroupBy),youmightfindyourselfneedingtocreateapaginatormanually.Thankfully,youcandothatwiththeIlluminate\Pagination\PaginatororIlluminate\Pagination\LengthAwarePaginatorclasses.
ThedifferencebetweenthetwoclassesisthatPaginatorwillonlyprovidepreviousandnextbuttons,butnolinkstoeachpage;LengthAwarePaginatorneedstoknowthelengthofthefullresult,sothatitcangeneratelinksforeachindividualpage.YoumayfindyourselfwantingtousethePaginatoronlargeresultsets,soyourpaginatordoesn’thavetobeawareofamassivecountofresultsthatmightbecostlytorun.
BoththePaginatorandtheLengthAwarePaginatorrequireyoutomanuallyextractthesubsetofcontentthatyouwanttopasstotheview.TakealookatExample5-14foranexample.
Example5-14.ManuallycreatingapaginatorinLaravel5.2and5.3useIlluminate\Http\Request;
useIlluminate\Pagination\Paginator;
Route::get('people',function(Request$request){
$people=[...];//hugelistofpeople
$perPage=15;
$offsetPages=$request->input('page',1)-1;
//ThePaginatorwillnotsliceyourarrayforyou
$people=array_slice(
$people,
$offsetPages*$perPage,
$perPage
);
returnnewPaginator(
$people,
$perPage
);
});
ThePaginatorsyntaxhaschangedoverthelastfewversionsofLaravel,soifyou’reusing5.1,takealookatthedocstofindthecorrectsyntax.
MessageBagsAnothercommonbutpainfulfeatureinwebapplicationsispassingmessagesbetweenvariouscomponentsoftheapp,whentheendgoalistosharethemwiththeuser.Yourcontroller,forexample,mightwanttosendavalidationmessage:“Theemailfieldmustbeavalidemailaddress.”However,thatparticularmessagedoesn’tjustneedtomakeittotheviewlayer;itactuallyneedstosurvivearedirectandthenendupintheviewlayerofadifferentpage.Howdowestructurethismessaginglogic?
Illuminate\Support\MessageBagisaclasstaskedwithstoring,categorizing,andreturningmessagesthatareintendedfortheenduser.Itgroupsallmessagesbykey,wherethekeysarelikelytobesomethinglikeerrorsandmessages,andprovidesconveniencemethodsforgettingallitsstoredmessagesoronlythoseforaparticularkey,andforoutputtingthesemessagesinvariousformats.
YoucanspinupanewinstanceofMessageBagmanuallylikeinExample5-15.
Example5-15.ManuallycreatingandusingMessageBag$messages=[
'errors'=>[
'Somethingwentwrongwithedit1!'
],
'messages'=>[
'Edit2wassuccessful.'
]
];
$messagebag=new\Illuminate\Support\MessageBag($messages);
//Checkforerrors;ifthereareany,decorateandecho
if($messagebag->has('errors')){
echo'<ulid="errors">';
foreach($messagebag->get('errors','<li><b>:message</b></li>')as$error){
echo$error;
}
echo'</ul>';
}
MessagebagsarealsocloselyconnectedtoLaravel’svalidators(learnmorein“Validation”):whenvalidatorsreturnerrors,theyactuallyreturnaninstanceofMessageBag,whichyoucanthenpasstoyourvieworattachtoaredirectusingredirect('route')->withErrors($messagebag).
LaravelpassesanemptyinstanceofMessageBagtoeveryview,assignedtothevariable$errors,andifyou’veflashedamessagebagusingwithErrors()onaredirect,itwillgetassignedtothat$errorsvariableinstead.Thatmeanseveryviewcanalwaysassumeithasan$errorsMessageBagitcancheckinwhateverplaceitdoesitsvalidation,whichleadstoExample5-16asacommonsnippetdevelopersplaceoneverypage.
Example5-16.Errorbagsnippet//partials/errors.blade.php
@if($errors->any())
<divclass="alertalert-danger">
<ul>
@foreach($errorsas$error)
<li>{{$error}}</li>
@endforeach
</ul>
</div>
@endif
MISSING$ERRORSVARIABLEIfyouhaveanyroutesthataren’tunderthewebmiddlewaregroup,theywon’thavethesessionmiddleware,whichmeanstheywon’thavethis$errorsvariableavailable.
NamedErrorBagsSometimesyouneedtodifferentiatemessagebagsnotjustbykey(noticesversuserrors)butalsobycomponent.Maybeyouhavealoginformandasignupformonthesamepage;howdoyoudifferentiatethem?
WhenyousenderrorsalongwitharedirectusingwithErrors(),thesecondparameteristhenameofthebag:redirect('dashboard')->withErrors($validator,'login').Then,onthedashboard,youcanuse$errors->logintocallallofthemethodswesawbefore:any(),count(),andmore.
StringHelpers,Pluralization,andLocalizationAsdevelopers,wetendtolookatblocksoftextasbigplaceholderdivs,waitingfortheclienttoputrealcontentintothem.Seldomareweinvolvedinanylogicinsidetheseblocks.
Butthereareafewcircumstanceswhereyou’llbegratefulforthetoolsLaravelprovidesforstringmanipulation.
TheStringHelpersandPluralizationLaravelhasaseriesofhelpersformanipulatingstrings.They’reavailableasmethodsontheStrclass(e.g.,Str::plural()),butmostalsohaveaglobalhelperfunction(e.g.,str_plural()).
TheLaraveldocumentationcoversallofthestringhelpersindetail,buthereareafewofthemostcommonlyusedhelpers:
e
Ashortcutforhtml_entities
starts_with,ends_with,str_containsCheckastring(firstparameter)toseeifitstartswith,endswith,orcontainsanotherstring(secondparameter)
str_is
Checkswhetherastring(secondparameter)matchesaparticularpattern(firstparameter)—forexample,foo*willmatchfoobarandfoobaz
str_slug
ConvertsastringtoaURL-typeslugwithhyphens
str_plural(word,num),str_singularPluralizesawordorsingularizesit;English-only(e.g.,str_plural('dog')returnsdogs)
LocalizationLocalizationallowsyoutodefinemultiplelanguagesandmarkanystringsastargetsfortranslation.Youcansetafallbacklanguage,andevenhandlepluralizationvariations.
InLaravel,you’llneedtosetanapplicationlocaleatsomepointduringthepageloadsothelocalizationhelpersknowwhichbucketoftranslationstopullfrom.You’lldothiswithApp::setLocale($localeName),andyou’lllikelyputitinaserviceprovider.Fornowyoucanjustputitintheboot()methodofAppServiceProvider,butyoumaywanttocreateaLocaleServiceProviderifyouendupwithmorethanjustthisonelocale-relatedbinding.
SETTINGTHELOCALEFOR EACHREQUEST
ItcanbeconfusingatfirsttoworkouthowLaravel“knows”theuser’slocale,orprovidestranslations.Mostofthatworkisonyouasthedeveloper.Let’slookatalikelyscenario.
You’llprobablyhavesomefunctionalityallowingtheusertochoosealocale,orpossiblyattemptingtoautomaticallydetectit.Eitherway,yourapplicationwilldeterminethelocale,andthenyou’llstorethatinaURLparameterorasessioncookie.Thenyourserviceprovider—somethinglikeaLocaleServiceProvider,maybe—willgrabthatkeyandsetitasapartofLaravel’sbootstrap.
Somaybeyouruserisathttp://myapp.com/es/contacts.YourLocaleServiceProviderwillgrabthatesstring,andthenrunApp::setLocale('es').Goingforward,everytimeyouaskforatranslationofastring,LaravelwilllookfortheSpanishversionofthatstring,whichyouwillneedtohavedefinedsomewhere.
Youcandefineyourfallbacklocaleinconfig/app.php,whereyoushouldfindafallback_localekey.Thisallowsyoutodefineadefaultlanguageforyourapplication,whichLaravelwilluseifitcan’tfindatranslationfortherequestedlocale.
BasiclocalizationSo,howdowecallforatranslatedstring?There’sahelperfunction,trans($key),thatwillpullthestringforthecurrentlocaleforthepassedkeyor,ifitdoesn’texist,grabitfromthedefaultlocale.Example5-17demonstrateshowabasictranslationworks.We’llusetheexampleofa“backtothedashboard”linkatthetopofadetailpage.
Example5-17.Basicuseoftrans()//NormalPHP
<?phpechotrans('navigation.back');?>
//Blade
{{trans('navigation.back')}}
//Bladedirective
@lang('navigation.back')
Let’sassumeweareusingtheeslocalerightnow.Laravelwilllookforafileinresources/lang/es/navigation.php,whichitwillexpecttoreturnanarray.It’lllookforabackkeyonthatarray,andifitexists,it’llreturnitsvalue.TakealookatExample5-18forasample.
Example5-18.Usingatranslation//resources/lang/es/navigation.php
return[
'back'=>'Volveralpanel'
];
//routes/web.php
Route::get('/es/contacts/show/:id',function(){
//Settingitmanually,forthisexample,insteadofinaserviceprovider
App::setLocale('es');
returnview('contacts.show');
});
//resources/views/contacts/show.blade.php
<ahref="/contacts">{{trans('navigation.back')}}</a>
ParametersinlocalizationTheprecedingexamplewasrelativelysimple.Let’sdigintosomethataremorecomplex.Whatifwewanttodefinewhichdashboardwe’rereturningto?TakealookatExample5-19.
Example5-19.Parametersintranslations//resources/lang/en/navigation.php
return[
'back'=>'Backto:sectiondashbaord'
];
//resources/views/contacts/show.blade.php
{{trans('navigation.back',['section'=>'contacts'])}}
Asyoucansee,prependingawordwithacolon(:section)marksitasaplaceholderthatcanbereplaced.Thesecond,optional,parameteroftrans()isanarrayofvaluestoreplacetheplaceholderswith.
PluralizationinlocalizationWealreadycoveredpluralization,sonowjustimagineyou’redefiningyourownpluralizationrules.Therearetwowaystodoit;we’llstartwiththesimplest,inExample5-20.
Example5-20.Definingasimpletranslationwithanoptionforpluralization//resources/lang/en/messages.php
return[
'task-deletion'=>'Youhavedeletedatask|Youhavesuccessfullydeletedtasks'
];
//resources/views/dashboard.blade.php
@if($numTasksDeleted>0)
{{trans_choice('messages.task-deletion',$numTasksDeleted)}}
@endif
Asyoucansee,wehaveatrans_choice()method,whichtakesthecountofitemsaffectedasitssecondparameter;andfromthisitwilldeterminewhichstringtouse.
YoucanalsouseanytranslationdefinitionsthatarecompatiblewithSymfony’smuchmorecomplexTranslationcomponent;seeExample5-21foranexample.
Example5-21.UsingtheSymfony’sTranslationcomponent//resources/lang/es/messages.php
return[
'task-deletion'=>"{0}Youdidn'tmanagetodeleteanytasks.|".
"[1,4]Youdeletedafewtasks.|".
"[5,Inf]Youdeletedawholetonoftasks."
];
TestingInthischapterwefocusedprimarilyonLaravel’sfrontendcomponents.Thesearelesslikelytheobjectsofunittests,buttheymayattimesbeusedinyourintegrationtests.
TestingwithElixirYou’renotgoingtobewritinganytestsaroundyourElixirtasks.However,Elixirprovidessomefunctionsthatwillhelpwithyourtesting,solet’stalkaboutthoseforasecond.
Ifyouaddmix.phpunit()ormix.phpspec()toyourgulpfile.js,everytimeyourungulpitwillrunyourtestsonce,inline,asapartofyourbuildscript.
Andeverytimeyourungulpwatch,Elixirwilllistentoanychangetoyourtestfilesoranyothercorefiles(likeroutes/web.php)andre-runPHPUnitorPHPSpeceverytimeyoumakeanychangestothosefiles.
TestingMessageandErrorBagsTherearetwoprimarywaysoftestingmessagespassedalongwithmessageanderrorbags.First,youcanperformabehaviorinyourapplicationteststhatsetsamessagethatwilleventuallybedisplayedsomewhere,thenredirecttothatpageandassertthattheappropriatemessageisshown.
Second,forerrors(whichisthemostcommonusecase),youcanassertthesessionhaserrorswith$this->assertSessionHasErrors($bindings=[]).TakealookatExample5-22toseewhatthismightlooklike.
Example5-22.Assertingthesessionhaserrorspublicfunctiontest_missing_email_field_errors()
{
$this->post('person/create',['name'=>'Japheth']);
$this->assertSessionHasErrors(['email']);
}
TranslationandLocalizationThesimplestwaytotestlocalizationiswithapplicationtests.Settheappropriatecontext(whetherbyURLorsession),visit()thepage,andassertthatyouseetheappropriatecontent.
TL;DRAsafull-stackframework,Laravelprovidestoolsandcomponentsforthefrontendaswellasthebackend.
ElixirisawrapperaroundcommonGulpbuildtasksthatmakesitsimpletousethemostmodernbuildsteps.ElixirmakesiteasytoaddCSSpreprocessors;JavaScripttranspilation,concatenation,andminification;andmuchmore.
Laravelalsooffersotherinternaltoolsthattargetthefrontend,includingpagination,messageanderrorbags,andlocalization.
Chapter6.CollectingandHandlingUserData
WebsitesthatbenefitfromaframeworklikeLaraveloftendon’tjustservestaticcontent.Manydealwithcomplexandmixeddatasources,andoneofthemostcommon(andmostcomplex)ofthesesourcesisuserinputinitsmyriadforms:URLpaths,queryparameters,POSTdata,andfileuploads.
Laravelprovidesacollectionoftoolsforgathering,validating,normalizing,andfilteringuser-provideddata.We’lllookatthosehere.
InjectingaRequestObjectThemostcommontoolforaccessinguserdatainLaravelisinjectinganinstanceoftheIlluminate\Http\Requestobject.Itprovideseasyaccesstoallofthewaysuserscanprovideinputtoyoursite:POST,postedJSON,GET(queryparameters),andURLsegments.
OTHEROPTIONSFORACCESSINGREQUESTDATAThere’salsoarequest()globalhelperandaRequestfacade,bothofwhichexposethesamemethods.EachoftheseoptionsexposestheentireIlluminateRequestobject,butfornowwe’reonlygoingtocoverthemethodsthatspecificallyrelatetouserdata.
Sincewe’replanningoninjectingaRequestobject,let’stakeaquicklookathowtogetthe$requestobjectwe’llbecallingallthesemethodson:
Route::post('form',function(Illuminate\Http\Request$request){
//$request->etc()
});
$request->all()Justlikethenamesuggests,$request->all()givesyouanarraycontainingalloftheinputtheuserhasprovided,fromeverysource.Let’ssay,forsomereason,youdecidedtohaveaformPOSTtoaURLwithaqueryparameter—e.g.,sendingaPOSTtohttp://myapp.com/post?utm=12345.TakealookatExample6-1toseewhatyou’dgetfrom$request->all().(Notethat$request->all()alsocontainsinformationaboutanyfilesthatwereuploaded,butwe’llcoverthatlaterinthechapter.)
Example6-1.$request->all()<!--GETrouteformviewat/get-route-->
<formmethod="post"action="/post-route?utm=12345">
{{csrf_field()}}
<inputtype="text"name="firstName">
<inputtype="submit">
</form>
Route::post('/post-route',function(Request$request){
var_dump($request->all());
});
//Outputs:
/**
*[
*'_token'=>'CSRFtokenhere',
*'firstName'=>'value',
*'utm'=>12345
*]
*/
$request->except()and$request->only()$request->except()providesthesameoutputas$request->all,butyoucanchooseoneormorefieldstoexclude—forexample,_token.Youcanpassiteitherastringoranarrayofstrings.
Example6-2showswhatitlookslikewhenweuse$request->except()onthesameformasinExample6-1.
Example6-2.$request->except()Route::post('/post-route',function(Request$request){
var_dump($request->except('_token'));
});
//Outputs:
/**
*[
*'firstName'=>'value',
*'utm'=>12345
*]
*/
$request->only()istheinverseof$request->except(),asyoucanseeinExample6-3.
Example6-3.$request->except()Route::post('/post-route',function(Request$request){
var_dump($request->only(['firstName','utm']));
});
//Outputs:
/**
*[
*'firstName'=>'value',
*'utm'=>12345
*]
*/
$request->has()and$request->exists()With$request->has()youcandetectwhetheraparticularpieceofuserinputisavailabletoyou.CheckoutExample6-4forananalyticsexamplewithourutmquerystringparameterfromthepreviousexamples.
Example6-4.$request->has()//POSTrouteat/post-route
if($request->has('utm')){
//Dosomeanalyticswork
}
$request->exists()and$request->has()differinthattheyhandleemptyvaluesdifferently:has()returnsFALSEifthekeyexistsandisempty;exists()returnsTRUEifthekeyexists,evenifit’sempty.
$request->input()Whereas$request->all(),$request->except(),and$request->only()operateonthefullarrayofinputprovidedbytheuser,$request->input()allowsyoutogetthevalueofjustasinglefield.Example6-5providesanexample.Notethatthesecondparameteristhedefaultvalue,soiftheuserhasn’tpassedinavalue,youcanhaveasensible(andnonbreaking)fallback.
Example6-5.$request->input()Route::post('/post-route',function(Request$request){
$userName=$request->input('name','(anonymous)');
});
ArrayInputLaravelalsoprovidesconveniencehelpersforaccessingdatafromarrayinput.Justusethe“dot”notationtoindicatethestepsofdiggingintothearraystructure,likeinExample6-6.
Example6-6.Dotnotationtoaccessarrayvaluesinuserdata<!--GETrouteformviewat/get-route-->
<formmethod="post"action="/post-route">
{{csrf_field()}}
<inputtype="text"name="employees[0][firstName]">
<inputtype="text"name="employees[0][lastName]">
<inputtype="text"name="employees[1][firstName]">
<inputtype="text"name="employees[1][lastName]">
<inputtype="submit">
</form>
//POSTrouteat/post-route
Route::post('/post-route',function(Request$request){
$employeeZeroFirstName=$request->input('employees.0.firstName');
$allLastNames=$request->input('employees.*.lastName');
$employeeOne=$request->input('employees.1');
});
//Ifformsfilledoutas"Jim""Smith""Bob""Jones":
//$employeeZeroFirstName='Jim';
//$allLastNames=['Smith','Jones'];
//$employeeOne=['firstName'=>'Bob','lastName'=>'Jones']
JSONInput(and$request->json())Sofarwe’vecoveredinputfromquerystrings(GET)andformsubmissions(POST).Butthere’sanotherformofuserinputthat’sbecomingmorecommonwiththeadventofJavaScriptsingle-pageapps(SPAs):theJSONrequest.It’sessentiallyjustaPOSTrequestwiththebodysettoJSONinsteadofatraditionalformPOST.
Let’stakealookatwhatitmightlookliketosubmitsomeJSONtoaLaravelroute,andhowtouse$request->input()topulloutthatdata(Example6-7).
Example6-7.GettingdatafromJSONwith$request->input()POST/post-routeHTTP/1.1
Content-Type:application/json
{
"firstName":"Joe",
"lastName":"Schmoe",
"spouse":{
"firstName":"Jill",
"lastName":"Schmoe"
}
}
//post-route
Route::post('post-route',function(Request$request){
$firstName=$request->input('firstName');
$spouseFirstname=$request->input('spouse.firstName');
});
Since$request->input()issmartenoughtopulluserdatafromGET,POST,orJSON,youmaywonderwhyLaravelevenoffers$request->json().Therearetworeasonsyoumightprefer$request->json().First,youmightwanttojustbemoreexplicittootherprogrammersonyourprojectaboutwhereyou’reexpectingthedatatocomefrom.Andsecond,ifthePOSTdoesn’thavethecorrectapplication/jsonheaders,$request->input()won’tpickitupasJSON,but$request->json()will.
FACADENAMESPACES,THEREQUEST( ) GLOBALHELPER ,ANDINJECTING$REQUEST
Anytimeyou’reusingfacadesinsideofnamespacedclasses(e.g.,controllers),you’llhavetoaddthefullfacadepathtotheimportblockatthetopofyourfile(e.g.,useIlluminate\Support\facades\Request).
Becauseofthis,severalofthefacadesalsohaveacompanionglobalhelperfunction.Ifthesehelperfunctionsarerunwithnoparameters,theyexposethesamesyntaxasthefacade(e.g.,request()->has()isthesameasRequest::has()).Theyalsohaveadefaultbehaviorforwhenyoupassthemaparameter(e.g.,request('firstName')isashortcuttorequest()->input('firstName')).
WithRequest,we’vebeencoveringinjectinganinstanceoftheRequestobject,butyoucouldalsousetheRequestfacadeortherequest()globalhelper.TakealookatChapter10tolearnmore.
RouteDataItmightnotbethefirstthingyouthinkofwhenyouimagine“userdata,”buttheURLisjustasmuchuserdataasanythingelseinthischapter.
Therearethreeprimarywaysyou’llgetdatafromtheURL:viatheRequestfacade,routeparameters,andRequestobjects.We’llcoverRequestobjectsinmoredetailinChapter10.
FromRequestInjectedRequestobjects(andtheRequestfacadeandtherequest()helper)haveseveralmethodsavailabletorepresentthestateofthecurrentpage’sURL,butrightnowlet’slookprimarilyatgettinginformationabouttheURLsegments.
Ifyou’renotfamiliarwiththeideaofURLsegments,eachgroupofcharactersafterthedomainiscalledasegment.So,http://www.myapp.com/users/15/hastwosegments:usersand15.
Asyoucanprobablyguess,wehavetwomethodsavailabletous:$request->segments()returnsanarrayofallsegments,and$request->segment($segmentId)allowsustogetthevalueofasinglesegment.Notethatsegmentsarereturnedona1-basedindex,sointheprecedingexample,$request->segment(1)wouldreturnusers.
Requestobjects,theRequestfacade,andtherequest()globalhelperprovidequiteafewmoremethodstohelpusgetdataoutoftheURL.Tolearnmore,checkoutChapter10.
FromRouteParametersTheotherprimarywaywegetdataabouttheURLisfromrouteparameters,whichareinjectedintothecontrollermethodorclosurethatisservingacurrentrouteasshowninExample6-8.
Example6-8.GettingURLdetailsfromrouteparameters//routes/web.php
Route::get('users/{id}',function($id){
//Iftheuservisitsmyapp.com/users/15/,$idwillequal15
});
Tolearnmoreaboutroutesandroutebinding,checkoutChapter3.
UploadedFilesWe’vetalkedaboutdifferentwaystointeractwithusers’textinput,butthere’salsothematteroffileuploadstoconsider.TheRequestfacadeprovidesaccesstoanyuploadedfilesusingtheRequest::file()method,whichtakesthefile’sinputnameasaparameterandreturnsaninstanceofSymfony\Component\HttpFoundation\File\UploadedFile.
Let’swalkthroughanexample.First,ourform,inExample6-9.
Example6-9.Aformtouploadfiles<formmethod="post"enctype="multipart/form-data">
{{csrf_field()}}
<inputtype="text"name="name">
<inputtype="file"name="profile_picture">
<inputtype="submit">
</form>
Now,let’stakealookatwhatwegetfromrunning$request->all(),inExample6-10.Notethat$request->input('profile_picture')willreturnnull;weneedtouse$request->file('profile_picture')instead.
Example6-10.TheoutputfromsubmittingtheforminExample6-9Route::post('form',function(Request$request){
var_dump($request->all());
});
//Output:
//[
//"_token"=>"tokenhere"
//"name"=>"asdf"
//"profile_picture"=>UploadedFile{}
//]
Route::post('form',function(Request$request){
if($request->hasFile('profile_picture')){
var_dump($request->file('profile_picture'));
}
});
//Output:
//UploadedFile(details)
VALIDATINGAFILEUPLOAD
AsyoucanseeinExample6-10,wehaveaccessto$request->hasFile()toseewhethertheuseruploadedafile.WecanalsocheckwhetherthefileuploadwassuccessfulbyusingisValid()onthefileitself:
if($request->file('profile_picture')->isValid()){
//
}
BecauseisValid()iscalledonthefileitself,itwillerroriftheuserdidn’tuploadafile.So,tocheckforboth,you’dneedtocheckforthefile’sexistencefirst:
if(
$request->hasFile('profile_picture')&&
$request->file('profile_picture')->isValid()
){
//
}
Symfony’sUploadedFileclassextendsPHP’snativeSplFileInfowithmethodsallowingyoutoeasilyinspectandmanipulatethefile.Thislistisn’texhaustive,butitgivesyouatasteofwhatyoucando:
guessExtension()
getMimeType()
store($path,$storageDisk=defaultdisk)
storeAs($path,$newName,$storageDisk=defaultdisk)
storePublicly($path,$storageDisk=defaultdisk)
storePubliclyAs($path,$newName,$storageDisk=defaultdisk)
move($directory,$newName=null)
getClientOriginalName()
getClientOriginalExtension()
getClientMimeType()
guessClientExtension()
getClientSize()
getError()
isValid()
Asyoucansee,mostofthemethodshavetodowithgettinginformationabouttheuploadedfile,butthere’sonethatyou’lllikelyusemorethanalltheothers:store()(newinLaravel5.3),whichtakesthefilethatwasuploadedwiththerequestandstoresitinaspecifieddirectoryonyourserver.Itsfirstparameteristhedestinationdirectory,andtheoptionalsecondparameterwillbethestoragedisk(s3,local,etc.)tousetostorethefile.
YoucanseeacommonworkflowinExample6-11.
Example6-11.Commonfileuploadworkflowif($request->hasFile('profile_picture')){
$path=$request->profile_picture->store('profiles','s3');
auth()->user()->profile_picture=$path;
auth()->user()->save();
}
Ifyouneedtospecifythefilename,youcanusestoreAs()insteadofstore().Thefirstparameterisstillthepath;thesecondisthefilename,andtheoptionalthirdparameteristhestoragedisktouse.
PROPERFORMENCODINGFORFILEUPLOADSIfyougetnullwhenyoutrytogetthecontentsofafilefromyourrequest,youmight’veforgottentosettheencodingtypeonyourform.Makesuretoaddtheattributeenctype="multipart/form-data"onyourform:
<formmethod="post"enctype="multipart/form-data">
ValidationLaravelhasquiteafewwaysyoucanvalidateincomingdata.We’llcoverformrequestsinthenextsection,sothatleavesuswithtwoprimaryoptions:validatingmanuallyorusingthevalidate()methodinthecontroller.Let’sstartwiththesimpler,andmorecommon,validate().
validate()intheControllerUsingValidatesRequestsOutofthebox,allLaravelcontrollersusetheValidatesRequeststrait,whichprovidesaconvenientvalidate()method.Let’stakealookatwhatitlookslikeinExample6-12.
Example6-12.Basicusageofcontrollervalidation//routes/web.php
Route::get('recipes/create','RecipesController@create');
Route::post('recipes','RecipesController@store');
//app/Http/Controllers/RecipesController.php
<?php
namespaceApp\Http\Controllers;
useIlluminate\Http\Request;
classRecipesControllerextendsController
{
publicfunctioncreate()
{
returnview('recipes.create');
}
publicfunctionstore(Request$request)
{
$this->validate($request,[
'title'=>'required|unique:recipes|max:125',
'body'=>'required'
]);
//Recipeisvalid;proceedtosaveit
}
}
Weonlyhavefourlinesofcoderunningourvalidationhere,butthey’redoingalot.
First,we’reexplicitlydefiningthefieldsweexpectandapplyingrules(hereseparatedbythepipecharacter,|)toeachindividually.
Next,thevalidate()methodcheckstheincomingdatafromthe$request(whichmeansitcanuse$request->all()or$request->input()justlikewelearnedaboutearlierinthechapter)anddetermineswhetherornotitisvalid.
Ifthedataisvalid,thevalidatemethodendsandwecanmoveonwithyourcontrollermethod,savingthedataorwhateverelse.
Butifthedataisn’tvalid,itthrowsaValidationException.Thiscontainsinstructionstotherouterabouthowtohandlethisexception.IftherequestisAjax(orifit’srequestingJSONasaresponse),theexceptionwillcreateaJSONresponsecontainingthevalidationerrors.Ifnot,theexceptionwillreturnaredirecttothepreviouspage,togetherwithalloftheuserinputandthevalidationerrors—perfectforrepopulatingafailedformandshowingsomeerrors.
MOREONLARAVEL’SVALIDATIONRULES
Inourexampleshere(likeinthedocs)we’reusingthe“pipe”syntax:'fieldname':'rule|otherRule|anotherRule'.Butyoucanalsousethearraysyntaxtodothesamething:'fieldname':['rule','otherRule','anotherRule'].
Additionally,youcanvalidatenestedproperties.ThismattersifyouuseHTML’sarraysyntax,whichallowsyouto,forexample,havemultiple“users”onanHTMLform,eachwithanassociatedname.Here’showyouvalidatethat:
$this->validate($request,[
'user.name'=>'required',
'user.email'=>'required|email',
]);
Wedon’thaveenoughspacetocovereverypossiblevalidationrulehere,buthereareafewofthemostcommonrulesandtheirfunctions:
Requirethefieldrequired;required_if:anotherField,equalToThisValue;required_unless:anotherField,equalToThisValue
Fieldmustcontaincertaintypesofcharacteralpha,alpha_dash,alpha_num,numeric,integer
Fieldmustcontaincertainpatternsemail,active_url,ip
Datesafter:date,before:date(datecanbeanyvalidstringthatstrtotime()canhandle)
Numbersbetween:min,max,min:num,max:num,size:num(sizetestsagainstlengthforstrings,valueforintegers,countforarrays,orsizeinKBforfiles)
Imagedimensionsdimensions:min_width=XXX;canalsouseand/orcombinewithmax_width,min_height,max_height,width,height,andratio
Databasesexists:tableName,unique:tableName(expectstolookinthesametablecolumnasthefieldname;seethedocsforhowtocustomize)
ManualValidationIfyouarenotworkinginacontroller,orifforsomeotherreasonthepreviouslydescribedflowisnotagoodfit,youcanmanuallycreateaValidatorinstanceandcheckforsuccessorfailurelikeinExample6-13.
Example6-13.ManualvalidationRoute::get('recipes/create',function(){
returnview('recipes.create');
});
Route::post('recipes',function(Illuminate\Http\Request$request){
$validator=Validator::make($request->all(),[
'title'=>'required|unique:recipes|max:125',
'body'=>'required'
]);
if($validator->fails()){
returnredirect('recipes/create')
->withErrors($validator)
->withInput();
}
//Recipeisvalid;proceedtosaveit
});
Asyoucansee,wecreateaninstanceofavalidatorbypassingitourinputasthefirstparameterandthevalidationrulesasthesecondparameter.Thevalidatorexposesafails()methodthatwecancheckagainstandcanbepassedintothewithErrors()methodoftheredirect.
DisplayingValidationErrorMessagesWe’vealreadycoveredmuchofthisinChapter5,buthere’saquickrefresheronhowtodisplayerrorsfromvalidation.
Thevalidate()methodincontrollers(andthewithErrors()methodonredirectsthatitrelieson)flashesanyerrorstothesession.Theseerrorsaremadeavailabletotheviewyou’rebeingredirectedtointhe$errorsvariable.AndrememberthatasapartofLaravel’smagic,that$errorsvariablewillbeavailableeverytimeyouloadtheview,evenifit’sjustempty,soyoudon’thavetocheckifitexistswithisset().
ThatmeansyoucandosomethinglikeExample6-14oneverypage.
Example6-14.Echovalidationerrors@if($errors->any())
<ulid="errors">
@foreach($errors->all()as$error)
<li>{{$error}}</li>
@endforeach
</ul>
@endif
FormRequestsAsyoubuildoutyourapplications,youmightstartnoticingsomepatternsinyourcontrollermethods.Therearecertainpatternsthatarerepeated—forexample,inputvalidation,userauthenticationandauthorization,andpossibleredirects.Ifyoufindyourselfwantingastructuretonormalizeandextractthesecommonbehaviorsoutofyourcontrollermethods,youmaybeinterestedinLaravel’sformrequests.
Aformrequestisacustomrequestclassthatisintendedtomaptothesubmissionofaform,andtherequesttakestheresponsilibityforvalidatingtherequest,authorizingtheuser,andoptionallyredirectingtheuseruponafailedvalidation.Eachformrequestwillusually,butnotalways,explicitlymaptoasingleHTTPrequest—e.g.,“CreateComment.”
CreatingaFormRequestYoucancreateanewformrequestusingArtisan:
phpartisanmake:requestCreateCommentRequest
Younowhaveaformrequestobjectavailableatapp/Http/Requests/CreateCommentRequest.php.
Everyformrequestclassprovideseitheroneortwopublicmethods.Thefirstisrules(),whichneedstoreturnanarrayofvalidationrulesforthisrequest.Thesecond(optional)methodisauthorize();ifthisreturnstrue,theuserisauthorizedtoperformthisrequest,andiffalse,theuserisrejected.TakealookatExample6-15toseeasampleformrequest.
Example6-15.Sampleformrequest<?php
namespaceApp\Http\Requests;
useApp\BlogPost;
useApp\Http\Requests\Request;
classCreateCommentRequestextendsRequest
{
publicfunctionrules()
{
return[
'body'=>'required|max:1000'
];
}
publicfunctionauthorize()
{
$blogPostId=$this->route('blogPost');
returnauth()->check()&&BlogPost::where('id',$blogPostId)
->where('user_id',auth()->user()->id)->exists();
}
}
Therules()sectionofExample6-15isprettyself-explanatory,butlet’slookatauthorize()briefly.
We’regrabbingthesegmentfromtheroutenamedblogPost.That’simplyingtheroutedefinitionforthisrouteprobablylooksabitlikethis:Route::post('blogPosts/{blogPost}',function(){//Dostuff}).Asyoucansee,wenamedtherouteparameterblogPost,whichmakesitaccessibleinourRequestusing$this->route('parametername').
Wethenlookatwhethertheuserisloggedinand,ifso,whetheranyblogpostsexistwiththatidentifierthatareownedbythecurrentlylogged-inuser.We’llcoverwhatimplicationsthishasshortly,buttheimportantthingtoknowisthatreturningtruemeanstheuserisauthorizedtoperformthespecifiedaction(inthiscase,creatingacomment),andfalsemeanstheuserisnotauthorized.
UsingaFormRequestNowthatwe’vecreatedaformrequestobject,howdoweuseit?It’salittlebitofLaravelmagic.Anyroute(closureorcontrollermethod)thattypehintsaformrequestasoneofitsparameterswillbenefitfromthedefinitionsofthatformrequest.
Let’stryitout,inExample6-16.
Example6-16.UsingaformrequestRoute::post('comments',function(App\Http\Requests\CreateCommentRequest$request){
//Storecomment
});
Youmightbewonderingwherewecalltheformrequest,butLaraveldoesitforus.Itvalidatestheuserinputandauthorizestherequest.Iftheinputisinvalid,it’llactjustlikethein-controllervalidate()methodworks,redirectingtheusertothepreviouspagewiththeirinputpreservedandwiththeappropriateerrormessagespassedalong.Andiftheuserisnotauthorized,Laravelwillreturna403Forbiddenerrorandnotexecutetheroutecode.
EloquentModelMassAssignmentUntilnow,we’vebeenlookingatvalidatingatthecontrollerlevel,whichisabsolutelythebestplacetostart.Butyoucanalsofiltertheincomingdataatthemodellevel.
It’sacommonpatterntopasstheentiretyofaform’sinputdirectlytoadatabasemodel.InLaravel,thatmightlooklikeExample6-17.
Example6-17.PassingtheentiretyofaformtoanEloquentmodelRoute::post('posts',function(Request$request){
$newPost=Post::create($request->all());
});
We’reassumingherethattheenduseriskindandnotmalicious,andhaskeptonlythefieldswewanthimtoedit—maybetheposttitleorbody.
Butwhatifourendusercanguess,ordiscern,thatwehaveanauthor_idfieldonthatpoststable?Whatifheusedhisbrowsertoolstoaddanauthor_idfieldandsettheIDtobesomeoneelse’sID,andtheotherpersonimpersonatedtheotherpersonbycreatingfakeblogpostsattributedtoher?
Eloquenthasaconceptcalled“massassignment”thatallowsyoutoeitherwhitelistfieldsthatarefillableinthisway(usingthemodel’s$fillableproperty)orblacklistfieldsthataren’tfillable(usingthemodel’s$guardedproperty).CheckoutChapter8tolearnmore.
Inourexample,wemightwanttofilloutthemodellikeExample6-18tokeepourappsafe.
Example6-18.GuardinganEloquentmodelfrommischeviousmassassignment<?php
namespaceApp;
useIlluminate\Database\Eloquent\Model;
classPostextendsModel
{
//Disablemassassignmentontheauthor_idfield
protected$guarded=['author_id'];
}
Bysettingauthor_idtoguarded,weensurethatmalicioususerswillnolongerbeabletooverridethevalueofthisfieldbymanuallyaddingittothecontentsofaformthatthey’resendingtoourapp.
DOUBLEPROTECTIONUSING$REQUEST->ONLY()Whileit’simportanttodoagoodjobofprotectingourmodelsfrommassassignment,it’salsoworthbeingcarefulontheassigningend.Ratherthanusing$request->all(),consider$request->only()soyoucanspecifywhichfieldsyou’dliketopassintoyourmodel:
Route::post('posts',function(Request$request){
$newPost=Post::create($request->only([
'title',
'body'
]));
});
{{Versus{!!Anytimeyoudisplaycontentonawebpagethatwascreatedbyauser,youneedtoguardagainstmaliciousinput,suchasscriptinjection.
Let’ssayyouallowyouruserstowriteblogpostsonyoursite.Youprobablydon’twantthemtobeabletoinjectmaliciousJavaScriptthatwillruninyourunsuspectingvisitors’browsers,right?So,you’llwanttoescapeanyuserinputthatyoushowonthepagetoavoidthis.
Thankfully,thisisalmostentirelycoveredforyou.IfyouuseLaravel’sBladetemplatingengine,thedefault“echo”syntax({{$stuffToEcho}})runstheoutputthroughhtmlentities()(PHP’sbestwayofmakingusercontentsafetoecho)automatically.Youactuallyhavetodoextraworktoavoidescapingtheoutput,byusingthe{!!$stuffToEcho!!}syntax.
TestingIfyou’reinterestedintestingyourinteractionswithuserinput,you’reprobablymostinterestedinsimulatingvalidandinvaliduserinputandensuringthatiftheinputisinvalidtheuserisredirected,andiftheinputisvalid,itendsupintheproperplace(e.g.,thedatabase).
Laravel’send-to-endapplicationtestingmakesthissimple.Let’sstartwithaninvalidroutethatweexpecttoberejected,inExample6-19.
Example6-19.Testingthatinvalidinputisrejectedpublicfunctiontest_input_missing_a_title_is_rejected()
{
$this->post('posts',['body'=>'Thisisthebodyofmypost']);
$this->assertRedirectedTo('posts/create');
$this->assertSessionHasErrors();
$this->assertHasOldInput();
}
Hereweassertthatafterinvalidinputtheuserisredirected,witherrorsandwiththeoldinputcorrectlypassedback.Youcanseewe’reusingafewcustomPHPUnitassertionsthatLaraveladdshere.
So,howdowetestourroute’ssuccess?CheckoutExample6-20.
Example6-20.Testingthatvalidinputisprocessedpublicfunctiontest_valid_input_should_create_a_post_in_the_database()
{
$this->post('posts',['title'=>'PostTitle','body'=>'Thisisthebody']);
$this->seeInDatabase(['title'=>'PostTitle']);
}
Notethat,ifyou’retestingsomethingusingthedatabase,you’llneedtolearnmoreaboutdatabasemigrationsandtransactions.MoreonthatinChapter12.
TL;DRTherearealotofwaystogetthesamedata:theRequestfacade,therequest()globalhelper,andinjectinganinstanceofIlluminate\Http\Request.Eachexposestheabilitytogetallinput,someinput,orspecificpiecesofdata,andfilesandJSONinputcanhavesomespecialconsiderationsattimes.
URIpathsegmentsarealsoapossiblesourceofuserinput,andthey’realsoaccessibleviatherequesttools.
ValidationcanbeperformedmanuallywithValidator::make(),orautomaticallyusingthe$this->validate()controllermethodorformrequests.Eachautomatictool,uponfailedvalidation,redirectstheusertothepreviouspagewithalloldinputstoredanderrorspassedalong.
ViewsandEloquentmodelsalsoneedtobeprotectedfromnefarioususerinput.ProtectBladeviewsusingthedoublecurlybracesyntax({{}}),whichescapesuserinput,andprotectmodelsbyonlypassingspecificfieldsintobulkmethodsusing$request->only()andbydefiningthemassassignmentrulesonthemodelitself.
Chapter7.ArtisanandTinker
Frominstallationonward,modernPHPframeworksexpectmanyinteractionstotakeplaceonthecommandline.Laravelprovidesthreeprimarytoolsforcommand-lineinteraction:Artisan,asuiteofbuilt-incommand-lineactionswiththeabilitytoaddmore;Tinker,aREPLorinteractiveshellforyourapplication;andtheinstaller,whichwe’vealreadycoveredinChapter2.
AnIntroductiontoArtisanIfyou’vebeenreadingthroughthisbookchapterbychapter,you’vealreadylearnedhowtouseArtisancommands.Theylooksomethinglikethis:
phpartisanmake:controllerPostsController
Ifyoulookintherootfolderofyourapplication,you’llseethatartisanisactuallyjustaPHPfile.That’swhyyou’restartingyourcallwithphpartisan;you’repassingthatfileintoPHPtobeparsed.EverythingafterthatisjustpassedintoArtisanasarguments.
SYMFONYCONSOLESYNTAXArtisanisactuallyalayerontopoftheSymfonyConsolecomponent,soifyou’refamiliarwithwritingSymfonyConsolecommandsyoushouldberightathome.
SincethelistofArtisancommandsforanapplicationcanbechangedbyapackageorbythespecificcodeoftheapplication,it’sworthcheckingeverynewapplicationyouencountertoseewhatcommandsareavailable.
TogetalistofallavailableArtisancommands,youcanrunphpartisanlistfromtheprojectroot(althoughifyoujustrunphpartisanwithnoparameters,itwilldothesamething).
BasicArtisanCommandsThere’snotenoughspaceheretocoveralloftheArtisancommands,butwe’llcovermanyofthem.Let’sgetstartedwiththebasiccommands:
helpprovideshelpforacommand;e.g.,phpartisanhelpcommandName.
clear-compiledremovesLaravel’scompiledclassfile,whichislikeaninternalLaravelcache;runthisasafirstresortwhenthingsaregoingwrongandyoudon’tknowwhy.
downputsyourapplicationin“maintenancemode”inorderforyoutofixanerror,runmigrations,orwhateverelse;uprestoresanapplicationfrommaintenancemode.
envdisplayswhichenvironmentLaravelisrunningatthemoment;it’stheequivalentofechoingapp()->environment()in-app.
migraterunsalldatabasemigrations.
optimizeoptimizesyourapplicationforbetterperformancebycachingcorePHPclassesintobootstrap/cache/compile.php.
servespinsupaPHPserveratlocalhost:8000(youcancustomizethehostand/orportwith--hostand--port).
tinkerbringsuptheTinkerREPL,whichwe’llcoverlaterinthischapter.
OptionsBeforewecovertherestoftheArtisancommands,let’slookatafewnotableoptionsyoucanpassanytimeyourunanArtisancommand:
-qsuppressesalloutput.
-v,-vv,and-vvvarethethreelevelsofoutputverbosity(normal,verbose,anddebug).
--no-interactiondoesnotaskanyinteractivequestions,soitwon’tinterruptautomatedprocessesrunningit.
--envallowsyoutodefinewhichenvironmenttheArtisancommandshouldoperatein(e.g.,local,production,etc.).
--versionshowsyouwhichversionofLaravelyourapplicationisrunningon.
You’veprobablyguessedfromlookingattheseoptionsthatArtisancommandsareintendedtobeusedmuchlikebasicshellcommands:youmightrunthemmanually,buttheycanalsofunctionasapartofsomeautomatedprocessatsomepoint.
Forexample,therearemanyautomateddeployprocessesthatmightbenefitfromcertainArtisancommands.Youmightwanttorunphpartisanoptimizeeverytimeyoudeployanapplication.Flagslike-qand--no-interactionensurethatyourdeployscripts,notattendedbyahumanbeing,cankeeprunningsmoothly.
TheGroupedCommandsTherestofthecommandsavailableoutoftheboxaregroupedbycontext.Wewon’tcoverthemallhere,butwe’llcovereachcontextbroadly:
app
Thisjustcontainsapp:name,whichallowsyoutoreplaceeveryinstanceofthedefaulttop-levelApp\namespacewithanamespaceofyourchoosing.Forexample:phpartisanapp:nameMyApplication.
auth
Allwehavehereisauth:clear-resets,whichflushesalloftheexpiredpasswordresettokensfromthedatabase.
cache
cache:clearclearsthecaches,andcache:tablecreatesadatabasemigrationifyouplantousethedatabasecachedriver.
config
config:cachecachesyourconfigurationsettingsforfasterlookup;toclearthecache,useconfig:clear.
db
db:seedseedsyourdatabase,ifyouhaveconfigureddatabaseseeders.
event
event:generatebuildsmissingeventandeventlistenerfilesbasedonthedefinitionsinEventServiceProvider.We’lllearnmoreabouteventsinChapter16.
key
key:generatecreatesarandomapplicationencryptionkeyinyour.envfile.
RERUNNINGARTISANKEY:GENERATEMEANSLOSINGENCRYPTIONKEYS
Onlyrunphpartisankey:generateonce—thefirsttimeyousetuptheapplicationinanewenvironment—becausethiskeyisusedtoencryptyourdata;ifyouchangeitafterdatahasbeenstored,thatdatawillallbecomeinaccessible.
make
make:authscaffoldsouttheviewsandcorrespondingroutesforalandingpage,auserdashboard,andloginandregisterpages.Alltherestofthemake:actionscreateasingleitem,andhaveparametersthatvaryaccordingly.Tolearnmoreaboutanyindividualcommand’sparameters,usehelptoreaditsdocumentation.Forexample,youcouldrunphpartisanhelpmake:migrationandlearnthatyoucanpass--create=tableNameHeretocreateamigrationthatalreadyhasthecreatetablesyntaxinthefile,asshownhere:phpartisanmake:migrationcreate_posts_table--create=posts.
migrate
Wesawamigratecommandearliertorunourmigrations,butherewecanrunalltheothermigration-relatedcommands.Createthemigrationstable(tokeeptrackofthemigrationsthatareexecuted)withmigrate:install,resetyourmigrationsandstartfromscratchwithmigrate:reset,resetyourmigrationsandrunthemallagainwithmigrate:refresh,rollbackjustonemigrationwithmigrate:rollback,orcheckthestatusofyourmigrationswithmigrate:status.
notifications
notifications:tablegeneratesamigrationthatcreatesthetablefordatabasenotifications.
queue
We’llcoverLaravel’squeuesinChapter16,butthebasicideaisthatyoucanpushjobsupintoremotequeuestobeexecutedoneafteranotherbyaworker.Thiscommandgroupprovidesallthetoolsyouneedtointeractwithyourqueues,likequeue:listentostartlisteningtoaqueue,queue:tabletocreateamigrationfordatabase-backedqueues,andqueue:flushtoflushallfailedqueuejobs.Therearequiteafewmore,whichwe’lllearnaboutinChapter16.
route
Ifyourunroute:list,you’llseethedefinitionsofeveryroutedefinedintheapplication,includingeachroute’sverb(s),path,name,controller/closureaction,andmiddleware.Youcancachetheroutedefinitionsforfasterlookupswithroute:cacheandclearyourcachewithroute:clear.
schedule
We’llcoverLaravel’scron-likeschedulerinChapter16,butinorderforittowork,youneedtosetthesystemcrontorunschedule:runonceaminute:
*****php/home/myapp.com/artisanschedule:run>>/dev/null2>&1
Asyoucansee,thisArtisancommandisintendedtoberunregularlyinordertopoweracoreLaravelservice.
session
session:tablecreatesamigrationforapplicationsusingdatabase-backedsessions.
storage
storage:linkcreatesasymboliclinkfrompublic/storagetostorage/app/public.ThisisacommonconventioninLaravelapps,tomakeiteasytoputuseruploads(orotherfilesthatcommonlyendupinstorage/app)somewherewherethey’llbeaccessibleatapublicURL.
vendor
SomeLaravel-specificpackagesneedto“publish”someoftheirassets,eithersothattheycanbeservedfromyourpublicdirectoryorsothatyoucanmodifythem.Eitherway,thesepackagesregisterthese“publishableassets”withLaravel,andwhenyourunvendor:publish,itpublishesthemtotheirspecifiedlocations.
view
Laravel’sviewrenderingengineautomaticallycachesyourviews.Itusuallydoesagoodjobofhandlingitsowncacheinvalidation,butifyouevernoticeit’sgottenstuck,runview:cleartoclearthecache.
WritingCustomArtisanCommandsNowthatwe’vecoveredtheArtisancommandsthatcomewithLaraveloutofthebox,let’stalkaboutwritingyourown.
First,youshouldknow:there’sanArtisancommandforthat!Runningphpartisanmake:commandYourCommandNamegeneratesanewArtisancommandinapp/Console/Commands/{YourCommandName}.php.
PHPARTISANMAKE:COMMANDThecommandsignatureformake:commandhaschangedafewtimes.Itwasoriginallycommand:make,butforawhilein5.2itwasconsole:makeandthenmake:console.
Finally,in5.3,it’ssettled:allofthegeneratorsareunderthemake:namespace,andthecommandtogeneratenewArtisancommandsisnowmake:command.
Yourfirstargumentshouldbetheclassnameofthecommand,andyoucanoptionallypassa--commandparametertodefinewhattheterminalcommandwillbe(e.g.,appname:action).
So,let’sdoit:
phpartisanmake:consoleWelcomeNewUsers--command=email:newusers
TakealookatExample7-1toseewhatyou’llget.
Example7-1.ThedefaultskeletonofanArtisancommand<?php
namespaceApp\Console\Commands;
useIlluminate\Console\Command;
classWelcomeNewUsersextendsCommand
{
/**
*Thenameandsignatureoftheconsolecommand.
*
*@varstring
*/
protected$signature='email:newusers';
/**
*Theconsolecommanddescription.
*
*@varstring
*/
protected$description='Commanddescription';
/**
*Createanewcommandinstance.
*
*@returnvoid
*/
publicfunction__construct()
{
parent::__construct();
}
/**
*Executetheconsolecommand.
*
*@returnmixed
*/
publicfunctionhandle()
{
//
}
}
Asyoucansee,it’sveryeasytodefinethecommandsignature,thehelptextitshowsin
commandlists,andthecommand’sbehavioroninstantiation(__construct())andonexecution(handle()).
RegisteringCommandsThere’sonesteplefttomakethisnewcommandusableinyourapplication:youneedtoregisterit.
Openapp/Console/Kernel.php.You’llseeanarrayofcommandclassnamesunderthe$commandsproperty.Toregisteryournewcommand,additsclasstothisarray.Youcanwriteitout,orjustusethe::classclassnameaccessorontheclassasinExample7-2.
Example7-2.RegisteringanewcommandintheconsolekernelclassKernelextendsConsoleKernel
{
/**
*TheArtisancommandsprovidedbyyourapplication.
*
*@vararray
*/
protected$commands=[
\App\Console\Commands\WelcomeNewUsers::class,
];
WRITINGCLOSURE-BASEDCOMMANDSIfyou’dprefertokeepyourcommanddefinitionprocesssimpler,youcanwritecommandsasclosuresinsteadofclassesbydefiningtheminroutes/console.php.Everythingwediscussinthischapterwillapplythesameway,butyouwilljustdefineandregisterthecommandsinasinglestepinthatfile:
//routes/console.php
Artisan::command(
'password:reset{userId}{--sendEmail}',
function($userId,$sendEmail){
//dosomething...
}
);
ASampleCommandWehaven’tcoveredmailorEloquentyet(seeChapter15formailandChapter8forEloquent),butthesamplehandle()methodinExample7-3shouldreadprettyclearly.
Example7-3.AsampleArtisancommandhandle()method...
classWelcomeNewUsersextendsCommand
{
publicfunctionhandle()
{
User::signedUpThisWeek()->each(function($user){
Mail::send(
'emails.welcome',
['name'=>$user->name],
function($m)use($user){
$m->to($user->email)->subject('Welcome!');
}
);
});
}
Noweverytimeyourunphpartisanemail:newusers,thiscommandwillgrabeveryuserthatsignedupthisweekandsendthemthewelcomeemail.
Ifyouwouldpreferinjectingyourmailanduserdependenciesinsteadofusingfacades,youcantypehinttheminthecommandconstructor,andLaravel’scontainerwillinjectthemforyouwhenthecommandisinstantiated.
TakealookatExample7-4toseewhatExample7-3mightlooklikeusingdependencyinjectionandextractingitsbehaviorouttoaserviceclass.
Example7-4.Thesamecommand,refactored...
classWelcomeNewUsersextendsCommand
{
publicfunction__construct(UserMailer$userMailer)
{
parent::__construct();
$this->userMailer=$userMailer
}
publicfunctionhandle()
{
$this->userMailer->welcomeNewUsers();
}
KEEPITSIMPLE
ItispossibletocallArtisancommandsfromtherestofyourcode,soyoucanusethemtoencapsulatechunksofapplicationlogic.ThisisaverycommonpracticeintheLaravelcommunity.
However,theLaraveldocsrecommendinsteadpackagingtheapplicationlogicintoaserviceclass,andinjectingthatserviceintoyourcommand.Consolecommandsareseenasbeingsimilartocontrollers:they’renotdomainclasses,they’retrafficcopsthatjustrouteincomingrequeststothecorrectbehavior.
ArgumentsandOptionsThe$signaturepropertyofthenewcommandlookslikeitmightjustcontainthecommandname.Butthispropertyisalsowhereyou’lldefineanyargumentsandoptionsforthecommand.There’saspecific,simplesyntaxyoucanusetoaddargumentsandoptionstoyourArtisancommands.
Beforewedigintothatsyntax,takealookatanexampleforsomecontext:
protected$signature='password:reset{userId}{--sendEmail}';
Arguments,required,optional,and/orwithdefaultsTodefinearequiredargument,surrounditwithbraces:
password:reset{userId}
Tomaketheargumentoptional,addaquestionmark:
password:reset{userId?}
Tomakeitoptionalandprovideadefault,use:
password:reset{userId=1}
Options,requiredvalues,valuedefaults,andshortcutsOptionsaresimilartoarguments,butthey’reprefixedwith--andcanbeusedwithnovalue.Toaddabasicoption,surrounditwithbraces:
password:reset{userId}{--sendEmail}
Ifyouroptionrequiresavalue,addan=toitssignature:
password:reset{userId}{--password=}
Andifyouwanttopassadefaultvalue,additafterthe=:
password:reset{userId}{--queue=default}
ArrayargumentsandarrayoptionsBothforargumentsandforoptions,ifyouwanttoacceptanarrayasinput,usethe*character:
password:reset{userIds*}
password:reset{--ids=*}
UsingarrayargumentsandparameterslooksabitlikeExample7-5.
Example7-5.UsingarraysyntaxwithArtisancommands//Argument
phpartisanpassword:reset123
//Option
phpartisanpassword:reset--ids=1--ids=2--ids=3
ARRAYARGUMENTSMUSTBETHELASTARGUMENTSinceanarrayargumentcaptureseveryparameterafteritsdefinitionandaddsthemasarrayitems,anarrayargumenthastobethelastargumentoroptionwithinanArtisancommand’ssignature.
InputdescriptionsRememberhowthebuilt-inArtisancommandscangiveusmoreinformationabouttheirparametersifweuseartisanhelp?Wecanprovidethatsameinformationaboutourcustomcommands.Justaddacolonandthedescriptiontextwithinthecurlybraces,likeinExample7-6.
Example7-6.DefiningdescriptiontextforArtisanargumentsandoptionsprotected$signature='password:reset
{userId:TheIDoftheuser}
{--sendEmail:Whethertosenduseranemail}';
UsingInputNowthatwe’vepromptedforthisinput,howdoweuseitinourcommand’shandle()method?Wehavetwooptionsforretrievingthevaluesofargumentsandoptions.
argument()$this->argument()withnoparametersreturnsanarrayofallarguments(thefirstarrayitemwillbethecommandname).Withaparameterpassed,it’llreturnthevalueoftheargumentspecified:
//withdefinition"password:reset{userId}":
phpartisanpassword:reset5
//$this->argument()returnsthisarray
[
"command":"password:reset",
"userId':"5",
]
//$this->argument('userId')returnsthisstring
"5"
option()$this->option()withnoparametersreturnsanarrayofalloptions,includingsomethatwillbydefaultbefalseornull.Withaparameter,it’llreturnthevalueoftheoptionspecified:
//withdefinition"password:reset{--userId=}":
phpartisanpassword:reset--userId=5
//$this->option()returnsthisarray
[
"userId"=>"5"
"help"=>false
"quiet"=>false
"verbose"=>false
"version"=>false
"ansi"=>false
"no-ansi"=>false
"no-interaction"=>false
"env"=>null
]
//$this->option('userId')returnsthisstring
"5"
Example7-7showsanArtisancommandusingargument()andoption()initshandle()method.
Example7-7.GettinginputfromanArtisancommandpublicfunctionhandle()
{
//Allarguments,includingthecommandname
$arguments=$this->argument();
//Justthe'userId'argument
$userid=$this->argument('userId');
//Alloptions,includingsomedefaultslike'no-interaction'and'env'
$options=$this->option();
//Justthe'sendEmail'option
$sendEmail=$this->option('sendEmail');
}
PromptsThereareafewmorewaystogetuserinputfromwithinyourhandle()code,andtheyallinvolvepromptingtheusertoenterinformationduringtheexecutionofyourcommand:
ask()
Promptstheusertoenterfreeformtext:
$email=$this->ask('Whatisyouremailaddress?');
secret()
Promptstheusertoenterfreeformtext,buthidesthetypingwithasterisks:
$password=$this->ask('WhatistheDBpassword?');
confirm()
Promptstheuserforayes/noanswer,andreturnsaboolean:
if($this->confirm('Doyouwanttotruncatethetables?')){
//
}
AllanswersexceptyorYwillbetreatedasa“no.”
anticipate()
Promptstheusertoenterfreeformtext,andprovidesautocompletesuggestions.Stillallowstheusertotypewhatevershewants:
$album=$this->anticipate('Whatisthebestalbumever?',[
"TheJoshuaTree","PetSounds","What'sGoingOn"
]);
choice()
Promptstheusertochooseoneoftheprovidedoptions.Thelastparameteristhedefaultiftheuserdoesn’tchoose:
$winner=$this->choice(
'Whoisthebestfootballteam?',
['Gators','Wolverines'],
0
);
Notethatthefinalparameter,thedefault,shouldbethearraykey.Sincewepassedanonassociativearray,thekeyfor“Gators”is0.Youcouldalsokeyyourarray,ifyou’dprefer:
$winner=$this->choice(
'Whoisthebestfootballteam?',
['gators'=>'Gators','wolverines'=>'Wolverines'],
'gators'
);
OutputDuringtheexecutionofyourcommand,youmightwanttowritemessagestotheuser.Themostbasicwaytodothisistouse$this->info()tooutputbasicgreentext:
$this->info('Yourcommandhasrunsuccessfully.');
Youalsohaveavailablethecomment()(orange),question()(highlightedteal),error()(highlightedred),andline()(uncolored)methodstoechotothecommandline.
Pleasenotethattheexactcolorsmayvaryfrommachinetomachine,buttheytrytobeinlinewiththelocalmachine’sstandardsforcommunicatingtotheenduser.
TableoutputThetablecomponentmakesitsimpletocreateASCIItablesfullofyourdata.TakealookatExample7-8.
Example7-8.OutputtingtableswithArtisancommands$headers=['Name','Email'];
$data=[
['Dhriti','[email protected]'],
['Moses','[email protected]']
];
//Or,youcouldgetsimilardatafromthedatabase:
//$data=App\User::all(['name','email'])->toArray();
$this->table($headers,$data);
NotethatExample7-8hastwosetsofdata:theheaders,andthedataitself.Bothcontaintwo“cells”per“row”;thefirstcellineachrowisthename,andthesecondistheemail.ThatwaythedatafromtheEloquentcall(whichisconstrainedtopullonlynameandemail)matchesupwiththeheaders.
TakealookatExample7-9toseewhatthetableoutputlookslike.
Example7-9.SampleoutputofanArtisantable+---------+--------------------+
|Name|Email|
+---------+--------------------+
|Dhriti|[email protected]|
|Moses|[email protected]|
+---------+--------------------+
ProgressbarsIfyou’veeverrunnpminstall,you’veseenacommand-lineprogressbarbefore.Let’sbuildoneinExample7-10.
Example7-10.SampleArtisanprogressbar$totalUnits=10;
$this->output->progressStart($totalUnits);
for($i=0;$i<$totalUnits;$i++){
sleep(1);
$this->output->progressAdvance();
}
$this->output->progressFinish();
Whatdidwedohere?First,weinformedthesystemhowmany“units”weneededtoworkthrough.Maybeaunitisauser,andyouhave350users.Thebarwillthendividetheentirewidthithasavailableonyourscreenby350,andincrementitby1/350theverytimeyourunprogressAdvance().Onceyou’redone,runprogressFinish()soitknowsit’sdonedisplayingtheprogressbar.
CallingArtisanCommandsinNormalCodeWhileArtisancommandsaredesignedtoberunfromthecommandline,youcanalsocallthemfromothercode.
TheeasiestwayistousetheArtisanfacade.YoucaneithercallacommandusingArtisan::call()(whichwillreturnthecommand’sexitcode),orqueueacommandusingArtisan::queue().
Bothtaketwoparameters:first,theterminalcommand(password:reset);andsecond,anarrayofparameterstopassit.TakealookatExample7-11toseehowitworkswithargumentsandoptions.
Example7-11.CallingArtisancommandsfromothercodeRoute::get('test-artisan',function(){
$exitCode=Artisan::call('password:reset',[
'userId'=>15,'--sendEmail'=>true
]);
});
Asyoucansee,argumentsarepassedbykeyingtotheargumentname,andoptionswithnovaluecanbepassedtrueorfalse.
YoucanalsocallArtisancommandsfromothercommands,using$this->call,(whichisthesameasArtisan::call(),or$this->callSilent,whichisthesamebutsuppressesalloutput).SeeExample7-12foranexample.
Example7-12.CallingArtisancommandsfromotherArtisancommandspublicfunctionhandle()
{
$this->callSilent('password:reset',[
'userId'=>15
]);
}
Finally,youcaninjectaninstanceoftheIlluminate\Contracts\Console\Kernelcontract,anduseitscall()method.
TinkerTinkerisaREPL,orread–eval–printloop.Ifyou’veeverusedIRBinRuby,you’llbefamiliarwithhowaREPLworks.
REPLsgiveyouaprompt,similartothecommand-lineprompt,thatmimicsa“waiting”stateofyourapplication.YoutypeyourcommandsintotheREPL,hitReturn,andthenexpectwhatyoutypedtobeevaluatedandtheresponseprintedout.
Example7-13providesaquicksampletogiveyouasenseofhowitworksandhowitmightbeuseful.WestarttheREPLwithphpartisantinkerandarethenpresentedwithablankprompt(>>>);everyresponsetoourcommandsisprintedonalineprefacedwith=>.
Example7-13.UsingTinkerphpartisantinker
>>>$user=newApp\User;
=>App\User:{}
>>>$user->email='[email protected]';
>>>$user->password=bcrypt('superSecret');
=>"$2y$10$TWPGBC7e8d1bvJ1q5kv.VDUGfYDnE9gANl4mleuB3htIY2dxcQfQ5"
>>>$user->save();
=>true
Asyoucansee,wecreatedanewuser,setsomedata,andsavedittothedatabase.Andthisisreal.Ifthiswereaproductionapplication,wewould’vejustcreatedabrandnewuserinoursystem.
ThismakesTinkeragreattoolforsimpledatabaseinteractions,fortryingoutnewideas,andforrunningsnippetsofcodewhenit’dbeapaintofindaplacetoputthemintheapplicationsourcefiles.
TinkerispoweredbyPsyShell,socheckthatouttoseewhatelseyoucandowithTinker.
TestingSinceyouknowhowtocallArtisancommandsfromcode,it’seasytodothatinatestandensurethatwhateverbehavioryouexpectedtobeperformedhasbeenperformedcorrectly,asinExample7-14.
Example7-14.CallingArtisancommandsfromatestpublicfunctiontest_empty_log_command_empties_logs_table()
{
DB::table('logs')->insert(['message'=>'Didsomething']);
Artisan::call('logs:empty');
$this->assertCount(0,DB::table('logs')->get());
}
Asalways,facadesareeasytoswapout,butifyoudon’twanttodothisyoucaninsteadinjectyourdependenciesintotheconstructoroftheArtisancommand,whichwillmakethemeasytoswapoutattesttime.
TheArtisanfacadeprovidesaccesstotheIlluminate\Contracts\Console\Kernelcontract,soifyouwanttoavoidusingthefacadeinyourcode,youcaninsteadinjectaninstanceofthatanduseitscall()method,asinExample7-15.
Example7-15.InjectingthekernelinsteadofusingtheArtisanfacadeuseIlluminate\Contracts\Console\Kernel;
...
classNightlyCleanupextendsJob
{
...
publicfunctionhandle(Kernel$kernel)
{
//...dootherstuff
$kernel->call('logs:empty');
}
TL;DRArtisancommandsareLaravel’scommand-linetools.Laravelcomeswithquiteafewoutofthebox,butit’salsoeasytocreateyourownArtisancommandsandcallthemfromthecommandlineoryourowncode.
TinkerisaREPLthatmakesitsimpletogetintoyourapplicationenvironmentandinteractwithrealcodeandrealdata.
Chapter8.DatabaseandEloquent
Laravelprovidesasuiteoftoolsforinteractingwithyourapplication’sdatabases,butthemostnotableisEloquent,Laravel’sActiveRecordORM(object-relationalmapper).
EloquentisoneofLaravel’smostpopularandinfluentialfeatures.It’sagreatexampleofhowLaravelisdifferentfromthemajorityofPHPframeworks;inaworldofDataMapperORMsthatarepowerfulbutcomplex,Eloquentstandsoutforitssimplicity.There’soneclasspertable,whichisresponsibleforretrieving,representing,andpersistingdatainthattable.
WhetherornotyouchoosetouseEloquent,however,you’llstillgetatonofbenefitfromtheotherdatabasetoolsLaravelprovides.So,beforewedigintoEloquent,let’sstartbycoveringthebasicsofLaravel’sdatabasefunctionality:migrations,seeders,andthequerybuilder.
Thenwe’llcoverEloquent:definingyourmodels;inserting,updating,anddeleting;customizingyourresponseswithaccessors,mutators,andattributecasting;andfinallyrelationships.There’salotgoingonhere,andit’seasytogetoverwhelmed,butjusttakeitonestepatatimeandwe’llmakeitthrough.
ConfigurationBeforewegetintohowtouseLaravel’sdatabasetools,let’spauseforasecondandgooverhowtoconfigureyourdatabasecredentialsandconnections.
Theconfigurationfordatabaseaccesslivesinconfig/database.php.LikemanyotherconfigurationareasinLaravel,youcandefinemultiple“connections”andthendecidewhichthecodewillusebydefault.
DatabaseConnectionsBydefault,there’soneconnectionforeachoftheconnectiontypes,asyoucanseeinExample8-1.
Example8-1.Thedefaultdatabaseconnectionslist'connections'=>[
'sqlite'=>[
'driver'=>'sqlite',
'database'=>database_path('database.sqlite'),
'prefix'=>'',
],
'mysql'=>[
'driver'=>'mysql',
'host'=>env('DB_HOST','localhost'),
'database'=>env('DB_DATABASE','forge'),
'username'=>env('DB_USERNAME','forge'),
'password'=>env('DB_PASSWORD',''),
'charset'=>'utf8',
'collation'=>'utf8_unicode_ci',
'prefix'=>'',
'strict'=>false,
'engine'=>null,
],
'pgsql'=>[
'driver'=>'pgsql',
'host'=>env('DB_HOST','localhost'),
'database'=>env('DB_DATABASE','forge'),
'username'=>env('DB_USERNAME','forge'),
'password'=>env('DB_PASSWORD',''),
'charset'=>'utf8',
'prefix'=>'',
'schema'=>'public',
],
'sqlsrv'=>[
'driver'=>'sqlsrv',
'host'=>env('DB_HOST','localhost'),
'database'=>env('DB_DATABASE','forge'),
'username'=>env('DB_USERNAME','forge'),
'password'=>env('DB_PASSWORD',''),
'charset'=>'utf8',
'prefix'=>'',
],
]
Youcouldcreatenewnamedconnections,though,andstillbeabletosetthedrivers(MySQL,Postgres,etc.)inthosenewnamedconnections.So,whilethere’soneconnectionperdriverbydefault,that’snotaconstraint.
Eachconnectionallowsyoutodefinethepropertiesnecessaryforconnectingtoandcustomizingeachconnectiontype.
Thereareafewreasonsfortheideaofmultipledrivers.Tostartwith,the“connections”sectionasitcomesoutoftheboxisasimpletemplatethatmakesiteasytostartappsthatuseanyofthesupporteddatabaseconnectiontypes.Inmanyapps,youcanpickthedatabaseconnectionyou’llbeusing,filloutitsinformation,andevendeletetheothersifyou’dlike.Iusuallyjustkeepthemallthere,incaseImighteventuallyusethem.
Buttherearealsosomecaseswhereyoumightneedmultipleconnectionswithinthesameapplication.Forexample,youmightusedifferentdatabaseconnectionsfortwodifferenttypesofdata,oryoumightreadfromoneandwritetoanother.Supportformultipleconnectionsmakesthispossible.
OtherDatabaseConfigurationOptionsTheconfig.databaseconfigurationsectionhasquiteafewotherconfigurationsettings.YoucanconfigureRedisaccess,customizethetablenameusedformigrations,determinethedefaultconnection,andtogglewhethernon-EloquentcallsreturnstdClassorarrayinstances.
WithanyserviceinLaravelthatallowsmultiple“connections”—sessionscanbebackedbythedatabaseorfilestorage,thecachecanuseRedisorMemcached,databasescanuseMySQLorPostgreSQL—youcandefinemultipleconnectionsandalsochoosethataparticularconnectionwillbethe“default,”meaningitwillbeusedanytimeyoudon’texplicitlyaskforaparticularconnection.Here’showyouaskforaspecificconnection,ifyouwantto:
$users=DB::connection('secondary')->select('select*fromusers');
MigrationsModernframeworkslikeLaravelmakeiteasytodefineyourdatabasestructurewithcode-drivenmigrations.Everynewtable,column,index,andkeycanbedefinedincode,andanynewenvironmentcanbebroughtfrombaredatabasetoyourapp’sperfectschemainseconds.
DefiningMigrationsAmigrationisasinglefilethatdefinestwothings:themodificationsdesiredwhenrunningthismigrationupandthemodificationsdesiredwhenrunningthismigrationdown.
“UP”AND“DOWN”INMIGRATIONS
Migrationsarealwaysruninorderbydate.Everymigrationfileisnamedsomethinglikethis:2014_10_12_000000_create_users_table.php.Whenanewsystemismigrated,thesystemgrabseachmigration,startingattheearliestdate,andrunsitsup()method—you’remigratingit“up”atthispoint.Butthemigrationsystemalsoallowsyouto“rollback”yourmostrecentsetofmigrations.It’llgrabeachofthemandrunitsdown()method,whichshouldundowhateverchangestheupmigrationmade.
So,theup()methodofamigrationshould“do”itsmigration,andthedown()methodshould“undo”it.
Example8-2showswhatthedefault“createuserstable”migrationthatcomeswithLaravellookslike.
Example8-2.Laravel’sdefault“createuserstable”migration<?php
useIlluminate\Database\Schema\Blueprint;
useIlluminate\Database\Migrations\Migration;
classCreateUsersTableextendsMigration
{
/**
*Runthemigrations.
*
*@returnvoid
*/
publicfunctionup()
{
Schema::create('users',function(Blueprint$table){
$table->increments('id');
$table->string('name');
$table->string('email')->unique();
$table->string('password',60);
$table->rememberToken();
$table->timestamps();
});
}
/**
*Reversethemigrations.
*
*@returnvoid
*/
publicfunctiondown()
{
Schema::drop('users');
}
}
Asyoucansee,wehaveanup()methodandadown()method.up()tellsthemigrationtocreateanewtablenameduserswithafewfields,anddown()tellsittodroptheuserstable.
CreatingamigrationAswesawinChapter7,there’sanArtisancommandforcreatingamigrationfile.It’sphp
artisanmake:migration,andithasasingleparameter,whichisthenameofthemigration.Forexample,tocreatethetablewejustcovered,youwouldrunphpartisanmake:migrationcreate_users_table.
Therearetwoflagsyoucanoptionallypasstothiscommand.--create=table_nameprefillsthemigrationwithcodedesignedtocreateatablenamedtable_name,and--table=_table_name_justprefillsthemigrationformodificationstoanexistingtable.Hereareafewexamples:
phpartisanmake:migrationcreate_users_table
phpartisanmake:migrationadd_votes_to_users_table--table=users
phpartisanmake:migrationcreate_users_table--create=users
CreatingtablesWealreadysawinthedefaultcreate_users_tablemigrationthatourmigrationsdependontheSchemafacadeanditsmethods.EverythingwecandointhesemigrationswillrelyonthemethodsofSchema.
Tocreateanewtableinamigration,usethecreate()method—thefirstparameteristhetablename,andthesecondisaclosurethatdefinesitscolumns:
Schema::create('tablename',function(Blueprint$table){
//Createcolumnshere
});
CreatingcolumnsTocreatenewcolumnsinatable,whetherinacreatetablecalloramodifytablecall,usetheinstanceofBlueprintthat’spassedintoyourclosure:
Schema::create('users',function(Blueprint$table){
$table->string('name');
});
Let’slookatthevariousmethodsavailableonBlueprintinstancesforcreatingcolumns.I’lldescribehowtheyworkinMySQL,butifyou’reusinganotherdatabase,Laravelwilljustusetheclosestequivalent.
ThefollowingarethesimplefieldBlueprintmethods:
integer(colName),tinyInteger(colName),smallInteger(colName),mediumInteger(colName),bigInteger(colName)
AddsanINTEGERtypecolumn,oroneofitsmanyvariations
string(colName,OPTIONALlength)
AddsaVARCHARtypecolumn
binary(colName)
AddsaBLOBtypecolumn
boolean(colName)
AddsaBOOLEANtypecolumn(aTINYINT(1)inMySQL)
char(colName,length)
AddsaCHARcolumn
datetime(colName)
AddsaDATETIMEcolumn
decimal(colName,precision,scale)
AddsaDECIMALcolumn,withprecisionandscale—e.g.,decimal('amount',5,2)specifiesaprecisionof5andascaleof2
double(colName,totaldigits,digitsafterdecimal)
AddsaDOUBLEcolumn—e.g.,double('tolerance',12,8)specifies12digitslong,with8ofthosedigitstotherightofthedecimalplace,asin7204.05691739
enum(colName,[choiceOne,choiceTwo])
AddsanENUMcolumn,withprovidedchoices
float(colName)
AddsaFLOATcolumn(sameasdoubleinMySQL)
json(colName)andjsonb(colName)AddsaJSONorJSONBcolumn(oraTEXTcolumninLaravel5.1)
text(colName),mediumText(colName),longText(colName)AddsaTEXTcolumn(oritsvarioussizes)
time(colName)
AddsaTIMEcolumn
timestamp(colName)
AddsaTIMESTAMPcolumn
uuid(colName)
AddsaUUIDcolumn(CHAR(36)inMySQL)
Andthesearethespecial(joined)Blueprintmethods:
increments(colName)andbigIncrements(colName)AddanunsignedincrementingINTEGERorBIGINTEGERprimarykeyID
timestamps()andnullableTimestamps()
Addscreated_atandupdated_attimestampcolumns
rememberToken()
Addsaremember_tokencolumn(VARCHAR(100))foruser“rememberme”tokens
softDeletes()
Addsadeleted_attimestampforusewithsoftdeletes
morphs(colName)
Foraprovided+colName+,addsanintegercolName_idandastringcolName_type(e.g.,morphs('tag')addsintegertag_idandstringtag_type);foruseinpolymorphicrelationships
BuildingextrapropertiesfluentlyMostofthepropertiesofafielddefinition—itslength,forexample—aresetasthesecondparameterofthefieldcreationmethodwelookedatintheprevioussection.Butthereareafewotherpropertiesthatwe’llsetbychainingmoremethodcallsafterthecreationofthecolumn.Forexample,thisemailfieldisnullableandwillbeplaced(inMySQL)rightafterthelast_namefield:
Schema::table('users',function(Blueprint$table){
$table->string('email')->nullable()->after('last_name');
});
Thefollowingmethodsareusedtosetadditionalpropertiesofafield:
nullable()
AllowsNULLvaluestobeinsertedintothiscolumn
default('defaultcontent')
Specifiesthedefaultcontentforthiscolumnifnovalueisprovided
unsigned()
Marksintegercolumnsasunsigned
first()(MySQLonly)Placesthecolumnfirstinthecolumnorder
after(colName)(MySQLonly)Placesthecolumnafteranothercolumninthecolumnorder
unique()
AddsaUNIQUEindex
primary()
Addsaprimarykeyindex
index()
AddsabasicindexNotethatunique(),primary(),andindex()canalsobeusedoutsideofthefluentcolumnbuildingcontext,whichwe’llcoverlater.
DroppingtablesIfyouwanttodropatable,there’sadropmethodonSchemathattakesoneparameter,thetablename:
Schema::drop('contacts');
ModifyingcolumnsTomodifyacolumn,justwritethecodeyouwouldwritetocreatethecolumnasifitwerenew,andthenappendacalltothechange()methodafterit.
REQUIREDDEPENDENCYBEFOREMODIFYINGCOLUMNSBeforeyoumodifyanycolumns(ordropanycolumnsinSQLite),you’llneedtoaddthedoctrine/dbalpackageasarequirementinyourcomposer.json,andruncomposerupdatetobringitin.
So,ifwehaveastringcolumnnamednamethathasalengthof255andwewanttochangeitslengthto100,thisishowwewouldwriteit:
Schema::table('users',function($table){
$table->string('name',100)->change();
});
Thesameistrueifwewanttoadjustanyofitspropertiesthataren’tdefinedinthemethodname.Tomakeafieldnullable,wedothis:
Schema::table('contacts',function($table){
$table->string('deleted_at')->nullable()->change();
});
Here’showwerenameacolumn:
Schema::table('contacts',function($table)
{
$table->renameColumn('promoted','is_promoted');
});
Andthisishowwedropacolumn:
Schema::table('contacts',function($table)
{
$table->dropColumn('votes');
});
MODIFYINGMULTIPLECOLUMNSATONCEINSQLITEIfyoutrytodropormodifymultiplecolumnswithinasinglemigrationclosureandyouareusingSQLite,you’llrunintoerrors.
InChapter12I’llrecommendthatyouuseSQLiteforyourtestingdatabase,soevenifyou’reusingamoretraditionaldatabase,youmaywanttoconsiderthisalimitationfortestingpurposes.
However,youdon’thavetocreateanewmigrationforeach.Instead,justcreatemultiplecallstoSchema::table()withintheup()methodofyourmigration:
publicfunctionup()
{
Schema::table('contacts',function(Blueprint$table)
{
$table->dropColumn('is_promoted');
});
Schema::table('contacts',function(Blueprint$table)
{
$table->dropColumn('alternate_email');
});
}
IndexesandforeignkeysWe’vecoveredhowtocreate,modify,anddeletecolumns.Let’smoveontoindexingandrelatingthem.
AddingindexesCheckoutExample8-3forexamplesofhowtoaddindexestoyourcolumn.
Example8-3.Addingcolumnindexesinmigrations//aftercolumnsarecreated...
$table->primary('primary_id');//Primarykey;unnecessaryifusedincrements()
$table->primary(['first_name','last_name']);//Compositekeys
$table->unique('email');//Uniqueindex
$table->unique('email','optional_custom_index_name');//Uniqueindex
$table->index('amount');//Basicindex
$table->index('amount','optional_custom_index_name');//Basicindex
Notethatthefirstexample(primary())isnotnecessaryifyou’reusingtheincrements()methodtocreateyourindex;thiswillautomaticallyaddaprimarykeyindexforyou.
RemovingindexesWecanremoveindexesasshowninExample8-4.
Example8-4.Removingcolumnindexesinmigrations$table->dropPrimary('contacts_id_primary');
$table->dropUnique('contacts_email_unique');
$table->dropIndex('optional_custom_index_name');
//IfyoupassanarrayofcolumnnamestodropIndex,itwill
//guesstheindexnamesforyoubasedonthegenerationrules
$table->dropIndex(['email','amount']);
AddingandremovingforeignkeysToaddaforeignkeythatdefinesthataparticularcolumnreferencesacolumnonanothertable,Laravel’ssyntaxissimpleandclear:
$table->foreign('user_id')->references('id')->on('users');
Herewe’readdingaforeignindexontheuser_idcolumn,showingthatitreferencestheidcolumnontheuserstable.Couldn’tgetmuchsimpler.
Ifwewanttospecifyforeignkeyconstraints,wecandothattoo,withonDelete()andonUpdate().Forexample:
$table->foreign('user_id')
->references('id')
->on('users')
->onDelete('cascade');
Todropanindex,wecaneitherdeleteitbyreferencingitsindexname(whichisautomaticallygeneratedbycombiningthenamesofthecolumnsandtablesbeingreferenced):
$table->dropForeign('contacts_user_id_foreign');
orbypassingitanarrayofthefieldsthatit’sreferencingonthelocaltable:
$table->dropForeign(['user_id']);
RunningMigrationsOnceyouhaveyourmigrationsdefined,howdoyourunthem?There’sanArtisancommandforthat:
phpartisanmigrate
Thiscommandrunsall“outstanding”migrations.Laravelkeepstrackofwhichmigrationsyouhaverunandwhichyouhaven’t.Everytimeyourunthiscommand,itcheckswhetheryou’verunallavailablemigrations,andifyouhaven’t,it’llrunanythatremain.
Thereareafewoptionsinthisnamespacethatyoucanworkwith.First,youcanrunyourmigrationsandyourseeds(whichwe’llcovernext):
phpartisanmigrate--seed
Youcanalsorunanyofthefollowingcommands:migrate:installcreatesthedatabasetablethatkeepstrackofwhichmigrationsyouhaveandhaven’trun;thisisrunautomaticallywhenyourunyourmigrations.
migrate:resetrollsbackeverydatabasemigrationyou’verunonthisinstall.
migrate:refreshrollsbackeverydatabasemigrationyou’verunonthisinstall,andthenrunseverymigrationavailable.It’sthesameasrunningmigrate:resetandthenmigrate,oneaftertheother.
migrate:rollbackrollsbackjustthemigrationsthatranthelasttimeyouranmigrate,or,withtheaddedoption--step=1,rollsbackthenumberofmigrationsyouspecify.
migrate:statusshowsatablelistingeverymigration,withaYorNnexttoeachshowingwhetherornotithasrunyetinthisenvironment.
MIGRATINGWITHHOMESTEAD/VAGRANTIfyou’rerunningmigrationsonyourlocalmachineandyour.envfilepointstoadatabaseinaVagrantbox,yourmigrationswillfail.You’llneedtosshintoyourVagrantboxandthenrunthemigrationsfromthere.ThesameistrueforseedsandanyotherArtisancommandsthataffectorreadfromthedatabase.
SeedingSeedingwithLaravelissosimple,ithasgainedwidespreadadoptionasapartofnormaldevelopmentworkflowsinawayithasn’tinpreviousPHPframeworks.There’sadatabase/seedsfolderthatcomeswithaDatabaseSeederclass,whichhasarun()methodthatiscalledwhenyoucalltheseeder.
Therearetwoprimarywaystoruntheseeders:alongwithamigration,orseparately.
Torunaseederalongwithamigration,justadd--seedtoanymigrationcall:
phpartisanmigrate--seed
phpartisanmigrate:refresh--seed
Andtorunitindependently:
phpartisandb:seed
phpartisandb:seed--class=VotesTableSeeder
Thiswillrunwhateveryouhavedefinedintherun()methodsofeveryseederclass(orjusttheclassyoupassedto--class).
CreatingaSeederTocreateaseeder,usethemake:seederArtisancommand:
phpartisanmake:seederContactsTableSeeder
You’llnowseeaContactsTableSeederclassshowupinthedatabase/seedsdirectory.Beforeweeditit,let’saddittotheDatabaseSeederclasssoitwillrunwhenwerunourseeders:
//database/seeds/DatabaseSeeder.php
...
publicfunctionrun()
{
$this->call(ContactsTableSeeder::class);
}
Nowlet’sedittheseederitself.ThesimplestthingwecandothereismanuallyinsertarecordusingtheDBfacade:
<?php
useIlluminate\Database\Seeder;
useIlluminate\Database\Eloquent\Model;
classContactsTableSeederextendsSeeder
{
publicfunctionrun()
{
DB::table('contacts')->insert([
'name'=>'LupitaSmith'
'email'=>'[email protected]',
]);
}
}
Thiswillgetusasinglerecord,whichisagoodstart.Butfortrulyfunctionalseeds,you’lllikelywanttoloopoversomesortofrandomgeneratorandrunthisinsert()manytimes,right?
ModelFactoriesModelfactoriesdefineone(ormore)patternsforcreatingfakeentriesforyourdatabasetables.Bydefaultthey’renamedafteranEloquentclass,butyoucanalsojustnamethemafterthetablenameifyou’renotgoingtoworkwithEloquent.Here’sthesametablesetupbothways:
$factory->define(User::class,function(Faker\Generator$faker){
return[
'name'=>$faker->name,
];
});
$factory->define('users',function(Faker\Generator$faker){
return[
'name'=>$faker->name,
];
});
Theoretiallyyoucannamethesefactoriesanythingyoulike,butnamingthefactoryafteryourEloquentclassisthemostidiomaticapproach.
CreatingamodelfactoryModelfactoriesaredefinedindatabase/factories/ModelFactory.php.Eachfactoryhasanameandadefinitionofhowtocreateanewinstanceofthedefinedclass.The$factory->define()methodtakesthefactorynameasthefirstparameterandaclosurethat’srunforeachgenerationasthesecondparameter.
Thesimplestfactorywecoulddefinemightlooksomethinglikethis:
$factory->define(Contact::class,function(Faker\Generator$faker){
return[
'name'=>'LupitaSmith',
'email'=>'[email protected]',
];
});
Nowwecanusethefactory()globalhelpertocreateaninstanceofContactinourseedingandtesting:
//Createone
$contact=factory(Contact::class)->create();
//Createmany
factory(Contact::class,20)->create();
However,ifweusedthatfactorytocreate20contacts,all20wouldhavethesameinformation.That’slessuseful.
WewillgetevenmorebenefitfrommodelfactorieswhenwetakeadvantageoftheinstanceofFakerthat’spassedintotheclosure;Fakermakesiteasytorandomizethecreationofstructuredfakedata.Thepreviousexamplenowturnsintothis:
$factory->define(Contact::class,function(Faker\Generator$faker){
return[
'name'=>$faker->name,
'email'=>$faker->email,
];
});
Now,everytimewecreateafakecontactusingthismodelfactory,allofourpropertieswillbeunique.
UsingamodelfactoryTherearetwoprimarycontextsinwhichwe’llusemodelfactories:testing(whichwe’llcoverinChapter12)andseeding,whichwe’retalkingabouthere.Let’swriteaseederusingamodelfactory;takealookatExample8-5.
Example8-5.Usingmodelfactoriesfactory(Post::class)->create([
'title'=>'Mygreatestpostever'
]);
factory(User::class,20)->create()->each(function($u)use($post){
$post->comments()->save(factory(Comment::class)->make([
'user_id'=>$u->id
]));
});
Whenwe’reusingafactory,weusethefactory()globalhelper,andpassitthenameofthefactory—which,aswejustsaw,isthenameoftheEloquentclasswe’regeneratinganinstanceof.Thatreturnsthefactory,andthenwecanrunoneoftwomethodsonit:make()orcreate().
Bothmethodsgenerateaninstanceofthisclass,usingthedefinitioninmodelFactory.php.Thedifferenceisthatmake()createstheinstancebutdoesn’t(yet)saveittothedatabase,whereascreate()savesittothedatabaseinstantly.
ThesecondexamplewillmakemoresenseoncewecoverrelationshipsinEloquentlaterinthischapter.
OverridingpropertieswhencallingamodelfactoryIfyoupassanarraytoeithermake()orcreate(),youcanoverridespecifickeys,likewedidinExample8-7tosettheuser_idonthecommentandtomanuallysetthetitleofourpost.
GeneratingmorethanoneinstancewithamodelfactoryIfyoupassanumberasthesecondparametertothefactory()helper,youcanspecifythatyou’recreatingmorethanoneinstance.Insteadofreturningasingleinstance,it’llreturnacollectionofinstances.Thismeansyoucantreattheresultlikeanarray,youcanassociateeachofitsinstanceswithanotherentity,oryoucanuseotherentitymethodsoneachinstance—likeweusedeach()inExample8-5toaddacommentfromeachnewlycreateduser.
DefiningandaccessingmultiplemodelfactorytypesLet’sgobacktomodelFactory.phpforasecond.WehaveaContactfactorydefined:
$factory->define(Contact::class,function(Faker\Generator$faker){
return[
'name'=>$faker->name,
'email'=>$faker->email,
];
});
Butsometimesyouneedmorethanonefactoryforaclassofobject.Whatifweneedtobeabletoaddsomecontactswhoareveryimportantpeople(VIPs)?Wecandefineasecondfactorytypeforthis,asseeninExample8-6.
Example8-6.Definingmultiplefactorytypesforthesamemodel$factory->define(Contact::class,function(Faker\Generator$faker){
return[
'name'=>$faker->name,
'email'=>$faker->email,
];
});
$factory->defineAs(Contact::class,'vip',function(Faker\Generator$faker){
return[
'name'=>$faker->name,
'email'=>$faker->email,
'vip'=>true,
];
});
Butthat’salotofduplication,right?Thankfully,wecanmakeanygivenmodelfactoryextendanother,andthenitcanjustoverrideoneorafewproperties.Let’shaveour“VIP”contactnowjustextendthepreviousbyusing$factory->raw(),asshowninExample8-7.
Example8-7.Extendingafactorytype$factory->define(Contact::class,function(Faker\Generator$faker){
return[
'name'=>$faker->name,
'email'=>$faker->email,
];
});
$factory->defineAs(
Contact::class,
'vip',
function(Faker\Generator$faker)use($factory){
$contact=$factory->raw(Contact::class);
returnarray_merge($contact,['vip'=>true]);
});
Now,let’smakeaspecifictype:
$vip=factory(Contact::class,'vip')->create();
$vips=factory(Contact::class,'vip',3)->create();
QueryBuilderNowthatyou’reconnectedandyou’vemigratedandseededyourtables,let’sgetstartedwithhowtousethedatabasetools.AtthecoreofeverypieceofLaravel’sdatabasefunctionalityisthequerybuilder,afluentinterfaceforinteractingwithyourdatabase.
WHATISAFLUENTINTERFACE?
AfluentinterfaceisonethatprimarilyusesmethodchainingtoprovideasimplerAPItotheenduser.Ratherthanexpectingalloftherelevantdatatobepassedintoeitheraconstructororamethodcall,fluentcallchainscanbebuiltgradually,withconsecutivecalls.Considerthiscomparison:
//Non-fluent:
$users=DB::select(['table'=>'users','where'=>['type'=>'donor']]);
//Fluent:
$users=DB::table('users')->where('type','donor')->get();
Laravel’sdatabasearchitecturecanconnecttoMySQL,Postgres,SQLite,andSQLServerthroughasingleinterface,withjustthechangeofafewconfigurationsettings.
Ifyou’veeverusedaPHPframework,you’velikelyusedatoolthatallowsyoutorun“raw”SQLquerieswithbasicescapingforsecurity.Thequerybuilderisthat,withalotofconveniencelayersandhelpersontop,solet’sstartthere.
BasicUsageoftheDBFacadeBeforewegetintobuildingcomplexquerieswithfluentmethodchaining,let’stakealookatafewsampleDBfacadecommands.TheDBfacadeisusedbothforquerybuilderchainingandforsimplerrawqueries,asillustratedinExample8-8.
Example8-8.SamplerawSQLandquerybuilderusage//basicstatement
DB::statement('droptableusers')
//rawselect,andparameterbinding
DB::select('select*fromcontactswherevalidated=?',[true]);
//selectusingthefluentbuilder
$users=DB::table('users')->get();
//joinsandothercomplexcalls
DB::table('users')
->join('contacts',function($join){
$join->on('users.id','=','contacts.user_id')
->where('contacts.type','donor');
})
->get();
RawSQLAswesawinExample8-1,it’spossibletomakeanyrawcalltothedatabaseusingtheDBfacadeandthestatement()method:DB::statement('SQLstatementhere').
Buttherearealsospecificmethodsforvariouscommonactions:select(),insert(),update(),anddelete().Thesearestillrawcalls,buttherearedifferences.First,usingupdate()anddelete()willreturnthenumberofrowsaffected,whereasstatement()won’t;second,withthesemethodsit’sclearertofuturedevelopersexactlywhatsortofstatementyou’remaking.
RawselectsThesimplestofthespecificDBmethodsisselect().Youcanrunitwithoutanyadditionalparameters:
$users=DB::select('select*fromusers');
ThiswillreturnacollectionofstdClassobjects.
ILLUMINATECOLLECTIONS
PriortoLaravel5.3,theDBfacadereturnedastdClassobjectformethodsthatreturnonlyonerow(likefirst()),andanarrayforanythatreturnmultiplerows(likeall()).InLaravel5.3,theDBfacade,likeEloquent,returnsacollectionforanymethodthatreturns(orcanreturn)multiplerows.TheDBfacadereturnsaninstanceofIlluminate\Support\CollectionandEloquentreturnsaninstanceofIlluminate\Database\Eloquent\Collection,whichextendsIlluminate\Support\CollectionwithafewEloquent-specificmethods.
CollectionislikeaPHParraywithsuperpowers,allowingyoutorunmap(),filter(),reduce(),each(),andmuchmoreonyourdata.YoucanlearnmoreaboutcollectionsinChapter17.
ParameterbindingsandnamedbindingsLaravel’sdatabasearchitectureallowsfortheuseofPDOparameterbinding,whichprotectsyourqueriesfrompotentialSQLattacks.Passingaparametertoastatementisassimpleasreplacingthevalueinyourstatementwitha?,thenaddingthevaluetothesecondparameterofyourcall:
$usersOfType=DB::select(
'select*fromuserswheretype=?',
[$type]
);
Youcanalsonamethoseparametersforclarity:
$usersOfType=DB::select(
'select*fromuserswheretype=:type',
['type'=>$userType]
);
RawinsertsFromhere,therawcommandsalllookprettymuchthesame.Rawinsertslooklikethis:
DB::insert(
'insertintocontacts(name,email)values(?,?)',
['sally','[email protected]']
);
RawupdatesUpdateslooklikethis:
$countUpdated=DB::update(
'updatecontactssetstatus=?whereid=?',
['donor',$id]
);
RawdeletesAnddeleteslooklikethis:
$countDeleted=DB::delete(
'deletefromcontactswherearchived=?',
[true]
);
ChainingwiththeQueryBuilderUpuntilnow,wehaven’tactuallyusedthequerybuilder,perse.We’vejustusedsimplemethodcallsontheDBfacade.Let’sactuallybuildsomequeries.
Thequerybuildermakesitpossibletochainmethodstogetherto,youguessedit,buildaquery.Attheendofyourchainyou’llusesomemethod—likelyget()—totriggertheactualexecutionofthequeryyou’vejustbuilt.
Let’stakealookataquickexample:
$usersOfType=DB::table('users')
->where('type',$type)
->get();
Here,webuiltourquery—userstable,$typetype—andthenweexecutedthequeryandgotourresult.
Let’stakealookatwhatmethodsthequerybuilderallowsyoutochain.ThemethodscanbesplitupintowhatI’llcallconstrainingmethods,modifyingmethods,andending/returningmethods.
ConstrainingmethodsThesemethodstakethequeryasitisandconstrainittoreturnasmallersubsetofpossibledata:
select()
Allowsyoutochoosewhichcolumnsyou’reselecting:
$emails=DB::table('contacts')
->select('email','email2assecond_email')
->get();
//Or
$emails=DB::table('contacts')
->select('email')
->addSelect('email2assecond_email')
->get();
where()
Allowsyoutolimitthescopeofwhat’sbeingreturnedusingWHERE.Bydefault,thesignatureofthewhere()methodisthatittakesthreeparameters—thecolumn,thecomparisonoperator,andthevalue:
$newContacts=DB::table('contact')
->where('created_at','>',Carbon::now()->subDay())
->get();
However,ifyourcomparisonis=,whichisthemostcommoncomparison,youcandropthesecondoperator:$vipContacts=DB::table('contacts')->where('vip',true)->get();.
Ifyouwanttocombinewhere()statements,youcaneitherchainthemaftereachother,orpassanarrayofarrays:
$newVips=DB::table('contacts')
->where('vip',true)
->where('created_at','>',Carbon::now()
->subDay());
//Or
$newVips=DB::table('contacts')->where([
['vip',true],
['created_at','>',Carbon::now()->subDay()],
]);
orWhere()
CreatessimpleORWHEREstatements:
$priorityContacts=DB::table('contacts')
->where('vip',true)
->orWhere('created_at','>',Carbon::now()->subDay())
->get();
TocreateamorecomplexORWHEREstatementwithmultipleconditions,passorWhere()aclosure:
$contacts=DB::table('contacts')
->where('vip',true)
->orWhere(function($query){
$query->where('created_at','>',Carbon::now()->subDay())
->where('trial',false);
})
->get();
POTENTIALCONFUSIONWITHMULTIPLEWHEREANDORWHERECALLS
IfyouareusingorWhere()callsinconjunctionwithmultiplewhere()calls,youneedtobeverycarefultoensurethequeryisdoingwhatyouthinkitis.Thisisn’tbecauseofanyfaultwithLaravel,butbecauseaquerylikethefollowingmightnotdowhatyouexpect:
$canEdit=DB::table('users')
->where('admin',true)
->orWhere('plan','premium')
->where('is_plan_owner',true)
->get();
SELECT*FROMusers
WHEREadmin=1
ORplan='premium'
ANDis_plan_owner=1;
IfyouwanttowriteSQLthatsays“ifthisOR(thisandthis),”whichisclearlytheintentioninthepreviousexample,you’llwanttopassaclosureintotheorWhere()call:
$canEdit=DB::table('users')
->where('admin',true)
->orWhere(function($query){
$query->where('plan','premium')
->where('is_plan_owner',true);
})
->get();
SELECT*FROMusers
WHEREadmin=1
OR(plan='premium'ANDis_plan_owner=1);
whereBetween(colName,[low,high])
Allowsyoutoscopeaquerytoreturnonlyrowswhereacolumnisbetweentwovalues(inclusiveofthetwovalues):
$mediumDrinks=DB::table('drinks')
->whereBetween('size',[6,12])
->get();
ThesameworksforwhereNotBetween(),butitwillselecttheinverse.
whereIn(colName,[1,2,3])
Allowsyoutoscopeaquerytoreturnonlyrowswhereacolumnisinanexplicitlyprovidedlistofoptions:$closeBy=DB::table('contacts')->whereIn(state,[FL,GA,AL])->get().
$closeBy=DB::table('contacts')->whereIn('state',['FL','GA','AL'])->get()
ThesameworksforwhereNotIn(),butitwillselecttheinverse.
whereNull(colName)andwhereNotNull(colName)AllowyoutoselectonlyrowswhereagivencolumnisNULLorisNOTNULL,respectively.
whereRaw()
Allowsyoutopassinaraw,unescapedstringtobeaddedaftertheWHEREstatement:$goofs=DB::table('contacts')->whereRaw('id=12345')->get().
BEWAREOFSQLINJECTION!AnySQLqueriespassedtowhereRaw()willnotbeescaped.Usethismethodcarefullyandinfrequently;thisistheprimeopportunityforSQLinjectionattacksinyourapp.
whereExists()
Allowsyoutoselectonlyrowsthat,whenpassedintoaprovidedsubquery,returnatleastonerow.Imagineyouonlywanttogetthoseuserswhohaveleftatleastonecomment:
$commenters=DB::table('users')
->whereExists(function($query){
$query->select('id')
->from('comments')
->whereRaw('comments.user_id=users.id');
})
->get();
distinct()
Selectsonlydistinctrows.Usuallythisispairedwithselect(),becauseifyouuseaprimarykey,therewillbenoduplicatedrows:$lastNames=DB::table('contacts')->select('last_name')->distinct()->get().
ModifyingmethodsThesemethodschangethewaythequery’sresultswillbeoutput,ratherthanjustlimitingitsresults:
orderBy(colName,direction)
Orderstheresults.Thesecondparametermaybeeitherasc(thedefault)ordesc:
$contacts=DB::table('contacts')
->orderBy('last_name','asc')
->get();
groupBy()andhaving()orhavingRaw()Groupsyourresultsbyacolumn.Optionally,having()andhavingRaw()allowyoutofilteryourresultsbasedonpropertiesofthegroups.Forexample,youcouldlookforonlycitieswithatleast30peopleinthem:
$populousCities=DB::table('contacts')
->groupBy('city')
->havingRaw('count(contact_id)>30')
->get();
skip()andtake()
Mostoftenusedforpagination,theseallowyoutodefinehowmanyrowstoreturnandhowmanytoskipbeforestartingthereturn—likeapagenumberandapagesizeinapaginationsystem:
$page4=DB::table('contacts')->skip(30)->take(10)->get();
latest(colName)andoldest(colName)Sortbythepassedcolumn(orcreated_atifnocolumnnameispassed)indescending(latest())orascending(oldest())order.
inRandomOrder()
Sortstheresultrandomly.
Ending/returningmethodsThesemethodsstopthequerychainandtriggertheexecutionoftheSQLquery:
get()
Getsallresultsforthebuiltquery:
$contacts=DB::table('contacts')->get();
$vipContacts=DB::table('contacts')->where('vip',true)->get();
first()andfirstOrFail()Getonlythefirstresult—likeget(),butwithaLIMIT1added:
$newestContact=DB::table('contacts')
->orderBy('created_at','desc')
->first();
first()
Failssilentlyiftherearenoresults,whereasfirstOrFail()willthrowanexception.Ifyoupassanarrayofcolumnnamestoeithermethod,they’llreturnthedataforjustthosecolumnsinsteadofallcolumns.
find(id)andfindOrFail(id)Likefirst(),butyoupassinanIDvaluethatcorrespondstotheprimarykeytolookup.find()failssilentlyifarowwiththatIDdoesn’texist,whilefindOrFail()willthrowanexception:
$contactFive=DB::table('contacts')->find(5);
value()
Plucksjustthevaluefromasinglefieldfromthefirstrow.Likefirst(),butifyouonlywantasinglecolumn:
$newestContactEmail=DB::table('contacts')
->orderBy('created_at','desc')
->value('email');
count()
Returnsanintegercountofallofthematchingresults:
$countVips=DB::table('contacts')
->where('vip',true)
->count();
min()andmax()Returntheminimumormaximumvalueofaparticularcolumn:
$highestCost=DB::table('orders')->max('amount');
sum()andavg()Returnthesumoraverageofallofthevaluesinaparticularcolumn:
$averageCost=DB::table('orders')
->where('status','completed')
->avg('amount');
WritingrawqueriesinsidequerybuildermethodswithDB::rawWe’vealreadyseenafewcustommethodsforrawstatements—forexample,select()hasaselectRaw()counterpartthatallowsyoutopassinastringforthequerybuildertoplaceaftertheWHEREstatement.
Youcanalso,however,passintheresultofaDB::raw()calltoalmostanymethodinthequerybuildertoachievethesameresult:
$contacts=DB::table('contacts')
->select(DB::raw('*,(score*100)ASinteger_score'))
->get();
JoinsJoinscansometimesbeapaintodefine,andthere’sonlysomuchaframeworkcandotomakethemsimpler,butthequerybuilderdoesitsbest.Let’slookatasample:
$users=DB::table('users')
->join('contacts','users.id','=','contacts.user_id')
->select('users.*','contacts.name','contacts.status')
->get();
Thejoin()methodcreatesaninnerjoin.Youcanalsochaintogethermultiplejoinsoneafteranother,oruseleftJoin()togetaleftjoin.
Finally,youcancreatemorecomplexjoinsbypassingaclosureintothejoin()method:
DB::table('users')
->join('contacts',function($join){
$join
->on('users.id','=','contacts.user_id')
->orOn('users.id','=','contacts.proxy_user_id');
})
->get();
UnionsYoucanuniontwoqueriestogetherbycreatingthemfirstandthenusingtheunion()orunionAll()methodtounionthem:
$first=DB::table('contacts')
->whereNull('first_name');
$contacts=DB::table('contacts')
->whereNull('last_name')
->union($first)
->get();
InsertsTheinsert()methodisprettysimple.Passitanarraytoinsertasingleroworanarrayofarraystoinsertmultiplerows,anduseinsertGetId()insteadofinsert()togettheautoincrementingprimarykeyIDbackasareturn:
$id=DB::table('contacts')->insertGetId([
'name'=>'AbeThomas',
'email'=>'[email protected]',
]);
DB::table('contacts')->insert([
['name'=>'TamikaJohnson','email'=>'[email protected]'],
['name'=>'JimPatterson','email'=>'[email protected]'],
]);
UpdatesUpdatesarealsosimple.Createyourupdatequeryand,insteadofget()orfirst(),justuseupdate()andpassitanarrayofparameters:
DB::table('contacts')
->where('points','>',100)
->update(['status'=>'vip']);
Youcanalsoquicklyincrementanddecrementcolumnsusingtheincrement()anddecrement()methods.Thefirstparameterofeachisthecolumnname,andthesecondis(optionally)thenumbertoincrement/decrementby:
DB::table('contacts')->increment('tokens',5);
DB::table('contacts')->decrement('tokens');
DeletesDeletesareevensimpler.Buildyourqueryandthenenditwithdelete():
DB::table('users')
->where('last_login','<',Carbon::now()->subYear())
->delete();
Youcanalsotruncatethetable,whichbothdeleteseveryrowandalsoresetstheautoincrementingID:
DB::table('contacts')->truncate();
JSONoperationsIfyouhaveJSONcolumns,youcanupdateorselectrowsbasedonaspectsoftheJSONstructurebyusingthearrowsyntaxtotraversechildren:
//Selectallrecordswherethe"isAdmin"propertyofthe"options"
//JSONcolumnissettotrue
DB::table('users')->where('options->isAdmin',true)->get();
//Updateallrecords,settingthe"verified"property
//ofthe"options"JSONcolumntotrue
DB::table('users')->update(['options->isVerified',true]);
ThisisanewfeatureinLaravel5.3.
TransactionsIfyou’renotfamiliarwithdatabasetransactions,they’reatoolthatallowsyoutowrapupaseriesofdatabasequeriestobeperformedinabatch,whichyoucanchoosetorollback,undoingtheentireseriesofqueries.Transactionsareoftenusedtoensurethatallornone,butnotsome,ofaseriesofrelatedqueriesareperformed—ifonefails,theORMwillrollbacktheentireseriesofqueries.
WiththeLaravelquerybuilder ’stransactionfeature,ifanyexceptionsarethrownatanypointwithinthetransactionclosure,allthequeriesinthetransactionwillberolledback.Ifthetransactionclosurefinishessuccessfully,allthequerieswillbecommittedandnotrolledback.
Let’stakealookatExample8-9.
Example8-9.AsimpledatabasetransactionDB::transaction(function()use($userId,$numVotes)
{
//PossiblyfailingDBquery
DB::table('users')
->where('id',$userId)
->update(['votes'=>$numVotes]);
//Cachingquerythatwedon'twanttoruniftheabovequeryfails
DB::table('votes')
->where('user_id',$userId)
->delete();
});
Weclearlyhadsomepreviousprocessthatsummarizedthenumberofvotesfromthevotestable.Wewanttocachethatnumberontheuserstableandthenwipethosevotesfromthevotestable.But,ofcourse,wedon’twanttowipethevotesuntiltheupdatetotheuserstablehasrunsuccessfully.Andwedon’twanttokeeptheupdatednumberofvotesontheuserstableifthevotestabledeletionfails.
Ifanythinggoeswrongwitheitherquery,theotherwon’tbeapplied.That’sthemagicofdatabasetransactions.
Notethatyoucanalsomanuallybeginandendtransactions—andthisappliesbothforquerybuilderqueriesandforEloquentqueries.StartwithDB::beginTransaction(),endwithDB::commit(),andabortwithDB::rollBack().
IntroductiontoEloquentEloquentisanActiveRecordORM,whichmeansit’sadatabaseabstractionlayerthatprovidesasingleinterfacetointeractwithmultipledatabasetypes.“ActiveRecord”meansthatasingleEloquentclassisresponsiblefornotonlyprovidingtheabilitytointeractwiththetableasawhole(e.g.,User::all()getsallusers),butalsorepresentinganindividualtablerow(e.g.,$sharon=newUser).Additionally,eachinstanceiscapableofmanagingitsownpersistence;youcancall$sharon->save()or$sharon->delete().
Eloquenthasaprimaryfocusonsimplicity,andliketherestoftheframework,itrelieson“conventionoverconfiguration”toallowyoutobuildpowerfulmodelswithminimalcode.
Forexample,youcanperformalloftheoperationsinExample8-11withthemodeldefinedinExample8-10.
Example8-10.ThesimplestEloquentmodel<?php
useIlluminate\Database\Eloquent\Model;
classContactextendsModel{}
Example8-11.OperationsachievablewiththesimplestEloquentmodelpublicfunctionsave(Request$request)
{
//Createandsaveanewcontactfromuserinput
$contact=newContact();
$contact->first_name=$request->input('first_name');
$contact->last_name=$request->input('last_name');
$conatct->email=$request->input('email');
$contact->save();
returnredirect('contacts');
}
publicfunctionshow($contactId)
{
//ReturnaJSONrepresentationofaContactbasedonaURLsegment;
//ifthecontactdoesn'texist,throwanexception
returnContact::findOrFail($contactId);
}
publicfunctionvips()
{
//Unnecessarilycomplexexample,butstillpossiblewithbasicEloquent
//class;addsa"formalName"propertytoeveryVIPentry
returnContact::where('vip',true)->get()->map(function($contact){
$contact->formalName="Theexalted{$contact->first_name}ofthe
{$contact->last_name}s";
return$contact;
});
}
How?Convention.Eloquentassumesthetablename(Contactbecomescontacts),andwiththatyouhaveafullyfunctionalEloquentmodel.
Let’scoverhowweworkwithEloquentmodels.
CreatingandDefiningEloquentModelsFirst,let’screateamodel.There’sanArtisancommandforthat:
phpartisanmake:modelContact
Thisiswhatwe’llget,inapp/Contact.php:
<?php
namespaceApp;
useIlluminate\Database\Eloquent\Model;
classContactextendsModel
{
//
}
CREATINGAMIGRATIONALONGWITHYOURMODELIfyouwanttoautomaticallycreateamigrationwhenyoucreateyourmodel,passthe-mor--migrationflag:
phpartisanmake:modelContact--migration
TablenameThedefaultbehaviorfortablenamesisthatLaravel“snakecases”andpluralizesyourclassname,soSecondaryContactwouldaccessatablenamedsecondary_contacts.Ifyou’dliketocustomizethename,setthe$tablepropertyexplicitlyonthemodel:
protected$table='contacts_secondary';
PrimarykeyLaravelassumes,bydefault,thateachtablewillhaveanautoincrementingintegerprimarykey,anditwillbenamedid.
Ifyouwanttochangethenameofyourprimarykey,changethe$primaryKeyproperty:
protected$primaryKey='contact_id';
Andifyouwanttosetittobenonincrementing,use:
public$incrementing=false;
TimestampsEloquentexpectseverytabletohavecreated_atandupdated_attimestampcolumns.Ifyourtablewon’thavethem,disablethe$timestampsfunctionality:
public$timestamps=false;
YoucancustomizetheformatEloquentusestostoreyourtimestampstothedatabasebysettingthe$dateFormatclasspropertytoacustomstring.ThestringwillbeparsedusingPHP’sdate()syntax,sothefollowingexamplewillstorethedateassecondssincetheUnixepoch:
protected$dateFormat='U';
RetrievingDatawithEloquentMostofthetimeyoupulldatafromyourdatabasewithEloquent,you’llusestaticcallsonyourEloquentmodel.
Let’sstartbygettingeverything:
$allContacts=Contact::all();
Thatwaseasy.Let’sfilteritabit:
$vipContacts=Contact::where('vip',true)->get();
WecanseethattheEloquentfacadegivesustheabilitytochainconstraints,andfromtheretheconstraintsgetveryfamiliar:
$newestContacts=Contact::orderBy('created_at','desc')
->take(10)
->get();
Itturnsoutthatonceyoumovepasttheinitialfacadename,you’rejustworkingwithLaravel’squerybuilder.Youcandoalotmore—we’llcoverthatsoon—buteverythingyoucandowiththequerybuilderontheDBfacadeyoucandoonyourEloquentobjects.
GetoneLikewecoveredearlierinthechapter,youcanusefirst()toreturnonlythefirstrecordfromaquery,orfind()topulljusttherecordwiththeprovidedID.Foreither,ifyouappend“orFail”tothemethodname,itwillthrowanexceptioniftherearenomatchingresults.ThismakesfindOrFail()acommontoolinlookingupanentitybyaURLsegment(orthrowinganexceptionifamatchingentitydoesn’texist)likeyoucanseeinExample8-12.
Example8-12.UsinganEloquentOrFail()methodinacontrollermethod//ContactController
publicfunctionshow($contactId)
{
returnview('contacts.show')
->with('contact',Contact::findOrFail($contactId));
}
Anysinglereturn(first(),firstOrFail(),find(),orfindOrFail())willreturnaninstanceoftheEloquentclass.So,Contact::first()willreturnaninstanceoftheclassContactwiththedatafromrow1fillingitout.
EXCEPTIONSAsyoucanseeinExample8-12,wedon’tneedtocatchEloquent’smodelnotfoundexception(Illuminate\Database\Eloquent\ModelNotFoundException)inourcontrollers;Laravel’sroutingsystemwillcatchthemandthrowa404forus.
Youcould,ofcourse,catchthatparticularexceptionandhandleit,ifyou’dlike.
Getmanyget()workswithEloquentjustlikeitdoesinnormalquerybuildercalls—buildaqueryandcallget()attheendtogettheresults:
$vipContacts=Contact::where('vip',true)->get();
However,thereisanEloquent-onlymethod,all(),whichyou’lloftenseepeopleusewhentheywanttogetanunfilteredlistofalldatainthetable:
$contacts=Contact::all();
USINGGET()INSTEADOFALL()Anytimeyoucanuseall(),youcoulduseget().Contact::get()hasthesameresponseasContact::all().However,themomentyoustartmodifyingyourquery—addingawhere()filter,forexample—all()willnolongerwork,butget()willcontinueworking.
So,eventhoughall()isverycommon,I’drecommendusingget()foreverything,andignoringthefactthatall()evenexists.
Theotherthingthat’sdifferentaboutEloquent’sget()methodisthat,priortoLaravel5.3,itreturnedanarrayinsteadofacollection.In5.3andlater,theybothreturncollections.
Chunkingresponseswithchunk()Ifyou’veeverneededtoprocessalargeamount(thousandsormore)ofrecordsatatime,youmayhaverunintomemoryorlockingissues.Laravelmakesitpossibletobreakyourrequestsintosmallerpieces(chunks)andprocesstheminbatches,keepingthememoryloadofyourlargerequestsmaller.Example8-13illustratestheuseofchunk().
Example8-13.ChunkinganEloquentquerytolimitmemoryusageContact::chunk(100,function($contacts){
foreach($contactsas$contact){
//Dosomethingwith$contact
}
});
AggregatesTheaggregatesthatareavailableonthequerybuilderareavailableonEloquentqueriesaswell.Forexample:
$countVips=Contact::where('vip',true)->count();
$sumVotes=Contact::sum('votes');
$averageSkill=User::avg('skill_level');
InsertsandUpdateswithEloquentInsertingandupdatingvaluesisoneoftheplaceswhereEloquentstartstodivergefromnormalquerybuildersyntax.
InsertsTherearetwoprimarywaystoinsertanewrecordusingEloquent.
First,youcancreateanewinstanceofyourEloquentclass,setyourpropertiesmanually,andcallsave()onthatinstance,likeinExample8-14.
Example8-14.InsertinganEloquentrecordbycreatinganewinstance$contact=newContact;
$contact->name='KenHirata';
$contact->email='[email protected]';
$contact->save();
//or
$contact=newContact([
'name'=>'KenHirata',
'email'=>'[email protected]'
]);
$contact->save();
Untilyousave(),thisinstanceofContactrepresentsthecontactfully—exceptithasneverbeensavedtothedatabase.Thatmeansitdoesn’thaveanid,iftheapplicationquitsitwon’tpersist,anditdoesn’thaveitscreated_atandupdated_atvaluesset.
YoucanalsopassanarraytoModel::create()toachievethesameoutput,asshowninExample8-15.
Example8-15.InsertinganEloquentrecordbypassinganarraytocreate()$contact=Contact::create([
'name'=>'KeahiHale',
'email'=>'[email protected]'
]);
Alsobeawarethatinanycontextwhereyouarepassinganarray(eithertonewModel(),Model::create(),orModel::update()),everypropertyyousetviaModel::create()hastobeapprovedfor“massassignment,”whichwe’llcovershortly.
Notethatifyou’reusingModel::create(),youdon’tneedtosave()theinstance—that’shandledasapartofthemodel’screate()method.
UpdatesUpdatingrecordslooksverysimilartoinserting.Youcangetaspecificinstance,changeitsproperties,andthensave,oryoucanmakeasinglecallandpassanarrayofupdatedproperties.Example8-16illustratesthefirstapproach.
Example8-16.UpdatinganEloquentrecordbyupdatinganinstanceandsaving$contact=Contact::find(1);
$contact->email='[email protected]';
$contact->save();
Sincethisrecordalreadyexists,itwillalreadyhaveacreated_attimestampandanid,whichwillstaythesame,buttheupdated_atfieldwillbechangedtothecurrentdateandtime.Example8-17illustratesthesecondapproach.
Example8-17.UpdatingoneormoreEloquentrecordsbypassinganarraytotheupdate()methodContact::where('created_at','<',Carbon::now()->subYear())
->update(['longevity'=>'ancient']);
//or
$contact=Contact::find(1);
$contact->update(['longevity'=>'ancient']);
Thismethodexpectsanarraywhereeachkeyisthecolumnnameandeachvalueisthecolumnvalue.
MassassignmentWe’velookedatafewexamplesofhowtopassarraysofvaluesintoEloquentclassmethods.However,noneofthesewillactuallyworkuntilyoudefinewhichfieldsare“fillable”onthemodel.
Thegoalofthisistoprotect(malicious)userinputfromaccidentallysettingnewvaluesonfieldsyoudon’twantchanged.ConsiderthecommonscenarioinExample8-18.
Example8-18.UpdatinganEloquentmodelusingtheentiretyofarequest’sinput//ContactController
publicfunctionupdate(Contact$contact,Request$request)
{
$contact->update($request->all());
}
Ifyou’renotfamiliarwiththeIlluminateRequestobject,Example8-18willtakeeverypieceofuserinputandpassittotheupdate()method.Thatall()methodincludesthingslikeURLparametersandforminputs,soamalicioususercouldeasilyaddsomethingsinthere,likeidandowner_id,thatyoulikelydon’twantupdated.
Thankfully,thatwon’tactuallyworkuntilyoudefineyourmodel’sfillablefields.Youcaneitherwhitelistthefillablefields,orblacklistthe“guarded”fieldstodeterminewhichfieldscanorcannotbeeditedvia“massassignment”—i.e.,bypassinganarrayofvaluesintoeithercreate()orupdate().Notethatnonfillablepropertiescanstillbechangedbydirectassignment(e.g.,$contact\->password='abc';).Example8-19showsbothapproaches.
Example8-19.UsingEloquent’sfillableorguardedpropertiestodefinemass-assignablefieldsclassContact
{
protected$fillable=['name','email'];
//or
protected$guarded=['id','created_at','updated_at','owner_id'];
}
USINGREQUEST::ONLY()WITHELOQUENTMASSASSIGNMENTInExample8-18,weneededEloquent’smassassignmentguardbecausewewereusingtheall()methodontherequestobjecttopasstheentiretyoftheuserinputintoourEloquentobject.
Eloquent’smassassignmentprotectionisagreattoolhere,butthere’salsoahelpfultricktokeepyoufromacceptingjustanyinputfromtheuser.
TheRequestclasshasanonly()methodthatallowsyoutopluckonlyafewkeysfromtheuserinput.Sonowyoucandothis:
Contact::create($request->only('name','email'));
firstOrCreate()andfirstOrNew()Sometimesyouwanttototellyourapplication,“Getmeaninstancewiththeseproperties,orifitdoesn’texist,createit.”ThisiswherethefirstOr*()methodscomein.
ThefirstOrCreate()andfirstOrNew()methodstakeanarrayofkeysandvaluesastheirfirstparameter:
$contact=Contact::firstOrCreate(['email'=>'[email protected]']);
They’llbothlookforandretrievethefirstrecordmatchingthoseparameters,andiftherearenomatchingrecords,they’llcreateaninstancewiththoseproperties;firstOrCreate()willpersistthatinstancetothedatabaseandthenreturnit,whilefirstOrNew()willreturnitwithoutsavingit.
Ifyoupassanarrayofvaluesasthesecondparameter,thosevalueswillbeaddedtothecreatedentry(ifit’screated),butwon’tbeusedtolookupwhethertheentryexists.
DeletingwithEloquentDeletingwithEloquentisverysimilartoupdatingwithEloquent,butwithsoftdeletes,youcanarchiveyourdeleteditemsforlaterinspectionorevenrecovery.
NormaldeletesThesimplestwaytodeleteaninstanceistocallthedelete()methodontheinstanceitself:
$contact=Contact::find(5);
$contact->delete();
However,ifyouonlyhavetheID,there’snoreasontolookupaninstancejusttodeleteit;youcanpassanIDoranarrayofIDstothemodel’sdestroy()methodtodeletethemdirectly:
Contact::destroy(1);
//or
Contact::destroy([1,5,7]);
Finally,youcandeletealloftheresultsofaquery:
Contact::where('updated_at','<',Carbon::now()->subYear())->delete();
SoftdeletesSoftdeletesmarkdatabaserowsasdeletedwithoutactuallydeletingthemfromthedatabase.Thisgivesyoutheabilitytoinspectthemlater;tohaverecordsthatshowmorethan“noinformation,deleted”whendisplayinghistoricinformation;andtoallowyourusers(oradmins)torestoresomeoralldata.
Thehardpartabouthandcodinganapplicationwithsoftdeletesisthateveryqueryyoueverwritewillneedtoexcludethesoft-deleteddata.Thankfully,ifyouuseEloquent’ssoftdeletes,everyqueryyouevermakewillbescopedtoignoresoftdeletesbydefault,unlessyouexplicitlyasktobringthemback.
Eloquent’ssoftdeletefunctionalityrequiresadeleted_atcolumntobeaddedtothetable.OnceyouenablesoftdeletesonthatEloquentmodel,everyqueryyoueverwrite(unlessyouexplicitlyincludesoft-deletedrecords)willbescopedtoignoresoft-deletedrows.
WHENSHOULDIUSESOFTDELETES?
Justbecauseafeatureexists,itdoesn’tmeanyoushouldalwaysuseit.ManyfolksintheLaravelcommunitydefaulttousingsoftdeletesoneveryprojectjustbecausethefeatureisthere.Therearerealcoststosoftdeletes,though.It’sprettylikelythat,ifyouviewyourdatabasedirectlyinatoollikeSequelPro,you’llforgettocheckthedeleted_atcolumnatleastonce.Andifyoudon’tcleanupoldsoft-deletedrecords,yourdatabaseswillgetlargerandlarger.
Here’smyrecommendation:don’tusesoftdeletesbydefault.Instead,usethemwhenyouneedthem,andwhenyoudo,cleanoutoldsoftdeletesasaggressivelyasyoucan.It’sapowerfultool,butnotworthusingunlessyouneedit.
EnablingsoftdeletesYouenablesoftdeletesbydoingthreethings:addingthedeleted_atcolumninamigration,importingtheSoftDeletestraitinthemodel,andaddingthedeleted_atcolumntoyour$datesproperty.There’sasoftDeletes()methodavailableonthequerybuildertoaddthedeleted_atcolumntoatable,asyoucanseeinExample8-20.AndExample8-21showsanEloquentmodelwithsoftdeletesenabled.
Example8-20.MigrationtoaddthesoftdeletecolumntoatableSchema::table('contacts',function(Blueprint$table){
$table->softDeletes();
});
Example8-21.AnEloquentmodelwithsoftdeletesenabled<?php
useIlluminate\Database\Eloquent\Model;
useIlluminate\Database\Eloquent\SoftDeletes;
classContactextendsModel
{
useSoftDeletes;//usethetrait
protected$dates=['deleted_at'];//markthiscolumnasadate
}
Onceyoumakethesechanges,everydelete()anddestroy()callwillnowsetthedeleted_atcolumnonyourrowtobethecurrentdateandtimeinsteadofdeletingthatrow.Andallfuturequerieswillexcludethatrowasaresult.
QueryingwithsoftdeletesSo,howdowegetsoft-deleteditems?
First,youcanaddsoft-deleteditemstoaquery:
$allHistoricContacts=Contact::withTrashed()->get();
Next,youcanusethetrashed()methodtoseeifaparticularinstancehasbeensoftdeleted:
if($contact->trashed()){
//dosomething
}
Finally,youcangetonlysoft-deleteditems:
$deletedContacts=Contact::onlyTrashed()->get();
Restoringsoft-deletedentitiesIfyouwanttorestoreasoft-deleteditem,youcanrunrestore()onaninstanceoraquery:
$contact->restore();
//or
Contact::onlyTrashed()->where('vip',true)->restore();
Force-deletingsoft-deletedentitiesYoucandeleteasoft-deletedentitybycallingforceDelete()onanentityorquery:
$contact->forceDelete();
//or
Contact::onlyTrashed()->forceDelete();
ScopesWe’vecovered“filtered”queries,meaninganyquerywherewe’renotjustreturningeveryresultforatable.Buteverytimewe’vewrittenthemsofarinthischapter,it’sbeenamanualprocessusingthequerybuilder.
LocalandglobalscopesinEloquentallowyoutodefineprebuilt“scopes”(filters)thatyoucanuseeithereverytimeamodelisqueried(“global”)oreverytimeyouqueryitwithaparticularmethodchain(“local”).
LocalscopesLocalscopesarethesimplesttounderstand.Let’stakethisexample:
$activeVips=Contact::where('vip',true)->where('trial',false)->get();
Firstofall,ifwewritethiscombinationofquerymethodsoverandover,itwillgettedious.Butadditionally,theknowledgeofhowtodefinesomeonebeingan“activeVIP”isnowspreadaroundourapplication.Wewanttocentralizethatknowledge.Whatifwecouldjustwritethis?
$activeVips=Contact::activeVips()->get();
Wecan—it’scalledalocalscope.Andit’seasytodefineontheContactclass:
classContact
{
publicfunctionscopeActiveVips($query)
{
return$query->where('vip',true)->where('trial',false);
}
Todefinealocalscope,weaddamethodtotheEloquentclassthatbeginswith“scope”andthencontainsthetitle-casedversionofthescopename.Thismethodispassedaquerybuilderandneedstoreturnaquerybuilder,butofcourseyoucanmodifythequerybeforereturning—that’sthewholepoint.
Youcanalsodefinescopesthatacceptparameters:
classContact
{
publicfunctionscopeStatus($query,$status)
{
return$query->where('status',$status);
}
Andyouusetheminthesameway,justpassingtheparametertothescope:
$friends=Contact::status('friend')->get();
GlobalscopesRememberhowwetalkedaboutsoftdeletesonlyworkingifyouscopeeveryqueryonthemodeltoignorethesoft-deleteditems?That’saglobalscope.Andwecandefineourownglobalscopes,whichwillbeappliedoneveryquerymadefromagivenmodel.
Therearetwowaystodefineaglobalscope:usingaclosureorusinganentireclass.Ineach,you’llregisterthedefinedscopeinthemodel’sboot()method.Let’sstartwiththeclosuremethod,illustratedinExample8-22.
Example8-22.Addingaglobalscopeusingaclosure...
classContactextendsModel
{
protectedstaticfunctionboot()
{
parent::boot();
static::addGlobalScope('active',function(Builder$builder){
$builder->where('active',true);
});
}
That’sit.Wejustaddedaglobalscope,namedactive,andeveryqueryonthismodelwillbescopedtoonlyrowswithactivesettotrue.
Next,let’strythelongerway,asshowninExample8-23.CreateaclassthatimplementsIlluminate\Database\Eloquent\Scope,whichmeansitwillhaveanapply()methodthattakesaninstanceofaquerybuilderandaninstanceofthemodel.
Example8-23.Creatingaglobalscopeclass<?php
namespaceApp\Scopes;
useIlluminate\Database\Eloquent\Scope;
useIlluminate\Database\Eloquent\Model;
useIlluminate\Database\Eloquent\Builder;
classActiveScopeimplementsScope
{
publicfunctionapply(Builder$builder,Model$model)
{
return$builder->where('active',true);
}
}
Toapplythisscopetoamodel,onceagainoverridetheparent’sboot()methodandcalladdGlobalScope()ontheclassusingstatic,asshowninExample8-24.
Example8-24.Applyingaclass-basedglobalscope<?php
useApp\Scopes\ActiveScope;
useIlluminate\Database\Eloquent\Model;
classContactextendsModel
{
protectedstaticfunctionboot()
{
parent::boot();
static::addGlobalScope(newActiveScope);
}
}
CONTACTWITHNONAMESPACEYoumayhavenoticedthatseveraloftheseexampleshaveusedtheclassContact,withnonamespace.Thisisabnormal,andI’veonlydonethistosavespaceinthebook.Normallyevenyourtop-levelmodelswouldliveatsomethinglikeApp\Contact.
RemovingglobalscopesTherearethreewaystoremoveaglobalscope,andallthreeusethewithoutGlobalScope()orwithoutGlobalScopes()methods.Ifyou’reremovingaclosure-basedscope,thefirstparameterofthatscope’saddGlobalScope()registrationwillbethekeyyouusedtoenableit:
$allContacts=Contact::withoutGlobalScope('active')->get();
Ifyou’reremovingasingleclass-basedglobalscope,youcanpasstheclassnametowithoutGlobalScope()orwithoutGlobalScopes():
Contact::withoutGlobalScope(ActiveScope::class)->get();
Contact::withoutGlobalScopes([ActiveScope::class,VipScope::class])->get();
Or,youcanjustdisableallglobalscopesforaquery:
Contact::withoutGlobalScopes()->get();
CustomizingFieldInteractionswithAccessors,Mutators,andAttributeCastingNowthatwe’vecoveredhowtogetrecordsintoandoutofthedatabasewithEloquent,let’stalkaboutdecoratingandmanipulatingtheindividualattributesonyourEloquentmodels.
Accessors,mutators,andattributecastingallallowyoutocustomizethewayindividualattributesofEloquentinstancesareinputoroutput.Withoutusinganyofthese,eachattributeofyourEloquentinstanceistreatedlikeastring,andyoucan’thaveanyattributesonyourmodelsthatdon’texistonthedatabase.Butwecanchangethat.
AccessorsAccessorsallowyoutodefinecustomattributesonyourEloquentmodelsforwhenyouarereadingdatafromthemodelinstance.Thismaybebecauseyouwanttochangehowaparticularcolumnisoutput,orbecauseyouwanttocreateacustomattributethatdoesn’texistinthedatabasetableatall.
Youdefineanaccessorbywritingamethodonyourmodelwiththefollowingstructure:get{PascalCasedPropertyName}Attribute.So,ifyourpropertynameisfirst_name,theaccessormethodwouldbenamedgetFirstNameAttribute.
Let’stryitout.First,we’lldecorateapreexistingcolumn(Example8-25).
Example8-25.DecoratingapreexistingcolumnwithEloquentaccessors//Modeldefinition:
classContactextendsModel
{
publicfunctiongetNameAttribute($value)
{
return$value?:'(Nonameprovided)';
}
}
//Accessorusage:
$name=$contact->name;
Butwecanalsouseaccessorstodefineattributesthatneverexistedinthedatabase,asseeninExample8-26.
Example8-26.DefininganattributewithnobackingcolumnusingEloquentaccessors//Modeldefinition:
classContactextendsModel
{
publicfunctiongetFullNameAttribute()
{
return$this->first_name.''.$this->last_name;
}
}
//Accessorusage:
$fullName=$contact->full_name;
MutatorsMutatorsworkthesamewayasaccessors,exceptthey’refordetermininghowtoprocess
settingthedatainsteadofgettingit.Justlikewithaccessors,youcanuseittomodifytheprocessofwritingdatatoexistingcolumns,ortoallowforsettingcolumnsthatdon’texistinthedatabase.
Youdefineamutatorbywritingamethodonyourmodelwiththefollowingstructure:set{PascalCasedPropertyName}Attribute.So,ifyourpropertynameisfirst_name,themutatormethodwouldbenamedsetFirstNameAttribute.
Let’stryitout.First,we’lladdaconstrainttoupdatingapreexistingcolumn(Example8-27).
Example8-27.DecoratingsettingthevalueofanattributewithEloquentmutators//Definingthemutator
classOrderextendsModel
{
publicfunctionsetAmountAttribute($value)
{
$this->attributes['amount']=$value>0?$value:0;
}
}
//Usingthemutator
$order->amount='15';
Thisrevealsthatthewaymutatorsareexpectedto“set”dataonthemodelisbysettingitin$this->attributeswiththecolumnnameasthekey.
Now,let’saddaproxycolumnforsetting,asshowninExample8-28.
Example8-28.AllowingforsettingthevalueofanonexistentattributewithEloquentmutators//Definingthemutator
classOrderextendsModel
{
publicfunctionsetWorkgroupNameAttribute($workgroupName)
{
$this->attributes['email']="{$workgroupName}@ourcompany.com";
}
}
//Usingthemutator
$order->workgroup_name='jstott';
Asyoucanprobablyguess,it’srelativelyuncommontocreateamutatorforanon-existentcolumn,becauseitcanbeconfusingtosetonepropertyandhaveitchangeadifferentcolumn—butitispossible.
AttributecastingYoucanprobablyimaginewritingaccessorstocastallofyourinteger-typefieldsasintegers,encodeanddecodeJSONtostoreinTEXTcolumn,orconvertTINYINT0and1toandfrombooleanvalues.
Thankfully,there’sasystemforthatinEloquentalready.It’scalledattributecasting,anditallowsyoutodefinethatanyofyourcolumnsshouldalwaysbetreated,bothonreadandonwrite,asiftheyareofaparticulardatatype.TheoptionsarelistedinTable8-1.
Table8-1.Possibleattributecastingcolumntypes
Table8-1.Possibleattributecastingcolumntypes
Type Description
int|integer CastswithPHP(int)
real|float|double CastswithPHP(float)
string CastswithPHP(string)
bool|boolean CastswithPHP(bool)
object Parsesto/fromJSON,asastdClassobject
array Parsesto/fromJSON,asanarray
collection Parsesto/fromJSON,asacollection
date|datetime ParsesfromdatabaseDATETIMEtoCarbon,andback
timestamp ParsesfromdatabaseTIMESTAMPtoCarbon,andback
Example8-29showshowyouuseattributecastinginyourmodel.
Example8-29.UsingattributecastingonanEloquentmodelclassContact
{
protected$casts=[
'vip'=>'boolean',
'children_names'=>'array',
'birthday'=>'date',
];
}
DatemutatorsYoucanchooseforparticularcolumnstobemutatedastimestampcolumnsbyaddingthemtothedatesarray,asseeninExample8-30.
Example8-30.DefiningcolumnstobemutatedastimestampsclassContact
{
protected$dates=[
'met_at'
];
}
Bydefault,thisarraycontainscreated_atandupdated_at,soaddingentriestodatesjustaddsthemtothelist.
However,there’snodifferencebetweenaddingcolumnstothislistandaddingthemto$this->castsastimestamp,sothisisbecomingabitofanunnecessaryfeaturenowthatattributecastingcancasttimestamps(newinLaravel5.2).
EloquentCollectionsWhenyoumakeanyquerycallinEloquentthathasthepotentialtoreturnmultiplerows,insteadofanarraythey’llcomepackagedinanEloquentcollection,whichisaspecializedtypeofcollection.Let’stakealookatcollectionsandEloquentcollections,andwhatmakesthembetterthanplainarrays.
IntroducingthebasecollectionLaravel’sCollectionobjects(Illuminate\Support\Collection)arealittlebitlikearraysonsteroids.Themethodstheyexposeonarray-likeobjectsaresohelpfulthat,onceyou’vebeenusingthemforawhile,you’lllikelywanttopullIlluminateintoevennon-Laravelprojectsjustforcollections—whichyoucan,withtheTightenco/Collectpackage.
Youcancreateacollectionbypassinganarrayintoitsconstructor,orbycreatinganemptycollectionandpushingentriesontoit.Laravelalsohasacollect()helper,whichisthesimplestwaytocreateacollection.Let’stryit:
$collection=collect([1,2,3]);
Nowlet’ssaywewanttofilteroutanyevennumbers:
$odds=$collection->reject(function($item){
return$item%2===0;
});
Orwhatifwewanttogetaversionofthearraywhereeachitemismultipliedby10?Wecandothatasfollows:
$multiplied=$collection->map(function($item){
return$item*10;
});
Wecanevengetonlytheevens,multiplythemallby10,andreducethemtoasinglenumberbysum:
$sum=$collection
->filter(function($item){
return$item%2==0;
})->map(function($item){
return$item*10;
})->sum();
Asyoucansee,collectionsprovideaseriesofmethods,whichcanoptionallybechained,toperformfunctionaloperationsonyourarrays.TheyprovidethesamefunctionalityasnativePHPmethodslikearray_map()andarray_reduce(),butyoudon’thavetomemorizePHP’sunpredictableparameterorder,andthemethodchainingsyntaxisendlesslymorereadable.
Therearemorethan60methodsavailableontheCollectionclass,includingmethodslike
max()andwhereIn(),flatten(),andflip(),andthere’snotenoughspacetocoverthemallhere.We’llcovermorein“Collections”,oryoucancheckouttheLaraveldocsonCollectionstoseeallofthemethods.
COLLECTIONSINTHEPLACEOFARRAYSCollectionscanalsobeusedinanycontext(excepttypehinting)whereyoucanusearrays;theyallowforiteration,soyoucanpassthemtoforeach,andtheyallowforarrayaccess,soifthey’rekeyedyoucantry$a=$collection['a'].
WhatEloquentcollectionsaddEachEloquentcollectionisanormalcollection,butextendedfortheparticularneedsofacollectionofEloquentresults.
Onceagain,there’snotenoughroomheretocoveralloftheadditions,butthey’recenteredaroundtheuniqueaspectsofinteractingwithacollectionnotjustofgenericobjects,butobjectsmeanttorepresentdatabaserows.
Forexample,everyEloquentcollectionhasamethodcalledmodelKeys()thatreturnsanarrayoftheprimarykeysofeveryinstanceinthecollection.find($id)looksforaninstancethathastheprimarykeyof$id.
Oneadditionalfeatureavailablehereistheabilitytodefinethatanygivenmodelshouldreturnitsresultswrappedinaspecificclassofcollection.So,ifyouwanttoaddspecificmethodstoanycollectionofobjectsoftheOrderclass—possiblyrelatedtosummarizingthefinancialdetailsofyourorders—youcouldcreateacustomOrderCollectionthatextendstheIlluminate\Database\Eloquent\Collectionclass,andthenregisteritinyourmodel,asshowninExample8-31.
Example8-31.CustomCollectionclassesforEloquentmodels...
classOrderCollectionextendsCollection
{
publicfunctionsumBillableAmount()
{
return$this->reduce(function($carry,$order){
return$carry+($order->billable?$order->amount:0);
},0);
}
}
...
classOrderextendsModel
{
publicfunctionnewCollection(array$models=[])
{
returnnewOrderCollection($models);
}
Now,anytimeyougetbackacollectionofOrders(e.g.,fromOrder::all())it’llactuallybeaninstanceoftheOrderCollectionclass:
$orders=Order::all();
$billableAmount=$orders->sumBillableAmount();
EloquentSerializationSerializationiswhathappenswhenyoutakesomethingcomplex—anarray,oranobject—andconvertittoastring.Inaweb-basedcontext,thatstringisoftenJSON,butitcouldtakeotherformsaswell.
Serializingcomplexdatabaserecordscanbe,well,complex,andthisisoneoftheplacesmanyORMsfallshort.Thankfully,yougettwopowerfulmethodsforfreewithEloquent:toArray()andtoJson().CollectionsalsohavetoArray()andtoJson(),soallofthesearevalid:
$contactArray=Contact::first()->toArray();
$contactJson=Contact::first()->toJson();
$contactsArray=Contact::all()->toArray();
$contactsJson=Contact::all()->toJson();
YoucanalsocastanEloquentinstanceorcollectiontoastring($string=(string)$contact;),butbothmodelsandcollectionswilljustruntoJson()andreturntheresult.
ReturningmodelsdirectlyfromroutemethodsLaravel’sroutereventuallyconvertseverythingroutesreturntoastring,sothere’saclevertrickyoucanuse.IfyoureturntheresultofanEloquentcallinacontroller,itwillbeautomaticallycasttoastring,andthereforereturnedasJSON.ThatmeansaJSON-returningroutecanbeassimpleaseitheroftheonesinExample8-32.
Example8-32.ReturningJSONfromroutesdirectly//routes/web.php
Route::get('api/contacts',function(){
returnContact::all();
});
Route::get('api/contacts/{id}',function($id){
returnContact::findOrFail($id);
});
HidingattributesfromJSONIt’sverycommontouseJSONreturnsinAPIs,andit’sverycommontowanttohidecertainattributesinthesecontexts,soEloquentmakesiteasytohideanyattributeseverytimeyoucasttoJSON.
Youcaneitherblacklistattributes,hidingtheonesyoulist:
classContactextendsModel
{
public$hidden=['password','remember_token'];
orwhitelistattributes,showingonlytheonesyoulist:
classContactextendsModel
{
public$visible=['name','email','status'];
Thisalsoworksforrelationships:
classUserextendsModel
{
public$hidden=['contacts'];
publicfunctioncontacts()
{
return$this->hasMany(Contact::class);
}
LOADINGTHECONTENTSOFARELATIONSHIPBydefault,thecontentsofarelationshiparenotloadedwhenyougetadatabaserecord,soitdoesn’tmatterwhetheryouhidethemornot.But,aswe’lllearnshortly,it’spossibletogetarecordwithitsrelateditems,andinthiscontext,thoseitemswillnotbeincludedinaserializedcopyofthatrecordifyouchoosetohidethatrelationship.
Incaseyou’recuriousnow,youcangetaUserwithallcontacts—assumingyou’vesetuptherelationshipcorrectly—withthefollowingcall:
$user=User::with('contacts')->first();
Theremightbetimeswhenyouwanttomakeanattributevisiblejustforasinglecall.That’spossible,withtheEloquentmethodmakeVisible():
$array=$user->makeVisible('remember_token')->toArray();
ADDINGAGENERATEDCOLUMNTOARRAYANDJSONOUTPUTIfyouhavecreatedanaccessorforacolumnthatdoesn’texist—forexample,ourfull_namecolumnfromExample8-26—addittothe$appendsarrayonthemodeltoaddittothearrayandJSONoutput:
classContactextendsModel
{
protected$appends=['full_name'];
publicfunctiongetFullNameAttribute()
{
return"{$this->first_name}{$this->last_name}";
EloquentRelationshipsInarelationaldatabasemodel,it’sexpectedthatyouwillhavetablesthatarerelatedtoeachother—hencethename.Eloquentprovidessimpleandpowerfultoolstomaketheprocessofrelatingyourdatabasetableseasierthaneverbefore.
Manyofourexamplesinthischapterhavebeencenteredaroundauserwhohasmanycontacts,arelativelycommonsituation.
InanORMlikeEloquent,youwouldcallthisaone-to-manyrelationship:theoneuserhasmanycontacts.
IfitwasaCRMwhereacontactcouldbeassignedtomanyusers,thenthiswouldbeamany-to-manyrelationship:manyuserscanberelatedtoonecontact,andeachusercanberelatedtomanycontacts.Auserhasandbelongstomanycontacts.
Ifeachcontactcanhavemanyphonenumbers,andauserwantedadatabaseofeveryphonenumberfortheirCRM,youwouldsaytheuserhasmanyphonenumbersthroughcontacts—thatis,auserhasmanycontacts,andthecontacthasmanyphonenumbers,sothecontactissortofanintermediary.
Andwhatifeachcontacthasanaddress,butyou’reonlyinterestedintrackingoneaddress?YoucouldhavealltheaddressfieldsontheContact,butyoumightalsocreateanAddressmodel—meaningthecontacthasoneaddress.
Finally,whatifyouwanttobeabletostar(favorite)contacts,butalsoevents?Thiswouldbeapolymorphicrelationship,whereauserhasmanystars,butsomemaybecontactsandsomemaybeevents.
So,let’sdigintohowtodefineandaccesstheserelationships.
OnetooneLet’sstartsimple:aContacthasonePhoneNumber.ThisrelationshipisdefinedinExample8-33.
Example8-33.Definingaone-to-onerelationshipclassContactextendsModel
{
publicfunctionphoneNumber()
{
return$this->hasOne(PhoneNumber::class);
}
Asyoucantell,themethodsdefiningrelationshipsareontheEloquentmodelitself($this->hasOne())andtake,atleastinthisinstance,thefullyqualifiedclassnameoftheclassthatyou’rerelatingthemto.
Howshouldthisbedefinedinyourdatabase?Sincewe’vedefinedthattheContacthasonePhoneNumber,EloquentexpectsthatthetablesupportingthePhoneNumberclass(likely
phone_numbers)hasacontact_idcolumnonit.Ifyounameditsomethingdifferent(forinstance,owner_id),you’llneedtochangeyourdefinition:
return$this->hasOne(PhoneNumber::class,'owner_id');
Here’showweaccessthephonenumberonacontact:
$contact=Contact::first();
$contactPhone=$contact->phoneNumber;
NoticethatwedefinethemethodinExample8-33withphoneNumber(),butweaccessitwith->phoneNumber.That’sthemagic.Youcouldalsoaccessitwith->phone_number.ThiswillreturnafullEloquentinstanceoftherelatedPhoneNumberrecord.
ButwhatifwewanttoaccesstheContactfromthePhoneNumber?There’samethodforthat,too(seeExample8-34).
Example8-34.Definingaone-to-onerelationship’sinverseclassPhoneNumberextendsModel
{
publicfunctioncontact()
{
return$this->belongsTo(Contact::class);
}
Thenweaccessitthesameway:
$contact=$phoneNumber->contact;
INSERTINGRELATEDITEMSEachrelationshiptypehasitsownquirksforhowtorelatemodels,buthere’sthecoreofhowitworks:passaninstancetosave(),oranarrayofinstancestosaveMany().Youcanalsopasspropertiestocreate()andit’llmakeanewinstanceforyou:
$contact=Contact::first();
$phoneNumber=newPhoneNumber;
$phoneNumber->number=8008675309;
$contact->phoneNumbers()->save($phoneNumber);
//or
$contact->phoneNumbers()->saveMany([
PhoneNumber::find(1),
PhoneNumber::find(2),
]);
//or
$contact->phoneNumbers()->create([
'number'=>'+13138675309'
]);
OnetomanyTheone-to-manyrelationshipisbyfarthemostcommon.Let’stakealookathowtodefinethatourUserhasmanyContacts(Example8-35).
Example8-35.Definingaone-to-manyrelationshipclassUserextendsModel
{
publicfunctioncontacts()
{
return$this->hasMany(Contact::class);
}
Onceagain,thisexpectsthattheContactmodel’sbackingtable(likelycontacts)hasauser_idcolumnonit.Ifitdoesn’t,overrideitbypassingthecorrectcolumnnameasthesecondparameterofhasMany().
Wecangetauser ’scontactsasfollows:
$user=User::first();
$usersContacts=$user->contacts;
Justlikewithonetoone,weusethenameoftherelationshipmethodandcallitasifitwereapropertyinsteadofamethod.However,thismethodreturnsacollectioninsteadofamodelinstance.AndthisisanormalEloquentcollection,soyoucanhaveallsortsoffunwithit:
$donors=$user->contacts->filter(function($contact){
return$contact->status=='donor';
});
$lifetimeValue=$contact->orders->reduce(function($carry,$order){
return$carry+$order->amount;
},0);
Justlikewithonetoone,wecanalsodefinetheinverse(Example8-36).
Example8-36.Definingaone-to-manyrelationship’sinverseclassContactextendsModel
{
publicfunctionuser()
{
return$this->belongsTo(User::class);
}
Andjustlikeonetoone,wecanaccesstheUserfromtheContact:
$userName=$contact->user->name;
ATTACHINGANDDETACHINGRELATEDITEMSFROMTHEATTACHEDITEM
Mostofthetimeweattachrelateditemsbyrunningsave()ontheparentandpassingintherelateditem,asin$user->contacts()->save($contact).Butifyouwanttoperformthebehaviorsontheattached(“child”)item,youcanuseassociate()anddissociate()onthemethodthatreturnsthebelongsTo():
$contact=Contact::first();
$contact->user()->associate(User::first());
$contact->save();
//andlater
$contact->user()->dissociate();
$contact->save();
UsingrelationshipsasquerybuildersUntilnow,we’vetakenthemethodname(e.g.,contacts())andcalleditasifwereaproperty(e.g.,$user->contacts).Whathappensifwecallitasamethod?Insteadofprocessingtherelationship,itwillreturnaprescopedquerybuilder.
SoifyouhaveUser1,andyoucallitscontacts()method,youwillnowhaveaquerybuilderprescopedto“allcontactsthathaveafielduser_idwiththevalueof1.”Youcanthenbuildoutafunctionalqueryfromthere:
$donors=$user->contacts()->where('status','donor')->get();
SelectingonlyrecordsthathavearelateditemYoucanalsochoosetoselectonlyrecordsthatmeetparticularcriteriawithregardtotheirrelateditemsusinghas():
$postsWithComments=Post::has('comments')->get();
Youcanalsoadjustthecriteriafurther:
$postsWithManyComments=Post::has('comments','>=',5)->get();
Youcannestthecriteria:
$usersWithPhoneBooks=User::has('contacts.phoneNumbers')->get();
Andfinally,youcanwritecustomqueriesontherelateditems:
//Getsallcontactswithaphonenumbercontainingthestring"867-5309"
$jennyIGotYourNumber=Contact::whereHas('phoneNumbers',function($query){
$query->where('number','like','%867-5309%');
});
Hasmanythrough“Hasmanythrough”isreallyaconveniencemethodforpullinginrelationshipsofarelationship.ThisistheexampleIgaveearlierwhereaUserhasmanyContactsandeachContacthasmanyPhoneNumbers.Whatifyouwanttogetauser ’slistofcontactphonenumbers?That’shasmanythrough.
Thisstructureassumesthatyourcontactstablehasauser_idtorelatethecontactstotheusers,andthephone_numberstablehasacontact_idtorelateittothecontacts.Then,wedefinetherelationshipontheUserasinExample8-37.
Example8-37.Definingahas-many-throughrelationshipclassUserextendsModel
{
publicfunctionphoneNumbers()
{
return$this->hasManyThrough(PhoneNumber::class,Contact::class);
}
You’daccessthisrelationshipusing$user->phone_numbers,andasalwaysyoucancustomizetherelationshipkeyontheintermediatemodel(withthethirdparameterofhasmanyThrough())andtherelationshipkeyonthedistantmodel(withthefourthparameter).
ManytomanyThisiswherethingsstarttogetcomplex.Let’stakeourexampleofaCRMthatallowsaUsertohavemanyContacts,andeachContacttoberelatedtomultipleusers.
First,wedefinetherelationshipontheUserasinExample8-38.
Example8-38.Definingamany-to-manyrelationshipclassUserextendsModel
{
publicfunctioncontacts()
{
return$this->belongsToMany(Contact::class);
}
}
Andsincethisismanytomany,theinverselooksexactlythesame(Example8-39).
Example8-39.Definingamany-to-manyrelationship’sinverseclassContactextendsModel
{
publicfunctionusers()
{
return$this->belongsToMany(User::class);
}
}
SinceasingleContactcan’thaveauser_idcolumnandasingleUsercan’thaveacontact_idcolumn,many-to-manyrelationshipsrelyonapivottablethatconnectsthetwo.The
conventionalnamingofthistableisdonebyplacingthetwosingulartablenamestogether,orderedalphabetically,andseparatingthembyanunderscore.
So,sincewe’relinkingusersandcontacts,ourpivottableshouldbenamedcontacts_users(ifyou’dliketocustomizethetablename,passitasthesecondparametertothebelongsToMany()methods).Itneedstwocolumns:contact_idanduser_id.
JustlikewithhasMany(),wegetaccesstoacollectionoftherelateditems,butthistimeit’sfrombothsides(Example8-40).
Example8-40.Accessingtherelateditemsfrombothsidesofamany-to-manyrelationship$user=User::first();
$user->contacts->each(function($contact){
//dosomething
});
$contact=Contact::first();
$contact->users->each(function($user){
//dosomething
});
$donors=$user->contacts()->where('status','donor')->get();
UNIQUEASPECTSOFATTACHINGANDDETACHINGMANY-TO-MANYRELATEDITEMS
Sinceyourpivottablecanhaveitsownproperties,youneedtobeabletosetthosepropertieswhenyou’reattachingamany-to-manyrelateditem.Youcandothatbypassinganarrayasthesecondparametertosave():
$user=User::first();
$contact=Contact::first();
$user->contacts()->save($contact,['status'=>'donor']);
Additionally,youcanuseattach()anddetach()and,insteadofpassinginaninstanceofarelateditem,youcanjustpassanID.Theyworkjustthesameassave(),butcanalsoacceptanarrayofIDswithoutyouneedingtorenamethemethodtosomethinglikeattachMany():
$user=User::first();
$user->contacts()->attach(1);
$user->contacts()->attach(2,['status'=>'donor']);
$user->contacts()->attach([1,2,3]);
$user->contacts()->attach([
1=>['status'=>'donor'],
2,
3
]);
$user->contacts()->detach(1);
$user->contacts()->detach([1,2]);
$user->contacts()->detach();//Detachesallcontacts
YoucanalsouseupdateExistingPivot()tomakechangesjusttothepivotrecord:
$user->contacts()->updateExistingPivot($contactId,[
'status'=>'inactive'
]);
Andifyou’dliketoreplacethecurrentrelationships,effectivelydetachingallpreviousrelationshipsandattachingnewones,youcanpassanarraytosync():
$user->contacts()->sync([1,2,3]);
$user->contacts()->sync([
1=>['status'=>'donor'],
2,
3
]);
GettingdatafromthepivottableOnethingthat’suniqueaboutmanytomanyisthatit’sourfirstrelationshipthathasapivottable.Thelessdatayouhaveonapivottable,thebetter,buttherearesomecaseswhereit’svaluabletostoreinformationonyourpivottable—forexample,youmightwanttostoreacreated_atfieldtoseewhenthisrelationshipwascreated.
Inordertostorethesefields,youhavetoaddthemtotherelationshipdefinition,likeinExample8-41.YoucandefinespecificfieldsusingwithPivot()oraddcreated_atandupdated_attimestampsusingwithTimestamps().
Example8-41.Addingfieldstoapivotrecordpublicfunctioncontacts()
{
return$this->belongsToMany(Contact::class)
->withTimestamps()
->withPivot('status','preferred_greeting');
}
Whenyougetamodelinstancethrougharelationship,itwillhaveapivotpropertyonit,whichwillrepresentitsplaceinthepivottableyoujustpulleditfrom.So,youcandosomethinglikeExample8-42.
Example8-42.Gettingdatafromarelateditem’spivotentry$user=User::first();
$user->contacts->each(function($contact){
echosprintf(
'Contactassociatedwiththisuserat:%s',
$contact->pivot->created_at
);
});
PolymorphicRemember,ourpolymorphicrelationshipiswherewehavemultipleEloquentclassescorrespondingtothesamerelationship.We’regoingtouseStars(likefavorites)rightnow.AusercanstarbothContactsandEvents,andthat’swherethenamepolymorphiccomesfrom:asingleinterfacetoobjectsofmultipletypes.
So,we’llneedthreetables,andthreemodels:Star,Contact,andEvent(and,ofcourse,User,butwe’llgetthereinasecond).Thecontactsandeventstableswilljustbeastheynormallyare,andthestarstablewillcontainanidfield,astarrable_id,andastarrable_type.ForeachStar,you’llbedefiningwhich“type”(e.g.,ContactorEvent)andwhichIDofthattype(e.g.,1)itis.
Let’screateourmodels,asseeninExample8-43.
Example8-43.CreatingthemodelsforapolymorphicstarringsystemclassStarextendsModel
{
publicfunctionstarrable()
{
return$this->morphsTo();
}
}
classContactextendsModel
{
publicfunctionstars()
{
return$this->morphMany(Star::class,'starrable');
}
}
classEventextendsModel
{
publicfunctionstars()
{
return$this->morphMany(Star::class,'starrable');
}
}
So,howdowecreateaStar?
$contact=Contact::first();
$contact->stars()->create();
It’sthateasy.TheContactisnowstarred.
InordertofindalloftheStarsonagivenContact,wecallthestars()methodlikeinExample8-44.
Example8-44.Retrievingtheinstancesofapolymorphicrelationship$contact=Contact::first();
$contact->stars->each(function($star){
//Dostuff
});
IfwehaveaninstanceofStar,wecangetitstargetbycallingthemethodweusedtodefineitsmorphTo(),whichinthiscontextisstarrable().TakealookatExample8-45.
Example8-45.Retrievingthetargetofpolymorphicinstance$stars=Star::all();
$stars->each(function($star){
var_dump($star->starrable);//AninstanceofContactorEvent
});
Finally,youmightbewondering,“WhatifIcarewhostarredthiscontact?”That’sagreatquestion;ofcourseyoudo.It’sassimpleasaddinguser_idtoyourstarstable,andthensettingupthataUserhasmanyStarsandaStarbelongsto_oneUser—aone-to-manyrelationship(Example8-46).ThestarstablebecomesalmostapivottablebetweenyourUserandyourContactsandEvents.
Example8-46.ExtendingapolymorphicsystemtodifferentiatebyuserclassStarextendsModel
{
publicfunctionstarrable()
{
return$this->morphsTo;
}
publicfunctionuser()
{
return$this->belongsTo(User::class);
}
}
classUserextendsModel
{
publicfunctionstars()
{
return$this->hasMany(Star::class);
}
}
That’sit!Youcannowrun$star->useror$user->starstofindalistofaUser’sStarsortofindthestarringUserfromaStar.Also,whenyoucreateanewStar,you’llnowwanttopasstheUser:
$user=User::first();
$event=Event::first();
$event->stars()->create(['user_id'=>$user->id]);
ManytomanypolymorphicThemostcomplexandleastcommonoftherelationshiptypes,many-to-manypolymorphicrelationshipsarelikepolymorphicrelationships,exceptinsteadofbeingonetomanythey’remanytomany.
Themostcommonexampleforthisrelationshiptypeisthetag,soI’llkeepitsafeandusethatasourexample.Let’simagineyouwanttobeabletotagContactsandEvents.Theuniquenessofmany-to-manypolymorphismisthatit’smanytomany:eachtagmaybeappliedtomultipleitems,andeachtaggeditemmighthavemultipletags.Andtoaddtothat,it’spolymorphic:tagscanberelatedtoitemsofseveraldifferenttypes.Forthedatabase,we’llstartwiththenormalstructureofthepolymorphicrelationshipbutalsoaddapivottable.
Thismeanswe’llneedacontactstable,aneventstable,andatagstable,allshapedlikenormalwithanIDandwhateverpropertiesyouwant,andanewtaggablestable,whichwillhaveatag_id,ataggable_id,andataggable_type.Eachentryintothetaggablestablewillrelateatagwithoneofthetaggablecontenttypes.
Nowlet’sdefinethisrelationshiponourmodels,asseeninExample8-47.
Example8-47.Definingapolymorphicmany-to-manyrelationshipclassContactextendsModel
{
publicfunctiontags()
{
return$this->morphToMany(Tag::class,'taggable');
}
}
classEventextendsModel
{
publicfunctiontags()
{
return$this->morphToMany(Tag::class,'taggable');
}
}
classTagextendsModel
{
publicfunctioncontacts()
{
return$this->morphedByMany(Contact::class,'taggable');
}
publicfunctionevents()
{
return$this->morphedByMany(Event::class,'taggable');
}
}
Here’showtocreateyourfirsttag:
$tag=Tag::firstOrCreate(['name'=>'likes-cheese']);
$contact=Contact::first();
$contact->tags()->attach($tag->id);
Wegettheresultsofthisrelationshiplikenormal,asseeninExample8-48.
Example8-48.Accessingtherelateditemsfrombothsidesofamany-to-manypolymorphicrelationship$contact=Contact::first();
$contact->tags->each(function($tag){
//Dostuff
});
$tag=Tag::first();
$tag->contacts->each(function($contact){
//Dostuff
});
ChildRecordsUpdatingParentRecordTimestampsRemember,anyEloquentmodelsbydefaultwillhavecreated_atandupdated_attimestamps.Eloquentwillsettheupdated_attimestampautomaticallyanytimeyoumakeanychangestoarecord.
WhenarelateditemeitherbelongsTo()orbelongsToMany()anotheritem,itmightbevaluabletomarktheotheritemasupdatedanytimetherelateditemisupdated.Forexample,ifaPhoneNumberisupdated,theContactit’sconnectedtoshouldbemarkedashavingbeenupdatedaswell.
Wecanaccomplishthisbyaddingthemethodnameforthatrelationshiptoa$touchesarraypropertyonthechildclass,asinExample8-49.
Example8-49.UpdatingaparentrecordanytimethechildrecordisupdatedclassPhoneNumberextendsModel
{
protected$touches=['contact'];
publicfunctioncontact()
{
return$this->belongsTo(Contact::class);
}
EagerloadingBydefault,Eloquentloadsrelationshipsusing“lazyloading.”Thismeanswhenyoufirstloadamodelinstance,itsrelatedmodelswillnotbeloadedalongwithit.Rather,they’llonlybeloadedifyoumakeaseparatecalltopullthemin;they’re“lazy”anddon’tdoanyworkuntilcalledupon.
Thiscanbecomeaproblemifyou’reiteratingoveralistofmodelinstancesandeachhasarelateditem(oritems)thatyou’reworkingon.Theproblemwithlazyloadingisthatitcanintroducesignificantdatabaseload(oftentheN+1problem,ifyou’refamiliarwiththeterm;ifnot,justignorethisparentheticalremark).Forinstance,everytimetheloopinExample8-50runs,itexecutesanewdatabasequerytolookupthePhoneNumberforthatContact.
Example8-50.$contacts=Contact::all();
foreach($contactsas$contact){
foreach($contact->phone_numbersas$phone_number){
echo$phone_number->number;
}
}
Ifyouareloadingamodelinstanceandyouknowyou’llbeworkingwithitsrelationships,youcaninsteadchooseto“eager-load”oneormanyofitssetsofrelateditems:
$contacts=Contact::with('phoneNumbers')->get();
Usingthewith()methodwitharetrievalgetsalloftheitemsrelatedtothepulleditem(s),and
asyoucanseeinthisexample,youpassitthenameofthemethodtherelationshipisdefinedby.
Whenweuseeagerloading,insteadofpullingtherelateditemsoneatatimewhenthey’rerequested(selectingonephonenumbereachtimeaforeachloopruns),wehaveasinglequerytopulltheinitialitems(selectingallcontacts)andasecondquerytopullalltheirrelateditems(selectingallphonenumbersownedbythecontactswejustpulled).
Youcaneager-loadmultiplerelationshipsbypassingmultipleparameterstothewith()call:
$contacts=Contact::with('phoneNumbers','addresses')->get();
Andyoucannesteagerloadingtoeager-loadtherelationshipsofrelationships:
$authors=Author::with('posts.comments')->get();
ConstrainingeagerloadsIfyouwanttoeager-loadarelationship,butnotalloftheitems,youcanpassaclosuretowith()todefineexactlywhichrelateditemstoeager-load:
$contacts=Contact::with(['addresses'=>function($query){
$query->where('mailable',true);
}])->get();
LazyeagerloadingIknowitsoundscrazy,becausewejustdefinedeagerloadingassortoftheoppositeoflazyloading,butsometimesyoudon’tknowyouwanttoperformaneager-loadqueryuntilaftertheinitialinstanceshavebeenpulled.Youcanstillperformaneagerloadafterthefact,withlazyeagerloading:
$contacts=Contact::all();
if($showPhoneNumbers){
$contacts->load('phoneNumbers');
}
EagerloadingonlythecountIfyouwanttoeager-loadrelationshipsbutonlysoyoucanhaveaccesstothecountofitemsineachrelationship,youcantrywithCount():
$authors=Author::withCount('posts')->get();
//addsa"posts_count"integertoeachAuthorwithacountofthat
//Author'snumberofrelatedposts
EloquentEventsEloquentmodelsfireeventsoutintothevoidofyourapplicationeverytimecertainactionshappen,regardlessofwhetheryou’relistening.Ifyou’refamiliarwithpub/sub,it’sthissamemodel(andyoucanlearnmoreaboutLaravel’sentireeventsysteminChapter16).
Here’saquickrundownofbindingalistenertowhenanewContactiscreated.We’regoingtobinditintheboot()methodofAppServiceProvider,andlet’simaginewe’renotifyingathird-partyserviceeverytimewecreateanewContact.
Example8-51.BindingalistenertoanEloquenteventclassAppServiceProviderextendsServiceProvider
{
publicfunctionboot()
{
$thirdPartyService=newSomeThirdPartyService;
Contact::creating(function($contact)use($thirdPartyService){
try{
$thirdPartyService->addContact($contact);
}catch(Exception$e){
Log::error('FailedaddingcontacttoThirdPartyService;cancelled.');
returnfalse;
}
});
}
WecanseeafewthingsinExample8-51.First,weuseModelname::eventName()asthemethod,andpassitaclosure.Theclosuregetsaccesstothemodelinstancethatisbeingoperatedon.Second,we’regoingtoneedtodefinethislistenerinaserviceprovidersomewhere.Andthird,ifwereturnfalse,theoperationwillcancelandthesave()orupdate()willbecancelled.
HerearetheeventsthateveryEloquentmodelfires:creating
created
updating
updated
saving
saved
deleting
deleted
restoring
restored
Mostoftheseshouldbeprettyclear,exceptpossiblyrestoringandrestored,whichfirewhenyou’rerestoringasoft-deletedrow.Also,savingisfiredforbothcreatingandupdatingandsavedisfiredforbothcreatedandupdated.
TestingLaravel’sentireapplicationtestingframeworkmakesiteasytotestyourdatabase—notbywritingunittestsagainstEloquent,butbyjustbeingwillingtotestyourentireapplication.
Takethisscenario.Youwanttotesttoensurethataparticularpageshowsonecontactbutnotanother.SomeofthatlogichastodowiththeinterplaybetweentheURLandthecontrollerandthedatabase,sothebestwaytotestitisanapplicationtest.YoumightbethinkingaboutmockingEloquentcallsandtryingtoavoidthesystemhittingthedatabase.Don’tdoit.TryExample8-52instead.
Example8-52.Testingdatabaseinteractionswithsimpleapplicationtestspublicfunctiontest_active_page_shows_active_and_not_inactive_contacts()
{
$activeContact=factory(Contact::class,'active')->create();
$inactiveContact=factory(Contact::class,'inactive')->create();
$this->visit('active-contacts')
->see($activeContact->name)
->dontSee($inactiveContact->name);
}
Asyoucansee,modelfactoriesandLaravel’sapplicationtestingfeaturesaregreatfortestingdatabasecalls.
Alternatively,youcanlookforthatrecorddirectlyinthedatabase,asinExample8-53.
Example8-53.UsingseeInDatabase()tocheckforcertainrecordsinthedatabasepublicfunctiontest_contact_creation_works()
{
$this->post('contacts',[
'email'=>'[email protected]'
]);
$this->seeInDatabase('contacts',[
'email'=>'[email protected]'
]);
}
EloquentandLaravel’sdatabaseframeworkaretestedextensively.Youdon’tneedtotestthem.Youdon’tneedtomockthem.Ifyoureallywanttoavoidhittingthedatabase,youcanusearepositoryandthenreturnunsavedinstancesofyourEloquentmodels.Butthemostimportantmessageis,testthewayyourapplicationusesyourdatabaselogic.
Ifyouhavecustomaccessors,mutators,scopes,orwhateverelse,youcanalsotestthemdirectly,asinExample8-54.
Example8-54.Testingaccessors,mutators,andscopespublicfunctiontest_full_name_accessor_works()
{
$contact=factory(Contact::class)->make([
'first_name'=>'Alphonse',
'last_name'=>'Cumberbund'
]);
$this->assertEquals('AlphonseCumberbund',$contact->fullName);
}
publicfunctiontest_vip_scope_filters_out_non_vips()
{
$vip=factory(Contact::class,'vip')->create();
$nonVip=factory(Contact::class)->create();
$vips=Contact::vips()->get();
$this->assertTrue($vips->contains(['id'=>$vip->id]));
$this->assertFalse($vips->contains(['id'=>$nonVip->id]));
}
Justavoidwritingteststhatleaveyoucreatingcomplex“Demeterchains”toassertthataparticularfluentstackwascalledonsomedatabasemock.Ifyourtestingstartstogetoverwhelmingandcomplexaroundthedatabaselayer,it’sbecauseyou’reallowingpreconceivednotionstoforceyouintounnecessarilycomplexsystems.Keepitsimple.
TL;DRLaravelcomeswithasuiteofpowerfuldatabasetools,includingmigrations,seeding,anelegantquerybuilder,andEloquent,apowerfulActiveRecordORM.Laravel’sdatabasetoolsdon’trequireyoutouseEloquentatall—youcanaccessandmanipulatethedatabasewithathinlayerofconveniencewithouthavingtowriteSQLdirectly.ButaddinganORM,whetherit’sEloquentorDoctrineorwhateverelse,iseasyandcanworkneatlywithLaravel’scoredatabasetools.
EloquentfollowstheActiveRecordpattern,whichmakesitsimpletodefineaclassofdatabase-backedobjects,includingwhichtablethey’restoredin,theshapeoftheircolumns,accessorsandmutators,andmuchmore.EloquentcanhandleeverysortofnormalSQLactionandalsocomplexrelationships,uptoandincludingpolymorphicmany-to-manyrelationships.
Laravelalsohasarobustsystemfortestingdatabases,includingmodelfactories.
Chapter9.UserAuthenticationandAuthorization
Settingupabasicuserauthenticationsystem—includingregistration,login,sessions,passwordresets,andaccesspermissions—canoftenbeoneofthemoretime-consumingpiecesofcreatingthefoundationofanapplication.It’saprimecandidateforextractingfunctionalityouttoalibrary,andtherearequiteafewsuchlibraries.
Butbecauseofhowmuchauthenticationneedsvaryacrossprojects,mostauthenticationsystemsgrowbulkyandunusablequickly.Thankfully,Laravelhasfoundawaytomakeanauthenticationsystemthat’seasytouseandunderstand,butflexibleenoughtofitinavarietyofsettings.
EverynewinstallofLaravelhasacreate_users_tablemigrationandaUsermodelbuiltinoutofthebox.LaraveloffersanArtisanmake:authcommandthatseedsacollectionofauthentication-relatedviewsandroutes.AndeveryinstallcomeswithaRegisterController,aLoginController,aForgotPasswordController,andaResetPasswordController.TheAPIsarecleanandclear,andtheconventionsallworktogethertoprovideasimple—andseamless—authenticationandauthorizationsystem.
DIFFERENCESINAUTHSTRUCTUREINLARAVEL5.3NotethatinLaravel5.1and5.2,mostofthisfunctionalitylivedintheAuthController;in5.3,thisfunctionalityhasbeensplitoutintomultiplecontrollers.Manyofthespecificswe’llcoverhereabouthowtocustomizeredirectroutes,authguards,andsucharedifferentin5.1and5.2(thoughallthecorefunctionalityisthesame).So,ifyou’reon5.1or5.2andwanttochangesomeofthedefaultauthenticationbehaviors,you’lllikelyneedtodigabitintoyourAuthControllertoseehowexactlyyoushouldcustomizeit.
TheUserModelandMigrationWhenyoucreateanewLaravelapplication,thefirstmigrationandmodelyou’llseearethecreate_users_tablemigrationandtheApp\Usermodel.Example9-1shows,straightfromthemigration,thefieldsyou’llgetinyouruserstable.
Example9-1.Laravel’sdefaultusermigrationSchema::create('users',function(Blueprint$table){
$table->increments('id');
$table->string('name');
$table->string('email')->unique();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
WehaveanautoincrementingprimarykeyID,aname,auniqueemail,apassword,a“rememberme”token,andcreatedandmodifiedtimestamps.Thiscoverseverythingyouneedtohandlebasicuserauthenticationinmostapps.
THEDIFFERENCEBETWEENAUTHENTICATIONANDAUTHORIZATION
Authenticationmeansverifyingwhosomeoneis,andallowingthemtoactasthatpersoninyoursystem.Thisincludestheloginandlogoutprocesses,andanytoolsthatallowtheuserstoidentifythemselvesduringtheirtimeusingtheapplication.
Authorizationmeansdeterminingwhethertheauthenticateduserisallowed(authorized)toperformaspecificbehavior.Forexample,anauthorizationsystemallowsustoforbidanynonadministratorsfromviewingthesite’searnings.
TheUsermodelisabitmorecomplex,asyoucanseeinExample9-2.TheApp\Userclassitselfissimple,butitextendstheIlluminate\Foundation\Auth\Userclass,whichpullsinseveraltraits.
Example9-2.Laravel’sdefaultUsermodel<?php
//App\User
namespaceApp;
useIlluminate\Notifications\Notifiable;
useIlluminate\Foundation\Auth\UserasAuthenticatable;
classUserextendsAuthenticatable
{
useNotifiable;
/**
*Theattributesthataremassassignable.
*
*@vararray
*/
protected$fillable=[
'name','email','password',
];
/**
*Theattributesexcludedfromthemodel'sJSONform.
*
*@vararray
*/
protected$hidden=[
'password','remember_token',
];
}
<?php
//Illuminate\Foundation\Auth\User
namespaceIlluminate\Foundation\Auth;
useIlluminate\Auth\Authenticatable;
useIlluminate\Database\Eloquent\Model;
useIlluminate\Auth\Passwords\CanResetPassword;
useIlluminate\Foundation\Auth\Access\Authorizable;
useIlluminate\Contracts\Auth\AuthenticatableasAuthenticatableContract;
useIlluminate\Contracts\Auth\Access\AuthorizableasAuthorizableContract;
useIlluminate\Contracts\Auth\CanResetPasswordasCanResetPasswordContract;
classUserextendsModelimplements
AuthenticatableContract,
AuthorizableContract,
CanResetPasswordContract
{
useAuthenticatable,Authorizable,CanResetPassword;
}
ELOQUENTMODELREFRESHERIfthisisentirelyunfamiliar,considerreadingChapter8beforecontinuingtolearnhowEloquentmodelswork.
So,whatcanwelearnfromthismodel?First,usersliveintheuserstable;Laravelwillinferthisfromtheclassname.Weareabletofilloutthename,email,andpasswordpropertieswhencreatinganewuser,andthepasswordandremember_tokenpropertiesareexcludedwhenoutputtingtheuserasJSON.Lookinggoodsofar.
WealsocanseefromthecontractsandthetraitsintheIlluminate\Foundation\AuthverisonofUserthattherearesomefeaturesintheframework(theabilitytoauthenticate,toauthorize,andtoresetpasswords)thattheoreticallycouldbeappliedtoothermodels,notjusttheUsermodel,andthatcouldbeappliedindividuallyortogether.
CONTRACTSANDINTERFACES
YoumayhavenoticedthatsometimesIwritetheword“contract”andsometimes“interface,”andthatalmostalloftheinterfacesinLaravelareundertheContractsnamespace.
APHPinterfaceisessentiallyanagreementbetweentwoclassesthatoneoftheclasseswill“behave”acertainway.It’sabitlikeacontractbetweenthem,andthinkingaboutitasacontractgivesabitmoreinherentmeaningtothenamethancallingitaninterfacedoes.
Intheend,though,they’rethesamething:anagreementthataclasswillprovidecertainmethodswithacertainsignature.
Onarelatednote,theIlluminate\ContractsnamespacecontainsagroupofinterfacesthatLaravelcomponentsimplementandtypehint.ThismakesiteasytodevelopsimilarcomponentsthatimplementthesameinterfacesandswapthemintoyourapplicationinplaceofthestockIlluminatecomponents.WhentheLaravelcoreandcomponentstypehintamailer,forexample,theydon’ttypehinttheMailerclass.Instead,theytypehinttheMailercontract(interface),makingiteasytoprovideyourownmailer.Tolearnmoreabouthowtodothis,takealookatChapter11.
TheAuthenticatablecontractrequiresmethods(getAuthIdentifier(),etc.)thatallowtheframeworktoauthenticateinstancesofthismodeltotheauthsystem;theAuthenticatabletraitincludesthemethodsnecessarytosatisfythatcontractwithanaverageEloquentmodel.
TheAuthorizablecontractrequiresmethods(can(),cannot())thatallowtheframeworktoauthorizeinstancesofthismodelfortheiraccesspermissionsindifferentcontexts.Unsurprisingly,theAuthorizabletraitprovidesmethodsthatwillsatisfytheAuthorizablecontractforanaverageEloquentmodel.
Andfinally,theCanResetPasswordcontractrequiresonemethod(getEmailForPasswordReset())thatallowstheframeworkto,youguessedit,resetthepasswordofanyentitythatsatisfiesthiscontract.ThetraitprovidesthatmethodforanaverageEloquentmodel.
Atthispoint,wehavetheabilitytoeasilyrepresentanindividualuserinthedatabase(withthemigration),andtopullthemoutwithamodelinstancethatcanbeauthenticated(loggedinand
out),authorized(checkedforaccesspermissionstoaparticularresource),andsentapasswordresetemail.
Usingtheauth()GlobalHelperandtheAuthFacadeTheauth()globalhelperistheeasiestwaytointeractwiththestatusoftheauthenticateduserthroughoutyourapp.YoucanalsoinjectaninstanceofIlluminate\Auth\AuthManagerandgetthesamefunctionality,orusetheAuthfacade.
Themostcommonusagesaretocheckwhetherauserisloggedin(auth()->check()returnstrueifthecurrentuserisloggedin;auth()->guest()returnstrueiftheuserisnotloggedin)andtogetthecurrentlylogged-inuser(useauth()->user(),orauth()->id()forjusttheID;bothreturnnullifnouserisloggedin).
TakealookatExample9-3forasampleusageoftheglobalhelperinacontroller.
Example9-3.Sampleusageoftheauth()globalhelperinacontrollerpublicfunctiondashboard()
{
if(auth()->guest()){
returnredirect('sign-up');
}
returnview('dashboard')
->with('user',auth()->user());
}
TheAuthControllersSo,howdoweactuallylogusersin?Andhowdowetriggerthosepasswordresets?
ItallhappensintheAuth-namespacedcontrollers:RegisterController,LoginController,ResetPasswordController,andForgotPasswordController.
RegisterControllerTheRegisterController,incombinationwiththeRegistersUserstrait,containssensibledefaultsforhowtoshownewusersaregistrationform,howtovalidatetheirinput,howtocreatenewusersoncetheirinputisvalidated,andwheretoredirectthemafterward.
Thecontrolleritselfjustcontainsafewhooksthatthetraitswillcallatgivenpoints.Thatmakesiteasytocustomizeafewcommonbehaviorswithouthavingtodigdeeplyintothecodethatmakesitallwork.
The$redirectTopropertydefineswhereuserswillberedirectedafterregistration.Thevalidator()methoddefineshowtovalidateregistrations.Andthecreate()methoddefineshowtocreateanewuserbasedonanincomingregistration.TakealookatExample9-4toseethedefaultRegisterController.
Example9-4.Laravel’sdefaultRegisterController...
classRegisterControllerextendsController
{
useRegistersUsers;
protected$redirectTo='/home';
...
protectedfunctionvalidator(array$data)
{
returnValidator::make($data,[
'name'=>'required|max:255',
'email'=>'required|email|max:255|unique:users',
'password'=>'required|min:6|confirmed',
]);
}
protectedfunctioncreate(array$data)
{
returnUser::create([
'name'=>$data['name'],
'email'=>$data['email'],
'password'=>bcrypt($data['password']),
]);
}
}
RegistersUserstraitTheRegistersUserstrait,whichtheRegisterControllerimports,handlesafewprimaryfunctionsfortheregistrationprocess.First,itshowsuserstheregistrationformview,withtheshowRegistrationForm()method.Ifyouwantnewuserstoregisterwithaviewotherthanauth.registeryoucanoverridetheshowRegistrationForm()methodinyourRegisterController.
Next,ithandlesthePOSToftheregistrationformwiththeregister()method.Thismethodpassestheuser ’sregistrationinputtothevalidatorfromthevalidator()methodofyourRegisterController,andthenontothecreate()method.
Andfinally,theredirectPath()method(pulledinviatheRedirectsUserstrait)defineswhereusersshouldberedirectedafterasuccessfulregistration.YoucandefinethisURIwiththeredirectTopropertyonyourcontroller,oryoucanoverridetheredirectPath()methodandreturnwhateveryouwant.
Ifyouwantthistraittouseadifferentauthguardthanthedefault(you’lllearnmoreaboutguardsin“Guards”),youcanoverridetheauth()methodandhaveitreturnwhicheverguardyou’dlike.
LoginControllerTheLoginController,unsurprisingly,allowstheusertologin.ItbringsintheAuthenticatesUserstrait,whichbringsintheRedirectsUsersandThrottlesLoginstraits.
LiketheRegistrationController,theLoginControllerhasa$redirectTopropertythatallowsyoutocustomizethepaththeuserwillberedirectedtoafterasuccessfullogin.EverythingelselivesbehindtheAuthenticatesUserstrait.
AuthenticatesUserstraitTheAuthenticatesUserstraitisresponsibleforshowinguserstheloginform,validatingtheirlogins,throttlingfailedlogins,handlinglogouts,andredirectingusersafterasuccessfullogin.
TheshowLoginForm()methoddefaultstoshowingtheusertheauth.loginview,butyoucanoverrideitifyou’dlikeittouseadifferentview.
Thelogin()methodacceptsthePOSTfromtheloginform.ItvalidatestherequestusingthevalidateLogin()method,whichyoucanoverrideifyou’dliketocustomizethevalidation.ItthenhooksintothefunctionalityoftheThrottlesLoginstrait,whichwe’llcovershortly,torejectuserswithtoomanyfailedlogins.Andfinally,itredirectstheuser,eithertoherintendedpath(iftheuserwasredirectedtotheloginpagewhenattemptingtovisitapagewithintheapp)ortothepathdefinedbytheredirectPath()method,whichreturnsyour$redirectToproperty.
Thetraitcallstheemptyauthenticated()methodafterasuccessfullogin,soifyou’dliketoperformanysortofbehaviorinresponsetoasuccessfullogin,justoverridethismethodinyourLoginController.
There’sausername()methodthatdefineswhichofyouruserscolumnsisthe“username”;thisdefaultstoemailbutyoucanchangethatbyoverwritingtheusername()methodinyourcontrollertoreturnthenameofyourusernamecolumn.
And,likeintheRegistersUserstrait,youcanoverridetheguard()methodtodefinewhichauthguard(moreonthatin“Guards”)thiscontrollershoulduse.
ThrottlesLoginstraitTheThrottlesLoginstraitisaninterfacetoLaravel’sIlluminate\Cache\RateLimiterclass,whichisautilitytorate-limitanyeventusingthecache.Thistraitappliesratelimitingtouserlogins,limitingusersfromusingtheloginformifthey’vehadtoomanyfailedloginswithinacertainamountoftime.ThisfunctionalitydoesnotexistinLaravel5.1.
IfyouimporttheThrottlesLoginstrait,allofitsmethodsareprotected,whichmeansthey
can’tactuallybeaccessedasroutes.Instead,theAuthenticatesUserstraitlookstoseewhetheryou’veimportedtheThrottlesLoginstrait,andifso,it’llattachitsfunctionalitytoyourloginswithoutanyworkonyourpart.SincethedefaultLoginControllerimportsboth,you’llgetthisfunctionalityforfreeifyouusetheauthscaffold.
ThrottlesLoginslimitsanygivencombinationofusernameandIPaddressto5attemptsper60seconds.Usingthecache,itincrementsthe“failedlogin”countofagivenusername/IPaddresscombination,andifanyuserreaches5failedloginattemptswithin60seconds,itredirectsthatuserbacktotheloginpagewithanappropriateerroruntilthe60secondsisover.
ResetPasswordControllerTheResetPasswordControllersimplypullsintheResetsPasswordstrait.Thistraitprovidesvalidationandaccesstobasicpasswordresetviews,andthenusesaninstanceofLaravel’sPasswordBrokerclass(oranythingelseimplementingthePasswordBrokerinterface,ifyouchoosetowriteyourown)tohandlesendingpasswordresetemailsandactuallyresettingthepasswords.
Justliketheothertraitswe’vecovered,ithandlesshowingtheresetpasswordview(showResetForm()showstheauth.passwords.resetview),andthePOSTrequestthatissentfromthatview(reset()validatesandsendstheappropriateresponse).TheresetPassword()methodactuallyresetsthepassword,andyoucancustomizethebrokerwithbroker()andtheauthguardwithguard().
Ifyou’reinterestedincustomizinganyofthisbehavior,justoverridethespecificmethodyouwanttocustomizeinyourcontroller.
ForgotPasswordControllerTheForgotPasswordControllersimplypullsintheSendsPasswordResetEmailstrait.Itshowstheauth.passwords.emailformwiththeshowLinkRequestForm()method,andhandlesthePOSTofthatformwiththesendResetLinkEmail()method.Youcancustomizethebrokerwiththebroker()method.
Auth::routes()Nowthatwehavetheauthcontrollersprovidingsomemethodsforaseriesofpre-definedroutes,we’llwantouruserstoactuallybeabletohitthoseroutes.Wecouldaddalltheseroutesmanuallytoroutes/web.php,butthere’salreadyaconveniencetoolforthat,calledAuth::routes():
//routes/web.php
Auth::routes();
Asyoucanprobablyguess,Auth::routes()bringsinabundleofpredefinedroutestoyourroutesfile.InExample9-5youcanseetheroutesthatareactuallybeingdefinedthere.
Example9-5.TheroutesprovidedbyAuth::routes()//AuthenticationRoutes
$this->get('login','Auth\LoginController@showLoginForm');
$this->post('login','Auth\LoginController@login');
$this->get('logout','Auth\LoginController@logout');
//RegistrationRoutes
$this->get('register','Auth\RegisterController@showRegistrationForm');
$this->post('register','Auth\RegisterController@register');
//PasswordResetRoutes
$this->get('password/reset','Auth\ForgotPasswordController@showLinkRequestForm');
$this->post('password/email','Auth\ForgotPasswordController@sendResetLinkEmail');
$this->get('password/reset/{token}','Auth\ResetPasswordController@showResetForm');
$this->post('password/reset','Auth\ResetPasswordController@reset');
Basically,Auth::routes()includestheroutesforauthentication,registration,andpasswordresets.
LARAVEL’SCONTROLLER/METHODREFERENCESYNTAX
Laravelhasaconventionforhowtorefertoaparticularmethodinagivencontroller:ControllerName@methodName.Sometimesthisisjustacasualcommunicationconvention,butit’salsousedinrealbindings,likeinExample9-5.Laravelparseswhat’sbeforeandafterthe@andusesthosesegmentstoidentifythecontrollerandmethod.
TheAuthScaffoldAtthispointyouhaveamigration,amodel,controllers,androutesforyourauthenticationsystem.Butwhataboutyourviews?
Laravelhandlesthatbyprovidinganauthscaffold(newinLaravel5.2),whichisintendedtoberunonanewapplicationandprovideyouwithevenmoreskeletoncodetogetyourauthsystemrunningquickly.
TheauthscaffoldtakescareofaddingAuth::routes()toyourroutesfile,addsaviewforeachroute,andcreatesaHomeControllertoserveasthelandingpageforlogged-inusers;italsoroutestotheindex()methodofHomeControlleratthe/homeURI.
Justrunphpartisanmake:auth,andthefollowingfileswillbemadeavailabletoyou:
app/Http/Controllers/HomeController.php
resources/views/auth/login.blade.php
resources/views/auth/register.blade.php
resources/views/auth/passwords/email.blade.php
resources/views/auth/passwords/reset.blade.php
resources/views/layouts/app.blade.php
resources/views/home.blade.php
Atthispoint,youhave/returningthewelcomeview,/homereturningthehomeview,andaseriesofauthroutesforlogin,logout,registration,andpasswordresetpointingtotheauthcontrollers.EachoftheseededviewshasBootstrap-basedlayoutsandformfieldsforallnecessaryfieldsforlogin,registration,andpasswordreset,andtheyalreadypointtothecorrectroutes.
Atthispoint,youhaveallofthepiecesinplaceforeverystepofthenormaluserregistrationandauthenticationflow.Youcantweakallyouwant,butyou’reentirelyreadytoregisterandauthenticateusers.
Let’sreviewquicklythestepsfromnewsitetofullauthenticationsystem:
laravelnewMyApp
cdMyApp
phpartisanmake:auth
phpartisanmigrate
That’sit.Runthosecommands,andyouwillhavealandingpageandabootstrap-baseduserregistration,login,logout,andpasswordresetsystem,withabasiclandingpageforallauthenticatedusers.
“RememberMe”Theauthscaffoldhasthisimplementedoutofthebox,butit’sstillworthlearninghowitworksandhowtouseitonyourown.Ifyouwanttoimplementa“rememberme”–stylelong-livedaccesstoken,makesureyouhavearemember_tokencolumnonyouruserstable(whichyouwillifyouusedthedefaultmigration).
Whenyou’renormallylogginginauser(andthisishowtheLoginControllerdoesit,withtheAuthenticatesUserstrait),you’ll“attempt”anauthenticationwiththeuser-providedinformation,likeinExample9-6.
Example9-6.Attemptingauserauthenticationif(auth()->attempt([
'email'=>request()->input('email'),
'password'=>request()->input('password')
])){
//Handlethesuccessfullogin
}
Thisprovidesyouwithauserloginthatlastsaslongastheuser ’ssession.IfyouwantLaraveltoextendtheloginindefinitelyusingcookies(aslongastheuserisonthesamecomputeranddoesn’tlogout),youcanpassabooleantrueasthesecondparameteroftheauth()->attempt()method.TakealookatExample9-7toseewhatthatrequestlookslike.
Example9-7.Attemptingauserauthenticationwitha“rememberme”checkboxcheckif(auth()->attempt([
'email'=>request()->input('email'),
'password'=>request()->input('password')
]),request()->has('remember')){
//Handlethesuccessfullogin
}
Youcanseethatwecheckedwhethertheinputhasarememberproperty,whichwillreturnaboolean.Thisallowsouruserstodecideiftheywanttoberememberedwithacheckboxintheloginform.
Andlater,ifyouneedtomanuallycheckwhetherthecurrentuserwasauthenticatedbyaremembertoken,there’samethodforthat:auth()->viaRemember()returnsabooleanindicatingwhetherornotthecurrentuserauthenticatedviaaremembertoken.Thiswillallowyoutopreventcertainhigher-sensitivityfeaturesfrombeingaccessiblebyremembertoken,andyoucanrequireuserstoreentertheirpasswords.
ManuallyAuthenticatingUsersThemostcommoncaseforuserauthenticationisthatyou’llallowtheuserstoprovidetheircredentials,andthenuseauth()->attempt()toseewhethertheprovidedcredentialsmatchanyrealusers.Ifso,youlogthemin.
Butsometimestherearecontextswhereit’svaluableforyoutobeabletochoosetologauserinonyourown.Forexample,youmaywanttoallowadminuserstoswitchusers.
Therearetwomethodsthatmakethispossible.First,youcanjustpassauserID:
auth()->loginUsingId(5);
Second,youcanpassaUserobject(oranyotherobjectthatimplementstheIlluminate\Contracts\Auth\Authenticatablecontract):
auth()->login($user);
AuthMiddlewareInExample9-3,wesawhowtocheckwhethervisitorsareloggedinandredirectthemifnot.Youcouldperformthesesortsofchecksoneveryrouteinyourapplication,butitwouldveryquicklygettedious.Itturnsoutthatroutemiddleware(seeChapter10tolearnmoreabouthowtheywork)areaperfectfitforrestrictingcertainroutestoguestsortoauthenticatedusers.
Onceagain,Laravelcomeswiththemiddlewareweneedtodothisoutofthebox.YoucanseewhichroutemiddlewareyouhavedefinedinApp\Http\Kernel:
protected$routeMiddleware=[
'auth'=>\Illuminate\Auth\Middleware\Authenticate::class,
'auth.basic'=>\Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings'=>\Illuminate\Routing\Middleware\SubstituteBindings::class,
'can'=>\Illuminate\Auth\Middleware\Authorize::class,
'guest'=>\App\Http\Middleware\RedirectIfAuthenticated::class,
'throttle'=>\Illuminate\Routing\Middleware\ThrottleRequests::class,
];
Threeofthedefaultroutemiddlewareareauthentication-related:authrestrictsrouteaccesstoauthenticatedusers,auth.basicrestrictsaccesstoauthenticatedusersusingHTTPBasicAuthentication,andguestrestrictsaccesstounauthenticatedusers.canisusedforauthorizinguseraccesstogivenroutes.
It’smostcommontouseauthforyourauthenticated-user-onlysectionsandguestforanyroutesyoudon’twantauthenticateduserstosee(liketheloginform).auth.basicisamuchlesscommonlyusedmiddlewareforauthenticatingviarequestheaders.
Example9-8showsafewsampleroutesprotectedbytheauthmiddleware.
Example9-8.SampleroutesprotectedbyauthmiddlewareRoute::group(['middleware'=>'auth'],function(){
Route::get('account','AccountController@dashboard');
});
Route::get('login','Auth\LoginController@getLogin')->middleware('guest');
GuardsEveryaspectofLaravel’sauthenticationsystemisroutedthroughsomethingcalledaguard.Eachguardisacombinationoftwopieces:adriverthatdefineshowitpersistsandretrievestheauthenticationstate(forexample,session),andaproviderthatallowsyoutogetauserbycertaincriteria(forexample,users).
OutoftheboxLaravelhastwoguards:webandapi.webisthemoretraditionalauthenticationstyle,usingthesessiondriverandthebasicuserprovider.apialsousesthesameuserprovider,butitusesthetokendriverinsteadofthesessiontoauthenticateeachrequest.
You’dchangedriversifyouwantedtohandletheidentificationandpersistenceofauser ’sidentitydifferently(forexample,changingfromalong-runningsessiontoaprovided-every-page-loadtoken),andyou’dchangeprovidersifyouwantedtochangethestoragetypeorretrievalmethodsforyourusers(forexample,movingtostoringyourusersinMongoinsteadofMySQL).
ChangingtheDefaultGuardTheguardsaredefinedinconfig/auth.php,andyoucanchangethem,addnewguards,andalsodefinewhichguardwillbethedefaultthere.
Thedefaultguardwillbethatwhichisusedanytimeyouuseanyauthfeatures.auth()->user()willpullthecurrentlyauthenticateduserusingthedefaultguard.Youcanchangethisguardbychangingtheauth.defaults.guardsettinginconfig/auth.php:
'defaults'=>[
'guard'=>'web',//Changethedefaulthere
'passwords'=>'users',
],
Ifyou’reusingLaravel5.1,you’llnoticethatthestructureoftheauthenticationinformationisalittledifferentfromthis.Don’tworry;thefeaturesallstillworkthesame,they’rejuststructureddifferently.
CONFIGURATIONCONVENTIONSYoumayhavenoticedthatIrefertoconfigurationsectionswithreferenceslikeauth.defaults.guard.Whatthattranslatestois:inconfig/auth.php,inthearraysectionkeyeddefaults,thereshouldbeapropertykeyedguard.Thatoneisauth.defaults.guard.
UsingOtherGuardsWithoutChangingtheDefaultIfyouwanttouseanotherguard,butnotchangethedefault,youcanstartyourAuthcallswithguard():
$apiUser=auth()->guard('api')->user();
Thiswill,justforthiscall,getthecurrentuserusingtheapiguard.
AddingaNewGuardYoucanaddanewguardatanytimeinconfig/auth.php,intheauth.guardssetting:
'guards'=>[
'trainees'=>[
'driver'=>'session',
'provider'=>'trainees',
],
],
Asyoucanseehere,we’vecreatedanewguard(inadditiontowebandapi)namedtrainees.Let’simagine,fortherestofthissection,thatwe’rebuildinganappwhereourusersarephysicaltrainersandtheyeachhavetheirownusers—trainees—whocanlogintotheirsubdomains.So,weneedaseparateguardforthem.
Theonlytwooptionsfordriveraretokenandsession.Outofthebox,theonlyoptionforproviderisusers,butyoucancreateyourownprovidereasily.
CreatingaCustomUserProviderJustbelowwheretheguardsaredefinedinconfig/auth.php,there’sanauth.providerssectionthatdefinestheavailableproviders.Let’screateanewprovidernamedtrainees:
'providers'=>[
'users'=>[
'driver'=>'eloquent',
'model'=>App\User::class,
],
'trainees'=>[
'driver'=>'eloquent',
'model'=>App\Trainee::class,
],
],
Thetwooptionsfordriverareeloquentanddatabase;ifyouuseeloquent,you’llneedamodelpropertythatcontainsanEloquentclassname(themodeltouseforyourUserclass),andifyouusedatabase,you’llneedatablepropertytodefinewhichtableitshouldauthenticateagainst.
Inourexample,youcanseethatthisapplicationhasaUserandaTrainee,andtheyneedtobeauthenticatedseparately.Thisway,thecodecandifferentiatebetweenauth()->guard(users)andauth()->guard(trainees).
Onelastnote:theauthroutemiddlewarecantakeaparameterthatistheguardname.So,youcanguardcertainrouteswithaspecificguard:
Route::group(['middleware'=>'auth:trainees'],function(){
//Trainee-onlyrouteshere
});
CustomUserProvidersforNonrelationalDatabasesTheuserprovidercreationflowjustdescribedstillreliesonthesameUserProviderclass,whichmeansit’sexpectingtopulltheidentifyinginformationoutofarelationaldatabase.Butifyou’reusingMongoorRiakorsomethingsimilar,you’llactuallyneedtocreateyourownclass.
Todothis,createanewclassthatimplementstheIlluminate\Contracts\Auth\UserProviderinterface,andthenbinditinAuthServiceProvider@boot:
auth()->provider('riak',function($app,array$config){
//ReturnaninstanceofIlluminate\Contracts\Auth\UserProvider...
returnnewRiakUserProvider($app['riak.connection']);
});
AuthEventsWe’lltalkmoreabouteventsinChapter16,butLaravel’seventsystemisabasicpub/subframework.Therearesystem-anduser-generatedeventsthatarebroadcast,andtheuserhastheabilitytocreateeventlistenersthatdocertainthingsinresponsetocertainevents.
So,whatifyouwantedtosendapingtoaparticularsecurityserviceeverytimeauserwaslockedoutaftertoomanyfailedloginattempts?Maybethisservicewatchesoutforacertainnumberoffailedloginsfromcertaingeographicregions,orsomethingelse.Youcould,ofcourse,injectacallintheappropriatecontroller.Butwithevents,youcanjustcreateaneventlistenerthatlistenstothe“userlockedout”event,andregisterthat.
TakealookatExample9-9toseealloftheeventsthattheauthenticationsystememits.
Example9-9.Authenticationeventsgeneratedbytheframeworkprotected$listen=[
'Illuminate\Auth\Events\Attempting'=>[],
'Illuminate\Auth\Events\Login'=>[],
'Illuminate\Auth\Events\Logout'=>[],
'Illuminate\Auth\Events\Lockout'=>[],
];
Asyoucansee,therearelistenersfor“userattemptinglogin,”“successfullogin,”“logout,”and“lockout.”Tolearnmoreabouthowtobuildeventlistenersfortheseevents,checkoutChapter16.
Authorization(ACL)andRolesFinally,let’scoverLaravel’sauthorizationsystem.Itenablesyoutodeterminewhetherauserisauthorizedtodoaparticularthing,whichyou’llcheckusingafewprimaryverbs:can,cannot,allows,anddenies.Theaccesscontrollist(ACL)systemisnewinLaravel5.2.
MostofthisauthorizationcontrolwillbeperformedusingtheGatefacade,buttherearealsoconveniencehelpersinyourcontrollers,ontheUsermodel,asmiddleware,andavailableasBladedirectives.Takealookatthisexampletogetatasteofwhatwe’llbeabletodo:
if(Gate::denies('edit',$contact)){
abort(403);
}
if(!Gate::check('create',Contact::class)){
abort(403);
}
DefiningAuthorizationRulesThedefaultplacetodefineauthorizationrulesistheboot()methodoftheAuthServiceProvider.ItshouldalreadyhaveaninstanceofIlluminate\Contracts\Auth\Access\Gate(aliasedasGateContract)typehintedandinjectedas$gate.
Anauthorizationruleiscalledanability,andiscomprisedoftwothings:astringkey(e.g.,update-contact)andaclosurethatreturnsaboolean.Example9-10showsanabilityforupdatingacontact.
Example9-10.SampleabilityforupdatingacontactclassAuthServiceProviderextendsServiceProvider
{
publicfunctionboot(GateContract$gate)
{
$this->registerPolicies($gate);
$gate->define('update-contact',function($user,$contact){
return$user->id===$contact->user_id;
});
}
}
Let’swalkthroughthestepsfordefininganability.
First,youwanttodefineakey.Innamingthiskey,youshouldconsiderwhatstringmakessenseinyourcode’sflowtorefertotheabilityyou’reprovidingtheuser.Youcanseethecodesampleusestheconvention{verb}-{modelName}:create-contact,update-contact,etc.
Second,youdefinetheclosure.Thefirstparameterwillbethecurrentlyauthenticateduser,andallparametersafterthatwillbetheobject(s)you’recheckingforaccessto—inthisinstance,thecontact.
So,giventhosetwoobjects,wecancheckwhethertheuserisauthorizedtoupdatethiscontact.Youcanwritethislogichoweveryouwant,butintheappwe’relookingatatthemoment,authorizationdependsonbeingthecreatorofthecontactrow.Theclosurewillreturntrue(authorized)ifthecurrentusercreatedthecontact,andfalse(unauthorized)ifnot.
Justlikewithroutedefinitions,youcouldalsouseaclassandmethodinsteadofaclosuretoresolvethisdefinition:
$gate->define('update-contact','ContactACLChecker@updateContact');
TheGateFacade(andInjectingGate)Nowthatyou’vedefinedanability,it’stimetotestagainstit.ThesimplestwayistousetheGatefacade,asinExample9-11(oryoucaninjectaninstanceofIlluminate\Contracts\Auth\Access\Gate).
Example9-11.BasicGatefacadeusageif(Gate::allows('update-contact',$contact)){
//Updatecontact
}
//or...
if(Gate::denies('update-contact',$contact)){
abort(403);
}
Youmightalsodefineanabilitywithmultipleparameters—maybeacontactcanbeingroups,andyouwanttoauthorizewhethertheuserhasaccesstoaddacontacttoagroup.Example9-12showshowtodothis.
Example9-12.Abilitieswithmultipleparameters//Definition
$gate->define('add-contact-to-group',function($user,$contact,$group){
return$user->id===$contact->user_id&&$user->id===$group->user_id;
});
//Usage
if(Gate::denies('add-contact-to-group',[$contact,$group])){
abort(403);
}
Andifyouneedtocheckauthorizationforauserotherthanthecurrentlyauthenticateduser,tryforUser(),likeinExample9-13.
Example9-13.SpecifyingtheuserforGateif(Gate::forUser($user)->denies('create-contact')){
abort(403);
}
TheAuthorizeMiddlewareIfyouwanttoauthorizeentireroutes,youcanusetheAuthorizemiddleware(whichhasashortcutofcan),likeinExample9-14.
Example9-14.UsingtheAuthorizemiddlewareRoute::get('people/create',function(){
//Createperson...
})->middleware('can:create-person');
Route::get('people/{person}/edit',function(){
//Createperson...
})->middleware('can:create,person');
Here,the{person}parameter(whetherit’sdefinedasastringorasaboundroutemodel)willbepassedtotheabilitymethodasanadditionalparameter.
ControllerAuthorizationTheparentApp\Http\Controllers\ControllerclassinLaravelimportstheAuthorizesRequeststrait,whichprovidesthreemethodsforauthorization:authorize(),authorizeForUser(),andauthorizeResource().
authorize()takesanabilitykeyandanobject(orarrayofobjects)asparameters,andiftheauthorizationfails,it’llquittheapplicationwitha403(Unauthorized)statuscode.Thatmeansthisfeaturecanturnthreelinesofauthorizationcodeintojustone,asyoucanseeinExample9-15.
Example9-15.Simplifyingcontrollerauthorizationwithauthorize()//Fromthis:
publicfunctionshow($contactId)
{
$contact=Contact::findOrFail($contactId);
if(Gate::cannot('update-contact',$contact)){
abort(403);
}
}
//Tothis:
publicfunctionshow($contactId)
{
$contact=Contact::findOrFail($contactId);
$this->authorize('update-contact',$contact);
}
authorizeForUser()isthesame,butallowsyoutopassinaUserobjectinsteadofdefaultingtothecurrentlyauthenticateduser:
$this->authorizeForUser($user,'update-contact',$contact);
authorizeResource(),calledonceinthecontrollerconstructor,mapsapredefinedsetofauthorizationrulestoeachoftheRESTfulcontrollermethodsinthatcontroller—somethinglikeExample9-16.
Example9-16.Theauthorization-to-methodmappingsofauthorizeResource()...
classContactsControllerextendsController
{
publicfunction__construct()
{
//Thiscalldoeseverythingyouseeinthemethodsbelow.
//Ifyouputthishere,youcanremoveallauthorize
//callsintheindividualresourcemethodshere.
$this->authorizeResource(Contact::class);
}
publicfunctionindex()
{
$this->authorize('view',Contact::class);
}
publicfunctioncreate()
{
$this->authorize('create',Contact::class);
}
publicfunctionstore(Request$request)
{
$this->authorize('create',Contact::class);
}
publicfunctionshow(Contact$contact)
{
$this->authorize('view',$contact);
}
publicfunctionedit(Contact$contact)
{
$this->authorize('update',$contact);
}
publicfunctionupdate(Request$request,Contact$contact)
{
$this->authorize('update',$contact);
}
publicfunctiondestroy(Contact$contact)
{
$this->authorize('delete',$contact);
}
}
CheckingontheUserInstanceIfyou’renotinacontroller,you’remorelikelytobecheckingthecapabilitiesofaspecificuserthanthecurrentlyauthenticateduser.That’salreadypossiblewiththeGatefacadeusingtheforUser()method,butsometimesthesyntaxcanfeelalittleoff.
Thankfully,theAuthorizabletraitontheUserclassprovidesthreemethodstomakeamorereadableauthorizationfeature:$user->can(),$user->cant(),and$user->cannot().Asyoucanprobablyguess,cant()andcannot()dothesamething,andcan()istheirexactinverse.
ThatmeansyoucandosomethinglikeExample9-17.
Example9-17.Checkingauthorizationonauserinstance$user=User::find(1);
if($user->can('create-contact')){
//dosomething
}
Behindthescenes,thesemethodsarejustpassingyourparamstoGate;intheprecedingexample,Gate::forUser($user)->can('create-contact').
BladeChecksBladealsohasalittleconveniencehelper:[email protected].
Example9-18.UsingBlade’s@candirective<nav>
<ahref="/">Home</a>
@can('edit-contact',$contact)
<ahref="{{route('contacts.edit',[$contact->id])}}">EditThisContact</a>
@endcan
</nav>
Youcanalsouse@elseinbetween@canand@endcan,andyoucanuse@cannotand@endcannotasinExample9-19.
Example9-19.UsingBlade’s@cannotdirective<h1>{{$contact->name}}</h1>
@cannot('edit-contact',$contact)
LOCKED
@endcannot
InterceptingChecksIfyou’veeverbuiltanappwithanadminuserclass,you’veprobablylookedatallofthesimpleauthorizationclosuressofarinthischapterandthoughtabouthowyoucouldaddasuperuserclassthatoverridesthesechecksineverycase.Thankfully,there’salreadyatoolforthat.
InAuthServiceProvider,whereyou’realreadydefiningyourabilities,youcanalsoaddabefore()checkthatrunsbeforealltheothersandcanoptionallyoverridethem,likeinExample9-20.
Example9-20.OverridingGatecheckswithbefore()$gate->before(function($user,$ability){
if($user->isOwner()){
returntrue;
}
});
Notethatthestringnamefortheabilityisalsopassedin,soyoucandifferentiateyourbeforehooksbasedonyourabilitynamingscheme.
PoliciesUpuntilthispoint,alloftheaccesscontrolshaverequiredyoutomanuallyassociateEloquentmodelswiththeabilitynames.Youcouldhavecreatedanabilitynamedsomethinglikevisit-dashboardthat’snotrelatedtoaspecificEloquentmodel,butyou’llprobablyhavenoticedthatmostofourexampleshavehadtodowithdoingsomethingtosomething—andinmostofthesecases,thesomethingthat’stherecipientoftheactionisanEloquentmodel.
Authorizationpoliciesareorganizationalstructuresthathelpyougroupyourauthorizationlogicbasedontheresourceyou’recontrollingaccessto.TheymakeiteasytomanagedefiningauthorizationrulesforbehaviortowardaparticularEloquentmodel(orotherPHPclass),alltogetherinasinglelocation.
GeneratingpoliciesPoliciesarePHPclasses,whichcanbegeneratedwithanArtisancommand:
phpartisanmake:policyContactPolicy
Oncethey’regenerated,theyneedtoberegistered.TheAuthServiceProviderhasa$policiesproperty,whichisanarray.Thekeyofeachitemistheclassnameoftheprotectedresource(usually,butnotalways,anEloquentclass),andthevalueisthepolicyclassname:
classAuthServiceProviderextendsServiceProvider
{
protected$policies=[
Contact::class=>ContactPolicy::class,
]
Apolicyclassthat’sgeneratedbyArtisandoesn’thaveanyspecialpropertiesormethods.Buteverymethodthatyouaddisnowmappedasanabilitykeyforthisobject.
Let’sdefineanupdate()methodtotakealookathowitworks(Example9-21).
Example9-21.Asampleupdate()policymethod<?php
namespaceApp\Policies;
classContactPolicy
{
publicfunctionupdate($user,$contact)
{
return$user->id===$contact->user_id;
}
}
NoticethatthecontentsofthismethodlookexactlyliketheywouldinaGatedefinition.
POLICYMETHODSTHATDON’TTAKEANINSTANCE,BEFORE5.3InLaravel5.2,ifyouwanttodefineapolicymethodthatrelatestotheclassbutnotaspecificinstance—forexample,“canthisusercreatecontactsatall?”ratherthanjust“canthisuserviewthisspecificcontact?”—createthatmethodandadd“Any”attheendofitsname:
...
classContactPolicy
{
publicfunctioncreateAny($user)
{
return$user->canCreateContacts();
}
InLaravel5.3,youcandroptheAnysuffixandtreatthisjustlikeanormalmethod.
CheckingpoliciesIfthere’sapolicydefinedforaresourcetype,theGatewillusethefirstparametertofigureoutwhichmethodtocheckonyourpolicy.IfyourunGate::allows('update',$contact),itwillchecktheContactPolicy@updatemethodforauthorization.
ThisalsoworksfortheAuthorizemiddlewareandforUsermodelcheckingandBladechecking,asseeninExample9-22.
Example9-22.Checkingauthorizationagainstapolicy//Gate
if(Gate::denies('update',$contact)){
abort(403);
}
//Gateifyoudon'thaveanexplicitinstance
if(!Gate::check('create',Contact::class)){
abort(403);
}
//User
if($user->can('update',$contact)){
//Dostuff
}
//Blade
@can('update',$contact)
<!--showstuff-->
@endcan
Additionally,there’sapolicy()helperthatallowsyoutoretrieveapolicyclassandrunitsmethods:
if(policy($contact)->update($user,$contact)){
//Dostuff
}
OverridingpoliciesJustlikewithnormalabilitydefinitions,policiescandefineabefore()methodthatallows
youtooverrideanycallbeforeit’sevenprocessed(seeExample9-23).
Example9-23.Overridingpolicieswiththebefore()methodpublicfunctionbefore($user,$ability)
{
if($user->isAdmin()){
returntrue;
}
}
PASSPORTANDOAUTH
There’saLaravelpackagecalledPassportthatmakesiteasytosetupyourownOAuthserverasapartofyourLaravelapp.Takealookat“APIAuthenticationwithLaravelPassport”tolearnmoreabouthowitworks.
TestingApplicationtestsoftenneedtoperformaparticularbehavioronbehalfofaparticularuser.Wethereforeneedtobeabletoauthenticateasauserinapplicationtests,andweneedtotestauthorizationrulesandauthenticationroutes.
Ofcourse,it’spossibletowriteanapplicationtestthatmanuallyvisitstheloginpageandthenfillsouttheformandsubmitsit,butthat’snotnecessary.Instead,thesimplestoptionistousethe->be()methodtosimulatebeingloggedinasauser.TakealookatExample9-24.
Example9-24.Authenticatingasauserinapplicationtestspublicfunctiontest_it_creates_a_new_contact()
{
$user=factory(User::class)->create();
$this->be($user);
$this->post('contacts',[
'email'=>'[email protected]'
]);
$this->seeInDatabase('contacts',[
'email'=>'[email protected]',
'user_id'=>$user->id,
]);
}
WecanalsotestauthorizationlikeinExample9-25.
Example9-25.Testingauthorizationrulespublicfunctiontest_non_admins_cant_create_users()
{
$user=factory(User::class)->create([
'admin'=>false
]);
$this->be($user);
$this->post('users',['email'=>'[email protected]']);
$this->dontSeeInDatabase('users',[
'email'=>'[email protected]'
]);
}
Orwecantestfora403responselikeinExample9-26.
Example9-26.Testingauthorizationrulesbycheckingstatuscodepublicfunctiontest_non_admins_cant_create_users()
{
$user=factory(User::class)->create([
'admin'=>false
]);
$this->be($user);
$this->post('users',['email'=>'[email protected]']);
$this->assertResponseStatus(403);
}
Weneedtotestthatourauthentication(signupandsignin)routesworktoo,asillustratedinExample9-27.
Example9-27.Testingauthenticationroutespublicfunctiontest_users_can_register()
{
$this->post('register',[
'name'=>'SalLeibowitz',
'email'=>'[email protected]',
'password'=>'abcdefg123',
'password_confirmation'=>'abcdefg123',
]);
$this->followRedirects()->assertResponseOk();
$this->seeInDatabase('users',[
'name'=>'SalLeibowitz',
'email'=>'[email protected]',
]);
}
publicfunctiontest_users_can_log_in()
{
$user=factory(User::class)->create([
'password'=>bcrypt('abcdefg123')
]);
$this->post('login',[
'email'=>$user->email,
'password'=>'abcdefg123',
]);
$this->followRedirects()->assertResponseOk();
$this->assertTrue(auth()->check());
}
Youcouldalsousetheintegrationtestfeaturestodirectthetestto“click”yourauthenticationfieldsand“submit”thefieldstotesttheentireflow.
TL;DRBetweenthedefaultUsermodel,thecreate_users_tablemigration,theauthcontrollers,andtheauthscaffold,Laravelcomeswithafulluserauthenticationsystemoutofthebox.TheRegisterControllerhandlesuserregistration,theLoginControllerhandlesuserauthentication,andtheResetPasswordControllerandtheForgotPasswordControllerhandlepasswordresets.Eachhascertainpropertiesandmethodsthatcanbeusedtooverridesomeofthedefaultbehavior.
TheAuthfacadeandtheauth()globalhelperprovideaccesstothecurrentuser(auth()->user())andmakesiteasytocheckwhetherauserisloggedin(auth()->check()andauth()->guest()).
Laravelalsohasanauthorizationsystembuiltinthatallowsyoutodefinespecificabilities(create-contact,visit-secret-page)ordefinepoliciesforuserinteractionwithentiremodels.
YoucancheckforauthorizationwiththeGatefacade,the->can()and->cannot()methodsontheUserclass,the@canand@cannotdirectivesinBlade,the->authorize()methodsonthecontroller,orthecanmiddleware.
Chapter10.RequestsandResponses
We’vealreadycoveredtheIlluminateRequestobjectabit—howyoucantypehintitinconstructorstogetaninstance,andthenusethattogetinformationabouttheuser ’sinput.InChapter3,forexample,wesawhowyoucantypehintitinconstructorstogetaninstance,andinChapter6welookedathowyoucanuseittogetinformationabouttheuser ’sinput.
Inthischapter,we’lllearnmoreaboutwhatthatRequestobjectis,howit’sgeneratedandwhatitrepresents,andwhatpartitplaysinyourapplication’slifecycle.We’llalsotalkabouttheResponseobjectandLaravel’simplementationofthemiddlewarepattern.
Laravel’sRequestLifecycleEveryrequestcomingintoaLaravelapplication,whethergeneratedbyanHTTPrequestoracommand-lineinteraction,isimmediatelyconvertedintoanIlluminateRequestobject,whichthencrossesmanylayersandendsupbeingparsedbytheapplicationitself.TheapplicationthengeneratesanIlluminateResponseobject,whichissentbackoutacrossthoselayersandfinallyreturnedtotheenduser.
Thisrequest/responselifecycleisillustratedinFigure10-1.
Let’stakealookatwhatittakestomakeeachofthesestepshappen,fromthefirstlineofcodetothelast.
EveryLaravelapplicationhassomeformofconfigurationsetupatthewebserverlevel,inan.htaccessfileoranNginxconfigurationsettingorsomethingsimilar,thatcaptureseverywebrequestregardlessofURLandroutesittopublic/index.phpintheLaravelapplicationdirectory(app).
Figure10-1.Request/responselifecycle
BootstrappingtheApplicationindex.phpdoesn’tactuallyhavethatmuchcodeinit.Ithasthreeprimaryfunctions.
First,itloadsComposer ’sautoloadfileandLaravel’scompiledapplicationcache,whichlivesatbootstrap/cache/compiled.php.Thisfileiswhat’sgeneratedwhenyourunphpartisanoptimize,anditpreloadsallofthemostcommonlyusedclassesforfasterloading.
COMPOSER ANDLARAVEL
Laravel’scorefunctionalityisseparatedintoaseriesofcomponentsundertheIlluminatenamespace,whichareallpulledintoeachLaravelappusingComposer.LaravelalsopullsinquiteafewpackagesfromSymfonyandseveralothercommunity-developedpackages.Inthisway,Laravelisjustasmuchanopinionatedcollectionofcomponentsasitisaframework.
Next,itkicksoffLaravel’sbootstrap,creatingtheapplicationcontainer(you’lllearnmoreaboutthecontainerinChapter11)andregisteringafewcoreservices(includingthekernel,whichwe’lltalkaboutinjustabit).
Finally,itcreatesaninstanceofthekernel,createsarequestrepresentingthecurrentuser ’swebrequest,andpassestherequesttothekerneltohandle.ThekernelrespondswithanIlluminateResponseobject,whichindex.phpthenreturnstotheenduser,andterminatesthepagerequest.
Laravel’skernelThekernelisthecorerouterofeveryLaravelapplication,responsiblefortakinginauserrequest,processingitthroughmiddlewareandhandlingexceptionsandpassingittothepagerouter,andthenreturningthefinalresponse.Thereareactuallytwokernels,butonlyoneisusedforeachpagerequest.Oneoftheroutershandleswebrequests(theHTTPkernel)andtheotherhandlesconsole,cron,andArtisanrequests(theconsolekernel).Eachhasahandle()methodthat’sresponsiblefortakinginanIlluminateRequestobjectandreturninganIlluminateResponseobject.
Thekernelrunsallofthebootstrapsthatneedtorunbeforeeveryrequest,includingdeterminingwhichenvironmentthecurrentrequestisrunningin(staging,local,production,etc.)andrunningalloftheserviceproviders.TheHTTPkerneladditionallydefinesthelistofmiddlewarethatwillwrapeachrequest,includingthecoremiddlewareresponsibleforsessionsandCSRFprotection.
ServiceProvidersWhilethere’sabitofproceduralcodeinthesebootstraps,almostallofLaravel’sbootstrapcodeisseparatedintosomethingLaravelcallsserviceproviders.Aserviceproviderisaclassthatencapsulateslogicthatvariouspartsofyourapplicationneedtoruninordertobootstraptheircorefunctionality.
Forexample,there’sanAuthServiceProviderthatbootstrapsalloftheregistrationsnecessaryforLaravel’sauthenticationsystemandaRouteServiceProviderthatbootstrapstheroutingsystem.
Theconceptofserviceproviderscanbealittlehardtounderstandatfirst,sothinkaboutitthisway:manycomponentsofyourapplicationhavebootstrapcodethatneedstorunwhentheapplicationinitializes.Serviceprovidersareatoolforgroupingthatbootstrapcodeintorelatedclasses.Ifyouhaveanycodethatneedstoruninpreparationforyourapplicationcodetowork,it’sastrongcandidateforaserviceprovider.
Forexample,ifyoueverfindthatthefeatureyou’reworkingonrequiressomeclassesregisteredinthecontainer(we’lllearnmoreaboutthisinChapter11),youwouldcreateaserviceproviderjustforthatpieceoffunctionality.YoumighthaveaGitHubServiceProvideroraMailerServiceProvider.
Serviceprovidershavetwoimportantmethods:boot()andregister().There’salsoa$deferpropertythatyoumightchoosetouse.Here’showtheywork.
First,alloftheserviceproviders’register()methodsarecalled.Thisiswherewewanttobindclassesandaliasestothecontainer.Youdon’twanttodoanythinginregister()thatreliesontheentireapplicationbeingbootstrapped.
Second,alloftheserviceproviders’boot()methodsarecalled.Youcannowdoanyotherbootstrappinghere,likebindingeventlistenersordefiningroutes—anythingthatmayrelyontheentireLaravelapplicationhavingbeenbootstrapped.
Ifyourserviceproviderisonlygoingtoregisterbindingsinthecontainer(i.e.,teachthecontainerhowtoresolveagivenclassorinterface),butnotperformanyotherbootstrapping,youcan“defer”itsregistrations,whichmeanstheywon’trununlessoneoftheirbindingsisexplicitlyrequestedfromthecontainer.Thiscanspeedupyourapplication’saveragetimetobootstrap.
Ifyouwanttodeferyourserviceprovider ’sregistrations,firstgiveitaprotected$deferpropertyandsetittotrue,andthengiveitaprovides()methodthatreturnsalistofbindingstheproviderprovides,asshowninExample10-1.
Example10-1.Deferringtheregistrationofaserviceprovider...
classGitHubServiceProviderextendsServiceProvider
{
protected$defer=true;
publicfunctionprovides()
{
return[
GitHubClient::class
];
}
MOREUSESFORSERVICEPROVIDERSServiceprovidersalsohaveasuiteofmethodsandconfigurationoptionsthatcanprovideadvancedfunctionalitytotheenduserwhentheproviderispublishedaspartofaComposerpackage.TakealookattheserviceproviderdefinitionintheLaravelsourcetolearnmoreabouthowthiscanwork.
Nowthatwe’vecoveredtheapplicationbootstrap,let’stakealookattheRequestobject,themostimportantoutputofthebootstrap.
TheRequestObjectTheIlluminateRequestclassisaLaravel-specificextensionofSymfony’sHttpFoundation\Requestobject.
SYMFONYHTTPFOUNDATION
Ifyou’renotfamiliarwithit,Symfony’sHttpFoundationsuiteofclassespowersalmosteveryPHPframeworkinexistenceatthispoint;thisisthemostpopularandpowerfulsetofabstractionsavailableinPHPforrepresentingHTTPrequests,responses,headers,cookies,andmore.
EachRequestobjectisintendedtorepresenteveryrelevantpieceofinformationyoucouldcaretoknowaboutauser ’sHTTPrequest.
InnativePHPcode,youmightfindyourselflookingto$_SERVER,$_GET,$_POST,andothercombinationsofglobalsandprocessinglogictogetinformationaboutthecurrentuser ’srequest.Whatfileshastheuseruploaded?What’shisIPaddress?Whatfieldsdidhepost?Allofthisissprinkledaroundthelanguage—andyourcode—inawaythat’shardtounderstandandhardertomock.
Symfony’sRequestobjectinsteadcollectsalloftheinformationnecessarytorepresentasingleHTTPrequestintoasingleobject,andthentacksonconveniencemethodstomakeiteasytogetusefulinformationfromit.TheIlluminateRequestobjectaddsevenmoreconveniencemethodstogetinformationabouttherequestit’srepresenting.
CAPTURINGAREQUESTYou’llverylikelyneverneedtodothisinaLaravelapp,butifyoueverneedtocaptureyourownIlluminateRequestdirectlyfromPHP’sglobals,youcanusethecapture()method:
$request=Illuminate\Http\Request::capture();
GettingaRequestObjectinLaravelRealistically,you’renotgoingtobecapturingyourownrequests.Laraveldoesthisforyouinitsbootstrap,andthereareafewwaysyoucangetaccesstoit.
First—andagain,we’llcoverthismoreinChapter11—youcantypehinttheclassinanyconstructorormethodthat’sresolvedbythecontainer.Thatmeansyoucantypehintitinacontrollermethodoraserviceprovider,asseeninExample10-2.
Example10-2.Typehintinginacontainer-resolvedmethodtoreceiveaRequestobject...
useIlluminate\Http\Request;
classPeopleControllerextendsController
{
publicfunctionindex(Request$request)
{
$allInput=$request->all();
}
Youcanalsousetherequest()globalhelper,whichallowsyoutocallmethodsonit(e.g.,request()->input())andalsoallowsyoutocallitonitsowntogetaninstanceof$request:
$request=request();
$allInput=request()->all();
Andyoucanalsousetheapp()globalmethodtogetaninstanceofRequest.Youcanpasseitherthefullyqualifiedclassnameortheshortcut,request:
$request=app(Illuminate\Http\Request::class);
$request=app('request');
GettingBasicInformationAboutaRequestNowthatweknowhowtogetaninstanceofRequest,whatcanwedowithit?TheprimarypurposeoftheRequestobjectistorepresentthecurrentHTTPrequest,sotheprimaryfunctionalitytheRequestclassoffersistomakeiteasytogetusefulinformationaboutthecurrentrequest.
I’vecategorizedthemethodsdescribedhere,butnotethatthere’scertainlyoverlapbetweenthecategories,andthecategoriesareabitarbitrary—forexample,queryparameterscouldjustaseasilybein“Userandrequeststate”astheyarein“Basicuserinput.”Hopefullythesecategorieswillmakeiteasyforyoutolearn,andthenyoucanthrowawaythecategories.
Also,beawarethattherearemanymoremethodsavailableontheRequestobject;thesearejustthemostcommonlyusedmethods.
BasicuserinputThebasicuserinputmethodsmakeitsimpletogetinformationthattheusersthemselvesexplicitlyprovide—likelythroughsubmittingaformoranAjaxcomponent.WhenIreference“user-providedinput”here,I’mtalkingaboutinputfromquerystrings(GET),formsubmissions(POST),orJSON:
all()returnsanarrayofalluser-providedinput.
input(fieldName)returnsthevalueofasingleuser-providedinputfield.
only(fieldName|[array,of,field,names])returnsanarrayofalluser-providedinputforthespecifiedfieldname(s).
except(fieldName|[array,of,field,names])returnsanarrayofalluser-providedinputexceptforthespecifiedfieldname(s).
exists(fieldName)returnsabooleanofwhetherornotthefieldexistsintheinput.
has(fieldName)returnsabooleanofwhetherthefieldexistsintheinputandisnotempty(hasavalue).
json()returnsaParameterBagifthepagehadJSONsenttoit.
json(keyName)returnsthevalueofthegivenkeyfromJSONsenttothepage.
PARAMETERBAG
SometimesinLaravelyou’llrunintoaParameterBag.Thisclassissortoflikeanassociativearray.Youcangetaparticularkeyusingget():
echo$bag->get('name');
Youcanalsousehas()tocheckfortheexistenceofakey,all()togetanarrayofallkeysandvalues,count()tocountthenumberofitems,andkeys()togetanarrayofjustthekeys.
Example10-3givesafewquickexamplesofhowtousetheuser-providedinformationmethodsfromarequest.
Example10-3.Gettingbasicuser-providedinformationfromtherequest//form
<formmethod="POST"action="/form">
{{csrf_field()}}
<inputname="name">Name<br>
<inputtype="submit">
</form>
//routereceivingtheform
Route::post('form',function(Request$request){
echo'nameis'.$request->input('name');
echo'allinputis'.print_r($request->all());
echo'userprovidedemailaddress:'.$request->has('email')?'true':'false';
});
UserandrequeststateTheuserandrequeststatemethodsincludeinputthatwasn’texplicitlyprovidedbytheuserthroughaform:
method()returnsthemethod(GET,POST,PATCH,etc.)usedtoaccessthisroute.
path()returnsthepath(withoutthedomain)usedtoaccessthispage;e.g.,forhttp://www.myapp.com/abc/defitwouldreturnabc/def.
url()returnstheURL(withthedomain)usedtoaccessthispage;e.g.,forhttp://www.myapp.com/abcitwouldreturnhttp://www.myapp.com/abc.
is()returnsabooleanofwhetherornotthecurrentpagerequestfuzzy-matchesaprovidedstring(e.g.,/a/b/cwouldbematchedby$request->is('*b*'),where*standsforanycharacters).ItusesacustomregexparserfoundinStr::is.
ip()returnstheuser ’sIPaddress.
header()returnsanarrayofheaders(e.g.,['accept-language'=>['en-US,en;q=0.8']]),or,ifpassedaheadernameasaparameter,returnsjustthatheader.
server()returnsanarrayofthevariablestraditionallystoredin$_SERVER(e.g.,REMOTE_ADDR),or,ifpasseda$_SERVERvariablename,returnsjustthatvalue.
secure()returnsabooleanofwhetherthispagewasloadedusingHTTPS.
pjax()returnsabooleanofwhetherthispagerequestwasloadedusingPjax.
wantsJson()returnsabooleanofwhetherthisrequesthasany/jsoncontenttypesinitsAcceptheaders.
isJson()returnsabooleanofwhetherthispagerequesthasany/jsoncontenttypesinitsContent-Typeheader.
accepts()returnsabooleanofwhetherthispagerequestacceptsagivencontenttype.
FilesSofar,alloftheinputwe’vecoverediseitherexplicit(retrievedbymethodslikeall(),input(),etc.)ordefinedbythebrowserorreferringsite(retrievedbymethodslikepjax()).Fileinputsaresimilartoexplicituserinput,butthey’rehandledmuchdifferently:
file()returnsanarrayofalluploadedfiles,or,ifakeyispassed(thefileuploadfieldname),returnsjusttheonefile.
hasFile()returnsabooleanofwhetherafilewasuploadedatthespecifiedkey.
Everyfilethat’suploadedwillbeaninstanceofSymfony\Component\HttpFoundation\File\UploadedFile,whichprovidesasuiteoftoolsforvalidating,processing,andstoringuploadedfiles.
TakealookatChapter14formoreexamplesofhowtohandleuploadedfiles.
PersistenceTherequestcanalsoprovidefunctionalityforinteractingwiththesession.Mostsessionfunctionalityliveselsewhere,butthereareafewmethodsthatareparticularlyrelevanttothecurrentpagerequest:
flash()flashesthecurrentrequest’suserinputtothesessiontoberetrievedlater.
flashOnly()flashesthecurrentrequest’suserinputforanykeysintheprovidedarray.
flashExcept()flashesthecurrentrequests’suserinput,exceptforanykeysintheprovidedarray.
old()returnsanarrayofallpreviouslyflasheduserinput,or,ifpassedakey,returnsthevalueforthatkeyifitwaspreviouslyflashed.
flush()wipesallpreviouslyflasheduserinput.
cookie()retrievesallcookiesfromtherequest,or,ifakeyisprovided,retrievesjustthatcookie.
hasCookie()returnsabooleanofwhethertherequesthasacookieforthegivenkey.
Theflash*()andold()methodsareusedforstoringuserinputandretrievingitlater,oftenaftertheinputisvalidatedandrejected.
TheResponseObjectSimilartotheRequestobject,there’sanIlluminateResponseobjectthatrepresentstheresponseyourapplicationissendingtotheenduser,completewithheaders,cookies,content,andanythingelseusedforsendingtheenduser ’sbrowserinstructionsonrenderingapage.
JustlikeRequest,theIlluminate\Http\ResponseobjectextendsaSymfonyclass:Symfony\Component\HttpFoundation\Response.Thisisabaseclasswithaseriesofpropertiesandmethodsthatmakeitpossibletorepresentandrenderaresponse;Illuminate’sResponseclassdecoratesitwithafewhelpfulshortcuts.
UsingandCreatingResponseObjectsinControllersBeforewetalkabouthowyoucancustomizeyourresponseobjects,let’sstepbackandseehowwemostcommonlyworkwithresponseobjects.
Intheend,anyresponseobjectreturnedfromaroutedefinitionwillbeconvertedintoanHTTPresponse.Itmaydefinespecificheadersorspecificcontent,setcookies,orwhateverelse,buteventuallyitwillbeconvertedintoaresponseyourusers’browserscanparse.
Let’stakealookatthesimplestpossibleresponse,inExample10-4.
Example10-4.SimplestpossibleHTTPresponseRoute::get('route',function(){
returnnewIlluminate\Http\Response('Hello!');
});
//Same,usingglobalfunction:
Route::get('route',function(){
returnresponse('Hello!');
});
Wecreatearesponse,giveitsomecoredata,andthenreturnit.WecanalsocustomizetheHTTPstatus,headers,cookies,andmore,likeinExample10-5.
Example10-5.SimpleHTTPresponsewithcustomizedstatusandheadersRoute::get('route',function(){
returnresponse('Error!',400)
->header('X-Header-Name','header-value')
->cookie('cookie-name','cookie-value');
});
SettingheadersWedefineaheaderonaresponsebyusingtheheader()fluentmethod,likeinExample10-5.Thefirstparameteristheheadernameandthesecondistheheadervalue.
AddingcookiesWecanalsosetcookiesdirectlyontheresponseobjectifwe’dlike.We’llcoverLaravel’scookiehandlingabitmoreinChapter14,buttakealookatExample10-6forasimpleusecaseforattachingcookiestoaresponse.
Example10-6.Attachingacookietoaresponsereturnresponse($content)
->cookie('signup_dismissed',true);
SpecializedResponseTypesTherearealsoafewspecialresponsetypesforviews,downloads,files,andJSON.Eachisapredefinedmacrothatmakesiteasytoreuseparticulartemplatesforheadersorcontentstructure.
ViewresponsesInChapter4,Iusedtheglobalview()helpertoshowhowtoreturnatemplate—forexample,view(view.name.here)orsomethingsimilar.Butifyouneedtocustomizeheaders,HTTPstatus,oranythingelsewhenreturningaview,youcanusetheview()responsetypeasshowninExample10-7.
Example10-7.Usingtheview()responsetypeRoute::get('/',function(XmlGetterService$xml){
$data=$xml->get();
returnresponse()
->view('xml-structure',$data)
->header('Content-Type','text/xml');
});
DownloadresponsesSometimesyouwantyourapplicationtoforcetheuser ’sbrowsertodownloadafile,whetheryou’recreatingthefileinLaravelorservingitfromadatabaseoraprotectedlocation.Thedownload()responsetypemakesthissimple.
Therequiredfirstparameteristhepathforthefileyouwantthebrowsertodownload.Ifit’sageneratedfile,you’llneedtosaveitsomewheretemporarily.
Theoptionalsecondparameteristhefilenameforthedownloadedfile(e.g.,export.csv).Ifyoudon’tpassastringhere,itwillbeautomaticallygenerated.Theoptionalthirdparameterallowsyoutopassanarrayofheaders.Example10-8illustratestheuseofthedownload()responsetype.
Example10-8.Usingthedownload()responsetypepublicfunctionexport()
{
returnresponse()
->download('file.csv','export.csv',['header'=>'value']);
}
publicfunctionotherExport()
{
returnresponse()->download('file.pdf');
}
FileresponsesThefileresponseissimilartothedownloadresponse,exceptitallowsthebrowsertodisplaythefileinsteadofforcingadownload.ThisismostcommonwithimagesandPDFs.
Therequiredfirstparameteristhefilename,andtheoptionalsecondparametercanbean
arrayofheaders(seeExample10-9).
Example10-9.Usingthefile()responsetypepublicfunctioninvoice($id)
{
returnresponse()->file("./invoices/{$id}.pdf",['header'=>'value']);
}
JSONresponsesJSONresponsesaresocommonthat,eventhoughthey’renotreallyparticularlycomplextoprogram,there’sacustomresponseforthemaswell.
JSONresponsesconvertthepasseddatatoJSON(withjson_encode())andsettheContent-Typetoapplication/json.YoucanalsooptionallyusethesetCallback()methodtocreateaJSONPresponseinsteadofJSON,asseeninExample10-10.
Example10-10.Usingthejson()responsetypepublicfunctioncontacts()
{
returnresponse()->json(Contact::all());
}
publicfunctionjsonpContacts(Request$request)
{
returnresponse()
->json(Contact::all())
->setCallback($request->input('callback'));
}
publicfunctionnonEloquentContacts()
{
returnresponse()->json(['Tom','Jerry']);
}
RedirectresponsesRedirectsaren’tcommonlycalledontheresponse()helper,sothey’reabitdifferentfromtheothercustomresponsetypeswediscussedalready,butthey’restilljustadifferentsortofresponse.Redirects,returnedfromaLaravelroute,sendtheuseraredirect(oftena301)toanotherpageorbacktothepreviouspage.
Youtechnicallycancallaredirectfromresponse(),asinreturnresponse()->redirectTo('/').Butmorecommonlyyou’llusetheredirect-specificglobalhelpers.
Thereisaglobalredirect()functionthatcanbeusedtocreateredirectresponses,andaglobalback()functionthatisashortcuttoredirect()->back().
Justlikemostglobalhelpers,theredirect()globalfunctioncaneitherbepassedparametersorcanbeusedtogetaninstanceofitsclassthatyouthenchainmethodcallsonto.Ifyoudon’tchain,butjustpassparameters,redirect()performsthesameasredirect()->to();ittakesastringandredirectstothatstringURL.Example10-11showssomeexamplesofitsuse.
Example10-11.Examplesofusingtheredirect()globalhelperreturnredirect('account/payment');
returnredirect()->to('account/payment');
returnredirect()->route('account.payment');
returnredirect()->action('AccountController@showPayment');
//Ifnamedrouteorcontrollerneedsparameters:
returnredirect()->route('contacts.edit',['id'=>15]);
returnredirect()->action('ContactsController@edit',['id'=>15]);
Youcanalsoredirect“back”tothepreviouspage,whichisespeciallyusefulwhenhandlingandvalidatinguserinput.Example10-12showsacommonpatterninvalidationcontexts.
Example10-12.Redirectbackwithinputpublicfunctionstore()
{
//Ifvalidationfails...
returnback()->withInput();
}
Finally,youcanredirectandflashdatatothesessionatthesametime.Thisiscommonwitherrorandsuccessmessages,likeinExample10-13.
Example10-13.RedirectwithflasheddataRoute::post('contacts',function(){
//storethecontact...
returnredirect('dashboard')->with('message','Contactcreated!');
});
Route::get('dashboard',function(){
//Gettheflasheddatafromsession--usuallyhandledinBladetemplate
echosession('message');
});
CustomresponsemacrosYoucanalsocreateyourowncustomresponsetypesusing“macros”.Thisallowsyoutodefineaseriesofmodificationstomaketotheresponseanditsprovidedcontent.
Let’sre-createthejson()customresponsetype,justtoseehowitworks.Asalways,youshouldprobablycreateacustomserviceproviderforthesesortsofbindings,butfornowwe’lljustputitinAppServiceProvider,asseeninExample10-14.
Example10-14.Creatingacustomresponsemacro...
classAppServiceProvider
{
publicfunctionboot()
{
Response::macro('myJson',function($content){
returnresponse(json_encode($content))
->headers(['Content-Type'=>'application/json']);
});
}
Then,wecanuseitjustlikewewouldusethepredefinedjsonmacro:
returnresponse()->myJson(['name'=>'Sangeetha']);
ThiswillreturnaresponsewiththebodyofthatarrayencodedforJSON,withtheJSON-
appropriateContent-Typeheader.
LaravelandMiddlewareTakealookbackatFigure10-1,atthestartofthischapter.
We’vecoveredtherequestsandresponses,butwehaven’tactuallylookedintowhatmiddlewareis.Youmayalreadybefamiliarwithmiddleware;it’snotuniquetoLaravel,butratherawidelyusedarchitecturepattern.
AnIntroductiontoMiddlewareTheideaofmiddlewareisthatthereisaseriesoflayerswrappingaroundyourapplication,likeamultilayercakeoranonion.JustasshowninExample10-1,everyrequestpassesthrougheverymiddlewarelayeronitswayintotheapplication,andthentheresultingresponsepassesbackthroughthemiddlewarelayersonitswayouttotheenduser.
Middlewareismostoftenconsideredseparatefromyourapplicationlogic,andusuallyisconstructedinawaythatshouldtheoreticallybeapplicabletoanyapplication,notjusttheoneyou’reworkingonatthemoment.
Middlewarecaninspectarequestanddecorateit,orrejectit,basedonwhatitfinds.Thatmeansmiddlewareisgreatforsomethinglikeratelimiting:itcaninspecttheIPaddress,checkhowmanytimesit’saccessedthisresourceinthelastminute,andsendbacka429(TooManyRequests)statusifathresholdispassed.
Becausemiddlewarealsogetsaccesstotheresponseonitswayoutoftheapplication,it’sgreatfordecoratingresponses.Forexample,Laravelusesamiddlewaretoaddallofthequeuedcookiesfromagivenrequest/responsecycletotheresponserightbeforeitissenttotheenduser.
Butsomeofthemostpowerfulusesofmiddlewarecomefromthefactthatitcanbenearlythefirstandthelastthingtointeractwiththerequest/responsecycle.Thatmakesitperfectforsomethinglikeenablingsessions—PHPneedsyoutoopenthesessionveryearlyandcloseitverylate,andmiddlewareisgreatforthis.
CreatingCustomMiddlewareLet’simaginewewanttohaveamiddlewarethatrejectseveryrequestthatusestheDELETEHTTPmethod,andalsosendsacookiebackforeveryrequest.
There’sanArtisancommandtocreatecustommiddleware.Let’stryitout:
phpartisanmake:middlewareBanDeleteMethod
Youcannowopenupthefileatapp/Http/Middleware/BanDeleteMethod.php.ThedefaultcontentsareshowninExample10-15.
Example10-15.Defaultmiddlewarecontents...
classBanDeleteMethod
{
publicfunctionhandle($request,Closure$next)
{
return$next($request);
}
}
Howthishandle()methodrepresentstheprocessingofboththeincomingrequestandtheoutgoingresponseisthemostdifficultthingtounderstandaboutmiddleware,solet’swalkthroughit.
Understandingmiddleware’shandle()methodFirst,rememberthatmiddlewarearelayeredoneontopofanother,andthenfinallyontopoftheapp.Thefirstmiddlewarethat’sregisteredgetsfirstaccesstoarequestwhenitcomesin,thenthatrequestispassedtoeveryothermiddlewareinturn,thentotheapp;thentheresultingresponseispassedoutwardthroughthemiddleware,andfinallythisfirstmiddlewaregetslastaccesstotheresponsewhenitgoesout.
Let’simaginewe’veregisteredBanDeleteMethodasthefirstmiddlewaretorun.Thatmeansthe$requestcomingintoitistherawrequest,unadulteratedbyanyothermiddleware.Nowwhat?
Passingthatrequestto$next()meanshandingitofftotherestofthemiddleware.The$next()closurejusttakesthat$requestandpassesittothehandle()methodofthenextmiddlewareinthestack.Itthengetspassedondownthelineuntiltherearenomoremiddlewaretohanditto,anditfinallyendsupattheapplication.
Next,howdoestheresponsecomeout?Thisiswhereitmightbehardtofollow.Theapplicationreturnsaresponse,whichispassedbackupthechainofmiddleware—becauseeachmiddlewarereturnsitsresponse.So,withinthatsamehandle()method,themiddlewarecandecoratea$requestandpassittothe$next()closure,andcanthenchoosetodosomethingwiththeoutputitreceivesbeforefinallyreturningthatoutputtotheenduser.Let’slookatsomepseudocodetomakethisclearer(Example10-16).
Example10-16.Pseudocodeexplainingthemiddlewarecallprocess...
classBanDeleteMethod
{
publicfunctionhandle($request,Closure$next)
{
//Atthispoint,$requestistherawrequestfromtheuser.
//Let'sdosomethingwithit,justforfun.
if($request->ip()==='192.168.1.1'){
returnresponse('BANNEDIPADDRESS!',403);
}
//Nowwe'vedecidedtoacceptit.Let'spassitontothenext
//middlewareinthestack.Wepassitto$next(),andwhatis
//returnedistheresponseafterthe$requesthasbeenpassed
//downthestackofmiddlewaretotheapplicationandthe
//application'sresponsehasbeenpassedbackupthestack.
$response=$next($request);
//Atthispoint,wecanonceagaininteractwiththeresponse
//justbeforeitisreturnedtotheuser
$response->cookie('visited-our-site',true);
//Finally,wecanreleasethisresponsetotheenduser
return$response;
}
}
Finally,let’smakethemiddlewaredowhatweactuallypromised(Example10-17).
Example10-17.Samplemiddlewarebanningthedeletemethod...
classBanDeleteMethod
{
publicfunctionhandle($request,Closure$next)
{
//TestfortheDELETEmethod
if($request->method()==='DELETE'){
returnresponse(
"Getoutofherewiththatdeletemethod",
405
);
}
$response=$next($request);
//Assigncookie
$response->cookie('visited-our-site',true);
//Returnresponse
return$response;
}
}
BindingMiddlewareWe’renotquitedoneyet.Weneedtoregisterthismiddlewareinoneoftwoways:globallyorforspecificroutes.
Globalmiddlewareareappliedtoeveryroute;routemiddlewareareappliedonaroute-by-routebasis.
BindingglobalmiddlewareBothbindingshappeninapp/Http/Kernel.php.Toaddamiddlewareasglobal,additsclassnametothe$middlewareproperty,asinExample10-18.
Example10-18.Bindingglobalmiddleware//app/Http/Kernel.php
protected$middleware=[
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
\App\Http\Middleware\BanDeleteMethod::class,
];
BindingroutemiddlewareMiddlewareintendedforspecificroutescanbeaddedasaroutemiddlewareoraspartofamiddlewaregroup.Let’sstartwiththeformer.
Routemiddlewareareaddedtothe$routeMiddlewarearrayinapp/Http/Kernel.php.It’ssimilartoaddingthemto$middleware,exceptwehavetogiveoneakeythatwillbeusedwhenapplyingthismiddlewaretoaparticularroute,asseeninExample10-19.
Example10-19.Bindingroutemiddleware//app/Http/Kernel.php
protected$routeMiddleware=[
'auth'=>\App\Http\Middleware\Authenticate::class,
...
'nodelete'=>\App\Http\Middleware\BanDeleteMethod::class,
];
Wecannowusethismiddlewareinourroutedefinitions,likeinExample10-20.
Example10-20.Applyingroutemiddlewareinroutedefinitions//Doesn'tmakemuchsenseforourcurrentexample...
Route::get('contacts',[
'middleware'=>'nodelete',
'uses'=>'ContactsController@index'
]);
//Makesmoresenseforourcurrentexample...
Route::group(['prefix'=>'api','middleware'=>'nodelete',function(){
//AllroutesrelatedtoanAPI
}]);
UsingmiddlewaregroupsLaravel5.2introducedtheconceptofmiddlewaregroups.They’reessentiallypre-packagedbundlesofmiddlewarethatmakesensetobetogetherinspecificcontexts.
MIDDLEWAREGROUPSIN5.2Thedefaultroutesfileinearlierreleasesof5.2,routes.php,hadthreedistinctsections:therootroute(/)wasn’tunderanymiddlewaregroup,andthentherewasawebmiddlewaregroupandanapimiddlewaregroup.Itwasabitconfusingfornewusers,andthatmeanttherootroutedidn’thaveaccesstothesessionoranythingelsethat’skickedoffinthemiddleware.
Inlaterversionsof5.2everything’ssimplified:everyrouteinroutes.phpisinthewebmiddlewaregroup.In5.3,yougetaroutes/web.phpfileforwebroutesandaroutes/api.phpfileforAPIroutes.Ifyouwanttoaddroutesinothergroups,readon.
Outoftheboxtherearetwogroups:webandapi.webhasallthemiddlewarethatwillbeusefulonalmosteveryLaravelpagerequest,includingmiddlewareforcookies,sessions,CSRFprotection,andmore.apihasnoneofthose—ithasathrottlemiddlewareandaroutemodelbindingmiddleware,andthat’sit.Thesearealldefinedinapp/Http/Kernel.php.
Youcanapplymiddlewaregroupstoroutesjustlikeyouapplyroutemiddlewaretoroutes,withthemiddleware()fluentmethod:
Route::get('/','HomeController@index')->middleware('web');
Youcanalsocreateyourownmiddlewaregroupsandaddandremoveroutemiddlewaretoandfrompreexistingmiddlewaregroups.Itworksjustlikeaddingroutemiddlewarenormally,butyou’reinsteadaddingthemtokeyedgroupsinthe$middlewareGroupsarray.
Youmightbewonderinghowthesemiddlewaregroupsmatchupwiththetwodefaultroutesfiles.Unsurprisingly,theroutes/web.phpfileiswrappedwiththewebmiddlewaregroup,andtheroutes/api.phpfileiswrappedwiththeapimiddlewaregroup.
Theroutes/*filesareloadedintheRouteServiceProvider.Takealookatthemap()methodthere(Example10-21)andyou’llfindamapWebRoutes()andamapApiRoutes()method,eachofwhichloadsitsrespectivefilesalreadywrappedintheappropriatemiddlewaregroup.
Example10-21.DefaultrouteserviceproviderinLaravel5.3//App\Providers\RouteServiceProvider
publicfunctionmap()
{
$this->mapApiRoutes();
$this->mapWebRoutes();
}
protectedfunctionmapApiRoutes()
{
Route::group([
'middleware'=>'api',
'namespace'=>$this->namespace,
'prefix'=>'api',
],function($router){
requirebase_path('routes/api.php');
});
}
protectedfunctionmapWebRoutes()
{
Route::group([
'middleware'=>'web',
'namespace'=>$this->namespace,
],function($router){
requirebase_path('routes/web.php');
});
}
AsyoucanseeinExample10-21,we’reusingtheroutertoloadaroutegroupunderthedefaultnamespace(App\Http\Controllers)andwiththewebmiddlewaregroup,andanotherundertheapimiddlewaregroup.
PassingParameterstoMiddlewareIt’snotcommon,buttherearetimeswhenyouneedtopassparameterstoaroutemiddleware.Forexample,youmighthaveanauthenticationmiddlewarethatwillactdifferentlydependingonwhetheryou’reguardingforthememberusertypeortheownerusertype:
Route::get('company',function(){
returnview('company.admin');
})->middleware('auth:owner');
Tomakethiswork,you’llneedtoaddoneormoreparameterstothemiddleware’shandle()method,andupdatethatmethod’slogicaccordingly:
publicfunctionhandle($request,$next,$role)
{
if(auth()->check()&&auth()->user()->hasRole($role)){
return$next($request);
}
returnredirect('login');
}
Notethatyoucanalsoaddmorethanoneparametertothehandle()method,andpassmultipleparameterstotheroutedefinitionbyseparatingthemwithcommas:
Route::get('company',function(){
returnview('company.admin');
})->middleware('auth:owner,view');
FORMREQUESTOBJECTS
InthischapterwecoveredhowtoinjectanIlluminateRequestobject,whichisthebase—andmostcommon—requestobject.
However,youcanalsoextendtheRequestobjectandinjectthatinstead.You’lllearnmoreabouthowtobindandinjectcustomclassesinChapter11,butthere’sonespecialtype,calledtheformrequest,thathasitsownsetofbehaviors.
See“FormRequests”tolearnmoreaboutcreatingandusingformrequests.
TestingOutsideofthecontextofyouasadeveloperusingrequests,responses,andmiddlewareinyourowntesting,Laravelitselfactuallyuseseachquiteabit.
Whenyou’redoingapplicationtesting—callslike$this->visit('/'),clicks,andwhateverelse—you’reinstructingLaravel’sapplicationtestingframeworktogeneraterequestobjectsthatrepresenttheinteractionsthatyou’redescribing.Thenthoserequestobjectsarepassedtoyourapplicationasifitwereanactualvisit.That’swhytheapplicationtestsaresoaccurate:yourapplicationdoesn’tactually“know”thatit’snotarealuserthat’sinteractingwithit.
Inthiscontext,manyoftheassertionsyou’remaking—say,assertResponseOk()—areassertionsagainsttheresponseobjectgeneratedbytheapplicationtestingframework.TheassertResponseOk()methodjustlooksattheresponseobjectandassertsthatitsisOk()methodreturnstrue—whichisjustcheckingthatitsstatuscodeis200.Intheend,everythinginapplicationtestingisactingasifthiswerearealpagerequest.
Findyourselfinacontextwhereyouneedarequesttoworkwithinyourtests?Youcanalwayspullonefromthecontainerwith$request=request().Oryoucouldcreateyourown—theconstructorparametersfortheRequestclass,alloptional,areasfollows:
$request=newIlluminate\Http\Request(
$query,//GETarray
$request,//POSTarray
$attributes,//"attributes"array;emptyisfine
$cookies,//cookiesarray
$files,//filesarray
$server,//serversarray
$content//rawbodydata
);
Ifyou’rereallyinterestedinanexample,checkoutthemethodSymfonyusestocreateanewRequestfromtheglobalsPHPprovides:Symfony\Component\HttpFoundation\Request@createFromGlobals().
Responsesareevensimplertocreatemanually,ifyouneedto.Herearethe(optional)parameters:
$response=newIlluminate\Http\Response(
$content,//responsecontent
$status,//HTTPstatus,default200
$headers//arrayheadersarray
);
Finally,ifyouneedtodisableyourmiddlewareduringanapplicationtest,importtheWithoutMiddlewaretraitintothattest.
TL;DREveryrequestcomingintoaLaravelapplicationisconvertedintoanIlluminateRequestobject,whichthenpassesthroughallmiddleware,andisprocessedbytheapplication.Theapplicationgeneratesaresponseobject,whichisthenpassedbackthroughallofthemiddleware(inreverseorder)andreturnedtotheenduser.
Requestandresponseobjectsareresponsibleforencapsulatingandrepresentingeveryrelevantpieceofinformationabouttheincominguserrequestandtheoutgoingserverresponse.
Serviceproviderscollecttogetherrelatedbehaviorforbindingandregisteringclassesforusebytheapplication.
Middlewarewraptheapplicationandcanrejectordecorateanyrequestandresponse.
Chapter11.TheContainer
Laravel’sservicecontainer,ordependencyinjectioncontainer,sitsatthecoreofalmosteveryotherfeature.Thecontainerisasimpletoolyoucanusetobindandresolveconcreteinstancesofclassesandinterfaces,andatthesametimeit’sapowerfulandnuancedmanagerofanetworkofinterrelateddependencies.Inthischapter,we’lllearnmoreaboutwhatitis,howitworks,andhowyoucanuseit.
NAMINGANDTHECONTAINERYou’llnoticeinthisbook,inthedocumentation,andinothereducationalsourcesthattherearequiteafewnamesfolksuseforthecontainer.Theseinclude:
Applicationcontainer
IoC(inversionofcontrol)container
Servicecontainer
DI(dependencyinjection)container
Allareusefulandvalid,butjustknowthey’realltalkingaboutthesamething.They’reallreferringtotheservicecontainer.
AQuickIntroductiontoDependencyInjectionDependencyinjectionmeansthat,ratherthanbeinginstantiated(“newedup”)withinaclass,eachclass’sdependencieswillbeinjectedinfromtheoutside.Thismostcommonlyoccurswithconstructorinjection,whichmeansanobject’sdependenciesareinjectedwhenit’screated.Butthere’salsosetterinjection,wheretheclassexposesamethodspecificallyforinjectingagivendependency,andmethodinjection,whereoneormoremethodsexpecttheirdependenciestobeinjectedwhenthey’recalled.
TakealookatExample11-1foraquickexampleofconstructorinjection,themostcommontypeofdependencyinjection.
Example11-1.Basicdependencyinjection<?php
classUserMailer
{
protected$mailer;
publicfunction__construct(Mailer$mailer)
{
$this->mailer=$mailer;
}
publicfunctionwelcome($user)
{
return$this->mailer->mail($user->email,'Welcome!');
}
}
Asyoucansee,thisUserMailerclassexpectsanobjectoftypeMailertobeinjectedwhenit’sinstantiated,anditsmethodsthenrefertothatinstance.
Theprimarybenefitsofdependencyinjectionarethatitgivesusthefreedomtochangewhatwe’reinjecting,mockdependenciesfortesting,andinstantiateshareddependenciesjustonceforshareduse.
INVERSIONOFCONTROL
Youmayhaveheardthephrase“inversionofcontrol”usedinconjunctionwith“dependencyinjection,”andsometimesLaravel’scontaineriscalledtheIoCcontainer.
Thetwoconceptsareverysimilar.Inversionofcontrolreferencestheideathat,intraditionalprogramming,thelowest-levelcode—specificclasses,instances,andproceduralcode—“controls”whichinstanceofaparticularpatternorinterfacetouse.Forexample,ifyou’reinstantiatingyourmailerineachclassthatneedsit,eachclassgetstodecidewhethertouseMailgunorMandrillorSendgrid.
Theideaofinversionofcontrolreferstoflippingthat“control”toliveattheoppositeendofyourapplication.Nowthedefinitionofwhichmailertouselivesatthehighest,mostabstractlevelofyourapplication,ofteninconfiguration.Everyinstance,everypieceoflow-levelcode,looksuptothehigh-levelconfigurationtoessentially“ask”:“Canyougivemeamailer?”Theydon’t“know”whichmailerthey’regetting,justthatthey’regettingone.
DependencyinjectionandespeciallyDIcontainersprovideagreatopportunityforinversionofcontrol,becauseyoucandefineoncewhichconcreteinstanceoftheMailerinterface,forexample,toprovidewheninjectingmailersintoanyclassthatneedsthem.
DependencyInjectionandLaravelAswesawinExample11-1,themostcommonpatternfordependencyinjectionisconstructorinjection,orinjectingthedependenciesofanobjectwhenit’sinstantiated(“constructed”).
Let’stakeourUserMailerclassfromExample11-1.Example11-2showswhatitmightlookliketocreateanduseaninstanceofit.
Example11-2.Simplemanualdependencyinjection$mailer=newMailgunMailer($mailgunKey,$mailgunSecret,$mailgunOptions);
$userMailer=newUserMailer($mailer);
$userMailer->welcome($user);
Nowlet’simaginewewantourUserMailerclasstobeabletologmessages,aswellassendinganotificationtoaSlackchanneleverytimeitsendsamessage.Example11-3showswhatthiswouldlooklike.Asyoucansee,itwouldstarttogetprettyunwieldyifwehadtodoallthisworkeverytimewewantedtocreateanewinstance—especiallywhenyouconsiderthatwe’llhavetogetalltheseparametersfromsomewhere.
Example11-3.Morecomplexmanualdependencyinjection$mailer=newMailgunMailer($mailgunKey,$mailgunSecret,$mailgunOptions);
$logger=newLogger($logPath,$minimumLogLevel);
$slack=newSlack($slackKey,$slackSecret,$channelName,$channelIcon);
$userMailer=newUserMailer($mailer,$logger,$slack);
$userMailer->welcome($user);
ImaginehavingtowritethatcodeeverytimeyouwantedaUserMailer.Dependencyinjectionisgreat,butthisisamess.
Theapp()GlobalHelperBeforewegotoofarintohowthecontaineractuallyworks,let’stakeaquicklookatthesimplestwaytogetanobjectoutofthecontainer:theapp()helper.
Passanystringtothathelper,whetherit’safullyqualifiedclassname(FQCN)oraLaravelshortcut(we’lltalkaboutthosemoreinasecond),andit’llreturnaninstanceofthatclass:
$logger=app(Logger::class);
Thisistheabsolutesimplestwaytointeractwiththecontainer.Itcreatesaninstanceofthisclassandreturnsitforyou.Niceandeasy.
DIFFERENTSYNTAXESFOR MAKINGACONCRETEINSTANCE
Thesimplestwayto“make”aconcreteinstanceistousetheglobalhelperandpasstheclassorinterfacenamedirectlytothehelper,usingapp('FQCN').
However,ifyouhaveaninstanceofthecontainer—whetheritwasinjectedsomewhere,orifyou’reinaserviceproviderandusing$this->app,or(alesser-knowntrick)ifyougetonebyjustrunning$container=app()—thereareafewwaystomakeaninstancefromthere.
Themostcommonwayistorunthemake()method.$app->make('FQCN')workswell.However,youmayalsoseeotherdevelopersandthedocumentationusethissyntaxsometimes:$app['FQCN'].Don’tworry.That’sdoingthesamething;it’sjustadifferentwayofwritingit.
CreatingtheLoggerinstanceasshownhereseemssimpleenough,butyoumight’venoticedthatour$loggerclassinExample11-3hastwoparameters:$logPathand$minimumLogLevel.Howdoesthecontainerknowwhattopasshere?
Shortanswer:itdoesn’t.Youcanusetheapp()globalhelpertocreateaninstanceofaclassthathasnoparametersinitsconstructor,butatthatpointyoucould’vejustrunnewLoggeryourself.Thecontainershineswhenthere’ssomecomplexityintheconstructor,andthat’swhenweneedtolookathowexactlythecontainercanfigureouthowtoconstructclasseswithconstructorparameters.
HowtheContainerIsWiredBeforewedigfurtherintotheLoggerclass,takealookatExample11-4.
Example11-4.LaravelautowiringclassBar
{
publicfunction__construct(){}
}
classBaz
{
publicfunction__construct(){}
}
classFoo
{
publicfunction__construct(Bar$bar,Baz$baz){}
}
$foo=app(Foo::class);
ThislookssimilartoourmailerexampleinExample11-3.What’sdifferentisthatthesedependencies(BarandBaz)arebothsosimplethatthecontainercanresolvethemwithoutanyfurtherinformation.Thecontainerreadsthetypehintsintheconstructor,resolvesaninstanceofeach,andtheninjectsthemintothenewFooinstancewhenit’screatingit.Thisiscalledautowiring:resolvinginstancesbasedontype-hintswithoutthedeveloperneedingtoexplicitlybindthoseclassesinthecontainer.
TYPEHINTSINPHP“Typehinting”inPHPmeansputtingthenameofaclassorinterfaceinfrontofavariableinamethodsignature:
publicfunction__construct(Logger$logger){}
ThistypehintistellingPHPthatwhateverispassedintothemethodmustbeoftypeLogger,whichcouldbeeitheraninterfaceoraclass.
Autowiringmeansthat,ifaclasshasnotbeenexplicitlyboundtothecontainer(likeFoo,Bar,orBazinthiscontext)butthecontainercanfigureouthowtoresolveitanyway,thecontainerwillresolveit.Thismeansanyclasswithnoconstructordependencies(likeBarandBaz)andanyclasswithconstructordependenciesthatthecontainercanresolve(likeFoo)canberesolvedoutofthecontainer.
Thatleavesusonlyneedingtobindclassesthathaveunresolvableconstructorparameters—forexample,our$loggerclassinExample11-3,whichhasparametersrelatedtoourlogpathandloglevel.
Forthose,we’llneedtolearnhowtoexplicitlybindsomethingtothecontainer.
BindingClassestotheContainerBindingaclasstoLaravel’scontainerisessentiallytellingthecontainer,“IfadeveloperasksforaninstanceofLogger,here’sthecodetoruninordertoinstantiateonewiththecorrectparametersanddependenciesandthenreturnitcorrectly.”
We’reteachingthecontainerthat,whensomeoneasksforthisparticularstring(whichisusuallytheFQCNofaclass),itshouldresolveitthisway.
BindingtoaClosureSo,let’slookathowtobindtothecontainer.Notethattheappropriateplacetobindtothecontainerisinaserviceprovider ’sregister()method(seeExample11-5).
Example11-5.Basiccontainerbinding//Inserviceprovider
publicfunctionregister()
{
$this->app->bind(Logger::class,function($app){
returnnewLogger('\log\path\here','error');
});
}
Thereareafewimportantthingstonoteinthisexample.First,we’rerunning$this->app->bind().$this->appisaninstanceofthecontainerthat’salwaysavailableoneveryserviceprovider.Thecontainer ’sbind()methodiswhatweusetobindtothecontainer.
Thefirstparameterofbind()isthe“key”we’rebindingto.Herewe’veusedtheFQCNoftheclass.Thesecondparameterdiffersdependingonwhatyou’redoing,butessentiallyitshouldbesomethingthatshowsthecontainerwhattodotoresolveaninstanceofthatboundkey.
So,inthisexample,we’repassingaclosure.Andnow,anytimesomeonerunsapp(Logger::class),they’llgettheresultofthisclosure.Theclosureispassedaninstanceofthecontaineritself($app),soiftheclassyou’reresolvinghasadependencyyouwantresolvedoutofthecontainer,youcanuseitinyourdefinition:
$this->app->bind(UserMailer::class,function($app){
returnnewUserMailer(
$app->make(Mailer::class),
$app->make(Logger::class),
$app->make(Slack::class)
);
});
Notethateverytimeyouaskforanewinstanceofyourclass,thisclosurewillberunagainandthenewoutputreturned.
BindingtoSingletons,Aliases,andInstancesIfyouwanttheoutputofthebindingclosuretobecachedsothatthisclosureisn’tre-runeverytimeyouaskforaninstance,that’stheSingletonpattern,andyoucanrun$this->app->singleton()todothat:
publicfunctionregister()
{
$this->app->singleton(Logger::class,function(){
returnnewLogger('\log\path\here','error');
});
}
Youcanalsogetsimilarbehaviorifyoualreadyhaveaninstanceoftheobjectyouwantthesingletontoreturn:
publicfunctionregister()
{
$logger=newLogger('\log\path\here','error');
$this->app->instance(Logger::class,$logger);
}
Finally,ifyouwanttoaliasoneclasstoanother,bindaclasstoashortcut,orbindashortcuttoaclass,youcanjustpasstwostrings:
$this->bind(Logger::class,FirstLogger::class);
//or
$this->bind('log',FirstLogger::class);
//or
$this->bind(FirstLogger::class,'log');
NotethattheseshortcutsarecommoninLaravel’score;itprovidesasystemofshortcutstoclassesthatprovidecorefunctionality,usingeasy-to-rememberkeyslikelog.
BindingaConcreteInstancetoanInterfaceJustlikewecanbindaclasstoanotherclass,oraclasstoashortcut,wecanalsobindtoaninterface.Thisisextremelypowerful,becausewecannowtypehintinterfacesinsteadofclassnames,likeinExample11-6.
Example11-6.Typehintingandbindingtoaninterface...
useInterfaces\Mailer;
classUserMailer
{
protected$mailer;
publicfunction__construct(Mailer$mailer)
{
$this->mailer=$mailer;
}
}
//serviceprovider
publicfunctionregister()
{
$this->app->bind(\Interfaces\Mailer::class,function(){
returnnewMailgunMailer(...);
});
}
YoucannowtypehintMailerorLoggerinterfacesallacrossyourcode,andthenchooseonceinaserviceproviderwhichspecificmailerorloggeryouwanttouseeverywhere.That’sinversionofcontrol.
ContextualBindingSometimesyouneedtochangehowtoresolveaninterfacedependingonthecontext.Youmightwanttologeventsfromoneplacetoalocalsyslogandfromothersouttoanexternalservice.So,let’stellthecontainertodifferentiate—checkoutExample11-7.
Example11-7.Contextualbinding//Inaserviceprovider
publicfunctionregister()
{
$this->app->when(FileWrangler::class)
->needs(Interfaces\Logger::class)
->give(Loggers\Syslog::class);
$this->app->when(Jobs\SendWelcomeEmail::class)
->needs(Interfaces\Logger::class)
->give(Loggers\PaperTrail::class);
}
ConstructorInjectionWe’vecoveredtheconceptofconstructorinjection,andwe’velookedathowthecontainermakesiteasytoresolveinstancesofaclassorinterfaceoutofthecontainer.Wesawhoweasyitistousetheapp()helpertomakeinstances,andalsohowthecontainerwillresolvetheconstructordependenciesofaclasswhenit’screatingit.
Whatwehaven’tcoveredyetishowthecontainerisalsoresponsibleforresolvingmanyofthecoreoperatingclassesofyourapplication.Forexample,everycontrollerisinstantiatedbythecontainer.Thatmeansifyouwantaninstanceofaloggerinyourcontroller,youcansimplytypehinttheloggerclassinyourcontroller ’sconstructor,andwhenLaravelcreatesthecontroller,itwillresolveitoutofthecontainerandthatloggerinstancewillbeavailabletoyourcontroller.TakealookatExample11-8foranexample.
Example11-8.Injectingdependenciesintoacontroller...
classMyControllerextendsController
{
protected$logger;
publicfunction__construct(Logger$logger)
{
$this->logger=$logger;
}
publicfunctionindex()
{
//Dosomething
$this->logger->error('Somethinghappened');
}
}
Thecontainerisresponsibleforresolvingcontrollers,middleware,queuejobs,eventlisteners,andanyotherclassesthatareautomaticallygeneratedbyLaravelintheprocessofyourapplication’slifecycle—soanyofthoseclassescantypehintdependenciesintheirconstructorsandexpectthemtobeautomaticallyinjected.
MethodInjectionThereareafewplacesinyourapplicationwhereLaraveldoesn’tjustreadtheconstructorsignature:italsoreadsthemethodsignatureandwillinjectdependenciesforyouthereaswell.
Themostcommonplacetousemethodinjectionisincontrollermethods.Ifyouhaveadependencyyouonlywanttouseforasinglecontrollermethod,youcaninjectitintojustthatmethodlikeinExample11-9.
Example11-9.Injectingdependenciesintoacontrollermethod...
classMyControllerextendsController
{
//Methoddependenciescancomeafterorbeforerouteparameters
publicfunctionshow(Logger$logger,$id)
{
//Dosomething
$logger->error('Somethinghappened');
}
}
Thisisalsoavailableontheboot()methodofserviceproviders,andyoucanalsoarbitrarilycallamethodonanyclassusingthecontainer,whichwillallowformethodinjectionthere(seeExample11-10).
Example11-10.Manuallycallingaclassmethodusingthecontainer’scall()methodclassFoo
{
publicfunctionbar($parameter1){}
}
$foo=newFoo;
//Callsthe'bar'methodon$foowithafirstparameterof"value"
app()->call($foo,'bar',['parameter1'=>'value']);
FacadesandtheContainerWe’vecoveredfacadesquiteabitsofarinthebook,butwehaven’tactuallytalkedabouthowtheywork.
Laravel’sfacadesareclassesthatprovidesimpleaccesstocorepiecesofLaravel’sfunctionality.Therearetwotrademarkfeaturesoffacades:first,they’reallavailableintheglobalnamespace(\Logisanaliasto\Illuminate\Support\Facades\Log),andsecond,theyusestaticmethodstoaccessnonstaticresources.
Let’stakealookattheLogfacade,sincewe’vebeenlookingatloggingalreadyinthischapter.Inyourcontrollerorviewsyoucouldusethiscall:
Log::alert('Somethinghasgonewrong!');
Here’swhatitwouldlookliketomakethatsamecallwithoutthefacade:
$logger=app('log');
$logger->alert('Somethinghasgonewrong!');
Asyoucansee,facadestranslatestaticcalls(anymethodcallthatyoumakeonaclassitself,using::,insteadofonaninstance)tonormalmethodcallsoninstances.
IMPORTINGFACADENAMESPACESIfyou’reinanamespacedclass,you’llwanttobesuretoimportthefacadeatthetop:
...
useIlluminate\Support\Facades\Log;
classControllerextendsController
{
publicfunctionindex()
{
//...
Log::error('Somethingwentwrong!');
}
HowFacadesWorkSo,let’stakealookattheLogfacadeandseehowitactuallyworks.
First,openuptheclassIlluminate\Support\Facades\Log.You’llseesomethinglikeExample11-11.
Example11-11.TheLogfacadeclass<?php
namespaceIlluminate\Support\Facades;
classLogextendsfacade
{
protectedstaticfunctiongetFacadeAccessor()
{
return'log';
}
}
Everyfacadehasasinglemethod:getFacadeAccessor().ThisdefinesthekeythatLaravelshouldusetolookupthisfacade’sbackinginstancefromthecontainer.
Inthisinstance,wecanseethateverycalltotheLogfacadeisproxiedtobeacalltoaninstanceofthelogshortcutfromthecontainer.Ofcourse,that’snotarealclassorinterfacename,soweknowit’soneofthoseshortcutsImentionedearlier.
So,here’swhat’sreallyhappening:
Log::error('Help!');
//isthesameas...
app('log')->error('Help!');
Thereareafewwaystolookupexactlywhatclasseachfacadeaccessorpointsto,butcheckingthedocumentationistheeasiest.There’satableonthefacadesdocumentationpagethatshowsyou,foreachfacade,whichcontainerbinding(shortcut,likelog)it’sconnectedto,andwhichclassthatreturns.Itlookslikethis:
Facade Class ServiceContainerBinding
App Illuminate\Foundation\Application app
… … …
Log Illuminate\Log\Writer log
Nowthatyouhavethisreference,youcandothreethings.
First,youcanalwaysfigureoutwhatmethodsareavailableonafacade.Justfinditsbackingclassandlookatthedefinitionofthatclass,andyou’llknowthatanyofitspublicmethodsarecallableonthefacade.
Second,youcanfigureouthowtoinjectafacade’sbackingclassusingdependencyinjection.Ifyoueverwantthefunctionalityofafacadebutprefertousedependencyinjection,justtypehintthefacade’sbackingclassorgetaninstanceofitwithapp()andcallthesamemethodsyouwould’vecalledonthefacade.
Third,youcanseehowtocreateyourownfacades.CreateaclassforthefacadethatextendsIlluminate\Support\Facades\Facade,andgiveitagetFacadeAccessor()method,whichreturnsastring.Makethatstringsomethingthatcanbeusedtoresolveyourbackingclassoutofthecontainer—maybejusttheFQCNoftheclass.Finally,youhavetoregisterthefacadebyaddingittothealiasesarrayinconfig/app.php.Done!Youjustmadeyourownfacade.
ServiceProvidersWe’vecoveredthebasicsofserviceprovidersinthepreviouschapter(see“ServiceProviders”).What’smostimportantwithregardtothecontaineristhatyouremembertoregisteryourbindingsintheregister()methodofsomeserviceprovidersomewhere.
YoucanjustdumploosebindingsintoApp\Providers\AppServiceProvider,whichisabitofacatchall,butit’sgenerallybetterpracticetocreateauniqueserviceproviderforeachgroupoffunctionalityyou’redeveloping,andbinditsclassesinitsuniqueregister()method.
TestingTheabilitytouseinversionofcontrolanddependencyinjectionmakestestinginLaravelextremelyversatile.Youcanbindadifferentlogger,forinstance,dependingonwhethertheappisliveorundertesting.OryoucanchangethetransactionalemailservicefromMailguntoalocalemailloggerforeasyinspection.Bothoftheseswapsareactuallysocommonthatit’seveneasiertomakethemusingLaravel’s.envconfigurationfiles,butyoucanmakesimilarswapswithanyinterfacesorclassesyou’dlike.
Theeasiestwaytodothisistoexplicitlyre-bindclassesandinterfaceswhenyouneedthemrebound,directlyinthetest.Example11-12showshow.
Example11-12.Overridingabindingintestspublicfunctiontest_it_does_something()
{
app()->bind(Interfaces\Logger,function(){
returnnewDevNullLogger;
});
//dostuff
}
Ifyouneedcertainclassesorinterfacesreboundgloballyforyourtests(whichisnotaparticularlycommonoccurrence),youcandothiseitherinthetestclass’ssetUp()methodorinLaravel’sTestCasebasetest’ssetUp()method,asinExample11-13.
Example11-13.OverridingabindingforalltestsclassTestCaseextends\Illuminate\Foundation\Testing\TestCase
{
publicfunctionsetUp()
{
parent::setUp();
app()->bind('whatever','whateverelse');
}
}
WhenusingsomethinglikeMockery,it’scommontocreateamockorspyorstubofaclass,andthenre-bindthattothecontainerinplaceofitsreferent.
TL;DRLaravel’sservicecontainerhasmanynames,butintheenditsgoalistomakeiteasytodefinehowtoresolvecertainstringnamesasconcreteinstances.Thesestringnamesaregoingtobethefullyqualifiedclassnamesofclassesorinterfaces,orshortcutslikelog.
Eachbindingteachestheapplication,givenastringkey(e.g.,app('log')),howtoresolveaconcreteinstance.
Thecontainerissmartenoughtodorecursivedependencyresolution,soifyoutrytoresolveaninstanceofsomethingthathasconstructordependencies,thecontainerwilltrytoresolvethosedependenciesbasedontheirtypehints,thenpassthemintoyourclass,andfinallyreturnaninstance.
Thereareafewwaystobindtothecontainer,butintheendtheyalldefinewhattoreturngivenaparticularstring.
Facadesaresimpleshortcutsthatmakeiteasytousestaticcallsonaroot-namespacedclasstocallnonstaticmethodsonclassesresolvedoutofthecontainer.
Chapter12.Testing
MostdevelopersknowthattestingyourcodeisAGoodThing.We’resupposedtodoit.Welikelyhaveanideaofwhyit’sgood,andwemight’veevenreadsometutorialsabouthowit’ssupposedtowork.
Butthegapbetweenknowingwhyyoushouldtestandknowinghowtotestiswide.Thankfully,toolslikePHPUnit,Mockery,andPHPSpechaveprovidedanincrediblenumberofoptionsfortestinginPHP—butitcanstillbeprettyoverwhelmingtogeteverythingsetup.
Outofthebox,Laravelcomeswithbaked-inintegrationstoPHPUnit(unittesting),Behat(behavior-drivendevelopment),Mockery(mocking),andFaker(creatingfakedataforseedingandtesting).Italsocomeswithitsownsimpleandpowerfulsuiteofapplicationtestingtools,whichallowyouto“crawl”yoursite’sURIs,clickbuttons,submitforms,checkHTTPstatuscodes,andvalidateandassertagainstJSON.
Laravel’stestingsetupevenhasasampleapplicationtestthatcanrunsuccessfullythemomentyoucreateanewapp.Thatmeansyoudon’thavetospendanytimeconfiguringyourtestingenvironment,andthat’sonelessbarriertowritingyourtests.
TESTINGTERMS
It’shardtogetanygroupofprogrammerstoagreeonwhichtermstheyusetodefinedifferenttypesoftests.
Inthisbook,I’llusethreeprimaryterms:
UnittestsUnitteststargetsmall,relativelyisolatedunits—aclassormethod,usually.
IntegrationtestsIntegrationteststestthewayindividualunitsworktogetherandpassmessages.
ApplicationtestsOftencalledacceptanceorfunctionaltests,applicationteststesttheentirebehavioroftheapplication,usuallyatanouterboundarybyemployingsomethinglikeadocumentobjectmodel(DOM)crawler—whichisexactlywhatLaravel’sapplicationtestsuiteoffers.
TestingBasicsTestsinLaravelliveinthetestsfolder,andyoucanseetherearetwofilesintherebydefault:TestCase.php,whichisabaseclassintendedtobeextendedbyanyapplicationtests,andExampleTest.php,whichisaready-to-runapplicationtestthatwillreturngreenonanynewapp.
AsyoucanseeinExample12-1,ExampleTest“crawls”theDOMofthepagereturnedattherootpathofyourapplicationandchecksfortheword“Laravel.”Ifitfindsit,it’llpass;ifnot,it’llfail.
Example12-1.tests/ExampleTest.php<?php
useIlluminate\Foundation\Testing\WithoutMiddleware;
useIlluminate\Foundation\Testing\DatabaseMigrations;
useIlluminate\Foundation\Testing\DatabaseTransactions;
classExampleTestextendsTestCase
{
/**
*Abasicfunctionaltestexample.
*
*@returnvoid
*/
publicfunctiontestBasicExample()
{
$this->visit('/')
->see('Laravel');
}
}
Torunthistest,gotothecommandlineandrun./vendor/bin/phpunitfromtherootfolderofyourapplication.YoushouldseesomethingliketheoutputinExample12-2.
Example12-2.SampleExampleTestoutputPHPUnit5.5.2bySebastianBergmannandcontributors.
.
Time:139ms,Memory:12.00Mb
OK(1test,2assertions)
YoujustranyourfirstLaravelapplicationtest!Asyoucansee,you’resetupoutoftheboxnotonlywithafunctioningPHPUnitinstance,butalsoafull-fledgedapplicationtestingsuitecompletewithaDOMcrawler.
Incaseyou’renotfamiliarwithPHPUnit,let’schangethetesttolookfor“Applesauce,”likeinExample12-3,toseewhatanerrorlookslike.
Example12-3.tests/ExampleTest.php,editedtofailpublicfunctiontestBasicExample()
{
$this->visit('/')
->see('Applesauce');
}
Whoops!ThistimetheoutputwillprobablylookabitlikeExample12-4.
Example12-4.SamplefailingExampleTestoutputPHPUnit5.5.2bySebastianBergmannandcontributors.
F
Time:115ms,Memory:12.00Mb
Therewas1failure:
1)ExampleTest::testBasicExample
<sourceofpagehere>
FailedassertingthatthepagecontainstheHTML[Applesauce].
Pleasecheckthecontentabove.
/path-to-your-app/vendor/.../Foundation/Testing/Constraints/PageConstraint.php:90
/path-to-your-app/vendor/.../Foundation/Testing/Concerns/InteractsWithPages.php:271
/path-to-your-app/vendor/.../Foundation/Testing/Concerns/InteractsWithPages.php:287
/path-to-your-app/tests/ExampleTest.php:21
FAILURES!
Tests:1,Assertions:2,Failures:1.
Let’sbreakthisdown.First,wegetanFinsteadofa.uptop(justbelowthePHPUnitattributioninformation).Then,foreacherror,itshowsusthetestname(here,1)ExampleTest::testBasicExample),theerrormessage(Failedasserting...),andafullstacktraceofourerror,sowecanseewhatwascalled.Sincethiswasanapplicationtest,thestacktracejustshowsusthatitwascalledviatheInteractsWithPagestrait,butifthiswereaunitorapplicationtest,we’dseetheentirecallstackofthetest.
ASAMPLEJSONTEST
Asyoucanseeinthisexample,JSONtestingissimpleandclear—perhapssimplerthananyothersortofapplicationtesting:
publicfunctiontest_people_list_shows_person_after_creation()
{
$this->json('post','people',['name'=>'matt']);
$this->json('get','people');
$this->seeJson(['name'=>'matt']);
}
JustrunyourPOST,GET,DELETE,orwhateverelse,andthenassertthatthedatabase,oradditionalGETresponse,oranythingelsereturnswhatyouexpectafteryou’veperformedthegivenaction.
Let’slearnmoreaboutLaravel’stestingenvironment.
NamingTestsBydefault,Laravel’stestingsystemwillrunanyfilesinthetestsdirectorywhosenamesendwiththewordTest.That’swhytests/ExampleTest.phpwasrunbydefault.
Ifyou’renotfamiliarwithPHPUnit,youmightnotknowthatonlythemethodsinyourtestswithnamesthatstartwiththewordtestwillberun—ormethodswitha@testdocblock.SeeExample12-5forwhichmethodswillandwon’trun.
Example12-5.NamingPHPUnitmethodsclassNaming
{
publicfunctiontest_it_names_things_well()
{
//Runsas"testitnamesthingswell"
}
publicfunctiontestItNamesThingsWell()
{
//Runsas"Itnamesthingswell"
}
/**@test*/
publicfunctionit_names_things_well()
{
//Runsas"itnamesthingswell"
}
publicfunctionit_names_things_well()
{
//Doesn'trun
}
}
TheTestingEnvironmentAnytimeaLaravelapplicationisrunning,ithasacurrent“environment”namethatrepresentstheenvironmentit’srunningin.Thisnamemaybesettolocal,staging,production,oranythingelseyouwant.Youcanretrievethisbyrunningapp()->environment(),oryoucanrunsomethinglikeif(app()->environment('local'))totestwhetherthecurrentenvironmentmatchesthepassedname.
Whenyouruntests,Laravelautomaticallysetstheenvironmenttotesting.Thismeansyoucantestforif(app()->environment('testing'))toenableordisablecertainbehaviorsinthetestingenvironment.
Additionally,Laraveldoesn’tloadthenormalenvironmentvariablesfrom.envfortesting.Ifyouwanttosetanyenvironmentvariablesforyourtests,editphpunit.xmland,inthe<php>section,addanew<env>foreachenvironmentvariableyouwanttopassin—forexample,<envname="DB_CONNECTION"value="sqlite"/>.
USING.ENV.TESTTOEXCLUDETESTINGENVIRONMENTVARIABLESFROMVERSIONCONTROL
Ifyouwanttosetenvironmentvariablesforyourtest,youcandosoinphpunit.xmlasjustdescribed.Butwhatifyouhaveenvironmentvariablesforyourteststhatyouwanttobedifferentforeachtestingenvironment?Orwhatifyouwantthemtobeexcludedfromsourcecontrol?
Thankfully,handlingtheseconditionsisprettyeasy.First,createan.env.test.examplefile—justlikeLaravel’s.env.examplefile—andadd.env.testtoyour.gitignorefilejustbelow.env.Next,addthevariablesyou’dliketobeenvironment-specificto.env.test.example,justlikethey’resetin.env.example.Then,makeacopyof.env.test.exampleandnameit.env.test.
Finally,let’sloadthatfileintoourtests.Intests/TestCase.php,inthecreateApplication()method,pastethiscodejustbelowthe$app=require(...)line:
if(file_exists(dirname(__DIR__).'/.env.test')){
(new\Dotenv\Dotenv(dirname(__DIR__),'.env.test'))->load();
}
That’sit!You’renowloading.env.testtoprovideenvironmentvariablestoeverytest.
TheTestingTraitsBeforewegetintothemethodsyoucanusefortesting,you’llwanttoknowaboutthethreetestingtraitsyoucanpullintoanytestclass.
WithoutMiddlewareIfyouimportIlluminate\Foundation\Testing\WithoutMiddlewareintoyourtestclass,itwilldisableallmiddlewareforanytestinthatclass.Thismeansyouwon’thavetoworryabouttheauthenticationmiddleware,orCSRFprotection,oranythingelsethatmightbeusefulintherealapplicationbutdistractinginatest.
DatabaseMigrationsLaravelprovidestwotoolsoutoftheboxtokeepyourdatabaseintherightstatebetweentests:theDatabaseMigrationstraitandtheDatabaseTransactionstrait.
IfyouimporttheDatabaseMigrationstrait,itwillrunyourentiresetofdatabasemigrationsupbeforeeachtestanddownaftereachtest.LaravelmakesthishappenbyrunningphpartisanmigrateinthesetUp()methodbeforeeverytestrunsandphpartisanmigrate:rollbackinthetearDown()methodaftereachtestfinishes.
DatabaseTransactionsDatabaseTransactions,ontheotherhand,expectsyourdatabasetobeproperlymigratedbeforeyourtestsstart.Then,itwrapseverytestinadatabasetransaction,whichitrollsbackattheendofeachtest.Thismeansthat,attheendofeachtest,yourdatabasewillbereturnedtotheexactsamestateitwasinpriortothetest.
ApplicationTestingNowthatwe’velaidoutthebasicframeworkofLaravel’stestingenvironment,let’stakealookathowitactuallyworks.
InLaravel’sdefaultExampleTest(tests/ExampleTest.php)youcanseethat,withafewlinesofcode,wecan“crawl”toparticularURIsinourapplicationandactuallychecktheoutputforcertainwords.ButhowcanPHPUnitnavigatepagesasifitwereabrowser?
TestCaseAnyapplicationtestsshouldextendtheTestCaseclass(tests/TestCase.php)that’sincludedwithLaravelbydefault.Yourapplication’sTestCaseclasswillextendtheabstractIlluminate\Foundation\Testing\TestCaseclass,whichbringsinquiteafewgoodies.
ThefirstthingthetwoTestCaseclasses(yoursanditsabstractparent)doishandlebootingtheIlluminateapplicationinstanceforyou,soyouhaveafullybootstrappedapplicationavailable.Theyalso“refresh”theapplicationbetweeneachtest,whichmeansthey’renotentirelyre-creatingtheapplicationbetweentests,butrathermakingsureyoudon’thaveanydatalingering.
TheparentTestCasealsosetsupasystemofhooksthatallowcallbackstoberunbeforeandaftertheapplicationiscreated,andimportsaseriesoftraitsthatprovideyouwithmethodsforinteractingwitheveryaspectofyourapplication.ThesetraitsincludeInteractsWithContainer,MakesHttpRequests,InteractsWithConsole,andmore,andtheybringinabroadvarietyofcustomassertionsandtestingmethods.
Asaresult,yourapplicationtestshaveaccesstoafullybootstrappedapplicationinstance,application-test-mindedcustomassertions,andaDOMcrawler,withaseriesofsimpleandpowerfulwrappersaroundeachtomakethemeasytouse.
Thatmeansyoucanwrite$this->visit('/')->see('Laravel')andknowthatyourapplicationisactuallybehavingasifitwererespondingtoanormalHTTPrequest,andthattheresponseisbeingpassedtoaDOMcrawlerthatischeckingforthattextforyou.It’sprettypowerfulstuff,consideringhowlittleworkyouhadtodotogetitrunning.
So,let’slookatsomebasicmethodsthisopensuptoyou.
DIFFERENTTRAITSTRUCTUREINLARAVEL5.1InLaravel5.1,thestructureoftestingtraitsandhowthetestingframeworkisorganizedisverydifferentfromwhatI’vedescribedhere;however,thefunctionalityisstillthesame.
“Visiting”RoutesThemostcomplexofLaravel’sapplicationtestingfunctionalityisalsothesimplest—andmostpowerful—touse.Usingthesemethods,yourtestscaninteractwith(“visit”)pagesinyourapplicationlikeneverbefore:
$this->visit($uri)
VisitingarouteisatthecoreofLaravel’sapplicationtesting.Whenyoucall$this->visit('dashboard'),you’remimickingtheactiontheframeworktakeswhenawebrequestcomesinforthatsameroute.Theapplicationwillcreatearequestobjectforthatrequest,handleitlikenormal,andstoretheresponseobject(aninstanceofIlluminate\Http\Response)in$this->response.Thisisthesameresponseobjectthatwouldnormallybereturnedanddisplayedtothebrowser,butit’sjustcachedforLaravel’sapplicationtestingassertionstocheckagainst(orforyourcode,ifyouwanttointeractwiththeresponse).Onitsown,visitingdoesn’tdomuch,butnowthatyouhavearesponsecachedin$this->response,youcanwriteassertionsagainstit.
WHATMAKESVISIT( ) DIFFERENTFROMTHEOTHER VISITINGMETHODS
We’reabouttocovercall(),andget(),andmanyothermethodsrelatedtovisitingroutes.Butthey’remuchsimplerthanvisit(),andit’sworthseeingjustwhatmakesvisit()different.
Here’sashorteneddefinitionofthevisit()method:
publicfunctionvisit($uri)
{
$uri=$this->prepareUrlForRequest($uri);
$this->call($method,$uri,$parameters,$cookies,$files);
$this->clearInputs()->followRedirects()->assertPageLoaded($uri);
$this->currentUri=$this->app->make('request')->fullUrl();
$this->crawler=newCrawler(
$this->response->getContent(),
$this->currentUri
);
return$this;
}
Iknowthisisalottotakein,sojustsufficeittosayvisit()isdoingalot.Whenyouwanttocheckthatapageloads,whenyouwanttocrawlapage,whenyouwanttodoallthisnearlymagicalapplicationtesting,usevisit().
Ifyoujustwanttogetaresponseandnothingelse,orifyou’reusingmoretraditionalcheckstoPOSTtoapageandassertcertainbehaviorhappensorsomethingelse,you’llbefineusingthesimplermethodslikecall().
$this->call($method,$uri,$params=[],$cookies=[],$files=[],$server=[],$content=null)
IfyouneedtomakecallsagainsttheserverwithoutworryingaboutcrawlingthereturnedDOM—forexample,ifyouwanttoassertthatagivenPOSThascertaineffects—there’samethodforthat.visit()isactuallybasedoncall(),butyoucanalsousecall()directly.Asyoucanseefromthemethoddefinition,wehavealotofoptionsavailabletouswhenweusecall()—theHTTPmethod,theURI,parameters,cookies,andfiles,allpretendingtobesentalongwithourcall.Justlikewhenyouusevisit(),theserequestswillmakearequestandstoretheresponseon$this->response,buttheywon’tenableanyDOM-crawling-basedassertionslikesee().
$this->get($uri,$headers=[]),->post($uri,$data=[],$headers=[]),->put($uri,$data=[],$headers=[]),->patch(),and->delete()
Theseareaseriesofconveniencehelpersthatwrapcall();they’realljustshortcutstopassingaparticularstringtothefirstparameterofcall(),theHTTPmethod.Otherwise,youcanusethemexactlythesameasyouwouldcall().
$this->json($method,$uri,$data=[],$headers=[])Justlikeget(),post(),andtheothermethodsjustmentionedjson()isawrapperaroundcall().ItconvertsthepasseddatatoJSONandaddsJSONrequestheaders,andthenpassesitallintocall().json()isexceptionallyuseful,unsurprisingly,fortestingJSONAPIs.Becauseyoucanevendefineyourheadersanddata,youcanusethismethodtofullyinteractwithyourRESTAPIsinyourtests,likewesawin“ASampleJSONTest”.
$this->followRedirects()
There’sactuallyanotherthingthatvisit()doesthatcall()doesn’t:ittellsLaraveltofollowanyredirectsusingfollowRedirects(),andthenchecksthattheeventuallandingpageloadedusingassertPageLoaded().WithoutfollowRedirects(),theresponseyou’llgetaftercallingaredirectedpagewilljustbethecontentsoftheredirect,notthepagethatyouwerebeingredirectedto.
CustomApplicationTestingAssertionsSo,whatarethenewapplicationtestingassertionswe’vegained?Therearequiteafew.Let’sstartsimpleandmoveup:
$this->assertPageLoaded()
assertPageLoaded()checksthatyougotanHTTPstatuscodeof200whenloadingthepage.
$this->see()and->dontSee()Likewesawearlierinthischapter,see()takesastringandusesaregularexpressiontocheckthatthatstringispresentsomewhereonthepagethat’srendered.dontSee()isitsinverse.
$this->seeLink()and->dontSeeLink()seeLink()takestwoparameters:first,thelinktexttofind,andsecond,optionally,theURL.dontSeeLink()isitsinverse.
$this->seeHeader()
seeHeader()takestwoparameters:first,thenameoftheheader,andsecond,optionally,thevalueoftheheader.
$this->seeCookie()
seeCookie()takestwoparameters:first,thenameofthecookie,andsecond,optionally,thevalueofthecookie.
$this->seeInField()and->dontSeeInField()seeInField()takestwoparameters:first,thenameorIDoftheinputortextareatolookat,andsecond,thevaluetolookfor.dontSeeInField()isitsinverse.
$this->seeIsChecked()and->dontSeeIsChecked()seeIsChecked()takesoneparameter,thenameorIDofthecheckboxinputtoinspect.dontSeeIsChecked()isitsinverse.
$this->seeIsSelected()and->dontSeeIsSelected()seeIsSelected()takestwoparameters:first,thenameorIDoftheselectboxtoinspect,andsecond,thevaluetocheckwhetheritissetto.dontSeeIsSelected()isitsinverse.
$this->seePageIs()
seePageIs()assertsthatthecurrentloadedpageURIisthesameastheparameteryoupasstoit.
$this->seeInDatabase()and->dontSeeInDatabase()Tocheckforrecordsinthedatabasetable,passinthetablenameasthefirstparameterofseeInDatabase()andthedatayou’relookingforasthesecond:
publicfunctiontest_database_has_user_after_registration()
{
$this
->visit('register')
->fillForm([
'email'=>'[email protected]'
])
->submitForm();
$this->seeInDatabase('emails',['email'=>'[email protected]']);
}
Asyoucansee,thesecond“data”parameterofseeInDatabase()isstructuredlikeaSQLWHEREstatement—youpassakeyandavalue(ormultiplekeysandvalues),andthenLaravellooksforanyrecordsinthespecifieddatabasetablethatmatchyourkey(s)andvalue(s).Asalways,dontSeeInDatabase()istheinverse.
JSONandNon-visit()ApplicationTestingAssertionsTheremainingapplicationassertionsaretiedlesscloselytothevisit()methodologyandalittlemorecloselytotheimplementationdetailsofyourapplication.QuiteafewofthesearealsooftenusedfortestingJSONAPIs:
$this->seeJson(),->dontSeeJson(),->seeJsonEquals()seeJson()withnoparameterscheckstomakesurethatthecontentoftheresponsewasvalidJSON.Itsoptionalparameterrepresentsthedatathatyou’recheckingfor.Forinstance,inthefollowingexamplewereceivearesponse,andwe’recheckingboththatitisvalidJSONandthatitcontainsakey/valuepairofusername/mattstauffersomewhereinit:
publicfunctiontest_api_returns_certain_json()
{
$this->json('get','users');
$this->seeJson(['username'=>'mattstauffer']);
}
Asalways,seeJson()hasaninverse,dontSeeJson().dontSeeJson()stillexpectsvalidJSON,butitexpectstonotseeanythingpassedinasdata.Finally,ifyouwanttocheckthattheJSONmapsexactlytoyourdata,youcantryseeJsonEquals(),whichcomparesadataarraytotheJSONresponseandthrowsanexceptioniftheydon’tmatchexactly.
$this->assertResponseOK()and->assertResponseStatus($status)Afteranycall()orvisit(),onevaluableassertionisjustthatthepageloadedwiththeHTTPstatusyouexpected.assertResponseOK()assertsthatthepagereturneda200HTTPstatuscode,butyoucanalsopassaspecificstatusthatyouexpect:
publicfunctiontest_pages_load_the_way_we_want()
{
$this->get('people');
$this->assertResponseOK();
$this->call('post','owners');
$this->assertResponseStatus(405);//Methodnotallowed
}
Youcouldevenchecktheauthorizationsettingsforaparticularroutebyassertingthatitgivesa401(Unauthorized)statuscode,thenauthenticateandassertitgivesa200statuscode.
$this->assertViewHas($key,$value=null),->assertViewHasAll(array$bindings),->assertViewMissing($key)
Sometimestheonlyoptionyouhaveistoassertthatyouseeaparticularphraseonapageusingsee(),butwhat’smorelikelyisthatyou’rereallycheckingthatthecorrectdatawaspassedtoyourview.Thankfully,youcancheckthatdirectlywiththesemethods.assertViewHas()checksthatdatawithaparticularkeywassenttothemostrecentlyretrievedview,andifyoupasstheassertionasecondparameter,itwillassertthatthatdatawasequaltoit:
//Route
Route::get('test',function(){
returnview('test')->with('foo','bar');
});
//Test
publicfunctiontest_view_gets_data()
{
$this->get('test');
$this->assertViewHas('foo');//true
$this->assertViewHas('foo','bar');//true
$this->assertViewHas('foo','baz');//false
}
YoucanalsocheckformultipleviewvariablesatonceusingassertViewHasAll(),whichexpectsanarrayofkey/valuepairs:
//Route
Route::get('test',function(){
returnview('test')
->with('foo','bar')
->with('baz','qux');
});
//Test
publicfunctiontest_view_gets_data()
{
$this->get('test');
$this->assertViewHasAll([
'foo'=>'bar',
'baz'=>'qux'
]);//true
}
Youcanensurethattheviewhasn’tbeenpassedaparticularkeybypassingthatkeytoassertViewMissing().InLaravel5.3,asshowninthefollowingexample,youcanpassaclosuretothesecondparameterofassertViewHas().Thisgivesyoutheopportunitytoperformmuchmorenuancedchecksofthedatayourviewisprovided:
publicfunctiontest_events_are_owned_by_user_1()
{
$this->get('events');
$this->assertViewHas('events',function($events){
return$events->reject(function($event){
return$event->user_id===1;
})->isEmpty();
});
}
$this->assertRedirectedTo(),->assertRedirectedToRoute(),->assertRedirectedToAction()
Ifyouwanttoensurethattheusernotonlyendsupataparticularpagebutwassentthereasaredirect,thesemethodsprovidethatfunctionality.YoucancheckbyURL(to()),routename(toRoute()),orcontrollerandmethod(toAction()):
//Route
Route::get('redirector',function(){
returnredirect('/');
});
Route::get('/','HomeController@index')->name('home');
//Test
publicfunctiontest_redirector_works()
{
$this->get('redirector');
$this->assertRedirectedTo('/');
$this->assertRedirectedToRoute('home');
$this->assertRedirectedToAction('HomeController@index');
}
$this->assertSessionHas($key,$value=''),->assertSessionHasAll($bindings),->assertSessionHasErrors($bindings=[],$format=null),->assertHasOldInput()
Thesemethodsmakeiteasytocheckforspecificvaluesinthesession.assertSessionHas()andassertSessionHasAll()areshapedjustlikeassertViewHas()andassertViewHasAll().Whenpassedjustoneparameter,assertSessionHas()assertsthatthereisasessionvaluewiththatkey;ifyoupassittwoparameters,itassertsthatthevalueofthatsessionkeyisequaltothesessionparameter.assertSessionHasAll()takesanarrayofkey/valuepairsandassertsthateachkeyexistsinthesessionandissettoitscorrespondingvalue:
publicfunctiontest_session_has_stuff()
{
Session::put('foo','bar');
Session::put('baz','qux');
$this->assertSessionHas('foo');
$this->assertSessionHas('foo','bar');
$this->assertSessionHasAll([
'foo'=>'bar',
'baz'=>'qux'
]);
}
assertSessionHasErrors()withnoparametersassertsthatthere’satleastoneerrorsetinLaravel’sspecialerrorssessioncontainer.Itsfirstparametercanbeanarrayofkey/valuepairsthatdefinetheerrorsthatshouldbesetanditssecondparametercanbethestringformatthatthecheckederrorsshouldbeformattedagainst,asdemonstratedhere:
publicfunctiontest_posting_empty_errors_out()
{
//assumingthe"/form"routerequiresanemailfield,andwe're
//postinganemptyformtoittotriggertheerror
$this->post('form',[]);
$this->assertSessionHasErrors();
$this->assertSessionHasErrors(['email'=>'Theemailfieldisrequired.']);
$this->assertSessionHasErrors(
['email'=>'<p>Theemailfieldisrequired.</p>'],
'<p>:message</p>'
);
}
Finally,assertHasOldInput()assertsthatsomeoldinputhasbeensavedfromaformthatwassubmitted,likelyusingredirect()->withOldInput().
ClickingandFormsLet’smoveintosomemagicalyetterrifyingpowers:navigatingthroughforms,clickingandfillingandunchecking,andevenattachingfiles.Laravelprovidesthefollowingmethodsforworkingwithforms:
$this->click($name)
Givenalinkwiththeprovided$nameaseitherthebodyofthelinkoritsnameorID,click()grabstheURIfromthatlinkandvisitsit.
$this->type($text,$element)
Givenaninputonthepagewiththeprovided$elementasthenameorID,type()“types”theprovidedtextintoit.
MANIPULATINGFORMS
Withthistalkofclickinglinksandtypingintoformfields,itmayseemlikeLaravelisrunningsomesortofJavaScript-basedapplicationtestwhereit’sactuallydrivingabrowserinteractingwiththepage.Butit’snot,really.
It’sstoringupthis“input”you’recreating,andifatanypointyou“submit”theform,it’llgathertogetheryourinputandpostittothetargetoftheform.Intheoryit’sverydifferentfromtheuser’sexperience,butinpracticeit’sabeautifullyeloquentsyntaxforwritingteststhatmimicformsubmissions.
$this->check($element)
Givenacheckboxonthepagewiththeprovided$elementasthenameorID,check()“checks”it.
$this->uncheck($element)
Givenacheckboxonthepagewiththeprovided$elementasthenameorID,uncheck()“unchecks”it.
$this->select($option,$element)
Givenaselectboxonthepagewiththeprovided$elementasthenameorID,select()setsitsvalueto$option.
$this->attach($filePath,$element)
Givenafileuploadinputonthepagewiththeprovided$elementasthenameorID,attach()attachesafilefromthegivenlocalfilepathtoit,markedtouploadwhentheformissubmitted.
$this->press($buttonText)
Givenabuttononthepagewiththeprovidedtext,press()submitstheformthatbuttonisapartof.
$this->submitForm($buttonText,$inputs=[],$uploads=[])
Givenabuttononthepagewiththeprovidedtext,submitForm()submitstheformthatbuttonisapartof.Youcanalsooptionallysetoroverridealloftheinputsandfileuploadsusingthesecondandthirdparameters.
$this->fillForm($buttonText,$inputs=[])
Givenabuttononthepagewiththeprovidedtext,fillForm()findstheformthatbuttonisapartofandsetsallthevaluestobetheprovidedvalues.
$this->clearInputs()
clearInputs()wipesanyinputsoruploadsthathavebeenpreviouslyset.
JobsandEventsWe’llcoverthesejob-andevent-relatedtestsinmoredepthinChapter16,butlet’stakeaquicklookathowtheywork:
$this->expectsEvents($eventClassName)
Ifyouwanttoassertthataparticularclassofeventwasfiredduringyourtest,youcanpasstheclassnametoexpectsEvents():
publicfunctiontest_usersubscribed_event_fires_when_subscribing()
{
$this->expectsEvents(App\Events\UserSubscribed::class);
$this->visit('subscribe')->type('[email protected]','email')->press('Subscribe');
}
$this->withoutEvents()
withoutEvents()isnotactuallyanassertion;rather,itdisablesLaravel’seventhandlingsystemsothat,duringthistest,youdon’thavetoworryabouttheeffectsofanyofyoureventstakingplace—forexample,sendinganyemailsorwritinganylogs.
$this->expectsJobs()
Ifyouwanttoassertthataparticularclassofjobwasfiredduringyourtest,youcanpassthatclassnametoexpectsJobs():
publicfunctiontest_number_of_subscriptions_crunches_reports()
{
$this->expectsJobs(App\Jobs\CrunchReports::class);
$this->visit('subscribe')->type('[email protected]','email')->press('Subscribe');
}
AuthenticationandSessionsLaravelmakesitsimpletosetupatestenvironmentforyourtests,evenmakingiteasytocontrolthesessionandauthenticateasagivenuser:
$this->session(['key'=>'value'])
session()startsthesessionandsavesanykey/valuepairsofdataintheprovidedarraytothesession.Youcanrunthismultipletimesduringatesttoadddifferentpiecesofsessiondata,ifyou’dlike.
$this->flushSession()
flushSession()wipesallofthedatainthecurrentsession.
$this->be($authenticatable)
be()takesanyobjectthatfulfillstheIlluminate\Contracts\Auth\Authenticatableinterface(including,ofcourse,thebaseApp\Userclass)andauthenticateseverypagerequestorinteractioninthetestasthatuser.Thismeansyoucanwritetestslikethis:
publicfunctiontest_members_cant_see_admin_dashboard()
{
$member=factory(\App\User::class,'member')->create();
$this->be($member);
$this->get('admin-dashboard');
$this->assertResponseStatus(403);
}
publicfunctiontest_admins_can_see_admin_dashboard()
{
$admin=factory(\App\User::class,'admin')->create();
$this->be($admin);
$this->get('admin-dashboard');
$this->assertResponseOK();
}
ArtisanandSeedAlmostdone.Therearetwomoretestmethodsyoumightwanttotakealookat:
$this->artisan($command,$parameters=[])
IfyouwanttouseanArtisancommandinatest,artisan()makesiteasy.Justpassthecommandnameasthefirstparameterand,optionally,passanyparametersasanarrayasthesecond.Doingsowillsavetheresponsecodeto$this->codeincaseyou’dliketoassertagainstit,butitwillalsoreturnit.So,thisfunctionsthesameasArtisan::call()withtheadditionofsavingtheresponseto$this->code:
publicfunctiontest_returns_certain_code()
{
$this->artisan('do:thing',['--flagOfSomeSort'=>true]);
$this->assertEquals(0,$this->code);//0means"noerrorswerereturned"
}
$this->seed($seederClassName='DatabaseSeeder')
Ifyouwanttoseedyourdatabase,seed()willdothatforyou;andifyoupassanargument,youcanchoosetoonlyrunasingleseeder.seed()providesthesamefunctionalityasrunning$this->artisan('db:seed').
MODELFACTORIESModelfactoriesareamazingtoolsthatmakeiteasytoseedrandomized,well-structureddatabasedatafortesting(orotherpurposes).
We’vealreadycoveredthemindepth,socheckout“ModelFactories”tolearnmore.
MockingMocks(andtheirbrethren,spiesandstubsanddummiesandfakesandwhoknowswhatelse)arecommontoolsintesting.Iwon’tgointogreatdetailhere,butit’sunlikelyyoucanthoroughlytestanapplicationofanysizewithoutmockingatleastonethingoranother.
Essentially,mocksandothersimilartoolsmakeitpossibletocreateanobjectthatinsomewaymimicsarealclass,butfortestingpurposesisn’ttherealclass.Sometimesthisisdonebecausetherealclassistoodifficulttoinstantiatejusttoinjectitintoatest,ormaybetherealclasscommunicateswithanexternalservice.
Asyoucanprobablytellfromtheseexamples,Laravelencouragesworkingwiththerealapplicationasmuchaspossible—whichmeansavoidingtoogreatofadependenceonmocks.Buttheyhavetheirplace,whichiswhyLaravelincludesMockery,amockinglibrary,outofthebox.
MockeryMockeryallowsyoutoquicklyandeasilycreatemocksfromanyPHPclassinyourapplication.ImagineyouhaveaclassthatdependsonaSlackclient,butyoudon’twantthecallstoactuallygoouttoSlack.MockerymakesitsimpletocreateafakeSlackclienttouseinyourtests,likeyoucanseeinExample12-6.
Example12-6.UsingMockeryinLaravel//app/SlackClient.php
classSlackClient
{
...
publicfunctionsend($message,$channel)
{
//ActuallysendsamessagetoSlack
}
}
//app/Notifier.php
classNotifier
{
private$slack;
publicfunction__construct(SlackClient$slack)
{
$this->slack=$slack;
}
publicfunctionnotifyAdmins($message)
{
$this->slack->send($message,'admins');
}
}
//tests/NotifierTest.php
publicfunctiontest_notifier_notifies_admins()
{
$slackMock=Mockery::mock(SlackClient::class)->shouldIgnoreMissing();
$notifier=newNotifier($slackMock);
$notifier->notifyAdmins('Testmessage');
}
Therearealotofmovingpieceshere,butlet’sbreakitdown.WehaveaclassnamedNotifierthatwe’retesting.IthasadependencynamedSlackClientthatdoessomethingthatwedon’twantittodowhenwe’rerunningourtests:itsendsactualSlacknotifications.Sowe’regoingtomockit.
WeuseMockerytogetamockofourSlackClientclass.Ifwedon’tcareaboutwhathappenstothatclass—ifitshouldsimplyexisttokeepourtestsfromthrowingerrors—wecanjustuseshouldIgnoreMissing():
$slackMock=Mockery::mock(SlackClient::class)-shouldIgnoreMissing();
NomatterwhatNotifiercallson$slackMock,it’lljustacceptitandreturnnull.
Buttakealookattest_notifier_notifies_admins().Atthispoint,itdoesn’tactuallytestanything.
WecouldjustkeepshouldIgnoreMissing()andthenwritesomeassertionsbelowit.That’susuallywhatwedowithshouldIgnoreMissing(),whichmakesthisobjecta“fake”ora“stub.”
Butwhatifwewanttoactuallyassertthatacallwasmadetothesend()methodofSlackClient?That’swhenwedropshouldIgnoreMissing()andreachfortheshould*methods(Example12-7).
Example12-7.UsingtheshouldReceivemethodonaMockerymockpublicfunctiontest_notifier_notifies_admins()
{
$slackMock=Mockery::mock(SlackClient::class);
$slackMock->shouldReceive('send')->once();
$notifier=newNotifier($slackMock);
$notifier->notifyAdmins('Testmessage');
}
shouldReceive('send')->once()isthesameassaying“assertthat$slackMockwillhaveitssend()methodcalledonceandonlyonce.”So,we’renowassertingthatNotifier,whenwecallnotifyAdmins(),mustmakeasinglecalltothesendmethodonSlackClient.
WecouldalsousesomethinglikeshouldReceive('send')->times(3)orshouldReceive('send')->never().
WhatifwewantedtousetheIoCcontainertoresolveourinstanceoftheNotifier?ThismightbeusefulifNotifierhadseveralotherdependenciesthatwedidn’tneedtomock.
Wecandothat!Wejustusetheinstance()methodonthecontainer,asinExample12-8,totellLaraveltoprovideaninstanceofourmocktoanyclassesthatrequestit(which,inthisexample,willbeNotifier).
Example12-8.BindingaMockeryinstancetothecontainerpublicfunctiontest_notifier_notifies_admins()
{
$slackMock=Mockery::mock(SlackClient::class);
$slackMock->shouldReceive('send')->once();
app()->instance(SlackClient::class,$slackMock);
$notifier=app(Notifier::class);
$notifier->notifyAdmins('Testmessage');
}
There’salotmoreyoucandowithMockery:youcanusespies,andpartialspies,andmuchmore.GoingdeeperintohowtouseMockeryisoutofthescopeofthisbook,butIencourageyoutolearnmoreaboutthelibraryandhowitworks.
MockingFacadesLet’ssayyouhaveacontrollermethodthatcallsafacade.Now,youwanttotestthatcontrollermethod,andassertthatthatfacadecallshouldbemade.Howdoyoudoit?Thankfully,it’ssimple:treatthefacadelikeaninstanceofMockeryinyourtest.Example12-9showshowthisworks.
Example12-9.Mockingafacade//PeopleController
publicfunctionindex()
{
returnCache::remember('people',function(){
returnPerson::all();
});
}
//PeopleTest
publicfunctiontest_all_people_route_should_be_cached()
{
$person=factory(Person::class)->make();
Cache::shouldReceive('remember')
->once()
->andReturn(collect([$person]));
$this->visit('people')->seeJson(['name'=>$person->name]);
}
Asyoucansee,youcanusemethodslikeshouldReceive()onthefacades,justlikeyoudoonaMockeryobject.
AsofLaravel5.3,youcanalsouseyourfacadesasspies,whichmeansyoucansetyourassertionsattheendanduseshouldHaveReceived()insteadofshouldReceive().Example12-10illustratesthis.
Example12-10.Facadespiespublicfunctiontest_queue_job_should_be_pushed_after_regisration()
{
Cache::spy();
$this->post('register',['email'=>'[email protected]']);
Cache::shouldHaveReceived('push')
->with(SendWelcomeEmail::class,['email'=>'[email protected]']);
}
TL;DRLaravelcanworkwithanymodernPHPtestingframework,butitbringsinalotofframework-specificpowerifyouusePHPUnitandifyourtestsextendLaravel’sTestCase.Laravel’sapplicationtestingframeworkmakesitsimpletosendfakerequeststhroughyourapplicationandinspecttheresults,even“typing”ininputsand“clicking”buttonsbeforesubmittingaform.
TheTestCaseclassbringsinagroupofmethodsthatmakeiteasytocustomizehowyourtestsinteractwithyourdatabase,disabletheeffectsofevents,andmakeassertionsagainstframework-levelstructureslikejobsandfacades.
LaravelbringsinMockeryincaseyouneedmocks,stubs,spies,dummies,oranythingelse,butthetestingphilosophyofLaravelistouserealcollaboratorsasmuchaspossible.Don’tfakeitunlessyouhaveto.
Chapter13.WritingAPIs
OneofthemostcommontasksLaraveldevelopersaregivenistocreateanAPI,usuallyJSONandRESTorREST-like,thatallowsthirdpartiestointeractwiththeLaravelapplication’sdata.
LaravelmakesitincrediblyeasytoworkwithJSON,anditsresourcecontrollersarealreadystructuredaroundRESTverbsandpatterns.Inthischapterwe’lllearnaboutsomebasicAPI-writingconcepts,thetoolsLaravelprovidesforwritingAPIs,andsomeexternaltoolsandorganizationalsystemsyou’llwanttoconsiderwhenwritingyourfirstLaravelAPI.
TheBasicsofREST-LikeJSONAPIsRepresentationalStateTransfer(REST)isanarchitecturalstyleforbuildingAPIs.Technically,RESTisabroaddefinitionthatcouldapplytoalmosttheentiretyoftheInternet,sodon’tletyourselfgetoverwhelmedbythedefinitionorcaughtinanargumentwithapedant.WhenwetalkaboutRESTfulorREST-likeAPIsintheLaravelworld,we’regenerallytalkingaboutAPIswithafewcommoncharacteristics:
Structuredaround“resources”thatcanbeuniquelyrepresentedbyURIs,like/catsforallcats,/cats/15forasinglecatwiththeIDof15,etc.
InteractionswithresourcesprimarilytakeplaceusingHTTPverbs(GET/cats/15versusDELETE/cats/15)
Stateless,meaningthere’snopersistentsessionauthenticationbetweenrequests;eachrequestmustuniquelyauthenticateitself
Cacheableandconsistent,meaningeachrequest(exceptforafewauthenticated-user-specificrequests)shouldreturnthesameresultregardlessofwhotherequesteris
ReturnJSON
ThemostcommonAPIpatternistohaveauniqueURLstructureforeachofyourEloquentmodelsthat’sexposedasanAPIresource,andallowforuserstointeractwiththatresourcewithspecificverbsandgetJSONback.Example13-1showsafewpossibleexamples.
Example13-1.CommonRESTAPIendpointstructuresGET/api/cats
[
{
id:1,
name:'Fluffy'
},
{
id:2,
name:'Killer'
}
]
GET/api/cats/2
{
id:2,
name:'Killer'
}
DELETE/api/cats/2
deletescat
POST/api/catswithbody:
{
name:'MrBigglesworth'
}
(createsnewcat)
PATCH/api/cats/3withbody:
{
name:'Mr.Bigglesworth'
}
(updatescat)
YoucanseethebasicsetofinteractionswearelikelytohavewithourAPIs.Let’sdigintohowtomakethemhappenwithLaravel.
ControllerOrganizationandJSONReturnsLaravel’sresourcecontrollersarestructuredverysimilarlytoaRESTfulAPIcontroller,solet’sgetstartedthere.Firstwe’llcreateanewcontrollerforourresource,whichwe’llrouteat/api/dogs:
phpartisanmake:controllerApi/\DogsController--resource
Remember,the--resourceflaggeneratesaresourcecontrollerinsteadofaplaincontroller.
ESCAPINGSLASHESINARTISANCOMMANDSNotethatinordertoputtheDogsControllerintheApinamespace,wehadtoescapethe\namespacebackslashwithaforwardslash.
Example13-2showswhatthatcontrollerwilllooklike.
Example13-2.Ageneratedresourcecontroller<?php
namespaceApp\Http\Controllers\Api;
useIlluminate\Http\Request;
useApp\Http\Requests;
useApp\Http\Controllers\Controller;
classDogsControllerextendsController
{
/**
*Displayalistingoftheresource.
*
*@return\Illuminate\Http\Response
*/
publicfunctionindex(){}
/**
*Showtheformforcreatinganewresource.
*
*@return\Illuminate\Http\Response
*/
publicfunctioncreate(){}
/**
*Storeanewlycreatedresourceinstorage.
*
*@param\Illuminate\Http\Request$request
*@return\Illuminate\Http\Response
*/
publicfunctionstore(Request$request){}
/**
*Displaythespecifiedresource.
*
*@paramint$id
*@return\Illuminate\Http\Response
*/
publicfunctionshow($id){}
/**
*Showtheformforeditingthespecifiedresource.
*
*@paramint$id
*@return\Illuminate\Http\Response
*/
publicfunctionedit($id){}
/**
*Updatethespecifiedresourceinstorage.
*
*@param\Illuminate\Http\Request$request
*@paramint$id
*@return\Illuminate\Http\Response
*/
publicfunctionupdate(Request$request,$id){}
/**
*Removethespecifiedresourcefromstorage.
*
*@paramint$id
*@return\Illuminate\Http\Response
*/
publicfunctiondestroy($id){}
}
Thedocblocksprettymuchtellthestory.index()listsallofthedogs,show()listsasingledog,create()showsthecreateview,store()storesadog,edit()showstheeditview,update()updatesadog,anddestroy()removesadog.
SincethisisanAPI,wecandeletecreate()andedit()offthebat;we’renotshowingviewshere.
Let’squicklymakeamodelandamigrationsowecanworkwithit:
phpartisanmake:modelDog--migration
phpartisanmigrate
Great!Nowwecanfilloutourcontrollermethods.
WecantakeadvantageofagreatfeatureofEloquenthere:ifyouechoanEloquentresultscollection,it’llautomaticallyconvertitselftoJSON(usingthe__toString()magicmethod,ifyou’recurious).Thatmeansifyoureturnacollectionofresultsfromaroute,you’llineffectbereturningJSON.
So,asExample13-3demonstrates,thiswillbesomeofthesimplestcodeyou’lleverwrite.
Example13-3.AsampleresourcecontrollerfortheDogentity...
classDogsControllerextendsController
{
publicfunctionindex()
{
returnDog::all();
}
publicfunctionstore(Request$request)
{
Dog::create($request->all());
}
publicfunctionshow($id)
{
returnDog::findOrFail($id);
}
publicfunctionupdate(Request$request,$id)
{
$dog=Dog::findOrFail($id);
$dog->update($request->all());
}
publicfunctiondestroy($id)
{
$dog=Dog::findOrFail($id);
$dog->delete();
}
}
Example13-4showshowwecanlinkthisupinourroutesfile.
Example13-4.Bindingtheroutesforaresourcecontroller//Routes.php
Route::group(['prefix'=>'api','namespace'=>'Api',function(){
Route::resource('dogs','DogsController');
}]);
Thereyouhaveit!YourfirstRESTfulAPIinLaravel.
Ofcourse,we’llneedmuchmorenuance:pagination,sorting,authentication,moredefinedresponseheaders.Butthisisthefoundationofeverythingelse.
ReadingandSendingHeadersRESTAPIsoftenread,andsend,non-contentinformationusingheaders.Forexample,anyrequesttoGitHub’sAPIwillreturnheadersdetailingthecurrentuser ’sratelimitingstatus:
X-RateLimit-Limit:5000
X-RateLimit-Remaining:4987
X-RateLimit-Reset:1350085394
X-*HEADERSYoumightbewonderingwhytheGitHubratelimitingheadersareprefixedwithX-,especiallyifyouseetheminthecontextofotherheadersreturnedwiththesamerequest:
HTTP/1.1200OK
Server:nginx
Date:Fri,12Oct201223:33:14GMT
Content-Type:application/json;charset=utf-8
Connection:keep-alive
Status:200OK
ETag:"a00049ba79152d03380c34652f2cb612"
X-GitHub-Media-Type:github.v3
X-RateLimit-Limit:5000
X-RateLimit-Remaining:4987
X-RateLimit-Reset:1350085394
Content-Length:5
Cache-Control:max-age=0,private,must-revalidate
X-Content-Type-Options:nosniff
AnyheaderwhosenamestartswithX-isaheaderthat’snotintheHTTPspec.Itmightbeentirelymadeup(e.g.,X-How-Much-Matt-Loves-This-Page),orpartofacommonconventionthathasn’tmadeitintothespecyet(e.g.,X-Requested-With).
Similarly,manyAPIsallowdeveloperstocustomizetheirrequestsusingrequestheaders.Forexample,GitHub’sAPImakesiteasytodefinewhichversionoftheAPIyou’dliketousewiththeAcceptheader:
Accept:application/vnd.github.v3+json
Ifyouweretochangev3tov2,GitHubwouldpassyourrequesttoversion2ofitsAPIinstead.
Let’slearnquicklyhowtodobothinLaravel.
SendingResponseHeadersinLaravelWealreadycoveredthistopicquiteabitinChapter10,buthere’saquickrefresher.Onceyouhavearesponseobject,youcanaddaheaderusingheader($headerName,$headerValue),asseeninExample13-5.
Example13-5.AddingaresponseheaderinLaravelRoute::get('dogs',function(){
returnresponse(Dog::all())
->header('X-Greatness-Index',9);
});
Niceandeasy.
ReadingRequestHeadersinLaravelIfwehaveanincomingrequest,it’salsosimpletoreadanygivenheader.Example13-6illustratesthis.
Example13-6.ReadingarequestheaderinLaravelRoute::get('dogs',function(Request$request){
echo$request->header('Accept');
});
NowthatyoucanreadincomingrequestheadersandsetheadersonyourAPIresponses,let’stakealookathowyoumightwanttocustomizeyourAPI.
EloquentPaginationPaginationisoneofthefirstplaceswheremostAPIsneedtoconsiderspecialinstructions.Eloquentcomesoutoftheboxwithapaginationsystemthathooksdirectlyintothequeryparametersofanypagerequest.WealreadycoveredthepaginatorcomponentabitinChapter5,buthere’saquickrefresher.
AnyEloquentcallprovidesapaginate()method,whichyoucanpassthenumberofitemsyou’dliketoreturnperpage.EloquentthencheckstheURLforapagequeryparameterand,ifit’sset,treatsthatasanindicatorofhowmanypagestheuserisintoapaginatedlist.
TomakeyourAPIroutereadyforautomatedLaravelpagination,usepaginate()insteadofall()orget()inyourroute;somethinglikeExample13-7.
Example13-7.ApaginatedAPIrouteRoute::get('dogs',function(){
returnDog::paginate(20);
});
We’vedefinedthatEloquentshouldget20resultsfromthedatabase.Dependingonwhatthepagequeryparameterissetto,Laravelwillknowexactlywhich20resultstopullforus:
GET/dogs-Returnresults1-20
GET/dogs?page=1-Returnresults1-20
GET/dogs?page=2-Returnresults21-40
Notethatthepaginate()methodisalsoavailableonquerybuildercalls,asseeninExample13-8.
Example13-8.Usingthepaginate()methodonaquerybuildercallRoute::get('dogs',function(){
returnDB::table('dogs')->paginate(20);
});
Here’ssomethinginteresting,though:thisisn’tjustgoingtoreturn20resultswhenyouconvertittoJSON.Instead,it’sgoingtobuildaresponseobjectthatautomaticallypassessomeusefulpagination-relateddetailstotheenduser,alongwiththepaginateddata.Example13-9showsapossibleresponsefromourcall,truncatedtoonlythreerecordstosavespace.
Example13-9.Sampleoutputfromapaginateddatabasecall{
"total":50,
"per_page":3,
"current_page":1,
"last_page":17,
"next_page_url":"http://myapp.com/api/dogs?page=2",
"prev_page_url":null,
"from":1,
"to":3,
"data":[
{
'name':'Fido'
},
{
'name':'Pickles'
},
{
'name':'Spot'
}
]
}
SortingandFilteringWhilethereisaconventionandsomebuilt-intoolingforpaginationinLaravel,thereisn’tanyforsorting,soyouhavetofigurethatoutonyourown.I’llgiveaquickcodesamplehere,andI’llstyleourqueryparameterssimilarlytotheJSONAPIspec(describedinthefollowingsidebar).
THEJSONAPISPEC
TheJSONAPIisastandardforhowtohandlemanyofthemostcommontasksinbuildingJSON-basedAPIs:filtering,sorting,pagination,authentication,embedding,links,metadata,andmore.
Laravel’sdefaultpaginationdoesn’tworkexactlyaccordingtotheJSONAPIspec,butitgetsyoustartedintherightdirection.AndthemajorityoftherestoftheJSONAPIspecissomethingyou’lljusthavetochoose(ornot)toimplementmanually.
Forexample,here’sapieceoftheJSONAPIspecthathelpfullyhandleshowtostructuredataversuserrorreturns:
AdocumentMUSTcontainatleastoneofthefollowingtop-levelmembers:
data:thedocument’s“primarydata”
errors:anarrayoferrorobjects
meta:ametaobjectthatcontainsnon-standardmeta-information.
ThemembersdataanderrorsMUSTNOTcoexistinthesamedocument.
Bewarned,however:it’swonderfultohavetheJSONAPIasaspec,butitalsotakesquiteabitofgroundworktogetrunningwithit.Wewon’tuseitentirelyintheseexamples,butI’lluseitsgeneralideasasinspiration.
SortingYourAPIResultsFirst,let’ssetuptheabilitytosortourresults.WestartinExample13-10withtheabilitytosortbyonlyasinglecolumn,andinonlyasingledirection.
Example13-10.SimplestAPIsorting//Handles/dogs?sort=name
Route::get('dogs',function(Request$request){
//Getthesortqueryparameter(orfallbacktodefaultsort"name")
$sortCol=$request->input('sort','name');
returnDog::orderBy($sortCol)->paginate(20);
});
Weaddtheabilitytoinvertit(e.g.,?sort=-weight)inExample13-11.
Example13-11.Single-columnAPIsorting,withdirectioncontrol//Handles/dogs?sort=nameand/dogs?sort=-name
Route::get('dogs',function(Request$request){
//Getthesortqueryparameter(orfallbacktodefaultsort"name")
$sortCol=$request->input('sort','name');
//Setthesortdirectionbasedonwhetherthekeystartswith-
//usingLaravel'sstarts_with()helperfunction
$sortDir=starts_with($sortCol,'-')?'desc':'asc';
$sortCol=ltrim($sort,'-');
returnDog::orderBy($sortCol,$sortDir)
->paginate(20);
});
Finally,wedothesameformultiplecolumns(e.g.,?sort=name,-weight)inExample13-12.
Example13-12.JSONAPI–stylesorting//Handles?sort=name,-weight
Route::get('dogs',function(Request$request){
//Grabthequeryparameterandturnitintoanarrayexplodedby,
$sorts=explode(',',$request->input('sort',''));
//Createaquery
$query=Dog::query();
//Addthesortsonebyone
foreach($sortsas$sortCol){
$sortDir=starts_with($sortCol,'-')?'desc':'asc';
$sortCol=ltrim($sort,'-');
$query->orderBy($sortCol,$sortDir);
}
//Return
return$query->paginate(20);
});
Asyoucansee,it’snotthesimplestprocessever,andyou’lllikelywanttobuildsomehelpertoolingaroundtherepetitiveprocesses,butwe’rebuildingupthecustomizabilityofourAPIpiecebypieceusinglogicalandsimplefeatures.
FilteringYourAPIResultsAnothercommontaskinbuildingAPIsisfilteringoutallbutacertainsubsetofdata.Forexample,theclientmightaskforalistofthedogsthatarefemale.
TheJSONAPIdoesn’tgiveusanygreatideasforsyntaxhere,otherthanthatweshouldusethefilterqueryparameter.Let’sthinkalongthelinesofthesortsyntax,wherewe’reputtingeverythingintoasinglekey—maybe?filter=sex:female.YoucanseehowtodothisinExample13-13.
Example13-13.SinglefilteronAPIresultsRoute::get('dogs',function(Request$request){
$query=Dog::query();
if($request->has('filter')){
list($criteria,$value)=explode(':',$request->input('filter'));
$query->where($criteria,$value);
}
return$query->paginate(20);
});
And,justforkicks,inExample13-14weallowformultiplefilters,like?filter=sex:female,color:brown.
Example13-14.MultiplefiltersonAPIresultsRoute::get('dogs',function(Request$request){
$query=Dog::query();
if($request->has('filter')){
$filters=explode(',',$request->input('filter'));
foreach($filtersas$filter){
list($criteria,$value)=explode(':',$filter);
$query->where($criteria,$value);
}
}
return$query->paginate(20);
});
TransformingResultsWe’vecoveredhowtosortandfilterourresultsets.Butrightnow,we’rerelyingonEloquent’sJSONserialization,whichmeanswegeteveryfieldoneverymodel.
Eloquentprovidesafewconveniencetoolsfordefiningwhichfieldstoshowwhenyou’reserializinganarray.YoucanreadmoreinChapter8,butthegististhatifyouseta$hiddenarraypropertyonyourEloquentclass,anyfieldlistedinthatarraywillnotbeshownintheserializedmodeloutput.Youcanalternativelyseta$visiblearraythatdefinesthefieldsthatareallowedtobeshown.YoucouldalsoeitheroverwriteormimicthetoArray()functiononyourmodel,craftingacustomoutputformat.
Anothercommonpatternistocreateatransformerforeachdatatype.There’safantasticpackageforthis,Fractal,thatsetsupaseriesofconveniencestructuresandclassesfortransformingyourdata,butlet’scoverasimpleimplementationtoshowwhatatransformerisandwhyyoumightwanttodothis.
WritingYourOwnTransformerThegeneralconceptofatransformeristhatwearegoingtoruneveryinstanceofourmodelthroughanotherclassthattransformsitsdatatoadifferentstate.Itmightaddfields,renamefields,deletefields,manipulatefields,addnestedchildren,orwhateverelse.Let’sstartwithasimpleexample(Example13-15).
Example13-15.AsimpletransformerRoute::get('users/{id}',function($userId){
return(newUserTransformer(User::findOrFail($userId)));
});
classUserTransformer
{
protected$user;
publicfunction__construct($user)
{
$this->user=$user;
}
publicfunctiontoArray()
{
return[
'id'=>$this->user->id,
'name'=>sprintf(
"%s%s",
$this->user->first_name,
$this->user->last_name
),
'friendsCount'=>$this->user->friends->count()
];
}
publicfunctiontoJson()
{
returnjson_encode($this->toArray());
}
publicfunction__toString()
{
return$this->toJson();
}
}
CLASSICTRANSFORMERSAmoreclassictransformerwouldprobablyofferatransform()methodthattakesa$userparameter.ThiswouldlikelyspitoutanarrayorJSONdirectly.
However,I’vebeenusingthispattern,whichwesometimescall“APIobjects,”forafewyearsandreallylovehowmuchmorepowerandflexibilityitprovides.
AsyoucanseeinExample13-15,transformersacceptthemodelthey’retransformingasaparameterandthenmanipulatethatmodel—anditsrelationships—tocreatethefinaloutputthatyouwanttosendtotheAPI.
Thisgivesyoumorecontrol,isolatesAPI-specificlogicawayfromthemodelitself,andallowsyoutoprovideamoreconsistentAPIevenwhenthemodelsandtheirrelationshipschangedowntheroad.
NestingandRelationshipsWhether,andhow,tonestrelationshipsinAPIsisanissueofmuchdebate.Thankfully,peoplemoreexperiencedthanmehavewrittenonthisatlength;I’drecommendreadingPhilSturgeon’sBuildAPIsYouWon’tHate(Leanpub)tolearnmoreaboutthisandaboutRESTAPIsingeneral.
Thereareafewprimarywaystoapproachnestingrelationships.Theseexampleswillassumeyourprimaryresourceisauserandyourrelatedresourceisafriend:
Embedrelatedresourcesdirectlyintheprimaryresource(e.g.,theusers/5resourcehasitsfriendsnestedinit).
Embedjusttheforeignkeysintheprimaryresource(e.g.,theusers/5resourcehasanarrayoffriendIDsnestedinit).
Allowtheusertoquerytherelatedresourcefilteredbytheoriginatingresource(e.g.,/friends?user=5,or“givemeallfriendswhoarerelatedtouser#5”).
Createasubresource(e.g.,/users/5/friends).
Allowoptionalembedding(e.g.,/users/5doesnotembed,but/users/5?embed=friendsdoesembed;sodoes/users/5?embed=friends,dogs).
Let’sassumeforaminutethatwewantto(optionally)embedtherelationships.Howwouldwedothat?OurtransformerexampleinExample13-15givesusagreatheadstart.Let’sadjustitinExample13-16toaddoptionalembedding.
Example13-16.AllowingforoptionalembeddingofaresourceinatransformerRoute::get('users/{id}',function($userId,Request$request){
//Gettheembedsqueryparameterandsplitbycommas
$embeds=explode(',',$request->input('embed',''));
//Passbothuserandembedstotheusertransformer
return(newUserTransformer(User::findOrFail($userId),$embeds));
});
classUserTransformer
{
protected$user;
protected$embeds;
publicfunction__construct($user,$embeds=[])
{
$this->user=$user;
$this->embeds=$embeds;
}
publicfunctiontoArray()
{
$append=[];
if(in_array('friends',$this->embeds)){
//Ifyouhavemorethanoneembed,you'llwanttogeneralizethis
$append['friends']=$this->user->friends->map(function($friend){
return(newFriendTransformer($friend))->toArray();
});
}
returnarray_merge([
'id'=>$this->user->id,
'name'=>sprintf(
"%s%s",
$this->user->first_name,
$this->user->last_name
)
],$append);
}
...
We’lllearnmoreaboutthemap()functionalitywhenwelookatcollectionsinChapter17,buteverythingelseinhereshouldbeprettyfamiliar.
Intheroute,we’resplittingtheembedqueryparameterbycommasandpassingitintoourtransformer.Currentlyourtransformercanjusthandlethefriendsembed,butitcouldbeabstractedtohandleothers.Iftheuserhasrequestedthefriendsembed,thetransformermapsovereachfriend(usingthehasmanyfriendsrelationshipontheusermodel),passesthatfriendtotheFriendTransformer,andembedsthearrayofalltransformedfriendsintheuserresponse.
APIAuthenticationwithLaravelPassportMostAPIsrequiresomeformofauthenticationtoaccesssomeorallofthedata.Laravel5.2introducedasimple“token”authenticationscheme,whichwe’llcovershortly,butinLaravel5.3wegotanewtoolcalledPassport(bywayofaseparatepackage,broughtinviaComposer)thatmakesiteasytosetupafull-featuredOAuth2.0serverinyourapplication,completewithanAPIandUIcomponentsformanagingclientsandtokens.PassportandthefeaturesitreliesonareonlycompatiblewithLaravel5.3andabove.
ABriefIntroductiontoOAuth2.0OAuthisbyfarthemostcommonauthsystemusedinRESTfulAPIs.Unfortunately,it’sfartoocomplexatopicforustocoverhereindepth.Forfurtherreading,MattFrosthaswrittenagreatbookonOAuthandPHPtitledIntegratingWebServiceswithOAuthandPHP(php[architect]).
Ifyou’reworkingwithLaravel5.1or5.2,there’saLaravelpackagecalledOAuth2.0ServerforLaravelthatmakesitrelativelyeasytoaddabasicOAuth2.0authenticationservertoyourLaravelapplication.It’saLaravelconveniencebridgetoaPHPpackagecalledPHPOAuth2.0Server.
However,ifyou’reonLaravel5.3,Passportgivesyoueverythingprovidedbythatpackageandmuchmore,withasimplerandmorepowerfulAPIandinterface.
InstallingPassportPassportisaseparatepackage,soyourfirststepistoinstallit.I’llsumupthestepshere,butyoucangetmorein-depthinstallationinstructionsinthedocs.
First,bringitinwithComposer:
composerrequirelaravel/passport
Next,addLaravel\Passport\PassportServiceProvider::classtotheprovidersarrayofconfig/app.php.ThiswillmakePassportbootupeverytimeyourapploads.
Passportimportsaseriesofmigrations,sorunthosewithphpartisanmigratetocreatethetablesnecessaryforOAuthclients,scopes,andtokens.
Next,runtheinstallerwithphpartisanpassport:install.ThisisgoingtocreateencryptionkeysfortheOAuthserver(storage/oauth-private.keyandstorage/oauth-public.key)andinsertOAuthclientsintothedatabaseforourpersonalandpasswordgranttypetokens(whichwe’llcoverlater).
You’llneedtoimporttheLaravel\Passport\HasApiTokenstraitintoyourUsermodel;thiswilladdOAuthclient-andtoken-relatedrelationshipstoeachUser,aswellasafewtoken-relatedhelpermethods.Next,addacalltoLaravel\Passport\Passport::routes()intheboot()methodoftheAuthServiceProvider.Thiswilladdthefollowingroutes:
oauth/authorize
oauth/clients
oauth/clients/{client_id}
oauth/personal-access-tokens
oauth/personal-access-tokens/{token_id}
oauth/scopes
oauth/token
oauth/token/refresh
oauth/tokens
oauth/tokens/{token_id}
Finally,lookfortheapiguardinconfig/auth.php.Bydefaultthisguardwillusethetokendriver(whichwe’llcovershortly),butwe’llchangethattobethepassportdriverinstead.
YounowhaveafullyfunctionalOAuth2.0server!Youcancreatenewclientswithphpartisanpassport:client,andyouhaveanAPIformanagingyourclientsandtokensavailableunderthe/oauthrouteprefix.
ToprotectaroutebehindyourPassportauthsystem,addtheauth:apimiddlewaretotherouteorroutegroup,asshowninExample13-17.
Example13-17.ProtectinganAPIroutewiththePassportauthmiddleware//routes/api.php
Route::get('/user',function(Request$request){
return$request->user();
})->middleware('auth:api');
Inordertoauthenticatetotheseprotectedroutes,yourclientappswillneedtopassatoken(we’llcoverhowtogetonenext)asaBearertokenintheAuthorizationheader.Example13-18showswhatthiswouldlooklikeifyouweremakingarequestusingtheGuzzleHTTPlibrary.
Example13-18.MakingasampleAPIrequestwithaBearertoken$http=newGuzzleHttp\Client;
$response=$http->request('GET','http://speakr.dev/api/user',[
'headers'=>[
'Accept'=>'application/json',
'Authorization'=>'Bearer'.$accessToken,
],
]);
Now,let’slearnalittlemoreabouthowitallworks.
Passport’sAPIPassportexposesanAPIinyourapplicationunderthe/oauthrouteprefix.TheAPIprovidestwoprimaryfunctions:first,toauthorizeuserswithOAuth2.0authorizationflows(/oauth/authorizeand/oauth/token),andsecond,toallowuserstomanagetheirclientsandtokens(therestoftheroutes).
Thisisanimportantdistinction,especiallyifyou’reunfamiliarwithOAuth.EveryOAuthserverneedstoexposetheabilityforconsumerstoauthenticatewithyourserver;that’stheentirepointoftheservice.ButPassportalsoexposesanAPIformanagingthestateofyourOAuthserver ’sclientsandtokens.ThismeansyoucaneasilybuildafrontendtoletyourusersmanagetheirinformationinyourOAuthapplication,andPassportactuallycomeswithVue-basedmanagercomponentsthatyoucaneitheruseoruseforinspiration.
We’llcovertheAPIroutesthatallowyoutomanageclientsandtokens,andtheVuecomponentsthatPassportshipswithtomakeiteasy,butfirstlet’sdigintothevariouswaysyouruserscanauthenticatewithyourPassport-protectedAPI.
Passport’sAvailableGrantTypesPassportmakesitpossibleforyoutoauthenticateusersinfourdifferentways.TwoaretraditionalOAuth2.0grants(thepasswordgrantandauthorizationcodegrant)andtwoareconveniencemethodsthatareuniquetoPassport(thepersonaltokenandsynchronizertoken).
PasswordgrantThepasswordgrant,whilelesscommonthantheauthorizationcodegrant,ismuchsimpler.IfyouwantuserstobeabletoauthenticatedirectlywithyourAPIusingtheirusernameandpassword—forexample,ifyouhaveamobileappforyourcompanyconsumingyourownAPI—youcanusethepasswordgrant.
CREATINGAPASSWORDGRANTCLIENTInordertousethepasswordgrantflow,youneedapasswordgrantclientinyourdatabase.Onewillhavebeenaddedwhenyouranphpartisanpassport:install,butifyoueverneedtogenerateanewpasswordgrantclientforanyreason,youcan:
phpartisanpassport:client--password
Whatshouldwenamethepasswordgrantclient?
[MyApplicationPasswordGrantClient]:
>SpaceBook_internal
Passwordgrantclientcreatedsuccessfully.
Withthepasswordgranttype,thereisjustonesteptogettingatoken:sendingtheuser ’scredentialstothe/oauth/tokenroute,likeinExample13-19.
Example13-19.Makingarequestwiththepasswordgranttype//AssumingSpaceBookisnotanexternalapp,butactually
//atrustedinternalapp...thisisSpaceBook'sroutes/web.php
Route::get('speakr/password-grant-auth',function(){
$http=newGuzzleHttp\Client;
$response=$http->post('http://speakr.dev/oauth/token',[
'form_params'=>[
'grant_type'=>'password',
'client_id'=>config('speakr.id'),
'client_secret'=>config('speakr.secret'),
'username'=>'[email protected]',
'password'=>'my-speakr-password',
],
]);
$thisUsersTokens=json_decode((string)$response->getBody(),true);
//dostuffwiththetokens
});
Thisroutewillreturnanaccess_tokenandarefresh_token.YoucannowsavethosetokenstousetoauthenticatewiththeAPI(accesstoken)andtorequestmoretokenslater(refreshtoken).
NotethattheIDandsecretwe’duseforthepasswordgranttypewouldbethoseintheclientsdatabasetableofourPassportappintherowwiththenameSpaceBook_internal.
AuthorizationcodegrantThemostcommonOAuth2.0authworkflowisalsothemostcomplexonePassportsupports.Let’simaginewe’redevelopinganapplicationthat’slikeTwitterbutforsoundclips;we’llcallitSpeakr.Andwe’llimagineanotherwebsite,asocialnetworkforsciencefictionfans,calledSpaceBook.SpaceBook’sdeveloperwantstoletpeopleembedtheirSpeakrdataintotheirSpaceBooknewsfeeds.We’regoingtoinstallPassportinourappsothatotherapps—SpaceBook,forexample—canallowtheirmutualuserstoauthenticatewiththeirSpeakrinformation.
Intheauthorizationcodegranttype,eachconsumingwebsite—SpaceBook,inthisexample—needstocreatea“client”inourPassport-enabledapp.Inmostscenarios,theothersites’adminswillhaveuseraccountsatSpeakr,andwe’llbuildtoolsforthemtocreateclientsthere.Butforstarters,wecanjustmanuallycreateaclientfortheSpaceBookadmins:
phpartisanpassport:client
WhichuserIDshouldtheclientbeassignedto?:
>1
Whatshouldwenametheclient?:
>SpaceBook
Whereshouldweredirecttherequestafterauthorization?
[http://passport.dev/auth/callback]:
>http://spacebook.dev/auth/callback
Newclientcreatedsuccessfully.
ClientID:3
Clientsecret:RiQstsWDqd9SqQY3lQhiZF50ulKdw4iPhPAdkeO3
Everyclientneedstobeassignedtoauserinyourapp.ImagineJill,user#1,iswritingSpaceBook;she’llbethe“owner”ofthisclientwe’recreating.
NowwehavetheIDandsecretfortheSpaceBookclient.Atthispoint,SpaceBookcanusethisIDandsecrettobuildtoolingallowinganindividualSpaceBookuser(whoisalsoaSpeakruser)togetanauthtokenfromSpeakrforusewhenSpaceBookwantstomakeAPIcallstoSpeakronthatuser ’sbehalf.Example13-20illustratesthis.(ThisandthefollowingexamplesassumeSpaceBookisaLaravelapp,too;theyalsoassumewe’vecreatedafileatconfig/speakr.phpthatreturnstheIDandsecretwejustcreated.)
Example13-20.AconsumerappredirectingausertoourOAuthserver//InSpaceBook'sroutes/web.php:
Route::get('speakr/redirect',function(){
$query=http_build_query([
'client_id'=>config('speakr.id'),
'redirect_uri'=>url('speakr/callback'),
'response_type'=>'code',
]);
//Buildsastringlike:
//client_id={$client_id}&redirect_uri={$redirect_uri}&response_type=code
returnredirect('http://speakr.dev/oauth/authorize?'.$query);
});
WhenusershitthatrouteinSpaceBook,they’llnowberedirectedtothe/oauth/authorizePassportrouteonourSpeakrapp.Atthispointthey’llseeaconfirmationpage;youcanusethedefaultPassportconfirmationpagebyrunningthiscommand:
phpartisanvendor:publish--tag=passport-views
Thiswillpublishtheviewtoresources/views/vendor/passport/authorize.blade.php,andyouruserswillseethepageshowninFigure13-1.
Figure13-1.OAuthauthorizationcodeapprovalpage
Onceauserchoosestoacceptorrejecttheauthorization,Passportwillredirectthatuserbacktotheprovidedredirect_uri.InExample13-20wesetaredirect_uriofurl('speakr/callback'),sotheuserwillberedirectedbacktohttp://spacebook.dev/speakr/callback.
Anapprovalrequestwillcontainacodethatourconsumerapp’scallbackroutecannowusetogetatokenbackfromourPassport-enabledapp,Speakr.Arejectionrequestwillcontainanerror.SpaceBook’scallbackroutemightlooksomethinglikeExample13-21.
Example13-21.Theauthorizationcallbackrouteinoursampleconsumingapp//InSpaceBook'sroutes/web.php:
Route::get('speakr/callback',function(Request$request){
if($request->has('error')){
//handleerrorcondition
}
$http=newGuzzleHttp\Client;
$response=$http->post('http://speakr.dev/oauth/token',[
'form_params'=>[
'grant_type'=>'authorization_code',
'client_id'=>config('speakr.id'),
'client_secret'=>config('speakr.secret'),
'redirect_uri'=>url('speakr/callback'),
'code'=>$request->code,
],
]);
$thisUsersTokens=json_decode((string)$response->getBody(),true);
//dostuffwiththetokens
});
Whatwe’redoinghereisbuildingaGuzzleHTTPrequesttothe/oauth/tokenPassportrouteonSpeakr.WesendaPOSTrequestcontainingtheauthorizationcodewereceivedwhentheuserapprovedaccess,andSpeakrwillreturnaJSONresponsecontainingafewkeys:
access_tokenisthetokenSpaceBookwillwanttosaveforthisuser.ThistokeniswhattheuserwillusetoauthenticateinfuturerequeststoSpeakr.
refresh_tokenisatokenSpaceBookwillneedifyoudecidetosetyourtokenstoexpire.
Bydefault,Passport’saccesstokensneverneedtoberefreshed,soyoudon’tneedtoconcernyourselfwiththisandcanjustignoreit.
expires_inisthenumberofsecondsuntilanaccess_tokenexpires(needstoberefreshed).
USINGREFRESHTOKENS
Ifyou’dliketoforceuserstoreauthenticatemoreoften,youneedtosetashorterrefreshtimeonthetokens,andthenyoucanusetherefresh_tokentorequestanewaccess_token.
Tosetashorterrefreshtime:
//AuthServiceProvider'sboot()method
publicfunctionboot()
{
$this->registerPolicies();
Passport::routes();
//Howlongatokenlastsbeforeneedingrefreshing
Passport::tokensExpireIn(
Carbon::now()->addDays(15)
);
//Howlongarefreshtokenwilllastbeforere-auth
Passport::refreshTokensExpireIn(
Carbon::now()->addDays(30)
);
}
Torequestanewtokenusingarefreshtoken,youneedtohavefirstsavedtherefresh_tokenfromtheinitialauthresponseinExample13-21.Onceit’stimetorefresh,you’llmakeacallsimilartothatexample,butmodifiedslightly:
//InSpaceBook'sroutes/web.php:
Route::get(
'speakr/request-refresh',
function(Request$request){
$http=newGuzzleHttp\Client;
$params=[
'grant_type'=>'refresh_token',
'client_id'=>config('speakr.id'),
'client_secret'=>config('speakr.secret'),
'redirect_uri'=>url('speakr/callback'),
'refresh_token'=>$theTokenYouSavedEarlier,
];
$response=$http->post(
'http://speakr.dev/oauth/token',
['form_params'=>$params,]
);
$thisUsersTokens=json_decode(
(string)$response->getBody(),
true
);
//dostuffwiththetokens
}
);
Intheresponse,you’llreceiveafreshsetoftokenstosavetoyouruser.
Younowhaveallthetoolsyouneedtoperformbasicauthorizationcodeflows.We’llcoverhowtobuildanadminpanelforyourclientsandtokenslater,butfirst,let’stakeaquicklookattheothergranttypes.
PersonalaccesstokensTheauthorizationcodegrantisgreatforyourusers’appsandthepasswordcodegrantisgreatforyourownapps,butwhatifyouruserswanttocreatetokensforthemselvestotestoutyourAPIortousewhenthey’redevelopingtheirapps?That’swhatpersonaltokensarefor.
CREATINGAPERSONALACCESSCLIENTInordertocreatepersonaltokens,youneedapersonalaccessclientinyourdatabase.Runningphpartisanpassport:installwillhaveaddedonealready,butifyoueverneedtogenerateanewpersonalaccessclientforanyreason,youcanrunphpartisanpassport:client--personal:
phpartisanpassport:client--personal
Whatshouldwenamethepasswordgrantclient?
[MyApplicationPersonalAccessClient]:
>MyApplicationPersonalAccessClient
Personalaccessclientcreatedsuccessfully.
Personalaccesstokensarenotquitea“grant”type;there’snoOAuth-prescribedflowhere.Rather,they’reaconveniencemethodthatPassportaddstomakeiteasytohaveasingleclientregisteredinyoursystemthatexistssolelyfortheeasycreationofconveniencetokensforyouruserswhoaredevelopers.
Forexample,maybeyouhaveauserwho’sdevelopingacompetitortoSpaceBooknamedRaceBook(it’sformarathonrunners),andhewantstotoyaroundwiththeSpeakrAPIabittofigureouthowitworksbeforestartingtocode.Doesthisdeveloperhavethefacilitytocreatetokensusingtheauthorizationcodeflow?Notyet—hehasn’tevenwrittenanycodeyet!That’swhatpersonalaccesstokensarefor.
YoucancreatepersonalaccesstokensthroughtheJSONAPI,whichI’llcovershortly,butyoucanalsocreateoneforyouruserdirectlyincode:
//Creatingatokenwithoutscopes
$token=$user->createToken('TokenName')->accessToken;
//Creatingatokenwithscopes...
$token=$user->createToken('MyToken',['place-orders'])->accessToken;
Youruserscanusethesetokensjustasiftheyweretokenscreatedwiththeauthorizationcodegrantflow.
TokensfromLaravelsessionauthentication(synchronizertokens)There’sonefinalwayforyouruserstogettokenstoaccessyourAPI,andit’sanotherconveniencemethodthatPassportaddsbutwhichnormalOAuthserversdon’tprovide.Thismethodisforwhenyourusersarealreadyauthenticatedbecausethey’veloggedintoyourLaravelapplikenormal,andyouwanttheJavaScriptonyourLaravelapptobeabletoaccesstheAPI.It’dbeapaintohavetoreauthenticatetheuserswiththeauthorizationcodeorpasswordgrantflow,soLaravelprovidesahelperforthat.
IfyouaddtheLaravel\Passport\Http\Middleware\CreateFreshApiTokenmiddlewaretoyourwebmiddlewaregroup(inapp/Http/Kernel.php),everyresponseLaravelsendstoyour
authenticateduserswillhaveacookienamedlaravel_tokenattachedtoit.ThiscookieisaJSONWebToken(JWT)thatcontainsencodedinformationabouttheCSRFtoken.Now,ifyousendthenormalCSRFtokenwithyourJavaScriptandsenditalongintheX-CSRF-TOKENheaderonanyAPIrequestsyoumake,theAPIwillcompareyourCSRFtokenwiththiscookieandthiswillauthenticateyouruserstotheAPIjustlikeanyothertoken.
JSONWEBTOKENS( JWT)
JWTisanewerformatthatisjustbeginningtogainprominence.AJSONWebTokenisaJSONobjectcontainingalloftheinformationnecessarytodetermineauser’sauthenticationstateandaccesspermissions.ThisJSONobjectisdigitallysignedusingakeyed-hashmessageauthenticationcode(HMAC)orRSA,whichiswhatmakesittrustworthy.
ThetokenisusuallyencodedandthendeliveredviaURL,POSTrequest,orinaheader.Onceauserauthenticateswiththesystemsomehow,everyHTTPrequestafterthatwillcontainthetoken,describingtheuser’sidentityandauthorization.
JSONWebTokensconsistofthreeBase64-encodedstringsseparatedbydots(.);somethinglikexxx.yyy.zzz.ThefirstsectionisaBase64-encodedJSONobjectcontaininginformationaboutwhichhashingalgorithmisbeingused;thesecondsectionisaseriesof“claims”abouttheuser’sauthorizationandidentity;andthethirdisthesignature,orthefirstandsecondsectionsencryptedandsignedusingthealgorithmspecifiedinthefirstsection.
TolearnmoreaboutJWT,checkoutJWT.ioorthejwt-authLaravelpackage.
ThedefaultVuesetupthatLaravelcomesbundledwithsetsupthisheaderforyou,butifyou’reusingadifferentframework,you’llneedtosetitupmanually.Example13-22showshowtodoitwithjQuery.
Example13-22.SettingjQuerytopassLaravel’sCSRFtokenswithallAjaxrequests$.ajaxSetup({
headers:{
'X-CSRF-TOKEN':"{{csrf_token()}}"
}
});
IfyouaddtheCreateFreshApiTokensmiddlewaretoyourwebmiddlewaregroupandpassthatheaderwitheveryJavaScriptrequest,yourJavaScriptrequestswillbeabletohityourPassport-protectedAPIrouteswithoutworryingaboutanyofthecomplexityoftheauthorizationcodeorpasswordgrants.
ManagingClientsandTokenswiththePassportAPIandtheVueComponentsNowthatwe’vecoveredhowtomanuallycreateclientsandtokensandhowtoauthorizeasaconsumer,let’stakealookattheaspectsofthePassportAPIthatmakeitpossibletobuilduserinterfaceelementsallowingyouruserstomanagetheirclientsandtokens.
TheroutesTheeasiestwaytodigintotheAPIroutesisbylookingathowthesampleprovidedVuecomponentsworkandwhichroutestheyrelyon,soI’lljustgiveabriefoverview:
/oauth/clients(GET,POST)
/oauth/clients/{id}(DELETE,PUT)
/oauth/personal-access-tokens(GET,POST)
/oauth/personal-access-tokens/{id}(DELETE)
/oauth/scopes(GET)
/oauth/tokens(GET)
/oauth/tokens/{id}(DELETE)
Asyoucansee,wehaveafewentitieshere(clients,personalaccesstokens,scopes,andtokens).Wecanlistallofthem;wecancreatesome(youcan’tcreatescopes,sincethey’redefinedincode,andyoucan’tcreatetokens,becausethey’recreatedintheauthorizationflow);andwecandeleteandupdatesome.
TheVuecomponentsPassportcomeswithasetofVuecomponentsoutoftheboxthatmakeiteasytoallowyouruserstoadministertheirclients(thosethey’vecreated),authorizedclients(thosethey’veallowedaccesstotheiraccount),andpersonalaccesstokens(fortheirowntestingpurposes).
Topublishthesecomponentsintoyourapplication,runthiscommand:
phpartisanvendor:publish--tag=passport-components
You’llnowhavethreenewVuecomponentsinresources/assets/js/components/passport.ToaddthemtoyourVuebootstrapsothey’reaccessibleinyourtemplates,registertheminyourresources/assets/js/app.jsfileasshowninExample13-23.
Example13-23.ImportingPassport’sVuecomponentsintoapp.jsrequire('./bootstrap');
Vue.component(
'passport-clients',
require('./components/passport/Clients.vue')
);
Vue.component(
'passport-authorized-clients',
require('./components/passport/AuthorizedClients.vue')
);
Vue.component(
'passport-personal-access-tokens',
require('./components/passport/PersonalAccessTokens.vue')
);
constapp=newVue({
el:'body'
});
Younowgetthreecomponentsthatyoucanuseanywhereinyourapplication:
<passport-clients></passport-clients>
<passport-authorized-clients></passport-authorized-clients>
<passport-personal-access-tokens></passport-personal-access-tokens>
<passport-clients>showsyourusersalloftheclientsthey’vecreated.ThismeansSpaceBook’screatorwillseetheSpaceBookclientlistedherewhenshelogsintoSpeakr.
<passport-authorized-clients>showsyourusersalloftheclientsthey’veauthorizedtohaveaccesstotheiraccounts.ThismeansanyusersofbothSpaceBookandSpeakrwhohavegivenSpaceBookaccesstotheirSpeakraccountwillseeSpaceBooklistedhere.
<passport-personal-access-tokens>showsyourusersanypersonalaccesstokensthey’vecreatedhere.ThismeansthecreatorofRaceBook,theSpaceBookcompetitor,willseehispersonalaccesstokenherethathe’sbeenusingtotestouttheSpeakrAPI.
IfyouareonafreshinstallofLaravelandwanttotesttheseout,thereareafewstepstotaketogetitworking:
FollowtheinstructionsgivenearlierinthischaptertogetPassportinstalled.
Inyourterminal,runthefollowingcommands:phpartisanvendor:publish--tag=passport-components
npminstall
gulp
phpartisanmake:auth
Openresources/views/home.blade.phpandaddtheVuecomponentreferences(e.g.,<passport-clients>)justbelowthe<divclass="panel">.
Ifyou’dlike,youcanjustusethosecomponentsastheyare.ButyoucanalsousethemasreferencepointstounderstandhowtousetheAPIandcreateyourownfrontendcomponentsinwhateverformatyou’dlike.
PassportScopesIfyou’refamiliarwithOAuth,youprobablynoticedwehaven’ttalkedaboutscopes.Everythingwe’vecoveredsofarcanbecustomizedbyscope,butfirstlet’squicklycoverwhatscopesare.
InOAuth,scopesaredefinedsetsofprivilegesthataresomethingotherthan“candoeverything.”Ifyou’veevergottenaGitHubAPItokenbefore,forexample,youmight’venoticedthatsomeappswantaccessjusttoyournameandemailaddress,somewantaccesstoallofyourrepos,andsomewantaccesstoyourgists.Eachoftheseisa“scope,”whichallowsboththeuserandtheconsumerapptodefinewhataccesstheconsumerappneedstoperformitsjob.
AsshowninExample13-24,youcandefinethescopesforyourapplicationintheboot()methodofyourAuthServiceProvider.
Example13-24.DefiningPassportscopes//AuthServiceProvider
useLaravel\Passport\Passport;
...
publicfunctionboot()
{
...
Passport::tokensCan([
'list-clips'=>'Listsoundclips',
'add-delete-clips'=>'Addnewanddeleteoldsoundclips',
'admin-account'=>'Administeraccountdetails',
]);
}
Onceyouhaveyourscopesdefined,theconsumerappcandefinewhichscopesit’saskingforaccessto.Justaddaspace-separatedlistoftokensinthe“token”fieldtotheinitialredirect,inthescopefield,asshowninExample13-25.
Example13-25.Requestingauthorizationtoaccessspecificscopes//InSpaceBook'sroutes/web.php:
Route::get('speakr/redirect',function(){
$query=http_build_query([
'client_id'=>config('speakr.id'),
'redirect_uri'=>url('speakr/callback'),
'response_type'=>'code',
'scope'=>'list-clipsadd-delete-clips'
]);
returnredirect('http://speakr.dev/oauth/authorize?'.$query);
});
Whentheusertriestoauthorizewiththisapp,it’llpresentthelistofrequestedscopes.Thisway,theuserwillseethedifferencebetween“SpaceBookisrequestingtoseeyouremailaddress”and“SpaceBookisrequestingaccesstopostasyouanddeleteyourpostsandmessageyourfriends.”
YoucancheckforscopeusingmiddlewareorontheUserinstance.
Example13-26showshowtocheckontheUser.
Example13-26.CheckingwhetherthetokenauserauthenticatedwithcanperformagivenactionRoute::get('/events',function(){
if(auth()->user()->tokenCan('add-delete-clips')){
//
}
});
Therearetwomiddlewareyoucanuseforthistoo,scopeandscopes.Tousetheseinyourapp,addthemto$routeMiddlewareinyourapp/Http/Kernel.phpfile:
'scopes'=>\Laravel\Passport\Http\Middleware\CheckScopes::class,
'scope'=>\Laravel\Passport\Http\Middleware\CheckForAnyScope::class,
YoucannowusethemiddlewareasillustratedinExample13-27.scopesrequiresallofthedefinedscopestobeontheuser ’stokeninorderfortheusertoaccesstheroute,whilescoperequiresatleastoneofthedefinedscopestobeontheuser ’stoken.
Example13-27.Usingmiddlewaretorestrictaccessbasedontokenscopes//routes/api.php
Route::get('clips',function(){
//Accesstokenhasboththe"list-clips"and"add-delete-clips"scopes
})->middleware('scopes:list-clips,add-delete-clips');
//or
Route::get('clips',function(){
//Accesstokenhasatleastoneofthelistedscopes
})->middleware('scope:list-clips,add-delete-clips')
Ifyouhaven’tdefinedanyscopes,theappwilljustworkasiftheydon’texist.Themomentyouusescopes,however,yourconsumerappswillhavetoexplicitlydefinewhichscopesthey’rerequestingaccesswith.Theoneexceptiontothisruleisthatifyou’reusingthepasswordgranttypeyourconsumerappcanrequestthe*scope,whichgivesthetokenaccesstoeverything.
Laravel5.2+APITokenAuthenticationLaravel5.2introducedasimpleAPItokenauthenticationmechanism.It’snotmuchdifferentfromausernameandpassword:there’sasingletokenassignedtoeachuserthatclientscanpassalongwitharequesttoauthenticatethatrequestforthatuser.
ThisAPItokenmechanismisnotnearlyassecureasOAuth2.0,somakesureyouknowit’stherightfitforyourapplicationbeforedecidingtouseit.Butifitis,itcouldn’tbemuchsimplertoimplement.
First,adda60-characteruniqueapi_tokencolumntoyouruserstable:
$table->string('api_token',60)->unique();
Next,updatewhatevermethodcreatesyournewusersandensureitsetsavalueforthisfieldforeachnewuser.Laravelhasahelperforgeneratingrandomstrings,soifyouwanttousethat,justsetthefieldtostr_random(60)foreach.You’llalsoneedtodothisforpreexistingusersifyou’readdingthistoaliveapplication.
Towrapanyrouteswiththisauthenticationmethod,usetheauth:apiroutemiddleware,asinExample13-28.
Example13-28.ApplyingtheAPIauthmiddlewaretoaroutegroupRoute::group(['prefix'=>'api','middleware'=>'auth:api'],function(){
//
});
Notethat,sinceyou’reusinganauthenticationguardotherthanthestandardguard,you’llneedtospecifythatguardanytimeyouuseanyauth()methods:
$user=auth()->guard('api')->user();
TestingFortunately,testingAPIsisactuallysimplerthantestingalmostanythingelseinLaravel.
WecoverthisinmoredepthinChapter12,butthereareaseriesofmethodsformakingassertionsagainstJSON.Combinethatcapabilitywiththesimplicityoffull-stackapplicationtestsandyoucanputtogetheryourAPItestsquicklyandeasily.TakealookatthecommonAPItestingpatterninExample13-29.
Example13-29.AcommonAPItestingpattern...
classDogsApiTestextendsTestCase
{
useWithoutMiddleware,DatabaseMigrations;
publicfunctiontest_it_gets_all_dogs()
{
$this->be(factory(User::class)->create());
$dog1=factory(Dog::class)->create();
$dog2=factory(Dog::class)->create();
$this->visit('api/dogs');
$this->seeJson([
'name'=>$dog1->name
]);
$this->seeJson([
'name'=>$dog2->name
]);
}
}
Notethatwe’reusingWithoutMiddlewaretoavoidworryingabouttheauthentication.You’llwanttotestthatseparately,ifatall(formoreonauthentication,seeChapter9).
Wegenerateauserandauthenticateasthatuserwith$this->be().Wetheninserttwodogsintothedatabase,andthenvisittheAPIrouteforlistingalldogsandmakesurebotharepresentintheoutput.
YoucancoverallofyourAPIroutessimplyandeasilyhere,includingmodifyingactionslikePOSTandPATCH.
TL;DRLaravelisgearedtowardbuildingAPIsandmakesitsimpletoworkwithJSONandRESTfulAPIs.Therearesomeconventions,likeforpagination,butmuchofthedefinitionofexactlyhowyourAPIwillbesorted,orauthenticated,orwhateverelseisuptoyou.
Laravelprovidestoolsforauthenticationandtesting,easymanipulationandreadingofheaders,andworkingwithJSON,evenautomaticallyencodingallEloquentresultstoJSONifthey’rereturneddirectlyfromaroute.
LaravelPassportisaseparatepackagethatmakesitsimpletocreateandmanageanOAuthserverinyourLaravelapps.
Chapter14.StorageandRetrieval
WecoveredhowtostoredatainrelationaldatabasesinChapter8,butthere’salotmorethatcanbestored,bothlocallyandremotely.Inthischapterwe’llcoverfilesystemandin-memorystorage,fileuploadsandmanipulation,nonrelationaldatastores,sessions,thecache,cookies,andfull-textsearch.
LocalandCloudFileManagersLaravelprovidesaseriesoffilemanipulationtoolsthroughtheStoragefacade,andafewhelperfunctions.
Laravel’sfilesystemaccesstoolscanconnecttothelocalfilesystemaswellasS3,Rackspace,andFTP.TheS3andRackspacefiledriversareprovidedbyFlysystem,andit’ssimpletoaddadditionalFlysystemproviderstoyourLaravelapp—forexample,DropboxorWebDAV.
ConfiguringFileAccessThedefinitionsforLaravel’sfilemanagerliveinconfig/filesystems.php.Eachconnectioniscalleda“disk,”andExample14-1liststhedisksthatareavailableoutofthebox.
Example14-1.Defaultavailablestoragedisks...
'disks'=>[
'local'=>[
'driver'=>'local',
'root'=>storage_path('app'),
],
'public'=>[
'driver'=>'local',
'root'=>storage_path('app/public'),
'visibility'=>'public',
],
's3'=>[
'driver'=>'s3',
'key'=>'your-key',
'secret'=>'your-secret',
'region'=>'your-region',
'bucket'=>'your-bucket',
],
],
THESTORAGE_PATH()HELPERThestorage_path()helperusedinExample14-1linkstoLaravel’sconfiguredstoragedirectory,storage/.Anythingyoupasstoitisaddedtotheendofthedirectoryname,sostorage_path('public')willreturnthestringstorage/public.
Thelocaldiskconnectstoyourlocalstoragesystemandpresumesitwillbeinteractingwiththeappdirectoryofthestoragepath,whichisstorage/app.
Thepublicdiskisalsoalocaldisk(althoughyoucanchangeitifyou’dlike),whichisintendedforusewithanyfilesyouintendtobeservedbyyourapplication.Itdefaultstothestorage/app/publicdirectory,andifyouwanttousethisdirectorytoservefilestothepublic,you’llneedtoaddasymboliclink(symlink)tosomewherewithinthepublic/directory.Thankfully,there’sanArtisancommandforthat:
#Mapspublic/storagetoservethefilesfromstorage/app/public
phpartisanstorage:link
Thes3diskshowshowLaravelconnectstocloud-basedfilestoragesystems.Ifyou’veeverconnectedtoS3oranyothercloudstorageprovider,thiswillbefamiliar;passityourkeyandsecretandsomeinformationdefiningthe“folder”you’reworkingwith,whichinS3istheregionandthebucket.
UsingtheStorageFacadeInconfig/filesystem.phpyoucansetthedefaultdisk,whichiswhatwillbeusedanytimeyoucalltheStoragefacadewithoutspecifyingadisk.Tospecifyadisk,calldisk('diskname')onthefacade:
Storage::disk('s3')->get('file.jpg');
Thefilesystemseachprovidethefollowingmethods:
get('file.jpg')
Retrievesthefileatfile.jpg
put('file.jpg',$contentsOrStream)
Putsthegivenfilecontentstofile.jpg
putFile('myDir',$file)
Putsthecontentsofaprovidedfile(intheformofaninstanceofeitherIlluminate\Http\FileorIlluminate\Http\UploadedFile)tothemyDirdirectory,butwithLaravelmanagingtheentirestreamingprocessandnamingthefile
exists('file.jpg')
Returnsabooleanofwhetherfile.jpgexists
copy('file.jpg','newfile.jpg')
Copiesfile.jpgtonewfile.jpg
move('file.jpg','newfile.jpg')
Movesfile.jpgtonewfile.jpg
prepend('my.log','logtext')
Addscontentatthebeginningofmy.log
append('my.log','logtext')
Addscontenttotheendofmy.log
delete('file.jpg')
Deletesfile.jpg
deleteDirectory('myDir')
DeletesmyDir
size('file.jpg')
Returnsthesizeinbytesoffile.jpg
lastModified('file.jpg')
ReturnstheUnixtimestampofwhenfile.jpgwaslastmodified
files('myDir')
ReturnsanarrayoffilenamesinthedirectorymyDir
allFiles('myDir')
ReturnsanarrayoffilenamesinthedirectorymyDirandallsubdirectories
directories('myDir')
ReturnsanarrayofdirectorynamesinthedirectorymyDir
allDirectories('myDir')
ReturnsanarrayofdirectorynamesinthedirectorymyDirandallsubdirectories
INJECTINGANINSTANCEIfyou’dpreferinjectinganinstanceinsteadofusingtheFilefacade,typehintorinjectIlluminate\Filesystem\Filesystemandyou’llgetallthesamemethodsavailabletoyou.
AddingAdditionalFlysystemProvidersIfyouwanttoaddanadditionalFlysystemprovider,you’llneedto“extend”Laravel’snativestoragesystem.Inaserviceprovidersomewhere—itcouldbetheboot()methodofAppServiceProvider,butit’dbemoreappropriatetocreateauniqueserviceproviderforeachnewbinding—usetheStoragefacadetoaddnewstoragesystems,asseeninExample14-2.
Example14-2.AddingadditionalFlysytemproviders//Someserviceprovider
publicfunctionboot()
{
Storage::extend('dropbox',function($app,$config){
$client=newDropboxClient(
$config['accessToken'],$config['clientIdentifier']
);
returnnewFilesystem(newDropboxAdapter($client));
});
}
BasicFileUploadsandManipulationOneofthemorecommonusagesfortheStoragefacadeisacceptingfileuploadsfromyourapplication’susers.Let’slookatacommonworkflowforthat,inExample14-3.
Example14-3.Commonuseruploadworkflow...
classDogsController
{
publicfunctionupdatePicture(Request$request,Dog$dog)
{
Storage::put(
'dogs/'.$dog->id,
file_get_contents($request->file('picture')->getRealPath())
);
}
Weput()toafilenameddogs/{id},andwegrabourcontentsfromtheuploadedfile.EveryuploadedfileisadescendantoftheSplFileInfoclass,whichprovidesagetRealPath()methodthatreturnsthepathtothefile’slocation.So,wegetthetemporaryuploadpathfortheuser ’suploadedfile,readitwithfile_get_contents(),andpassitintoStorage::put().
Sincewehavethisfileavailabletoushere,wecandoanythingwewanttothefilebeforewestoreit—useanimagemanipulationpackagetoresizeitifit’sanimage,validateitandrejectitifitdoesn’tmeetourcriteria,orwhateverelsewelike.
IfwewantedtouploadthissamefiletoS3andwehadourcredentialsstoredinconfig/filesystems.php,wecouldjustadjustExample14-3tocallStorage::disk('s3')->put();we’llnowbeuploadingtoS3.TakealookatExample14-4toseeamorecomplexuploadexample.
Example14-4.Amorecomplexexampleoffileuploads,usingIntervention...
classDogsController
{
publicfunctionupdatePicture(Request$request,Dog$dog)
{
$original=$request->file('picture');
//Resizeimagetomaxwidth150
$image=Image::make($original)->resize(150,null,function($constraint){
$constraint->aspectRatio();
})->encode('jpg',75);
Storage::put(
'dogs/thumbs/'.$dog->id,
$image->getEncoded()
);
}
IusedanimagelibrarycalledInterventioninExample14-4justasanexample;youcanuseanylibraryyouwant.Theimportantpointisthatyouhavethefreedomtomanipulatethefileshoweveryouwantbeforeyoustorethem.
USINGSTORE()ANDSTOREAS()ONTHEUPLOADEDFILELaravel5.3introducedtheabilitytostoreanuploadedfileusingthefileitself.LearnmoreinExample6-11.
SessionsSessionstorageistheprimarytoolweuseinwebapplicationstostorestatebetweenpagerequests.Laravel’ssessionmanagersupportssessiondriversusingfiles,cookies,adatabase,MemcachedorRedis,orin-memoryarrays(whichexpireafterthepagerequestandareonlygoodfortests).
Youcanconfigureallofyoursessionsettingsanddriversinconfig/session.php.Youcanchoosewhetherornottoencryptyoursessiondata,selectwhichdrivertouse(fileisthedefault),andspecifymoreconnection-specificdetailslikethelengthofsessionstorageandwhichfilesordatabasetablestouse.Takealookatthesessiondocstolearnaboutspecificdependenciesandsettingsyouneedtoprepareforwhicheverdriveryouchoosetouse.
ThegeneralAPIofthesessiontoolsallowsyoutosaveandretrievedatabasedonindividualkeys:session()->put('user_id')andsession()->get('user_id'),forexample.Makesuretoavoidsavinganythingtoaflashsessionkey,sinceLaravelusesthatinternallyforflash(onlyavailableforthenextpagerequest)sessionstorage.
AccessingtheSessionThemostcommonwaytoaccessthesessionisusingtheSessionfacade:
session()->get('user_id');
Butyoucanalsousethesession()methodonanygivenIlluminateRequestobject,asinExample14-5.
Example14-5.Usingthesession()methodonaRequestobjectRoute::get('dashboard',function(Request$request){
$request->session()->get('user_id');
});
OryoucaninjectaninstanceofIlluminate\Session\Store,asinExample14-6.
Example14-6.InjectingthebackingclassforsessionsRoute::get('dashboard',function(Illuminate\Session\Store$session){
return$session->get('user_id');
});
Finally,youcanusetheglobalsession()helper.Useitwithnoparameterstogetasessioninstance,withasinglestringparameterto“get”fromthesession,orwithanarrayto“put”tothesession,asdemonstratedinExample14-7.
Example14-7.Usingtheglobalsession()helper//get
$value=session()->get('key');
$value=session('key');
//put
session()->put('key','value');
session(['key','value']);
Ifyou’renewtoLaravelandnotsurewhichtouse,I’drecommendusingtheglobalhelper.
TheMethodsAvailableonSessionInstancesThetwomostcommonmethodsareget()andput(),butlet’stakealookateachoftheavailablemethodsandtheirparameters:
session()->get($key,$fallbackValue)
get()pullsthevalueoftheprovidedkeyoutofthesession.Ifthereisnovalueattachedtothatkey,itwillreturnthefallbackvalueinstead(andifyoudon’tprovideafallback,itwillreturnnull).Thefallbackvaluecanbeastringoraclosure,asyoucanseeinthefollowingexamples.
$points=session()->get('points');
$points=session()->get('points',0);
$points=session()->get('points',function(){
return(newPointGetterService)->getPoints();
});
session()->put($key,$value)
put()storestheprovidedvalueinthesessionattheprovidedkey:
session()->put('points',45);
$points=session()->get('points');
session()->push($key,$value)
Ifanyofyoursessionvaluesarearrays,youcanusepush()toaddavalueontothearray:
session()->put('friends',['Saúl','Quang','Mechteld']);
session()->push('friends','Javier');
session()->has($key)
has()checkswhetherthere’savaluesetattheprovidedkey:
if(session()->has('points')){
//dosomething
}
Youcanalsopassanarrayofkeys,anditonlyreturnstrueifallofthekeysexist.
SESSION()->HAS()ANDNULLVALUESIfasessionvalueisset,butthevalueisnull,session()->has()willreturnfalse.
session()->all()
all()returnsanarrayofeverythingthat’sinthesession,includingthosevaluessetbytheframework.You’lllikelyseevaluesunderkeyslike_token(CSRFtokens),_previous(previouspage,forback()redirects),andflash(forflashstorage).
session()->forget($key)andsession()->flush()forget()removesapreviouslysetsessionvalue.flush()removeseverysessionvalue,eventhosesetbytheframework:
session()->put('a','awesome');
session()->put('b','bodacious');
session()->forget('a');
//aisnolongerset,bisstillset
session()->flush();
//sessionisnowempty
session()->pull($key,$fallbackValue)
pull()isthesameasget(),exceptthatitdeletesthevaluefromthesessionafterpullingit.
session()->regenerate()
It’snotcommon,butifyouneedtoregenerateyoursessionID,regenerate()isthereforyou.
FlashSessionStorageTherearethreemoremethodswehaven’tcoveredyet,andtheyallhavetodowithsomethingcalled“flash”sessionstorage.
Oneverycommonpatternforsessionstorageistosetavaluethatyouonlywantavailableforthenextpageload.Forexample,youmightwanttostoreamessagelike“Updatedpostsuccessfully.”Youcouldmanuallygetthatmessageandthenwipeitonthenextpageload,butifyouusethispatternalotitcangetwasteful.Enterflashsessionstorage:keysthatareexpectedtoonlylastforasinglepagerequest.
Laravelhandlestheworkforyou,andallyouneedtodoisuseflash()insteadofput().Theusefulmethodshereare:
session()->flash($key,$value)
flash()setsthesessionkeytotheprovidedvalueforjustthenextpagerequest.
session()->reflash()andsession()->keep($key)Ifyouneedthepreviouspage’sflash()sessiondatatostickaroundforonemorerequest,youcanusereflash()torestoretheentireflashcontentsforthenextrequestorkeep($key)tojustrestoreasingleflashvalueforthenextrequest.keep()canalsoacceptanarrayofkeystoreflash.
CacheCachesarestructuredverysimilarlytosessions.YouprovideakeyandLaravelstoresitforyou.Thebiggestdifferenceisthatthedatainacacheiscachedperapplicationandthedatainasessioniscachedperuser.Thatmeanscachesaremorecommonlyusedforstoringlargedatabaseresults,APIcalls,orotherslowqueriesthatcanstandtogetalittlebit“stale.”
Thecacheconfigurationsettingsareavailableatconfig/cache.php.Justlikewithasession,youcansetthespecificconfigurationdetailsforanyofyourdrivers,andalsochoosewhichwillbeyourdefault.Laravelusesthefilecachedriverbydefault,butyoucanalsouseMemcachedorRedis,APC,oradatabase,orwriteyourowncachedriver.Takealookatthecachedocstolearnaboutspecificdependenciesandsettingsyouneedtoprepareforwhicheverdriveryouchoosetouse.
AccessingtheCacheJustlikewithsessions,thereareafewdifferentwaystoaccessacache.Youcanusethefacade:
$users=Cache::get('users');
Youcangetaninstancefromthecontainer,asinExample14-8.
Example14-8.InjectinganinstanceofthecacheRoute::get('users',function(Illuminate\Contracts\Cache\Repository$cache){
return$cache->get('users');
});
Oryoucanusetheglobalcache()helper(introducedinLaravel5.3),asinExample14-9.
Example14-9.Usingtheglobalcache()helper//getfromcache
$users=cache('key','defaultvalue');
$users=cache()->get('key','defaultvalue');
//putfor$minutesduration
$users=cache(['key'=>'value'],$minutes);
$users=cache()->put('key','value',$minutes);
Ifyou’renewtoLaravelandnotsurewhichtouse,I’drecommendusingtheglobalhelper.
TheMethodsAvailableonCacheInstancesLet’stakealookatthemethodsyoucancallonaCacheinstance:
cache()->get($key,$fallbackValue)andcache()->pull($key,$fallbackValue)
get()makesiteasytoretrievethevalueforanygivenkey.pull()isthesameasget()exceptitremovesthecachedvalueafterretrievingit.
cache()->put($key,$value,$minutesOrExpiration)
put()setsthevalueofthespecifiedkeyforagivennumberofminutes.Ifyou’dprefersettinganexpirationdate/timeinsteadofanumberofminutes,youcanpassaCarbonobjectasthethirdparameter:
cache()->put('key','value',Carbon::now()->addDay());
cache()->add($key,$value)
add()issimilartoput(),exceptifthevaluealreadyexists,itwon’tsetit.Also,themethodreturnsabooleanofwhetherornotthevaluewasactuallyadded:
$someDate=Carbon::now();
cache()->add('someDate',$someDate);//returnstrue
$someOtherDate=Carbon::now()->addHour();
cache()->add('someDate',$someOtherDate);//returnsfalse
cache()->forever($key,$value)
forever()savesavaluetothecacheforaspecifickey;it’sthesameasput(),exceptthevalueswillneverexpire(untilthey’reremovedwithforget()).
cache()->has($key)
has()returnsabooleanofwhetherornotthere’savalueattheprovidedkey.
cache()->remember($key,$minutes,$closure)andcache()->rememberForever($key,$closure)
remember()providesasinglemethodtohandleaverycommonflow:lookupwhetheravalueexistsinthecacheforacertainkey,andifitdoesn’t,getthatvaluesomehow,saveittothecache,andreturnit.remember()letsyouprovideakeytolookup,thenumberofminutesitshouldbesavedfor,andaclosuretodefinehowtolookitup,incasethekeyhasnovalueset.rememberForever()isthesame,exceptitdoesn’tneedyoutosetthenumberofminutesitshouldexpireafter.Takealookatthefollowingexampletoseeacommonuserscenarioforremember():
//Eitherreturnsthevaluecachedat"users"orgets"User::all()",
//cachesitat"users",andreturnsit
$users=cache()->remember('users',120,function(){
returnUser::all();
});
cache()->increment($key,$amount)andcache()->decrement($key,$amount)increment()anddecrement()allowyoutoincrementanddecrementintegervaluesinthecache.Ifthereisnovalueatthegivenkey,it’llbetreatedasifitwere0,andifyoupassasecondparametertoincrementordecrement,it’llincrementordecrementbythatamountinsteadofby1.
cache()->forget($key)andcache()->flush()forget()worksjustlikeSession’sforget()method:passitakeyandit’llwipethatkey’svalue.flush()wipestheentirecache.
CookiesYoumightexpectcookiestoworkthesameasthesessionandthecache.Afacadeandaglobalhelperareavailableforthesetoo,andourmentalmodelsofallthreearesimilar:youcangetorsettheirvaluesinthesameway.
Butbecausecookiesareinherentlyattachedtotherequestsandresponses,you’llneedtointeractwithcookiesdifferently.Let’slookreallybrieflyatwhatmakescookiesdifferent.
CookiesinLaravelCookiescanexistinthreeplacesinLaravel.Theycancomeinviatherequest,whichmeanstheuserhadthecookiewhenshevisitedthepage.YoucanreadthatwiththeCookiefacade,oryoucanreaditoffoftherequestobject.
Theycanalsobesentoutwitharesponse,whichmeanstheresponsewillinstructtheuser ’sbrowsertosavethecookieforfuturevisits.Youcandothisbyaddingthecookietoyourresponseobjectbeforereturningit.
Andlastly,acookiecanbequeued.IfyouusetheCookiefacadetosetacookie,youhaveputitintoa“CookieJar”queue,anditwillberemovedandaddedtotheresponseobjectbytheAddQueuedCookiesToResponsemiddleware.
AccessingtheCookieToolsYoucangetandsetcookiesinthreeplaces:theCookiefacade,thecookie()globalhelper,andtherequestandresponseobjects.
TheCookiefacadeTheCookiefacadeisthemostfull-featuredoption,allowingyoutonotonlyreadandmakecookies,butalsotoqueuethemtobeaddedtotheresponse.Itprovidesthefollowingmethods:
Cookie::get($key)
Topullthevalueofacookiethatcameinwiththerequest,youcanjustrunCookie::get('cookie-name').Thisisthesimplestoption.
Cookie::has($key)
YoucancheckwhetheracookiecameinwiththerequestusingCookie::has('cookie-name'),whichreturnsaboolean.
Cookie::make(...params)
Ifyouwanttomakeacookiewithoutqueueingitanywhere,youcanuseCookie::make().Themostlikelyuseforthiswouldbetomakeacookieandthenmanuallyattachittotheresponseobject,whichwe’llcoverinabit.Herearetheparametersformake(),inorder:
$nameisthenameofthecookie
$valueisthecontentofthecookie
$minutesspecifieshowmanyminutesthecookieshouldlive
$pathisthepathunderwhichyourcookieshouldbevalid
$domainliststhedomainsforwhichyourcookieshouldwork
$secureindicateswhetherthecookieshouldonlybetransmittedoverasecure(HTTPS)connection
$httpOnlyindicateswhetherthecookiewillbemadeaccessibleonlythroughtheHTTPprotocol
Cookie::make()
ReturnsaninstanceofSymfony\Component\HttpFoundation\Cookie.
DEFAULTSETTINGSFORCOOKIESTheCookieJarthattheCookiefacadeinstanceusesreadsitsdefaultsfromthesessionconfig.So,ifyouchangeanyoftheconfigurationvaluesforthesessioncookieinconfig/session.php,thosesamedefaultswillbeappliedtoallofyourcookiesthatyoucreateusingtheCookiefacade.
Cookie::queue(Cookie||...params)
IfyouuseCookie::make(),you’llstillneedtoattachthecookietoyourresponse,whichwe’llcovershortly.Cookie::queue()hasthesamesyntaxasCookie::make(),butitenqueuesthecreatedcookietobeautomaticallyattachedtotheresponsebymiddleware.Ifyou’dlike,youcanalsojustpassacookieyou’vecreatedyourselfintoCookie::queue().Here’sthesimplestpossiblewaytoaddacookietotheresponseinLaravel:
Cookie::queue('dismissed-popup',true,15);
WHENYOURQUEUEDCOOKIESWON’TGETSETCookiescanonlybereturnedasapartofaresponse.So,ifyouenqueuecookieswiththeCookiefacadeandthenyourresponseisn’treturnedcorrectly—forexample,ifyouusePHP’sexit()orsomethinghaltstheexecutionofyourscript—yourcookieswon’tbeset.
Thecookie()globalhelperThecookie()globalhelperwillreturnaCookieJarinstanceifyoucallitwithnoparameters.However,twoofthemostconvenientmethodsontheCookiefacade—has()andget()—existonlyonthefacade,notontheCookieJar.So,inthiscontext,Ithinktheglobalhelperisactuallylessusefulthantheotheroptions.
Theonetaskforwhichthecookie()globalhelperisusefuliscreatingacookie.Ifyoupassparameterstocookie(),they’llbepasseddirectlytotheequivalentofCookie::make(),sothisisthefastestwaytocreateacookie:
$cookie=cookie('dismissed-popup',true,15);
INJECTINGANINSTANCEYoucanalsoinjectaninstanceofIlluminate\Cookie\CookieJaranywhereintheapp,butyou’llhavethesamelimitationsdiscussedhere.
CookiesonrequestandresponseobjectsSincecookiescomeinasapartoftherequestandaresetasapartoftheresponse,thoseIlluminateobjectsaretheplacestheyactuallylive.TheCookiefacade’sget(),has(),andqueue()methodsarejustproxiestointeractwiththerequestandresponseobjects.
So,thesimplestwaytointeractwithcookiesistopullcookiesfromtherequestandsetthemontheresponse.
ReadingcookiesfromrequestobjectsOnceyouhaveacopyofyourrequestobject—ifyoudon’tknowhowtogetone,justtryapp('request')—youcanusetherequestobject’scookie()methodtoreaditscookies,asshowninExample14-10.
Example14-10.ReadingacookiefromarequestobjectRoute::get('dashboard',function(Illuminate\Http\Request$request){
$userDismissedPopup=$request->cookie('dismissed-popup',false);
});
Asyoucanseeinthisexample,thecookie()methodhastwoparameters:thecookie’snameand,optionally,thefallbackvalue.
SettingcookiesonresponseobjectsWheneveryouhaveyourresponseobjectready,youcanusethecookie()method(orthewithCookie()methodinLaravelpriorto5.3)onittoaddacookietotheresponse,likeinExample14-11.
Example14-11.SettingacookieonaresponseobjectRoute::get('dashboard',function(){
$cookie=cookie('saw-dashboard',true);
returnResponse::view('dashboard')
->cookie($cookie);
});
Ifyou’renewtoLaravelandnotsurewhichoptiontouse,I’drecommendsettingcookiesontherequestandresponseobjects.It’sabitmorework,butwillleadtofewersurprisesiffuturedevelopersdon’tunderstandtheCookieJarqueue.
Full-TextSearchwithLaravelScoutLaravelScoutisaseparatepackagethatyoucanbringintoyourLaravelappstoaddfull-textsearchtoyourEloquentmodels.ScoutmakesiteasytoindexandsearchthecontentsofyourEloquentmodels;itshipswithAlgoliaandElasticsearchdrivers,buttherearealsocommunitypackagesforotherproviders.I’llassumeyou’reusingAlgolia.
InstallingScoutFirst,pullinthepackageinanyLaravel5.3+app:
composerrequirelaravel/scout
Next,addLaravel\Scout\ScoutServiceProvider::class,totheproviderssectionofconfig/app.php.
You’llwanttosetupyourScoutconfiguration.Runphpartisanvendor:publishandpasteyourAlgoliacredentialsinconfig/scout.php.
Finally,installtheAlgoliaSDK:
composerrequirealgolia/algoliasearch-client-php
MarkingYourModelforIndexingInyourmodel(we’lluseReview,forabookreview,forthisexample),importtheLaravel\Scout\Searchabletrait.
YoucandefinewhichpropertiesaresearchableusingthetoSearchableArray()method(itdefaultstomirroringtoArray()),anddefinethenameofthemodel’sindexusingthesearchableAs()method(itdefaultstothetablename).
Scoutsubscribestothecreate/delete/updateeventsonyourmarkedmodels.Whenyoucreate,update,ordeleteanyrows,ScoutwillsyncthosechangesuptoAlgolia.It’lleithermakethosechangessynchronouslywithyourupdatesor,ifyouconfigureScouttouseaqueue,queuetheupdates.
SearchingYourIndexScout’ssyntaxissimple.Forexample,tofindanyReviewwiththewordLlewinit:
Review::search('Llew')->get();
YoucanalsomodifyyourqueriesasyouwouldwithregularEloquentcalls:
//GetallrecordsfromtheReviewthatmatchtheterm"Llew",
//limitedto20perpageandreadingthepagequeryparameter,
//justlikeEloquentpagination
Review::search('Llew')->paginate(20);
//GetallrecordsfromtheReviewthatmatchtheterm"Llew"
//andhavetheaccount_idfieldsetto2
Review::search('Llew')->where('account_id',2)->get();
Whatcomesbackfromthesesearches?AcollectionofEloquentmodels,rehydratedfromyourdatabase.TheIDsarestoredinAlgolia,whichreturnsalistofmatchedIDs;ScoutthenpullsthedatabaserecordsforthoseandreturnsthemasEloquentobjects.
Youdon’thavefullaccesstothecomplexityofSQLWHEREcommands,butitprovidesabasicframeworkforcomparisoncheckslikeyoucanseeinthecodesampleshere.
QueuesandScoutAtthispointyourappwillbemakingHTTPrequeststoAlgoliaoneveryrequestthatmodifiesanydatabaserecords.Thiscanslowdownyourapplicationquickly,whichiswhyScoutmakesiteasytopushallofitsactionsontoaqueue.
Inconfig/scout.php,setqueuetotruesothattheseupdatesaresettobeindexedasynchronously.Yourfull-textindexisnowoperatingunder“eventualconsistency”;yourdatabaserecordswillreceivetheupdatesimmediately,andtheupdatestoyoursearchindexeswillbequeuedandupdatedasfastasyourqueueworkerallows.
PerformOperationsWithoutIndexingIfyouneedtoperformasetofoperationsandavoidtriggeringtheindexinginresponse,wraptheoperationsinthewithoutSyncingToSearch()methodonyourmodel:
Review::withoutSyncingToSearch(function(){
//makeabunchofreviews,e.g.
factory(Review::class,10)->create();
});
ManuallyTriggerIndexingviaCodeIfyouwanttomanuallytriggerindexingyourmodel,youcandoitusingcodeinyourapporviathecommandline.
Tomanuallytriggerindexingfromyourcode,addsearchable()totheendofanyEloquentqueryanditwillindexalloftherecordsthatwerefoundinthatquery:
Review::all()->searchable();
Youcanalsochoosetoscopethequerytoonlythoseyouwanttoindex.However,Scoutissmartenoughtoinsertnewrecordsandupdateoldrecords,soyoumaychoosetojustreindextheentirecontentsofthemodel’sdatabasetable.
Youcanalsorunsearchable()onrelationshipmethods:
$user->reviews()->searchable();
Ifyouwanttounindexanyrecordswiththesamesortofquerychaining,justuseunsearchable()instead:
Review::where('sucky',true)->unsearchable();
ManuallyTriggerIndexingviatheCLIYoucanalsotriggerindexingwithanArtisancommand:
phpartisanscout:importApp\\Review
ThiswillchunkalloftheReviewmodelsandindexthemall.
TestingTestingmostofthesefeaturesisassimpleasjustusingtheminyourtests;noneedtomockorstub.Thedefaultconfigurationwillalreadywork—forexample,takealookatphpunit.xmltoseethatyoursessiondriverandcachedriverhavebeensettovaluesappropriatefortests.
However,thereareafewconveniencemethodsandafewgotchasthatyoushouldknowaboutbeforeyouattempttotestthemall.
FileStorageTestingfileuploadscanbeabitofapain,butfollowthesestepsanditwillbeclear.
UploadingfakefilesFirst,let’slookathowtomanuallycreateaSymfonyUploadedFileobjectforuseinourapplicationtesting(Example14-12).Notethatthisassumeswehaveastorage/testsdirectorywherewe’replacingafilenamedfor-tests.jpgthatwe’lluseforourtests.
Example14-12.CreatingafakeUploadedFilefortestingpublicfunctiontest_file_should_be_stored()
{
$path=storage_path('tests/for-tests.jpg');
$file=newUploadedFile(
$path,//filepath
'for-tests.jpg',//originalfilename
'image/jpg',//MIMEtype
filesize($path),//filesize;besttogetonce&hardcodeintoyourtest,
null,//errorcode
true//whetherwe'reintestmode
);
$this->call('post','upload-route',[],[],['upload'=>$file]);
$this->assertResponseOk();
}
We’vecreatedanewinstanceofUploadedFilethatreferstoourtestingfile,andwecannowuseittotestourroutes.
ReturningfakefilesIfyourrouteisexpectingarealfiletoexist,sometimesthebestwaytomakeittestableistomakethatrealfileactuallyexist.Let’ssayeveryusermusthaveaprofilepicture.
First,let’ssetupthemodelfactoryfortheusertouseFakertomakeacopyofthepicture,asinExample14-13.
Example14-13.ReturningfakefileswithFaker$factory->define(User::class,function(Faker\Generator$faker){
return[
'picture'=>$faker->file(
storage_path('tests'),//sourcedirectory
storage_path('app'),//targetdirectory
false//returnjustfilename,notfullpath
),
'name'=>$faker->name,
];
});
Faker ’sfile()methodpicksarandomfilefromthesourcedirectoryandcopiesittothetargetdirectory,andthenreturnsthefilename.Sowe’vejustpickedarandomfilefromthestorage/testsdirectory,copiedittothestorage/appdirectory,andsetitsfilenameasthepicturepropertyonourUser.AtthispointwecanuseaUserintestsonroutesthatexpecttheUsertohaveapicture,asseeninExample14-14.
Example14-14.Assertingthatanimage’sURLisechoedpublicfunctiontest_user_profile_picture_echoes_correctly()
{
$user=factory(User::class)->create();
$this->visit("users/{$user->id}");
$this->see($user->picture);
}
Ofcourse,inmanycontextsyoucanjustgeneratearandomstringtherewithoutevencopyingafile.Butifyourroutescheckforthefile’sexistenceorrunanyoperationsonthefile,thisisyourbestoption.
SessionIfyouneedtoassertsomethinghasbeensetinthesession,youcanusesomeconveniencemethodsLaravelmakesavailableineverytest.Allofthesemethodsareavailableinyourtestsonthe$thisobject:
assertSessionHas($key,$value=null)
Assertsthatthesessionhasavalueforaparticularkey,and,ifthesecondparameterispassed,thatthatkeyisaparticularvalue:
publicfunctiontest_some_thing()
{
//dostuff
$this->assertSessionHas('key','value');
}
assertSessionHasAll(array$bindings)
Ifpassedanarrayofkey/valuepairs,assertsthatallofthekeysareequaltoallofthevalues.Ifoneormoreofthearrayentriesisjustavalue(withPHP’sdefaultnumerickey),itwilljustbecheckedforexistenceinthesession:
$check=[
'has',
'hasWithThisValue'=>'thisValue',
]
$this->assertSessionHasAll($check);
assertSessionMissing($key)
Assertsthatthesessiondoesnothaveavalueforaparticularkey.
assertSessionHasErrors($bindings=[],$format=null)
Assertsthatthesessionhasanerrorsvalue.ThisisthekeyLaravelusestosenderrorsbackfromvalidationfailures.Ifthearraycontainsjustkeys,itwillcheckthaterrorsaresetwiththosekeys:
$this->post('test-route',['failing'=>'data']);
$this->assertSessionHasErrors(['name','email']);
Youcanalsopassvaluesforthosekeys,andoptionallya$format,tocheckthatthemessagesforthoseerrorscamebackthewayyouexpected:
$this->post('test-route',['failing'=>'data']);
$this->assertSessionHasErrors([
'email'=>'<strong>Theemailfieldisrequired.</strong>'
],'<strong>:message</strong>');
assertHasOldInput()
Sinceyoucanflashthepreviouspage’sinputtothesession,youmaywanttoassertthatit’sbeenflashedcorrectly:
$this->post('test-route',['failing'=>'data']);
$this->assertHasOldInput();
CacheThere’snothingspecialabouttestingyourfeaturesthatusecache—justdoit:
Cache::put('key','value',15);
$this->assertEquals('value',Cache::get('key'));
Laravelusesthe“array”cachedriverbydefaultinyourtestingenvironment,whichjuststoresyourcachevaluesinmemory.
CookiesIfyouneedtosetacookiebeforetestingarouteinyourapplicationtests,youcanmanuallypasscookiestooneoftheparametersofthecall()method.Tolearnmoreaboutcall(),checkoutChapter12.
EXCLUDINGYOURCOOKIEFROMENCRYPTIONDURINGTESTING
Yourcookieswon’tworkinyourtestsunlessyouexcludethemfromLaravel’scookieencryptionmiddleware.YoucandothisbyteachingtheEncryptCookiesmiddlewaretotemporarilydisableitselfforthatcookie:
useIlluminate\Cookie\Middleware\EncryptCookies;
...
$this->app->resolving(
EncryptCookies::class,
function($object){
$object->disableFor('cookie-name');
}
);
//...runtest
ThatmeansyoucansetandcheckagainstacookiewithsomethinglikeExample14-15.
Example14-15.Runningunittestsagainstcookiespublicfunctiontest_cookie()
{
$this->app->resolving(EncryptCookies::class,function($object){
$object->disableFor('my-cookie');
});
$this->call('get','route-echoing-my-cookie-value',[],['my-cookie'=>'baz']);
$this->see('baz');
}
If,forsomereason,you’drathernotdisableencryption,youcaninsteadsettheencryptedvalueofthecookielikeinExample14-16.
Example14-16.ManuallyencryptingacookiebeforesettingituseIlluminate\Contracts\Encryption\Encrypter;
...
publicfunctiontest_cookie()
{
$encryptedBaz=app(Encrypter::class)->encrypt('baz');
$this->call(
'get',
'route-echoing-my-cookie-value',
[],
['my-cookie'=>$encryptedBaz]
);
$this->see('baz');
}
Ifyouwanttotestthataresponsehasacookieset,youcanuseeitherseeCookie()totestforthecookie:
$this->visit('cookie-setting-route');
$this->seeCookie('cookie-name');
orseePlainCookie()totestforthecookieandtoassertthatit’snotencrypted.
TL;DRLaravelprovidessimpleinterfacestomanycommonstorageoperations:filesystemaccess,sessions,cookies,thecache,andsearch.EachoftheseAPIsisthesameregardlessofwhichprovideryouuse,whichLaravelenablesbyallowingmultiple“drivers”toservethesamepublicinterface.Thismakesitsimpletoswitchprovidersdependingontheenvironment,orastheneedsoftheapplicationchange.
Chapter15.MailandNotifications
Sendinganapplication’susersnotificationsviaemail,Slack,SMS,oranothernotificationsystemisacommonbutsurprisinglycomplexrequirement.Laravel’smailandnotificationfeaturesprovideconsistentAPIsthatabstractawaytheneedtopaytoocloseattentiontoanyparticularprovider.JustlikeinChapter14,you’llwriteyourcodeonceandchooseattheconfigurationlevelwhichprovideryou’llusetosendyouremailornotifications.
MailLaravel’smailfunctionalityisaconveniencelayerontopofSwiftMailer,andoutoftheboxLaravelcomeswithdriversforMailgun,Mandrill,Sparkpost,SES,SMTP,PHPMail,andSendmail.
Forallofthecloudservices,you’llsetyourauthenticationinformationinconfig/services.php.However,ifyoutakealookyou’llseetherearealreadykeysthere—andinconfig/mail.php—thatallowyoutocustomizeyourapplication’smailfunctionalityin.envusingvariableslikeMAIL_DRIVERandMAILGUN_SECRET.
CLOUD-BASEDAPIDRIVERDEPENDENCIESIfyou’reusinganyofthecloud-basedAPIdrivers,you’llneedtobringGuzzleinwithComposer.Youcanrunthefollowingcommandtoaddit:
composerrequireguzzlehttp/guzzle:"~5.3|~6.0"
IfyouusetheSESdriver,you’llneedtorunthefollowingcommand:
composerrequireaws/aws-sdk-php:~3.0
“Classic”MailTherearetwodifferentsyntaxesinLaravelforsendingmail:classicandmailable.Themailablesyntaxisthepreferredsyntaxfrom5.3onward,sowe’regoingtofocusonthatinthisbook.Butforthosewhoareworkingin5.1or5.2,here’saquicklookathowtheclassicsyntax(Example15-1)works.
Example15-1.Basic“classic”mailsyntaxMail::send(
'emails.assignment',
['trainer'=>$trainer,'trainee'=>$trainee],
function($m)use($trainer,$trainee){
$m->from($trainer->email,$trainer->name);
$m->to($trainee->email,$trainee->name)->subject('ANewAssignment!');
}
);
ThefirstparameterofMail::send()isthenameoftheview.Remember,emails.assignmentmeansresources/views/emails/assignment.blade.phporresources/views/emails/assignment.php.
Thesecondparameterisanarrayofdatathatyouwanttopasstotheview.
Thethirdparameterisaclosure,inwhichyoudefinehowandwheretosendtheemail:from,to,CC,BCC,subject,andanyothermetadata.Makesuretouseanyvariablesyouwantaccesstowithintheclosure.Andnotethattheclosureispassedoneparameter,whichwe’venamed$m;thisisthemessageobject.
Takealookattheolddocstolearnabouttheclassicmailsyntax.
Basic“Mailable”MailUsageLaravel5.3introducedanewmailsyntaxcalledthe“mailable.”Itworksthesameastheclassicmailsyntax,butinsteadofdefiningyourmailmessagesinaclosure,youinsteadcreateaspecificPHPclasstorepresenteachmail.
Tomakeamailable,usethemake:mailArtisancommand:
phpartisanmake:mailAssignment
Example15-2showswhatthatclasslookslike.
Example15-2.AnautogeneratedmailablePHPclass<?php
namespaceApp\Mail;
useIlluminate\Bus\Queueable;
useIlluminate\Mail\Mailable;
useIlluminate\Queue\SerializesModels;
useIlluminate\Contracts\Queue\ShouldQueue;
classAssignmentextendsMailable
{
useQueueable,SerializesModels;
/**
*Createanewmessageinstance.
*
*@returnvoid
*/
publicfunction__construct()
{
//
}
/**
*Buildthemessage.
*
*@return$this
*/
publicfunctionbuild()
{
return$this->view('view.name');
}
}
Thisclassprobablylooksfamiliar—it’sshapedalmostthesameasaJob.ItevenimportstheQueuabletraitforqueuingyourmailandtheSerializesModelstraitsoanyEloquentmodelsyoupasstotheconstructorwillbeserializedcorrectly.
So,howdoesthiswork?Thebuild()methodonamailableiswhereyou’regoingtodefinewhichviewtouse,whatthesubjectis,andanythingelseyouwanttotweakaboutthemailexceptwhoit’sgoingto.Theconstructoristheplacewhereyou’llpassinanydata,andanypublicpropertiesonyourmailableclasswillbeavailabletothetemplate.
TakealookatExample15-3toseehowwemightupdatetheautogeneratedmailableforourassignmentexample.
Example15-3.Asamplemailable<?php
namespaceApp\Mail;
useIlluminate\Bus\Queueable;
useIlluminate\Mail\Mailable;
useIlluminate\Queue\SerializesModels;
useIlluminate\Contracts\Queue\ShouldQueue;
classAssignmentextendsMailable
{
useQueueable,SerializesModels;
public$trainer;
public$trainee;
publicfunction__construct($trainer,$trainee)
{
$this->trainer=$trainer;
$this->trainee=$trainee;
}
publicfunctionbuild()
{
return$this->subject('Newassignmentfrom'.$this->trainer->name)
->view('emails.assignment');
}
}
Example15-4showshowtosendamailable.
Example15-4.Afewwaystosendmailables//Simplesend
Mail::to($user)->send(newAssignment($trainer,$trainee));
//WithCC/BCC/etc.
Mail::to($user1))
->cc($user2)
->bcc($user3)
->send(newAssignment($trainer,$trainee));
//Withcollections
Mail::to('[email protected]')
->bcc(User::all())
->send(newAssignment($trainer,$trainee))
MailTemplatesMailtemplatesarejustlikeanyothertemplate.Theycanextendothertemplates,usesections,parsevariables,containconditionalorloopingdirectives,anddoanythingelseyoucandoinanormalBladeview.
TakealookatExample15-5toseeapossibleemails.assignmentstemplateforExample15-3.
Example15-5.Sampleassignmentemailtemplate<!--resources/views/emails/assignment.blade.php-->
<p>Hey{{$trainee->name}}!</p>
<p>Youhavereceivedanewtrainingassignmentfrom<b>{{$trainer->name}}</b>.
Checkoutyour<ahref="{{route('training-dashboard')}}">training
dashboard</a>now!</p>
InExample15-3,both$trainerand$traineearepublicpropertiesonyourmailable,whichmakesthemavailabletothetemplate.
Ifyouwanttoexplicitlydefinewhichvariablesarepassedtothetemplate,youcanchainthewith()methodontoyourbuild()callasinExample15-6.
Example15-6.Customizingthetemplatevariablespublicfunctionbuild()
{
return$this->subject('Youhaveanewassignment!')
->view('emails.assignment')
->with(['assignment'=>$this->event->name]);
}
HTMLVERSUSPLAIN-TEXTEMAILSSofarwe’veusedtheview()methodinourbuild()callstacks.Thisexpectsthetemplatewe’rereferencingtopassbackHTML.Ifyou’dliketopassaplain-textversion,thetext()methoddefinesyourplain-textview:
publicfunctionbuild()
{
return$this->view('emails.reminder')
->text('emails.reminder_plain');
}
MethodsAvailableinbuild()Hereareafewofthemethodsavailabletoyoutocustomizeyourmessageinthebuild()methodofyourmailable:
from($address,$name=null)
Setsthe“from”nameandaddress—representstheauthor
subject($subject)
Setstheemailsubject
attach($pathToFile,array$options=[])
Attachesafile;validoptionsaremimeforMIMEtypeandasfordisplayname
attachData($data,$name,array$options=[])
Attachesafilefromarawstring;sameoptionsasattach()
priority($priority)
Settheemail’spriority,where1isthehighestand5isthelowest
Finally,ifyouwanttoperformanymanualmodificationsontheunderlyingSwiftmessage,youcandothatusingwithSwiftMessage(),asshowninExample15-7.
Example15-7.ModifyingtheunderlyingSwiftMessageobjectpublicfunctionbuild()
{
return$this->subject('Howdy!')
->withSwiftMessage(function($swift){
$swift->setReplyTo('[email protected]');
})
->view('emails.howdy');
}
AttachmentsandInlineImagesExample15-8showstwooptionsforhowtoattachfilesorrawdatatoyouremail.
Example15-8.Attachingfilesordatatomailables//Attachafileusingthelocalfilename
publicfunctionbuild()
{
return$this->subject('Yourwhitepaperdownload')
->attach(storage_path('pdfs/whitepaper.pdf'),[
'mime'=>'application/pdf',//Optional
'as'=>'whitepaper-barasa.pdf'//Optional
])
->view('emails.whitepaper');
}
//Attachafilepassingtherawdata
publicfunctionbuild()
{
return$this->subject('Yourwhitepaperdownload')
->attachData(
file_get_contents(storage_path('pdfs/whitepaper.pdf')),
'whitepaper-barasa.pdf',
[
'mime'=>'application/pdf'//Optional
]
)
->view('emails.whitepaper');
}
AndyoucanseehowtoembedimagesdirectlyintoyouremailinExample15-9.
Example15-9.Inliningimages<!--emails/image.blade.php--!>
Hereisanimage:
<imgsrc="{{$message->embed(storage_path('embed.jpg'))}}">
Or,thesameimageembeddingthedata:
<imgsrc="{{$message->embedData(
file_get_contents(storage_path('embed.jpg')),'embed.jpg'
)}}">
QueuesSendingemailisatime-consumingtaskthatcancauseapplicationstoslowdown,soit’scommontomovesendingemailtoabackgroundqueue.It’ssocommon,infact,thatLaravelhasasetofbuilt-intoolstomakeiteasiertoqueueyourmessageswithoutwritingqueuejobsforeachemail.
CONFIGURINGQUEUESEverythingwe’llcoverhererequiresyourqueuestobeconfiguredcorrectly.TakealookatChapter16tolearnmoreabouthowqueuesworkandhowtogetthemrunninginyourapplication.
queue()Toqueueamailobjectinsteadofsendingitimmediately,simplypassyourmailableobjecttoMail::queue()insteadofMail::send():
Mail::queue(newAssignment($trainer,$trainee));
later()Mail::later()worksthesameasMail::queue(),butitallowsyoutoaddadelay—eitherinminutes,orataspecifictimebypassinganinstanceofDateTimeorCarbon—towhentheemailwillbepulledfromthequeueandsent:
$when=Carbon::now()->addMinutes(30);
Mail::later($when,newAssignment($trainer,$trainee));
SpecifyingthequeueorconnectionForbothqueue()andlater(),ifyou’dliketospecifywhichqueueorqueueconnectionyourmailisaddedto,usetheonConnection()andonQueue()methodsonyourmailableobject:
$message=(newAssignment($trainer,$trainee))
->onConnection('sqs')
->onQueue('emails');
Mail::to($user)->queue($message);
LocalDevelopmentThisisallwellandgoodforsendingmailinyourproductionenvironments.Buthowdoyoutestthisallout?Therearethreeprimarytoolsyou’llwanttoconsider:Laravel’slogdriver,aSoftwareasaService(SaaS)appnamedMailtrap,andthe“universalto”configurationoption.
ThelogdriverLaravelprovidesalogdriverthatlogseveryemailyoutrytosendtoyourlocallaravel.logfile(whichis,bydefault,instorage/logs).
Ifyouwanttousethis,edit.envandsetMAIL_DRIVERtolog.Nowopenuportailstorage/logs/laravel.logandsendanemailfromyourapp.You’llseesomethinglikethis:
Message-ID:<04ee2e97289c68f0c9191f4b04fc0de1@localhost>
Date:Tue,17May201602:52:46+0000
Subject:Welcometoourapp!
From:MattStauffer<[email protected]>
MIME-Version:1.0
Content-Type:text/html;charset=utf-8
Content-Transfer-Encoding:quoted-printable
Welcometoourapp!
Mailtrap.ioMailtrapisaserviceforcapturingandinspectingemailsindevelopmentenvironments.YousendyourmailtotheMailtrapserversviaSMTP,butinsteadofsendingthoseemailsofftotheintendedrecipients,Mailtrapcapturesthemallandprovidesyouwithaweb-basedemailclientforinspectingthem,regardlessofwhichemailaddressisinthetofield.
TosetupMailtrap,signupforafreeMailtrapaccountandvisitthebasedashboardforyourdemo.CopyyourusernameandpasswordfromtheSMTPcolumn.
Nowedityourapp’s.envfileandsetthefollowingvaluesinthemailsection:
MAIL_DRIVER=smtp
MAIL_HOST=mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=your_username_from_mailtrap_here
MAIL_PASSWORD=your_password_from_mailtrap_here
MAIL_ENCRYPTION=null
Now,anyemailyousendfromyourappwillshowupinyourMailtrapinbox.
UniversaltoIfyou’dliketoinspecttheemailsinyourpreferredclient,youcanoverridethetofieldoneachmessagewiththe“universalto”configurationsetting.Tosetthisup,adda“to”keytoyourconfig/mail.phpfilethatlookssomethinglikethis:
'to'=>[
'address'=>'[email protected]',
'name'=>'MattTestingMyApplication'
],
Notethatyou’llneedtoactuallysetuparealemaildriverwithsomethinglikeMailgunorSendmailinordertousethis.
NotificationsMostofthemailthat’ssentfromwebappsreallyhasthepurposeofnotifyingusersthataparticularactionhashappenedorneedstohappen.Asusers’communicationpreferencesgrowmoreandmorediverse,wegatherevermore—andmoredisparate—packagestocommunicateviaSlack,SMS,andothermeans.
Laravel5.3introducedanewconceptinLaravelcalled,fittingly,notifications.Justlikemailables,anotificationisaPHPclassthatrepresentsasinglecommunicationthatyoumightwanttosendtoyourusers.Fornow,let’simaginewe’renotifyingusersofourphysicaltrainingappthattheyhaveanewworkoutavailableonourapp.
Eachclassrepresentsalloftheinformationnecessarytosendnotificationstoyourusersusingoneormanynotificationchannels.Asinglenotificationcouldsendanemail,sendanSMSviaNexmo,sendaWebSocketsping,addarecordtoadatabase,sendamessagetoaSlackchannel,andmuchmore.
So,let’screateournotification:
phpartisanmake:notificationWorkoutAvailable
Example15-10showswhatthatgivesus.
Example15-10.Anautogeneratednotificationclass<?php
namespaceApp\Notifications;
useIlluminate\Bus\Queueable;
useIlluminate\Notifications\Notification;
useIlluminate\Contracts\Queue\ShouldQueue;
useIlluminate\Notifications\Messages\MailMessage;
classWorkoutAvailableextendsNotification
{
useQueueable;
/**
*Createanewnotificationinstance.
*
*@returnvoid
*/
publicfunction__construct()
{
//
}
/**
*Getthenotification'sdeliverychannels.
*
*@parammixed$notifiable
*@returnarray
*/
publicfunctionvia($notifiable)
{
return['mail'];
}
/**
*Getthemailrepresentationofthenotification.
*
*@parammixed$notifiable
*@return\Illuminate\Notifications\Messages\MailMessage
*/
publicfunctiontoMail($notifiable)
{
return(newMailMessage)
->line('Theintroductiontothenotification.')
->action('NotificationAction','https://laravel.com')
->line('Thankyouforusingourapplication!');
}
/**
*Getthearrayrepresentationofthenotification.
*
*@parammixed$notifiable
*@returnarray
*/
publicfunctiontoArray($notifiable)
{
return[
//
];
}
}
Wecanlearnafewthingshere.First,we’regoingtopassinrelevantdatatotheconstructor.Second,there’savia()methodthatallowsustodefine,foragivenuser,whichnotificationchannelstouse($notifiablerepresentswhateverentitiesyouwanttonotifyinyoursystem;formostapps,it’llbeauser,butthat’snotalwaysthecase).Andthird,thereareindividualmethodsforeachnotificationchannelthatallowyoutospecificallydefinehowtosendoneofthesenotificationsthroughthatchannel.
WHENWOULDA$NOTIFIABLENOTBEAUSER?Whilethemostcommonnotificationtargetswillbeusers,it’spossibleyoumaywanttonotifysomethingelse.Thismaysimplybebecauseyourapplicationhasmultipleusertypes—so,youmightwanttobeabletonotifyboth“trainers”and“trainees.”Butyoualsomightfindyourselfwantingtonotifya“group,”a“company,”ora“server.”
So,let’smodifythisclassforourWorkoutAvailableexample.TakealookatExample15-11.
Example15-11.OurWorkoutAvailablenotificationclass...
classWorkoutAvailableextendsNotification
{
useQueueable;
public$workout;
publicfunction__construct($workout)
{
$this->workout=$workout;
}
publicfunctionvia($notifiable)
{
//Thismethoddoesn'texistontheUser...we'regoingtomakeitup
return$notifiable->preferredNotificationChannels();
}
publicfunctiontoMail($notifiable)
{
return(newMailMessage)
->line('Youhaveanewworkoutavailable!')
->action('Checkitoutnow',route('workout',[$this->workout]))
->line('Thankyoufortrainingwithus!');
}
publicfunctiontoArray($notifiable)
{
return[];
}
}
Definingthevia()MethodforYourNotifiablesAsyoucanseeinExample15-11,we’resomehowresponsiblefordeciding,foreachnotificationandeachnotifiable,whichnotificationchannelswe’regoingtouse.
YoucouldjustsendeverythingasmailorjustsendeverythingasSMS(Example15-12).
Example15-12.Simplestpossiblevia()methodpublicfunctionvia($notifiable)
{
return'nexmo';
}
YoucouldalsoleteachuserchoosetheironepreferredmethodandsavethatontheUseritself(Example15-13).
Example15-13.Customizingthevia()methodperuserpublicfunctionvia($notifiable)
{
return$notifiable->preferred_notification_channel;
}
Or,asweimaginedinExample15-11,youcouldcreateamethodoneachnotifiablethatallowsforsomecomplexnotificationlogic.Forexample,youcouldnotifytheuserovercertainchannelsduringworkhoursandotherchannelsintheevening.Whatisimportantisthatvia()isaPHPclassmethod,soyoucandowhatevercomplexlogicyouwantthere.
SendingNotificationsTherearetwowaystosendanotification:usingtheNotificationfacade,oraddingtheNotifiabletraittoanEloquentclass(likelyyourUserclass).
SendingnotificationsusingtheNotifiabletraitAnymodelthatimportstheLaravel\Notifications\Notifiabletrait(whichtheApp\Userclassdoesbydefault)hasanotify()methodthatcanbepassedanotification,whichwilllooklikeExample15-14.
Example15-14.SendinganotificationusingtheNotifiabletraituseApp\Notifications\WorkoutAvailable;
...
$user->notify(newWorkoutAvailable($workout));
SendingnotificationswiththeNotificationfacadeTheNotificationfacadeistheclumsierofthetwomethods,sinceyouhavetopassboththenotifiableandthenotification.However,it’shelpfulbecauseyoucanchoosetopassmorethanonenotifiableinatthesametime,likeyoucanseeinExample15-15.
Example15-15.SendingnotificationsusingtheNotificationfacadeuseApp\Notifications\WorkoutAvailable;
...
Notification::send($users,newWorkoutAvailable($workout));
QueueingNotificationsMostofthenotificationdriversneedtosendHTTPrequeststosendtheirnotifications,whichcouldslowdownyouruserexperience,soyouprobablywanttoqueueyournotifications.AllnotificationsimporttheQueuabletraitbydefault,soallyouneedtodoisaddimplementsShouldQueuetoyournotificationandLaravelwillinstantlymoveittoaqueue.
Aswithanyotherqueuedfeatures,you’llneedtomakesureyouhaveyourqueuesettingsconfiguredcorrectlyandaqueueworkerrunning.
Ifyou’dliketodelaythedeliveryofyournotifcation,youcanrunthedelay()methodonthenotification:
$delayUntil=Carbon::now()->addMinutes(15);
$user->notify((newWorkoutAvailable($workout))->delay($delayUntil));
Out-of-the-BoxNotificationTypesOutofthebox,Laravelcomeswithnotificationdriversforemail,database,broadcast,NexmoSMS,andSlack.I’llcovereachbriefly,butI’drecommendreferringtothedocsformorethoroughintroductionstoeach.
It’salsoeasytocreateyourownnotificationdrivers,anddozensofpeoplealreadyhave;youcanfindthematLaravelNotificationChannelswebsite.
EmailnotificationsLet’stakealookathowtheemailfromourearlierexample,Example15-11,isbuilt:
publicfunctiontoMail($notifiable)
{
return(newMailMessage)
->line('Youhaveanewworkoutavailable!')
->action('Checkitoutnow',route('workout',[$this->workout]))
->line('Thankyoufortrainingwithus!');
}
TheresultisshowninFigure15-1.Theemailnotificationsystemputsyourapplication’snameintheheaderoftheemail;youcancustomizethatappnameinthenamekeyofconfig/app.php.
Thisemailisautomaticallysenttotheemailpropertyonthenotifiable,butyoucancustomizethisbehaviorbyaddingamethodtoyournotifiableclassnamedrouteNotificationForMail()thatreturnstheemailaddressyou’dlikeemailnotificationssentto.
Theemail’ssubjectissetbyparsingthenotificationclassnameandconvertingittowords.So,ourWorkoutAvailablenotificationwouldhavethedefaultsubjectofWorkoutAvailable.Wecanalsocustomizethisbychainingthesubject()methodonourMailMessageinthetoMail()method.
Ifyouwanttomodifythetemplates,publishthemandedittoyourheart’scontent:
phpartisanvendor:publish--tag=laravel-notifications
Youcanalsochangethestyleofthedefaulttemplatetobean“error”message,whichusesabitofdifferentlanguageandchangestheprimarybuttoncolortored.Justaddacalltotheerror()methodtoyourMailMessagecallchaininthetoMail()method.
Figure15-1.Anemailsentwiththedefaultnotificationtemplate
DatabasenotificationsYoucansendnotificationstoadatabasetableusingthedatabasenotificationchannel.First,createyourtablewithphpartisannotifications:table.Next,createatoDatabase()methodonyournotificationandreturnanarrayofdatathere.ThisdatawillbeencodedasJSONandstoredinthedatabasetable’sdatacolumn.
TheNotifiabletraitaddsanotificationsrelationshiptoanymodelit’simportedin,allowingyoutoeasilyaccessrecordsinthenotificationstable.Soifyou’reusingdatabasenotifications,youcouldsosomethinglikethis:
User::first()->notifications->each(function($notification){
//dosomething
});
Thedatabasenotificationchannelalsohastheconceptofwhetherornotanotificationis“read.”Youcanscopetoonlythe“unread”notifications:
User::first()->unreadNotifications->each(function($notification){
//dosomething
});
Andyoucanmarkoneorallnotificationsasread:
//Individual
User::first()->notifications->each(function($notification){
if($condition){
$notification->markAsRead();
}
});
//All
User::first()->unreadNotifications->markAsRead();
BroadcastnotificationsThebroadcastchannelsendsnotificationsoutusingLaravel’seventbroadcastingfeatures(Echo).
CreateatoBroadcast()methodonyournotificationandreturnarrayofdata,andifyourappiscorrectlyconfiguredforeventbroadcasting,thatdatawillbebroadcastonaprivatechannelnamed{notifiable}.{id}.The{id}willbetheIDofthenotifiable,and{notifiable}willbethenotifiable’sfullyqualifiedclassname,withtheslashesreplacedbyperiods—forexample,theprivatechannelfortheApp\UserwiththeIDof1willbeApp.User.1.
SMSnotificationsSMSnotificationsaresentviaNexmo,soifyouwanttosendSMSnotifications,signupforaNexmoaccountandfollowtheinstructionsinthedocs.Likewiththeotherchannels,you’llbesettingupatoNexmo()methodandcustomizingtheSMSmessagethere.
SlacknotificationsTheslacknotificationchannelallowsyoutocustomizetheappearanceofyournotificationsandevenattachfilestoyournotifications.Likewiththeotherchannels,you’llsetupatoSlack()methodandcustomizethemessagethere.
TestingLet’stakealookathowtotestmailandnotifications.
MailTherearetwooptionsfortestingmailinLaravel.Ifyou’reusingthetraditionalmailsyntax,I’drecommendusingatoolcalledMailThief,whichAdamWathanwroteforTighten.OnceyoubringMailThiefintoyourapplicationwithComposer,youcanuseMailThief::hijack()inyourteststomakeMailThiefcaptureanycallstotheMailfacadeoranymailerclasses.
MailThiefthenmakesitpossibletomakeassertionsagainstthesenders,recipients,CCandBCCvalues,andevencontentandattachmentsofyourmail.TakealookattheGitHubrepotolearnmore,orbringitintoyourapp:
composerrequiretightenco/mailthief--dev
Ifyouareusingmailables,there’sasimplesyntaxforwritingassertionsagainstyoursentmail(Example15-16).
Example15-16.Assertingagainstmailablespublicfunctiontest_signup_triggers_welcome_email()
{
...
Mail::assertSent(WelcomeEmail::class,function($e){
return$e->subject=='Welcome!';
});
//YoucanalsouseassertSentTo()toexplicitlytesttherecipients
}
NotificationsLaravelprovidesabuilt-insetofassertionsfortestingyournotifications.Example15-17demonstrates.
Example15-17.Assertingnotificationsweresentpublicfunctiontest_new_signups_triggers_admin_notification()
{
...
Notification::assertSentTo($user,NewUsersSignedup::class,
function($n,$channels){
return$n->user->email=='[email protected]'
&&$channels==['mail'];
});
//YoucanalsouseassertNotSentTo()
}
TL;DRLaravel’smailandnotificationfeaturesprovidesimple,consistentinterfacestoavarietyofmessagingsystems.Laravel’smailsystemuses“mailables,”PHPclassesthatrepresentemails,toprovideaconsistentsyntaxtodifferentmaildrivers.Thenotificationsystemmakesiteasytobuildasinglenotificationthatcanbedeliveredinmanydifferentmedia—fromemailstoSMSmessagestophysicalpostcards.
Chapter16.Queues,Jobs,Events,Broadcasting,andtheScheduler
Sofarwe’vecoveredsomeofthemostcommonstructuresthatpowerwebapplications:databases,mail,filesystems,andmore.Eachofthesearecommonacrossamajorityofapplicationsandframeworks.
Laravelalsoprovidesfacilitiesforsomelesscommonarchitecturepatternsandapplicationstructures.Inthischapterwe’llcoverLaravel’stoolsforimplementingqueues,queuedjobs,events,andWebSocketeventpublishing.We’llalsocoverLaravel’sscheduler,whichmakescronathingofthepast.
QueuesTounderstandwhataqueueis,justthinkabouttheideaof“queueingup”inalineatthebank.Eveniftherearemultiplelines—queues—onlyonepersonisbeingservedatatimefromeachqueue,andeachpersonwilleventuallyreachthefrontandbeserved.Insomebanks,it’sastrictfirst-in-first-outsortofpolicy,butinotherbanks,there’snotanexactguaranteethatsomeonewon’tcutaheadofyouinlineatsomepoint.Essentially,someonecangetaddedtothequeue,beremovedfromthequeueprematurely,orbesuccessfully“processed”andthenremoved.Someonemightevenhitthefrontofthequeue,notbeabletobeservedcorrectly,returntothequeueforatime,andthenbeprocessedagain.
Queuesinprogrammingareverysimilar.Yourapplicationaddsa“job”toaqueue,whichisachunkofcodethattellstheapplicationhowtoperformaparticularbehavior.Thensomeotherseparateapplicationstructure,usuallya“queueworker,”takestheresponsibilityforpullingjobsoffofthequeueoneatatimeandperformingtheappropriatebehavior.Queueworkerscandeletethejobs,returnthemtothequeuewithadelay,ormarkthemassuccessfullyprocessed.
LaravelmakesiteasytoserveyourqueuesusingRedis,beanstalkd,Amazon’sSQS(SimpleQueueService),oradatabasetable.Youcanalsochoosethesyncdrivertohavethejobsrunrightinyourapplicationwithoutactuallybeingqueued,orthenulldriverforjobstojustbediscarded;thesetwoareusuallyusedinlocaldevelopmentortestingenvironments.
WhyQueues?Queuesmakeiteasytoremoveacostlyorslowprocessfromanysynchronouscall.Themostcommonexampleissendingmail—doingsocanbeslow,andyoudon’twantyouruserstohavetowaitformailtosendinresponsetotheiractions.Instead,triggera“sendmail”queuedjobandlettheusersgetonwiththeirday.Sometimesyoumaynotjustwanttosaveyouruserstime,butyoumighthaveaprocesslikeacronjoborawebhookthathasalotofworktoprocess;ratherthanlettingitallrunatonce(andpotentiallytimeout),youmaychoosetoqueueitsindividualpiecesandletthequeueworkerprocessthemoneatatime.
Additionally,ifyouhavesomeheavyprocessingthat’smorethanyourservercanhandle,youcanspinupmorethanonequeueworkertoworkthroughyourqueuefasterthanyournormalapplicationservercouldonitsown.
BasicQueueConfigurationLikemanyotherLaravelfeaturesthatabstractmultipleproviders,queueshavetheirowndedicatedconfigfile(config/queue.php)thatallowsyoutosetupmultipledriversanddefinewhichwillbethedefault.Thisisalsowhereyou’llstoreyourSQS,Redis,orbeanstalkdauthenticationinformation.
SIMPLEBEANSTALKDQUEUESONLARAVELFORGEWehaven’tcoveredLaravelForgeinmuchdepth,butit’sahostingserviceprovidedbyTaylorOtwell,thecreatorofLaravel.Everyserveryoucreatehasbeanstalkdconfiguredautomatically,soifyouvisitanysite’sForgeconsole,youcanjustgototheQueueWorkerstabandhitStartWorkerandyou’rereadytousebeanstalkdasyourqueuedriver;youcanleaveallthedefaultsettings,andnootherworkisnecessary.
QueuedJobsRememberourbankanalogy?Eachpersoninthebank“queue”(line)is,inprogrammingterms,ajob.Thisjobcouldbeshapedanyway;itcouldjustbeastring,oranarray,oranobject.InLaravel,it’sacollectionofinformationcontainingthejobname,thedatapayload,thenumberofattemptsthathavebeenmadesofartoprocessthisjob,andsomeothersimplemetadata.
Butyoudon’tneedtoworryaboutthatinyourinteractionswithLaravel.LaravelprovidesastructurecalledaJob,whichisintendedtoencapsulateasingletask—abehaviorthatyourapplicationcanbecommandedtodo—andallowittobeaddedtoandpulledfromaqueue.TherearealsosimplehelperstomakeiteasytoqueueArtisancommandsandmail.
Let’sstartwithanexamplewhere,everytimeauserchangeshisplanwithyourSaaSapp,youwanttorerunsomecalculationsaboutyouroverallprofit.
CreatingajobAsalways,there’sanArtisancommandforthat:
phpartisanmake:jobCrunchReports
TakealookatExample16-1toseewhatyou’llget.
Example16-1.ThedefaulttemplateforjobsinLaravel<?php
useIlluminate\Bus\Queueable;
useIlluminate\Queue\SerializesModels;
useIlluminate\Queue\InteractsWithQueue;
useIlluminate\Contracts\Queue\ShouldQueue;
classCrunchReportsimplementsShouldQueue
{
useInteractsWithQueue,Queueable,SerializesModels;
/**
*Createanewjobinstance.
*
*@returnvoid
*/
publicfunction__construct()
{
//
}
/**
*Executethejob.
*
*@returnvoid
*/
publicfunctionhandle()
{
//
}
}
Asyoucansee,thistemplateimportstheQueueable,InteractsWithQueue,and
SerializesModelstraits,andimplementstheShouldQueueinterface.PriortoLaravel5.3,someofthisfunctionalitycameinthroughtheparentApp\Jobsclass.
Wealsogettwomethodsfromthistemplate:theconstructor,whichyou’llwanttousetoattachdatatothejob,andthehandle()method,whichiswherethejob’slogicshouldreside(andisalsothemethodsignatureyou’llusetoinjectdependencies).
Thetraitsandinterfaceprovidetheclasswiththeabilitytobeaddedto,andinteractwith,thequeue.QueueableallowsyoutospecifyhowLaravelshouldpushthisjobtothequeue;InteractsWithQueueallowseachjob,whilebeinghandled,tocontrolitsrelationshipwiththequeue,includingdeletingorrequeueingitself;andSerializesModelsgivesthejobtheabilitytoserializeanddeserializeEloquentmodels.
SERIALIZINGMODELSTheSerializesModelstraitgivesthejobstheabilitytoserializeinjectedmodelssothatyourjob’shandle()methodwillhaveaccesstothem.However,becauseit’stoodifficulttoreliablyserializeanentireEloquentobject,thetraitensuresthatjusttheprimarykeysofanyattachedEloquentobjectsareserializedwhenthejobispushedontothequeue.Whenthejobisdeserializedandhandled,thetraitpullsthoseEloquentmodelsfreshfromthedatabasebytheirprimarykey.Thismeansthatwhenyourjobrunsitwillbepullingafreshinstanceofthismodel,notwhateverstateitwasinwhenyouqueuedthejob.
Let’sfilloutthemethodsforoursampleclass,asinExample16-2.
Example16-2.Anexamplejob...
useApp\ReportGenerator;
useIlluminate\Log\WriterasLogger;
classCrunchReportsimplementsShouldQueue
{
useInteractsWithQueue,SerializesModels;
protected$user;
publicfunction__construct($user)
{
$this->user=$user;
}
publicfunctionhandle(ReportGenerator$generator,Logger$logger)
{
$generator->generateReportsForUser($this->user);
$logger->info('Generatedreports.');
}
}
We’reexpectingtheUserinstancetobeinjectedwhenwecreatethejob,andthenwhenit’shandledwe’retypehintingaReportGeneratorclass(whichwepresumablywrote)andaLogger(whichLaravelprovides).Laravelwillreadbothtypehintsandinjectthosedependenciesautomatically.
PushingajobontoaqueueTherearetwoprimarywaysyoucanpushajobontoaqueue:theglobaldispatch()helperandthemethodsprovidedbytheDispatchesJobstrait,whichisimportedbydefaultineverycontroller.
Witheach,createaninstanceofyourjob,attachanynecessarydatabypassingittotheconstructor,andpassittothedispatch()method(seeExample16-3).
Example16-3.Dispatchingjobs//Inacontroller
publicfunctionindex()
{
$user=auth()->user();
$this->dispatch(new\App\Jobs\CrunchReports($user));
}
//Elsewhere
dispatch(new\App\Jobs\CrunchReports($user));
Therearethreesettingsyoucancontrolinordertocustomizeexactlyhowyoudispatchajob:theconnection,thequeue,andthedelay.
CustomizingtheconnectionIfyoueverhavemultiplequeueconnectionsinplaceatonce,youcancustomizetheconnectionbyrunningonConnection()onyourinstantiatedjob:
dispatch((newDoThingJob)->onConnection('redis'));
CustomizingthequeueWithinqueueservers,youcanspecifywhichnamedqueueyou’repushingajobonto.Forexample,youmaydifferentiateyourqueuesbasedontheirimportance,namingonelowandonehigh.
Youcancustomizewhichqueueyou’repushingajobontowiththeonQueue()method:
dispatch((newDoThingJob)->onQueue('high'));
CustomizingthedelayYoucancustomizetheamountoftimeyourqueueworkersshouldwaitbeforeprocessingajobwiththedelay()method,whichacceptsanintegerrepresentingthenumberofsecondstodelayajob:
//Delaysoneminutebeforereleasingthejobtoqueueworkers
dispatch((newDoThingJob)->delay(60));
NotethatAmazonSQSdoesn’tallowdelayslongerthan15minutes.
RunningaQueueWorkerSowhatisaqueueworker,andhowdoesitwork?InLaravel,it’sanArtisancommandthatstaysrunningforever(untilit’sstoppedmanually)andtakestheresponsibilityforpullingdownjobsfromyourqueueandrunningthem:
phpartisanqueue:work
Thiscommandstartsadaemon“listening”toyourqueue;everytimetherearejobsonthequeue,itwillpulldownthefirstjob,handleit,deleteit,andmoveontothenext.Ifatanypointtherearenojobs,it“sleeps”foraconfigurableamountoftimebeforecheckingagaintoseeifthereareanymorejobs.
Youcandefinehowmanysecondsajobshouldbeabletorunbeforethequeuelistenerstopsit(--timeout),howmanysecondsthelistenershould“sleep”whentherearenojobsleft(--sleep),howmanytrieseachjobshouldbeallowedbeforebeingdeleted(--tries),whichconnectiontheworkershouldlistento(thefirstparameterafterqueue:work),andwhichqueuesitshouldlistento(--queue=):
phpartisanqueue:workredis--timeout=60--sleep=15--tries=3
--queue=high,medium
Youcanalsoprocessjustasinglejobwithphpartisanqueue:work.
HandlingErrorsSo,whathappenswhensomethinggoeswrongwithyourjobwhenit’sinthemiddleofprocessing?
ExceptionsinhandlingIfanexceptionisthrown,thequeuelistenerwillreleasethatjobbackontothequeue.Thatjobwillberereleasedtobeprocessedagainandagainuntilitisabletofinishsuccessfullyoruntilithasbeenattemptedthemaximumnumberoftimesallowedbyyourqueuelistener.
LimitingthenumberoftriesThemaximumnumberoftriesisdefinedbythe--triesswitchpassedtothequeue:listenorqueue:workArtisancommands.
THEDANGEROFINFINITERETRIESIfyoudon’tset--tries,orifyousetitto0,thequeuelistenerwillallowforinfiniteretries.Thatmeansifthereareanycircumstancesinwhichajobcouldjustneverbesatisfied—forexample,ifitreliesonatweetthathassincebeendeleted—yourappwillslowlycrawltoahaltasitforeverretriesuncompletablejobs.
ThedocumentationandLaravelForgebothshow3asthedefaultstartingpointforthemaximumnumberofretries.So,incaseofconfusion,startthereandadjust:
phpartisanqueue:listen--tries=3
Ifatanypointyou’dliketocheckhowmanytimesajobhasbeenattemptedalready,usetheattempts()methodonthejobitself,asinExample16-4.
Example16-4.Checkinghowmanytimesajobhasalreadybeentriedpublicfunctionhandle()
{
...
if($this->attempts()>3){
//
}
}
HandlingfailedjobsOnceajobhasexceededitsallowablenumberofretries,it’sconsidereda“failed”job.Beforeyoudoanythingelse—evenifallyouwanttodoislimitthenumberoftimesajobcanbetried—you’llneedtocreatethe“failedjobs”databasetable.
There’sanArtisancommandtocreatethemigration(andyou’llthenwanttomigrate):
phpartisanqueue:failed-table
phpartisanmigrate
Anyjobthathassurpasseditsmaximumnumberofallowedattemptswillbedumpedthere.Buttherearequiteafewthingsyoucandowithyourfailedjobs.
First,youcandefineafailed()methodonthejobitself,whichwillrunwhenthatjobfails(seeExample16-5).
Example16-5.Definingamethodtorunwhenajobfails...
classCrunchReportsimplementsShouldQueue
{
...
publicfunctionfailed()
{
//Dowhateveryouwant
}
}
Next,youcanregisteraglobalhandlerforfailedjobs.Somewhereintheapplication’s
bootstrap—ifyoudon’tknowwheretoputit,justputitintheboot()methodofAppServiceProvider—placethecodeinExample16-6codetodefinealistener.
Example16-6.Registeringaglobalhandlertohandlefailedjobs//Someserviceprovider
useIlluminate\Support\Facades\Queue;
...
publicfunctionboot()
{
Queue::failing(function($connection,$job,$data){
//Dowhateveryouwant
});
}
ThereisalsoasuiteofArtisantoolsforinteractingwiththefailedjobstable.
queue:failedshowsyoualistofyourfailedjobs:
phpartisanqueue:failed
Thelistwilllooksomethinglikethis:
+----+------------+---------+----------------------+---------------------+
|ID|Connection|Queue|Class|FailedAt|
+----+------------+---------+----------------------+---------------------+
|9|database|default|App/Jobs/AlwaysFails|2016-01-2603:42:55|
+----+------------+---------+----------------------+---------------------+
Fromthere,youcangrabtheIDofanyindividualfailedjobandretryitwithqueue:retry:
phpartisanqueue:retry9
Ifyou’dratherretryallofthejobs,passallinsteadofanID:
phpartisanqueue:retryall
Youcandeleteanindividualfailedjobwithqueue:forget:
phpartisanqueue:forget5
Andyoucandeleteallofyourfailedjobswithqueue:flush:
phpartisanqueue:flush
ControllingtheQueueSometimes,fromwithinthehandlingofajob,you’llwanttoaddconditionsthatwillpotentiallyeitherreleasethejobtoberestartedlaterordeletethejobforever.
Toreleaseajobbackintothequeue,usetherelease()command,asinExample16-7.
Example16-7.Releasingajobbackontothequeuepublicfunctionhandle()
{
...
if(condition){
$this->release($numberOfSecondsToDelayBeforeRetrying);
}
}
Ifyouwanttodeleteajobduringitshandling,youcanjustreturnatanypoint,asseeninExample16-8;that’sthesignaltothequeuethatthejobwashandledappropriatelyandshouldnotbereturnedtothequeue.
Example16-8.Deletingajobpublicfunctionhandle()
{
...
if($jobShouldBeDeleted){
return;
}
}
QueuesSupportingOtherFunctionsTheprimaryuseforqueuesistopushjobsonto,butyoucanalsoqueuemailusingtheMail::queuefunctionality.Youcanlearnmoreaboutthisin“queue()”.YoucanalsoqueueArtisancommands,whichwecoveredinChapter7.
EventsWithjobs,thecallingcodeinformstheapplicationthatitshoulddosomething:CrunchReports,orNotifyAdminOfNewSignup.
Withanevent,thecallingcodeinsteadinformstheapplicationthatsomethinghappened:UserSubscribed,orUserSignedUp,orContactWasAdded.Eventsarenotificationsthatsomethinghastakenplace.
Someoftheseeventsmaybe“fired”bytheframeworkitself.Forexample,Eloquentmodelsfireeventswhentheyaresaved,orcreated,ordeleted.Butsomeeventsarealsomanuallytriggeredbytheapplication’scode.
Aneventbeingfireddoesn’tdoanythingonitsown.However,youcanbindeventlisteners,whosesolepurposeistolistenforthebroadcastingofspecificeventsandtoactinresponse.Anyeventcanhaveanywherefromzerotomanyeventlisteners.
Laravel’seventsarestructuredliketheobserver,or“pub/sub,”pattern.Manyeventsarefiredoutintotheapplication;somemayneverbelistenedfor,andothersmayhaveadozenlisteners.Theeventsdon’tknoworcare.
FiringanEventTherearethreewaystofireanevent.YoucanusetheEventfacade,injecttheDispatcher,orusetheevent()globalhelper:
Event::fire(newUserSubscribed($user,$plan));
//or
$dispatcher=app(Illuminate\Contracts\Events\Dispatcher);
$dispatcher->fire(newUserSubscribed($user,$plan));
//or
event(newUserSubscribed($user,$plan));
Ifindoubt,I’drecommendusingtheglobalhelperfunction.
Tocreateaneventtofire,usethemake:eventArtisancommand:
phpartisanmake:eventUserSubscribed
That’llmakeafilethatlookssomethinglikeExample16-9.
Example16-9.ThedefaulttemplateforaLaravelevent<?php
namespaceApp\Events;
useIlluminate\Broadcasting\Channel;
useIlluminate\Queue\SerializesModels;
useIlluminate\Broadcasting\PrivateChannel;
useIlluminate\Broadcasting\PresenceChannel;
useIlluminate\Broadcasting\InteractsWithSockets;
useIlluminate\Contracts\Broadcasting\ShouldBroadcast;
classUserSubscribed
{
useInteractsWithSockets,SerializesModels;
/**
*Createaneweventinstance.
*
*@returnvoid
*/
publicfunction__construct()
{
//
}
/**
*Getthechannelstheeventshouldbebroadcaston.
*
*@returnChannel|array
*/
publicfunctionbroadcastOn()
{
returnnewPrivateChannel('channel-name');
}
}
Let’stakealookatwhatwegethere.SerializesModelsworksjustlikewithjobs;itallowsyoutoacceptEloquentmodelsasparameters.InteractsWithSockets,ShouldBroadcast,andthebroadcastOn()methodprovidethebackingfunctionalityforbroadcastingeventsusing
WebSockets,whichwe’llcoverinabit.
Itmightseemstrangethatthere’snohandle()orfire()methodhere.Butremember,thisobjectexistsnottodetermineaparticularaction,butjusttoencapsulatesomedata.Thefirstpieceofdataisitsname;UserSubscribedtellsusthataparticulareventhappened(ausersubscribed).Therestofthedataisanydatawepassintotheconstructorandassociatewiththisentity.
Example16-10showswhatwemightwanttodowithourUserSubscribedevent.
Example16-10.Injectingdataintoanevent...
classUserSubscribed
{
useInteractsWithSockets,SerializesModels;
public$user;
public$plan;
publicfunction__construct($user,$plan)
{
$this->user=$user;
$this->plan=$plan;
}
}
Nowwehaveanobjectthatappropriatelyrepresentstheeventthathappened:$event->usersubscribedtothe$event->planplan.
ListeningforanEventWehaveanevent,andtheabilitytofireit.Nowlet’slookathowtolistenforit.
First,we’llcreateaneventlistener.Let’ssaywewanttoemailtheapp’sownereverytimeanewusersubscribes:
phpartisanmake:listenerEmailOwnerAboutSubscription--event=UserSubscribed
ThatgivesusthefileinExample16-11.
Example16-11.ThedefaulttemplateforaLaraveleventlistener<?php
namespaceApp\Listeners;
useApp\Events\UserSubscribed;
useIlluminate\Queue\InteractsWithQueue;
useIlluminate\Contracts\Queue\ShouldQueue;
classEmailOwnerAboutSubscription
{
/**
*Createtheeventlistener.
*
*@returnvoid
*/
publicfunction__construct()
{
//
}
/**
*Handletheevent.
*
*@paramUserSubscribed$event
*@returnvoid
*/
publicfunctionhandle(UserSubscribed$event)
{
//
}
}
Thisiswheretheactionhappens—wherethehandle()methodlives.ThismethodexpectstobepassedaneventoftypeUserSubscribedandactinresponsetoit.
So,let’smakeitsendanemail(Example16-12).
Example16-12.Asampleeventlistener...
useIlluminate\Contracts\Mail\Mailer;
classEmailOwnerAboutSubscription
{
protected$mailer;
publicfunction__construct(Mailer$mailer)
{
$this->mailer=$mailer;
}
publicfunctionhandle(UserSubscribed$event)
{
$this->mailer->send(
newOwnerSubscriptionEmail($event->user,$event->plan)
);
}
}
Great!Now,onelasttask:weneedtosetthislisteneruptolistentotheUserSubscribedevent.We’llsetthatupinthe$listenpropertyoftheEventServiceProviderclass(seeExample16-13).
Example16-13.BindinglistenerstoeventsinEventServiceProviderclassEventServiceProviderextendsServiceProvider
{
protected$listen=[
\App\Events\UserSubscribed::class=>[
\App\Listeners\EmailOwnerAboutSubscription::class,
],
];
Asyoucansee,thekeyofeacharrayentryistheclassnameoftheevent,andthevalueisanarrayoflistenerclassnames.WecanaddasmanyclassnamesaswewantundertheUserSubscribedkeyandtheywillalllistenandrespondtoeachUserSubscribedevent.
EventsubscribersThere’sonemorestructureyoucanusetodefinetherelationshipbetweenyoureventsandtheirlisteners.Laravelhasaconceptcalledaneventsubscriber,whichisaclassthatcontainsacollectionofmethodsthatactasseparatelistenerstouniqueevents,andalsocontainsthemappingofwhichmethodshouldhandlewhichevent.Inthiscase,it’seasiertoshowthantotell;takealookatExample16-14.
Example16-14.Asampleeventsubscriber<?php
namespaceApp\Listeners;
classUserEventSubscriber
{
publicfunctiononUserSubscription($event)
{
//HandlestheUserSubscribedevent
}
publicfunctiononUserCancellation($event)
{
//HandlestheUserCancelledevent
}
publicfunctionsubscribe($events)
{
$events->listen(
\App\Events\UserSubscribed::class,
'App\Listeners\UserEventSubscriber@onUserSubscription'
);
$events->listen(
\App\Events\UserCancelled::class,
'App\Listeners\UserEventSubscriber@onUserCancellation'
);
}
}
Subscribersneedtodefineasubscribe()method,whichispassedaninstanceoftheeventdispatcher.We’llusethattopaireventswiththeirlisteners,butinthiscase,thosearemethodsonthisclass,insteadofentireclasses.Asarefresher,anytimeyouseean@inlinelikethismeanstheclassnameistotheleftofthe@andthemethodnameistotheright.
So,inExample16-14,we’redefiningthattheonUserSubscription()methodofthissubscriberwilllistentoanyUserSubscribedevents.
There’sonelastthingweneedtodo:inApp\Providers\EventServiceProvider,weneedtoaddoursubscriber ’sclassnametothe$subscribeproperty,asseeninExample16-15.
Example16-15.Registeringaneventsubscriber...
classEventServiceProviderextendsServiceProvider
{
...
protected$subscribe=[
\App\Listeners\UserEventSubscriber::class
];
}
BroadcastingEventsoverWebSockets,andLaravelEchoWebSocket(oftencalledWebSockets)isaprotocol,popularizedbyPusher,thatmakesitsimpletoprovidenear-real-timecommunicationbetweenwebdevices.RatherthanrelyingoninformationpassingviaHTTPrequests,WebSocketslibrariesopenadirectconnectionbetweentheclientandtheserver.WebSocketsarebehindtoolslikethechatboxesinGmailandFacebook.
WebSocketsworkbestwithsmallpiecesofdatapassedinapub/substructure—justlikeLaravel’sevents.Laravelhasabuilt-insetoftoolsthatmakeiteasytodefinethatoneormoreofyoureventsshouldbebroadcasttoaWebSocketserver;thismakesiteasy,forexample,tohaveaMessageWasReceivedeventthatispublishedtothenotificationsboxofacertainuserorsetofusers,theinstantamessagearrivesatyourapplication.
LARAVELECHO
Laravelalsohasamorepowerfultooldesignedformorecomplexeventbroadcasting.Ifyouneedpresencenotification,orwanttokeepyourrichfrontenddatamodelinsyncwithyourLaravelapp,checkoutLaravelEcho,whichwe’llcovertowardtheendofthischapter.MuchofwhatcomprisesEchoisbuiltintotheLaravelcore,whichwecoverin“AdvancedBroadcastingTools”,butsomeofitrequirespullingintheexternalJavaScriptEcholibrary,whichwecoverin“LaravelEcho(theJavaScriptSide)”.
ConfigurationandSetupTakealookatconfig/broadcasting.phptofindtheconfigurationsettingsforyoureventbroadcasting.Laravelsupportsthreedriversforbroadcasting:Pusher,apaidSaaSoffering;Redis,forlocallyrunWebSocketservers;andlog,forlocaldevelopmentanddebugging.
QUEUELISTENERSInorderforeventbroadcastingtomovequickly,Laravelpushestheinstructiontobroadcastthemontoaqueue.Thatmeansyou’llneedtohaveaqueueworkerrunning(orusethesyncqueuedriverforlocaldevelopment).See“RunningaQueueWorker”tolearnhowtorunaqueueworker.
Laravelsuggestsadefaultdelayofthreesecondsbeforethequeueworkerlooksfornewjobs.However,witheventbroadcasting,youmaynoticesomeeventstakeasecondortwotobroadcast.Tospeedthisup,updateyourqueuesettingstoonlywaitonesecondbeforelookingfornewjobs.
BroadcastinganEventTobroadcastanevent,youneedtomarkthateventasabroadcasteventbyhavingitimplementtheIlluminate\Contracts\Broadcasting\ShouldBroadcastinterface.ThisinterfacerequiresyoutoaddthebroadcastOn()method,whichwillreturnanarrayofeitherstringsorChannelobjects,eachrepresentingaWebSocketchannel.
THESTRUCTUREOFWEBSOCKETEVENTS
EveryeventyousendwithWebSocketscanhavethreeprimarycharacteristics:thename,thechannel,andthedata.
Thenameofaneventmightbesomethinglikeuser-was-subscribed,butLaravel’sdefaultistousethefullyqualifiedclassnameoftheevent;i.e.,sosomethinglikeApp\Events\UserSubscribed.YoucancustomizethisbypassingthenametotheoptionalbroadcastAs()methodinyoureventclass.
Thechannelisthewayofdescribingwhichclientsshouldreceivethismessage.It’saverycommonpatterntohaveachannelforeachuser(e.g.,users.1,users.2,etc.),andpossiblyachannelforallusers(e.g.,users),andmaybeoneforjustuserswhoaremembersofacertainaccount(accounts.1).Ifthechannelyou’retargetingisaprivatechannel,prefacethechannelnamewithprivate-,andifit’sapresencechannel,prefacethechannelnamewithpresence-.So,aprivatePusherchannelnamedgroups.5shouldbe,instead,private-groups.5.IfyouuseLaravel’sPrivateChannelandPresenceChannelobjectsinyourbroadcastOn()method,they’lltakecareofaddingthoseprefacestoyourchannelnamesforyou.
Thedataisapayload,usuallyJSON,ofinformationrelevanttotheevent—themessage,maybe,orinformationabouttheuserorplanthatcanbeacteduponbytheconsumingJavaScript.
Example16-16showsourUserSubscribedevent,modifiedtobroadcastontwochannels:onefortheuser(toconfirmtheuser ’ssubscription)andoneforadmins(tonotifythemofanewsubscription).
Example16-16.Aneventbroadcastingonmultiplechannels...
useIlluminate\Contracts\Broadcasting\ShouldBroadcast;
classUserSubscribedextendsEventimplementsShouldBroadcast
{
useInteractsWithSockets,SerializesModels;
public$user;
public$plan;
publicfunction__construct($user,$plan)
{
$this->user=$user;
$this->plan=$plan;
}
publicfunctionbroadcastOn()
{
//Stringsyntax
return[
'users.'.$this->user->id,
'admins'
];
//Channelobjectsyntax
return[
newChannel('users.'.$this->user->id),
newChannel('admins'),
//Ifitwereaprivatechannel:newPrivateChannel('admins'),
//Ifitwereapresencechannel:newPresenceChannel('admins'),
];
}
}
Bydefault,anypublicpropertiesofyoureventwillbeserializedasJSONandsentalongasthedataofyourbroadcastevent.ThatmeansthedataofoneofourbroadcastUserSubscribedeventsmightlooklikeExample16-17.
Example16-17.Samplebroadcasteventdata{
'user':{
'id':5,
'name':'FredMcFeely',
...
},
'plan':'silver'
}
YoucanoverridethisbyreturninganarrayofdatafromthebroadcastWith()methodonyourevent,asinExample16-18.
Example16-18.CustomizingthebroadcasteventdatapublicfunctionbroadcastWith()
{
return[
'userId'=>$this->user->id,
'plan'=>$this->plan
];
}
Finally,youcancustomizewhichqueueyoureventispushedontowithitsonQueue()method,asinExample16-19.Youmaychoosetodothissoyoucankeepotherqueueitemsfromslowingdownyoureventbroadcast;real-timeWebSocketsaren’tmuchfunifalong-runningjobthat’shigherinthequeuekeepstheeventsfromgoingoutintime.
Example16-19.SpecifyingthequeueajobshouldrunonpublicfunctiononQueue()
{
return'websockets-for-faster-processing'
}
ReceivingtheMessageIfyouchoosetohostyourownRedisWebSocketsserver,theLaraveldocshaveagreatwalkthroughonhowtosetthatupusingsocket.ioandioredis.
However,it’smuchmorecommontousePusher.Plansoveracertainsizecostmoney,butthere’sagenerousfreeplan.PushermakesitincrediblysimpletosetupasimpleWebSocketserver,anditsJavaScriptSDKhandlesalloftheauthenticationandchannelmanagementwithalmostnoworkonyourpart.SDKsareavailableforiOS,Android,andmanymoreplatforms,languages,andframeworks.
TOECHOORNOTTOECHO?ThenextsectioncovershowtowriteaJavaScriptfrontendtointeractwithLaraveloverWebSocketsbothwithandwithoutEcho.It’shelpfultounderstandhowtodothiswithoutEchoevenifyouchoosetouseitintheend,butbecausemuchofthecodehereisnotnecessaryifyouuseEcho,I’drecommendreadingthefollowingsection,thentheEchosection,“LaravelEcho(theJavaScriptSide)”,beforeyoustartimplementinganyofit;youcandecidewhichwayyoupreferandthenwriteyourcodefromthere.
Togetstarted,pullinPusher ’slibrary,getanAPIkeyfromyourPusheraccount,andsubscribetoanyeventsonanychannelswithcodelikethatinExample16-20.
Example16-20.BasicusageofPusherJS...
<scriptsrc="https://js.pusher.com/3.1/pusher.min.js"></script>
<script>
//Globally,perhaps;justasampleofhowtogetdatain
varApp={
'userId':5,
'pusherKey':'your-pusher-api-key-here'
};
//Locally
varpusher=newPusher(App.pusherKey);
varpusherChannel=pusher.subscribe('users.'+App.userId);
pusherChannel.bind('App\\Events\\UserSubscribed',(data)=>{
console.log(data.user,data.plan);
});
</script>
ESCAPINGBACKSLASHESINJAVASCRIPTSince\isacontrolcharacterinJavaScript,youneedtowrite\\torepresentabackslashinyourstrings,whichiswhytherearetwobackslashesbetweeneachnamespacesegmentinExample16-20.
TopublishtoPusherfromLaravel,getyourPusherkey,secret,andappIDfromyourPusheraccountdashboard,andthensettheminyour.envfileunderthekeysPUSHER_KEY,PUSHER_SECRET,andPUSHER_APP_ID.
Ifyouserveyourapp,visitapagewiththeJavaScriptfromExample16-20embeddedinitinonewindow,pushabroadcasteventinanotherwindoworfromyourterminal,haveaqueuelistenerrunningorareusingthesyncdriver,andallofyourauthenticationinformationissetupcorrectly,youshouldseeeventlogspoppingupinyourJavaScriptwindow’sconsoleinnearrealtime.
Withthispower,it’snoweasyforyoutokeepyourusersup-to-datewithwhat’shappeningwiththeirdataanytimethey’reinyourapp.Youcannotifyusersoftheactionsofotherusers,oflong-runningprocessesthathavejustfinished,orofyourapplication’sresponsestoexternalactionslikeincomingemailsorwebhooks.Thepossibilitiesareendless.
REQUIREMENTSIfyouwanttobroadcastwithPusherorRedis,you’llneedtobringinthesedependencies:
Pusher:pusher/pusher-php-server:~2.0
Redis:predis/predis:~1.0
AdvancedBroadcastingToolsLaravelhasafewmoretoolstomakeitpossibletoperformmorecomplexinteractionsineventbroadcasting.Thesetools,acombinationofframeworkfeaturesandaJavaScriptlibrary,arecalledLaravelEcho.
TheseframeworkfeaturesworkbestwhenyouuseLaravelEchoinyourJavaScriptfrontend(whichwe’llcoverin“LaravelEcho(theJavaScriptSide)”),butyoucanstillenjoysomeofthebenefitsofEchowithoutusingtheJavaScriptcomponents.EchowillworkwithbothPusherandRedis,butI’mgoingtousePusherforanyexamples.
ExcludingthecurrentuserfrombroadcasteventsEveryconnectiontoPusherisassignedaunique“socketID”identifyingthatsocketconnection.Andit’seasytodefinethatanygivensocket(user)shouldbeexcludedfromreceivingaspecifiedbroadcastevent.
Thisfeaturemakesitpossibletodefinethatcertaineventsshouldnotbebroadcasttotheuserwhofiredthem.Let’ssayeveryuserinateamgetsnotifiedwhenotheruserscreateatask;wouldyouwanttobenotifiedofataskyoujustcreated?No,andthat’swhywehavethetoOthers()method.
Toimplementthis,therearetwostepstofollow.First,youneedtosetupyourJavaScripttosendacertainPOSTto/broadcasting/socketwhenyourWebSocketconnectionisinitialized.Thisattachesyoursocket_idtoyourLaravelsession.Echodoesthisforyou,butyoucanalsodoitmanually—takealookattheEchosourcetoseehowitworks.
Next,you’llwanttoupdateeveryrequestthatyourJavaScriptmakestohaveanX-Socket-IDheaderthatcontainsthatsocket_id.Example16-21showshowtodothatinVueorjQuery.
Example16-21.SendingthesocketIDalongwitheachAjaxrequestinVueorjQuery//Runthisrightafteryouinitializeecho
//Vue
Vue.http.interceptors.push((request,next)=>{
request.headers['X-Socket-Id']=Echo.socketId();
next();
});
//jQuery
$.ajaxSetup({
headers:{
'X-Socket-Id':Echo.socketId()
}
});
Onceyou’vehandledthis,youcanexcludeanyeventfrombeingbroadcasttotheuserwhotriggereditbyusingthebroadcast()globalhelperinsteadoftheevent()globalhelperandthenchainingtoOthers()afterit:
broadcast(newUserSubscribed($user,$plan))->toOthers();
ThebroadcastserviceproviderAlloftheotherfeaturesthatEchoprovidesrequireyourJavaScripttoauthenticatewiththeserver.TakealookatApp\Providers\BroadcastServiceProvider,whereyou’lldefinehowtoauthorizeusers’accesstoyourprivateandpresencechannels.
Thetwoprimaryactionsyoucantakearetodefinethemiddlewarethatwillbeusedonyourbroadcastingauthroutes,andtodefinetheauthorizationsettingsforyourchannels.
Ifyou’regoingtousethesefeatures,you’llneedtouncommenttheApp\Providers\BroadcastServiceProvider::classlineinconfig/app.php.
Andifyou’llbeusingthesefeatureswithoutLaravelEcho,you’lleitherneedtomanuallyhandlesendingCSRFtokensalongwithyourauthenticationrequests,orexclude/broadcasting/authand/broadcasting/socketfromCSRFprotectionbyaddingthemtothe$exceptpropertyoftheVerifyCsrfTokenmiddleware.
BindingauthorizationdefinitionsforWebSocketchannelsPrivateandpresenceWebSocketchannelsneedtobeabletopingyourapplicationtolearnwhetherthecurrentuserisauthorizedforthatchannel.You’llusetheBroadcast::channel()methodtodefinetherulesforthisauthorization.
PUBLIC,PRIVATE,ANDPRESENCECHANNELSTherearethreetypesofchannelsinWebSockets:public,private,andpresence.
Publicchannelscanbesubscribedtobyanyuser,authenticatedornot.
Privatechannelsrequiretheenduser’sJavaScripttoauthenticateagainsttheapplicationtoprovethattheuserisbothauthenticatedandauthorizedtojointhischannel.
Presencechannelsareatypeofprivatechannel,butinsteadofallowingformessagepassing,theysimplykeeptrackofwhichusersjoinandleavethechannel,andmakethisinformationavailabletotheapplication’sfrontend.
Broadcast::channel()takestwoparameters:first,astringrepresentingthechannel(s)youwantittomatch,andsecond,aclosurethatdefineshowtoauthorizeusersforanychannelmatchingthatstring.TheclosurewillbepassedanEloquentmodelofthecurrentuserasitsfirstparameter,andanymatched*segmentsasadditionalparameters.Forexample,achannelauthorizationdefinitionwithastringofteams.*,whenmatchedagainstthechannelteams.5,willpassitsclosure$userasthefirstparameterand5asthesecondparameter.
Ifyou’redefiningtherulesforaprivatechannel,yourBroadcast::channel()closurewillneedtoreturnaboolean:isthisuserauthorizedforthischannelornot?Ifyou’redefiningtherulesforapresencechannel,yourclosureshouldreturnanarrayofdatayouwantavailabletothepresencechannelforanyusersthatyouwanttoshowupinthechannel.Example16-22illustratesdefiningrulesforbothkindsofchannel.
Example16-22.DefiningauthorizationrulesforprivateandpresenceWebSocketchannels...
classBroadcastServiceProviderextendsServiceProvider
{
publicfunctionboot()
{
...
//Definehowtoauthenticateaprivatechannel
Broadcast::channel('teams.*',function($user,$teamId){
return$user->team_id==$teamId;
});
//Definehowtoauthenticateapresencechannel;returnanydata
//youwanttheapptohaveabouttheuserinthechannel
Broadcast::channel('rooms.*',function($user,$roomId){
if($user->rooms->contains($roomId)){
return[
'name'=>$user->name
];
}
});
YoumightbewonderinghowthisinformationgetsfromyourLaravelapplicationtoyourJavaScriptfrontend.Pusher ’sJavaScriptlibrarysendsaPOSTtoyourapplication;bydefaultitwillhit/pusher/auth,butyoucancustomizethat(andEchocustomizesitforyou)tohitLaravel’sauthenticationroute,/broadcasting/auth:
varpusher=newPusher(App.pusherKey,{
authEndpoint:'/broadcasting/auth'
});
Example16-23showshowwecantweakExample16-20forprivateandpresencechannels,withoutEcho’sfrontendcomponents.
Example16-23.BasicusageofPusherJSforprivateandpresencechannels...
<scriptsrc="https://js.pusher.com/3.1/pusher.min.js"></script>
<script>
//Globally,perhaps;justasampleofhowtogetdatain
varApp={
'userId':{{auth()->user()->id}},
'pusherKey':'yourpusherkeyhere'
};
//Locally
varpusher=newPusher(App.pusherKey,{
authEndpoint:'/broadcasting/auth'
});
//Privatechannel
varprivateChannel=pusher.subscribe('private-teams.1');
privateChannel.bind('App\\Events\\UserSubscribed',(data)=>{
console.log(data.user,data.plan);
});
//Presencechannel
varpresenceChannel=pusher.subscribe('presence-rooms.5');
console.log(presenceChannel.members);
</script>
WenowhavetheabilitytosendWebSocketmessagestousersdependingonwhethertheypassagivenchannel’sauthorizationrules.Wecanalsokeeptrackofwhichusersareactiveinaparticulargrouporsectionofthesite,anddisplayrelevantinformationtoeachuseraboutotherusersinthesamegroup.
LaravelEcho(theJavaScriptSide)LaravelEchoiscomprisedoftwopieces:theadvancedframeworkfeatureswejustcovered,andaJavaScriptpackagethattakesadvantageofthosefeaturesanddrasticallyreducestheamountofboilerplatecodeyouneedtowritepowerfulWebSocket-basedfrontends.TheEchoJavaScriptpackagemakesiteasytohandleauthentication,authorization,andsubscribingtoprivateandpresencechannels.EchocanbeusedwiththeSDKsforeitherPusherJS(forPusher)orsocket.io(forRedis).
BringingEchointoyourprojectTouseEchoinyourproject’sJavaScript,addittopackage.jsonusingnpminstall--save(besuretobringintheappropriatePusherorsocket.ioSDKaswell):
npminstallpusher-js--save
npminstalllaravel-echo--save
Let’sassumeyouhaveabasicGulpfilerunningyourapp.jsfilethroughWebpack,likeinExample16-24.
Example16-24.Compilingapp.jsthroughWebpackconstelixir=require('laravel-elixir');
elixir(mix=>{
mix.webpack('app.js');
});
Now,createabasicresources/assets/js/app.jsfile(Example16-25)tobringinyourdependenciesandinitializeEcho.
Example16-25.InitializingEchoinapp.jsimportEchofrom"laravel-echo"
window.Echo=newEcho({
broadcaster:'pusher',
key:'your-pusher-key'
});
//AddyourEchobindingshere
ForCSRFprotection,you’llalsoneedtoaddacsrf-token<meta>tagtoyourHTMLtemplate:
<metaname="csrf-token"content="{{csrf_token()}}">
And,ofcourse,remembertolinktoyourcompiledapp.jsinyourHTMLtemplate:
<scriptsrc="/js/app.js"></script>
Nowwe’rereadytogetstarted.
UsingEchoforbasiceventbroadcastingThisisnothingdifferentfromwhatwe’vealreadyusedPusherJSfor,butExample16-26isasimplecodesampletoshowhowtouseEchotolistentopublicchannelsforbasiceventinformation.
Example16-26.ListeningtoapublicchannelwithEchovarcurrentTeamId=5;//Likelysetelsewhere
Echo.channel('teams.'+currentTeamId)
.listen('UserSubscribed',(data)=>{
console.log(data);
});
Echoprovidesafewmethodsforsubscribingtovarioustypesofchannels;channel()willsubscribeyoutoapublicchannel.NotethatwhenyoulistentoaneventwithEcho,youcanignorethefulleventnamespaceandjustlistenfortheuniquenameofthisevent.Andnowwenowhaveaccesstothepublicdatathat’spassedalongwithourevent.
Wecanalsochainlisten()handlers,asinExample16-27.
Example16-27.ChainingeventlistenersinEchoEcho.channel('teams.'+currentTeamId)
.listen('UserSubscribed',(data)=>{
console.log(data);
})
.listen('UserCanceled',(data)=>{
console.log(data);
});
REMEMBERTOCOMPILEANDINCLUDE!Didyoutrythesecodesamplesandnotseeanythingchangeinyourbrowser?Makesuretorungulp(ifyou’rerunningitonce)orgulpwatch(torunalistener)tocompileyourcode.And,ifyouhaven’tyet,besuretoactuallyincludeapp.jsinyourtemplatesomewhere.
PrivatechannelsandbasicauthenticationEchoalsohasamethodforsubscribingtoprivatechannels:private().Itworksthesameaschannel(),butitrequiresyoutohavesetupchannelauthorizationdefinitionsinBroadcastServiceProvider,likewecoveredearlier.Additionally,unlikewiththeSDKs,youdon’tneedtoputprivate-infrontofyourchannelname.
Example16-28showswhatitlooksliketolistentoaprivatechannelnamedprivate-teams.5.
Example16-28.ListeningtoaprivatechannelwithEchovarcurrentTeamId=5;//Likelysetelsewhere
Echo.private('teams.'+currentTeamId)
.listen('UserSubscribed',(data)=>{
console.log(data);
});
PresencechannelsEchomakesitmuchsimplertojoinandlistentoeventsinpresencechannels.Thistimeyou’llwanttousethejoin()methodtobindtothischannel,asinExample16-29.
Example16-29.JoiningapresenceofchannelvarcurrentTeamId=5;//Likelysetelsewhere
Echo.join('teams.'+currentTeamId)
.here((members)=>{
console.log(members);
});
join()subscribestothepresencechannel,andhere()allowsyoutodefinethebehaviorwhentheuserjoinsandalsowhenanyotherusersjoinorleavethepresencechannel.
Youcanthinkofapresencechannellikea“who’sonline”sidebarinachatroom.Whenyoufirstjoinapresencechannel,yourhere()callbackwillbecalledandprovidedalistofallthemembersatthattime.Andanytimeanymembersjoinorleave,thatcallbackwillbecalledagainwiththeupdatedlist.There’snomessaginghappeninghere,butyoucanplaysounds,updatetheon-pagelistofmembers,ordowhateverelseyouwantinresponsetotheseactions.
Therearealsospecificmethodsforindividualevents,whichyoucanuseindividuallyorchained(seeExample16-30).
Example16-30.ListeningforspecificpresenceeventsvarcurrentTeamId=5;//Likelysetelsewhere
Echo.join('teams.'+currentTeamId)
.then((members)=>{
//runswhenyoujoin
console.table(members);
})
.joining((joiningMember,members)=>{
//runswhenanothermemberjoins
console.table(joiningMember);
})
.leaving((leavingMember,members)=>{
//runswhenanothermemberleaves
console.table(leavingMember);
});
ExcludingthecurrentuserWecoveredthispreviouslyinthechapter,butifyouwanttoexcludethecurrentuser,usethebroadcast()globalhelperinsteadoftheevent()globalhelperandthenchainthetoOthers()methodafteryourbroadcastcall.
Asyoucansee,theEchoJavaScriptlibrarydoesn’tdoanythingyoucouldn’tdoonyourown—butitmakesalotofcommontasksmuchsimpler,andprovidesacleaner,moreexpressivesyntaxforcommonWebSockettasks.
SubscribingtonotificationswithEchoLaravel’snotificationscomewithabroadcastdriveroutoftheboxthatpushesnotificationsoutasbroadcastevents.YoucansubscribetothesenotificationswithEchousingEcho.notification,asinExample16-31.
Example16-31.SubscribingtoanotificationwithEchoEcho.private('App.User.'+userId)
.notification((notification)=>{
console.log(notification.type);
});
SchedulerIfyou’veeverwrittenacronjobbefore,youlikelyalreadywishforabettertool.Notonlyisthesyntaxonerousandfrustratinglydifficulttoremember,butit’sonesignificantaspectofyourapplicationthatcan’tbestoredinversioncontrol.
Laravel’sschedulermakeshandlingscheduledtaskssimple.You’llwriteyourscheduledtasksincode,andthenpointonecronjobatyourapp:onceperminute,runphpartisanschedule:run.EverytimethisArtisancommandisrun,Laravelchecksyourscheduledefinitionstofindoutifanyscheduledtasksshouldrun.
Here’sthecronjobtodefinethatcommand:
*****php/home/myapp.com/artisanschedule:run>>/dev/null2>&1
Therearemanytasktypesyoucanscheduleandmanytimeframesyoucanusetoschedulethem.
app/Console/Kernel.phphasamethodnamed$schedule,whichiswhereyou’lldefineanytasksyou’dliketoschedule.
AvailableTaskTypesFirst,let’stakealookatthesimplestoption:aclosure,runeveryminute(Example16-32).Thatmeansthat,everytimethecronjobhitstheschedule:runcommand,itwillcallthisclosure.
Example16-32.Schedulingaclosuretorunonceeveryminute//app/Consoles/Kernel.php
publicfunctionschedule($schedule)
{
$schedule->call(function(){
dispatch(newCalculateTotals);
})->everyMinute();
}
Therearetwoothertypesoftasksyoucanschedule:Artisanandshellcommands.
YoucanscheduleArtisancommandsbypassingtheirsyntaxexactlyasyouwouldcallthemfromthecommandline:
$schedule->command('scores:tally--reset-cache')->everyMinute();
AndyoucanrunanyshellcommandsthatyoucouldrunwithPHP’sexec()method:
$schedule->exec('/home/myapp.com/bin/build.sh')->everyMinute();
AvailableTimeFramesThebeautyoftheschedulerisn’tjustthatyoucandefineyourtasksincode;it’sthatyoucanschedulethemincode,too.Laravelkeepstrackoftimepassingandevaluateswhetherit’stimeforanygiventasktorun.That’seasywitheveryMinute()becausetheanswerisalwayssimple:runthetask.ButLaravelkeepstherestsimpleforyou,too,evenforthemostcomplexofrequests.
Let’stakealookatyouroptionsbystartingwithamonstrousdefinitionthat’ssimpleinLaravel:
$schedule->call(function(){
//RunsonceaweekonSundayat23:50
})->weekly()->sundays()->at('23:50');
Noticethatwecanchaintimestogether:wecandefinefrequencyandspecifythedayoftheweekandthetime,andofcoursewecandosomuchmore.
Table16-1showsalistofpotentialdate/timemodifiersforusewhenschedulingajob.
Table16-1.Date/timemodifiersforusewiththescheduler
Command Description
->timezone('America/Detroit') Setthetimezoneforschedules
->cron('******') Definethescheduleusingthetraditionalcronnotation
->everyMinute() Runeveryminute
->everyFiveMinutes() Runevery5minutes
->everyTenMinutes() Runevery10minutes
->everyThirtyMinutes() Runevery30minutes
->hourly() Runeveryhour
->daily() Runeverydayatmidnight
->dailyAt('14:00') Runeverydayat14:00
->twiceDaily(1,14) Runeverydayat1:00and14:00
->weekly() Runeveryweek(midnightonSunday)
->weeklyOn(5,'10:00') RuneveryweekonFridayat10:00
->monthly() Runeverymonth(midnightonthe1st)
->monthlyOn(15,'23:00') Runeverymonthonthe15that23:00
->quarterly() Runeveryquarter(midnightonthe1stofJanuary,April,July,andOctober)
->yearly() Runeveryyear(midnightonthe1stofJanuary)
->when(closure) Limitthetasktowhenclosurereturnstrue
->skip(closure) Limitthetasktowhenclosurereturnsfalse
->between('8:00','12:00') Limitthetasktobetweenthegiventimes
->unlessBetween('8:00','12:00') Limitthetasktoanytimeexceptbetweenthegiventimes
->weekdays() Limittoweekdays
->sundays() LimittoSundays
->mondays() LimittoMondays
->tuesdays() LimittoTuesdays
->wednesdays() LimittoWednesdays
->thursdays() LimittoThursdays
->fridays() LimittoFridays
->saturdays() LimittoSaturdays
Mostofthesecanbechainedoneafteranother,butofcourse,anycombinationsthatdon’tmakesensechainedcan’tbechained.
Example16-33showsafewcombinationsyoucouldconsider.
Example16-33.Somesamplescheduledevents//BothrunweeklyonSundayat23:50
$schedule->command('do:thing')->weeklyOn(0,'23:50');
$schedule->command('do:thing')->weekly()->sundays()->at('23:50');
//Runonceperhour,weekdays,8am-5pm
$schedule->command('do:thing')->weekdays()->hourly()->when(function(){
returndate('H')>=8&&date('H')<=17;
});
//Runonceperhour,weekdays,8am-5pmusingnewLaravel5.3"between"
$schedule->command('do:thing')->weekdays()->hourly()->between('8:00','17:00');
$schedule->command('do:thing')->everyThirtyMinutes()->skip(function(){
returnapp('SkipDetector')->shouldSkip();
});
BlockingandOverlapIfyouwanttoavoidyourtasksoverlappingeachother—forexample,ifyouhaveataskrunningeveryminutethatmaysometimestakelongerthanaminutetorun—endtheschedulechainwiththewithoutOverlapping()method.Thismethodskipsataskifthepreviousinstanceofthattaskisstillrunning:
$schedule->command('do:thing')->everyMinute()->withoutOverlapping();
HandlingTaskOutputSometimestheoutputfromyourscheduledtaskisimportant,whetherforlogging,notifications,orjustensuringthatthetaskran.
Ifyouwanttowritethereturnedoutputofatasktoafile,usesendOutputTo():
$schedule->command('do:thing')->daily()->sendOutputTo($filePath);
Ifyouwanttoappendittoafileinstead,useappendOutputTo():
$schedule->command('do:thing')->daily()->appendOutputTo($filePath);
Andifyouwanttoemailtheoutputtoadesignatedrecipient,writeittoafilefirstandthenaddemailOutputTo():
$schedule->command('do:thing')
->daily()
->sendOutputTo($filePath)
->emailOutputTo('[email protected]');
MakesurethatyouremailsettingsareconfiguredcorrectlyinLaravel’sbasicemailconfiguration.
CLOSURESCHEDULEDEVENTSCAN’TSENDOUTPUTThesendOutputTo(),appendOutputTo(),andemailOutputTo()methodsonlyworkforcommandscheduledtasks.Youcan’tusethemforclosures,unfortunately.
Youmayalsowanttosendsomeoutputtoensurethatyourtasksrancorrectly.Thereareafewservicesthatprovidethissortofuptimemonitoring,mostsignificantlyLaravelEnvoyer(azero-downtimedeploymentservicethatalsoprovidescronuptimemonitoring)andDeadMan’sSnitch,atooldesignedpurelyformonitoringcronjobuptime.
Theseservicesdon’texpectsomethingtobeemailedtothem,butratherexpectanHTTP“ping,”soLaravelmakesthateasywithpingBefore()andthenPing():
$schedule->command('do:thing')
->daily()
->pingBefore($beforeUrl)
->thenPing($afterUrl);
Ifyouwanttousethepingfeatures,you’llneedtopullinGuzzleusingComposer:"guzzlehttp/guzzle":"~5.3|~6.0".
TaskHooksSpeakingofrunningsomethingbeforeandafteryourtask,therearehooksforthat,withbefore()andafter():
$schedule->command('do_thing')
->daily()
->before(function(){
//Prepare
})
->after(function(){
//Cleanup
});
TestingTestingqueuedjobs(oranythingelseinthequeue)iseasy.Inphpunit.xml,whichistheconfigurationfileforyourtests,theQUEUE_DRIVERenvironmentvariableissettosyncbydefault.Thatmeansyourtestswillrunyourjobsorotherqueuedtaskssynchronously,directlyinyourcode,withoutrelyingonaqueuesystemofanysort.Youcantestthemjustlikeanyothercode.
However,ifyou’djustliketocheckthatajobwasfired,youcandothatwiththeexpectsJobs()method,asinExample16-34.
Example16-34.Assertingthatajobofthespecifiedclasswasdispatchedpublicfunctiontest_changing_number_of_subscriptions_crunches_reports()
{
$this->expectsJobs(App\Jobs\CrunchReports::class);
...
}
Or,inLaravel5.3andlater,youcanassertagainstthespecificjobitself,asinExample16-35.
Example16-35.UsingaclosuretoverifythatadispatchedjobmeetsgivencriteriauseIlluminate\
publicfunctiontest_changing_subscriptions_triggers_crunch_job()
{
...
Bus::assertDispatched(CrunchReports::class,function($e){
return$e->subscriptions->contains(5);
});
//AlsocanuseassertNotDispatched
}
Totestthataneventfired,youhavethreeoptions.First,youcanjusttestthatthebehavioryouexpectedhappened,withoutconcerningyourselfwiththeeventitself.
Second,youcanexplicitlyassertthattheeventfired,asinExample16-36.ThisworksinLaravel5.2.
Example16-36.Assertingthataneventofthespecifiedclasswasfiredpublicfunctiontest_usersubscribed_event_fires()
{
$this->expectsEvents(App\Events\UserSubscribed::class);
...
}
Finally,youcanrunatestagainsttheeventthatwasfired,asinExample16-37.ThisisnewinLaravel5.3.
Example16-37.Usingaclosuretoverifythatafiredeventmeetsgivencriteriapublicfunctiontest_usersubscribed_event_fires()
{
...
Event::assertFired(UserSubscribed::class,function($e){
return$e->user->email='[email protected]';
});
//AlsocanuseassertNotFired()
}
Anothercommonscenarioisthatyou’retestingcodethatincidentallyfiresevents,andyouwanttodisabletheeventlistenersduringthattest.YoucandisabletheeventsystemwiththewithoutEvents()method,asinExample16-38.
Example16-38.Disablingeventlistenersduringatestpublicfunctiontest_something_subscription_related()
{
$this->withoutEvents();
...
}
TL;DRQueuesallowyoutoseparatechunksofyourapplication’scodefromthesynchronousflowofuserinteractionsouttoalistofcommandstobeprocessedbya“queueworker.”Thisallowsyouruserstoresumeinteractionswithyourapplicationwhileslowerprocessesarehandledasychronouslyinthebackground.
Jobsareclassesthatarestructuredwiththeintentionofencapsulatingachunkofapplicationbehaviorsothatitcanbepushedontoaqueue.
Laravel’seventsystemfollowsthepub/suborobserverpattern,allowingyoutosendoutnotificationsofaneventfromonepartofyourapplication,andelsewherebindlistenerstothosenotificationstodefinewhatbehaviorshouldhappeninresponsetothem.UsingWebSockets,eventscanalsobebroadcasttofrontendclients.
Laravel’sschedulersimplifiesschedulingtasks.Pointanevery-minutecronjobtophpartisanschedule:runandthenscheduleyourtaskswitheventhemostcomplexoftimerequirementsusingthescheduler,andLaravelwillhandleallthetimingsforyou.
Chapter17.HelpersandCollections
We’vealreadycoveredmanyglobalfunctionsthroughoutthebook:littlehelpersthatmakeiteasiertoperformcommontasks,likedispatch()forJobs,event()forEvents,app()fordependencyresolution,andmanymore.WealsotalkedabitaboutLaravel’scollections,orarraysonsteroids,inChapter8.
Inthischapterwe’llcoversomeofthemorecommonandpowerfulhelpersandsomeofthebasicsofprogrammingwithcollections.
HelpersYoucanfindafulllistofthehelpersLaraveloffersinthehelpersdocs,butwe’regoingtocoverafewofthemostusefulfunctionshere.
ArraysPHP’snativearraymanipulationfunctionsgiveyoualotofpower,butsometimestherearecommonmanipulationswewanttomakethatrequireunwieldyloopsandlogicchecks.Laravel’sarrayhelpersmakeafewcommonarraymanipulationsmuchsimpler:
array_first($array,$closure,$default=null)
Returnsthefirstarrayvaluethatpassesatest,definedinaclosure.Youcanoptionallysetthedefaultvalueasthethirdparameter:
$people=[
[
'email'=>'[email protected]',
'name'=>'MalcolmMe'
],
[
'email'=>'[email protected]',
'name'=>'JamesJo'
]
];
$value=array_first($people,function($key,$person){
return$person['email']=='[email protected]';
});
array_get($array,$key,$default=null)
Makesiteasytogetvaluesoutofanarray,withtwoaddedbenefits:itwon’tthrowanerrorifyouaskforakeythatdoesn’texist(andyoucanprovidedefaultswiththethirdparameter),andyoucanusedotnotationtotraversenestedarrays.Forexample:
$array=['owner'=>['address'=>['line1'=>'123MainSt.']]];
$line1=array_get($array,'owner.address.line1','Noaddress');
$line2=array_get($array,'owner.address.line2');
array_has($array,$key)
Makesiteasytocheckwhetheranarrayhasaparticularvaluesetusingdotnotationfortraversingnestedarrays:
$array=['owner'=>['address'=>['line1'=>'123MainSt.']]];
if(array_has($array,'owner.address.line2')){
//Dostuff
}
array_pluck($array,$key,$indexKey)
Returnsanarrayofthevaluescorrespondingtotheprovidedkey:
$array=[
['owner'=>['id'=>4,'name'=>'Tricia']],
['owner'=>['id'=>7,'name'=>'Kimberly']],
];
$array=array_pluck($array,'owner.name');
//Returns['Tricia','Kimberly'];
Ifyouwantthereturnedarraytobekeyedbyanothervaluefromthesourcearray,youcanpassthatvalue’sdot-notatedreferenceasthethirdparameter:
$array=array_pluck($array,'owner.name','owner.id');
//Returns[4=>'Tricia',7=>'Kimberly'];
StringsJustlikewitharrays,therearesomestringmanipulationsandchecksthatarepossiblewithnativePHPfunctions,butcanbecumbersome.Laravel’shelpersmakeafewcommonstringoperationsfasterandsimpler:
e($string)
Analiastohtmlentities();preparesa(oftenuser-provided)stringforsafeechoingonanHTMLpage.Forexample:
e('<script>dosomethingnefarious</script>');
//Returns<script>dosomethingnefarious</script>
starts_with($haystack,$needle),ends_with($haystack,$needle),andstr_contains($haystack,$needle)
Returnabooleanofwhethertheprovided“haystack”stringstartswith,endswith,orcontainstheprovided“needle”string:
if(starts_with($url,'https')){
//Dosomething
}
if(ends_with($abstract,'...')){
//Dosomething
}
if(str_contains($description,'1337h4x0r')){
//Runaway
}
str_limit($string,$numCharacters,$concatenationString='...')
Limitsastringtotheprovidednumberofcharacters.Ifthestringislessthanthelimit,justreturnsthestring;ifit’sgreater,trimstothenumberofcharactersprovidedandthenappendseither...ortheprovidedconcatenationstring.Forexample:
$abstract=str_limit($loremIpsum,30);
//Returns"Loremipsumdolorsitamet,co..."
$abstract=str_limit($loremIpsum,30,"…");
//Returns"Loremipsumdolorsitamet,co…"
str_is($pattern,$string)
Returnsabooleanofwhetherornotagivenstringmatchesagivenpattern.Thepatterncanbearegexpattern,oryoucanuseasteriskstoindicatewildcardpositions:
str_is('*.dev','myapp.dev');//true
str_is('*.dev','myapp.dev.co.uk');//false
str_is('*dev*','myapp.dev');//true
str_is('*myapp*','www.myapp.dev');//true
str_is('my*app','myfantasticapp');//true
str_is('my*app','myapp');//true
HOWTOPASSAREGEXTOSTR_IS()Ifyou’recuriousaboutwhatregexpatternsareacceptabletopasstostr_is(),checkoutthefunctiondefinitionhere(shortenedforspace)toseehowitworks.Notethatit’sanaliasofIlluminate\Support\Str::is:
publicfunctionis($pattern,$value)
{
if($pattern==$value)returntrue;
$pattern=preg_quote($pattern,'#');
$pattern=str_replace('\*','.*',$pattern);
return(bool)preg_match(
'#^'.$pattern.'\z#u',
$value
);
}
str_random($length)
Returnsarandomstringofalphanumericmixed-casecharactersofthelengthspecified:
$hash=str_random(64);
//Sample:J40uNWAvY60wE4BPEWxu7BZFQEmxEHmGiLmQncj0ThMGJK7O5Kfgptyb9ulwspmh
str_slug($string,$separator='-')
ReturnsaURL-friendlyslugfromastring—oftenusedforcreatingaURLsegmentforanameortitle:
str_slug('HowtoWinFriendsandInfluencePeople');
//Returns'how-to-win-friends-and-influence-people'
ApplicationPathsWhenyou’redealingwiththefilesystem,itcanoftenbetedioustomakelinkstocertaindirectoriesforgettingandsavingfiles.Thesehelpersgiveyouquickaccesstofindthefullyqualifiedpathstosomeofthemostimportantdirectoriesinyourapp.
Notethateachofthesecanbecalledwithnoparameters,butifaparameterispassed,itwillbeappendedtothenormaldirectorystringandreturnedasawhole:
app_path($append='')
Returnsthepathfortheappdirectory:
app_path();
//Returns/home/forge/myapp.com/app
base_path($append='')
Returnsthepathfortherootdirectoryofyourapp:
base_path();
//Returns/home/forge/myapp.com
config_path($append='')
Returnsthepathforconfigurationfilesinyourapp:
config_path();
//Returns/home/forge/myapp.com/config
database_path($append='')
Returnsthepathfordatabasefilesinyourapp:
database_path();
//Returns/home/forge/myapp.com/database
storage_path($append='')
Returnsthepathforthestoragedirectoryinyourapp:
storage_path();
//Returns/home/forge/myapp.com/storage
URLsSomefrontendfilepathsareconsistentbutattimesannoyingtotype—forexample,pathstoassets—andit’shelpfultohaveconvenientshortcutstothem,whichwe’llcoverhere.ButsomecanactuallyvaryasroutedefinitionsmoveornewfilesareversionedwithElixir,sosomeofthesehelpersarevitalinmakingsureallofyourlinksandassetsworkcorrectly:
action('Controller@method’,$params=[],$absolute=true)
AssumingacontrollermethodhasasingleURLmappedtoit,returnsthecorrectURLgivenacontrollerandmethodnamepair(separatedby@):
<ahref="{{action('PeopleController@index'}}">SeeallPeople</a>
//Returns<ahref="http://myapp.com/people">SeeallPeople</a>
Ifthecontrollermethodrequiresparameters,youcanpasstheminasthesecondparameter(asanarray,ifthere’smorethanonerequiredparameter).Youcankeythemifyouwantforclarity,butwhatmattersisjustthatthey’reintherightorder:
<ahref="{{action('PeopleController@show',['id'=>3]}}">SeePerson#3</a>
//or
<ahref="{{action('PeopleController@show',[3]}}">SeePerson#3</a>
//Returns<ahref="http://myapp.com/people/3">SeePerson#3</a>
Ifyoupassfalsetothethirdparameter,yourlinkswillgenerateasrelative(/people/3)insteadofabsolute(http://myapp.com/people/3).
route($routeName,$params=[],$absolute=true)
Ifaroutehasaname(usingasintheroutedefinition),returnstheURLforthatroute:
<ahref="{{route('people.index')}}">SeeallPeople</a>
//Returns<ahref="http://myapp.com/people">SeeallPeople</a>
Iftheroutedefinitionrequiresparameters,youcanpasstheminasthesecondparameter(asanarrayifmorethanoneparameterisrequired).Again,youcankeythemifyouwantforclarity,butwhatmattersisjustthatthey’reintherightorder:
<ahref="{{action('people.show',['id'=>3])}}">SeePerson#3</a>
//or
<ahref="{{action('people.show',[3])}}">SeePerson#3</a>
//Returns<ahref="http://myapp.com/people/3">SeePerson#3</a>
Ifyoupassfalsetothethirdparameter,yourlinkswillgenerateasrelativeinsteadofabsolute.
url($string)andsecure_url($string)Givenanypathstring,convertstoafullyqualifiedURL.secure_url()isthesameasurl()butforcesHTTPS.
url('people/3');
//Returnshttp://myapp.com/people/3
Ifnoparametersarepassed,thisinsteadgivesaninstanceofIlluminate\Routing\UrlGenerator,whichmakesmethodchainingpossible:
url()->current();
//Returnshttp://myapp.com/abc
url()->full();
//Returnshttp://myapp.com/abc?order=reverse
url()->previous();
//Returnshttp://myapp.com/login
//AndmanymoremethodsavailableontheUrlGenerator...
elixir($filePath)
IfassetsareversionedwithElixir ’sversioningsystem,giventhenonversionedpathname,returnsthefullyqualifiedURLfortheversionedfile:
<linkrel="stylesheet"href="{{elixir('css/app.css')}}">
//Returnssomethinglike/build/css/app-eb555e38.css
MiscThereareafewotherglobalhelpersthatI’drecommendgettingfamiliarwith.Ofcourse,youshouldcheckoutthewholelist,buttheonesmentionedherearedefinitelyworthtakingalookat:
abort($code,$message,$headers),abort_unless($boolean,$code,$message,$headers),andabort_if($boolean,$code,$message,$headers)
ThrowHTTPexceptions.abort()throwstheexceptiondefined,abort_unless()throwsitifthefirstparameterisfalse,andabort_if()throwsitifthefirstparameteristrue:
publicfunctioncontrollerMethod(Request$request)
{
abort(403,'Youshallnotpass');
abort_unless($request->has('magicToken'),403);
abort_if($request->user()->isBanned,403);
}
auth()
ReturnsaninstanceoftheLaravelauthenticator.LiketheAuthfacade,youcanusethistogetthecurrentuser,tocheckforloginstate,andmore:
$user=auth()->user();
if(auth()->check()){
//Dosomething
}
back()
Generatesa“redirectback”response,sendingtheusertothepreviouslocation:
Route::get('post',function(){
...
if($condition){
returnback();
}
});
collect($array)
Takesanarrayandreturnsthesamedata,convertedtoacollection:
$collection=collect(['Rachel','Hototo']);
We’llcovercollectionsinjustabit.
config($key)
Returnsthevalueforanydot-notatedconfigurationitem:
$defaultDbConnection=config('database.default');
csrf_field()andcsrf_token()ReturnafullHTMLhiddeninput(csrf_field())orjusttheappropriatetokenvalue(csrf_token())foraddingCSRFverificationtoyourformsubmission:
<form>
{{csrf_field()}}
</form>
//or
<form>
<inputtype="hidden"name="_token"value="{{csrf_token()}}">
</form>
dd($variable...)
Shortfor“dumpanddie,”runsvar_dump()onallprovidedparametersandthenexit()toquittheapplication(thisisusedfordebugging):
...
dd($var1,$var2,$state);//Whyisthisnotworking???
env($key,$default=null)
Returnstheenvironmentvariableforthegivenkey:
$key=env('API_KEY','');
USINGENV()OUTSIDEOFCONFIGFILESCertainfeaturesinLaravel,includingsomecachingandoptimizationfeatures,aren’tavailableifyouuseenv()callsanywhereoutsideofconfigfiles.
Thebestwaytopullinenvironmentvariablesistosetupconfigitemsforanythingyouwanttobeenvironment-specific.Havethoseconfigitemsreadtheenvironmentvariables,andthenreferencetheconfigvariablesanywherewithinyourapp:
//config/services.php
return[
'bugsnag'=>[
'key'=>env('BUGSNAG_API_KEY')
]
];
//incontroller,orwhatever
$bugsnag=newBugsnag(config('services.bugsnag.key'));
dispatch($job)
Dispatchesajob:
dispatch(newEmailAdminAboutNewUser($user));
event($event)
Firesanevent:
event(newContactAdded($contact));
factory($entityClass)
Returnsaninstanceofthefactorybuilderforthegivenclass:
$contact=factory(App\Contact::class)->make();
old($key,$default=null)
Returnstheoldvalue(fromthelastuserformsubmission)forthisformkey,ifitexists:
<inputname="name"value="{{old('value','Yournamehere')}}"
redirect($path)
Returnsaredirectresponsetothegivenpath:
Route::get('post',function(){
...
returnredirect('home');
});
Withoutparameters,generatesaninstanceoftheIlluminate\Routing\Redirectorclass.
response($body,$status,$headers)
Ifpassedwithparameters,returnsaprebuiltinstanceofResponse.Ifpassedwithnoresponse,returnsaninstanceoftheResponsefactory:
returnresponse('OK',200,['X-Header-Greatness'=>'Supergreat']);
returnresponse()->json(['status'=>'success'],200);
view($viewPath)
Returnsaviewinstance:
Route::get('home',function(){
returnview('home');//Gets/resources/views/home.blade.php
});
CollectionsCollectionsareoneofthemostpowerfulandyetunderappreciatedtoolsLaravelprovides.Wecoveredthemabitin“EloquentCollections”,buthere’saquickrecap.
Collectionsareessentiallyarrayswithsuperpowers.Thearray-traversingmethodsyounormallyhavetopassarraysinto(array_walk(),array_map(),array_reduce(),etc.),allofwhichhaveconfusinglyinconsistentmethodsignatures,areavailableasconsistent,clean,chainablemethodsoneverycollection.Youcangetatasteoffunctionalprogrammingandmap,reduce,andfilteryourwaytocleanercode.
We’llcoversomeofthebasicsofLaravel’scollectionsandcollectionpipelineprogramminghere,butforamuchdeeperoverview,checkoutAdamWathan’sbookRefactoringtoCollections(Gumroad).
TheBasicsofCollectionsCollectionsarenotanewideawithinLaravel.Manylanguagesmakecollection-styleprogrammingavailableonarraysoutofthebox,butwithPHPwe’renotquitesolucky.
UsingPHP’sarray*()functions,wecantakethemonstrosityshowninExample17-1,andturnitintotheslightlylessmonstrousmonstrosityshowninExample17-2.
Example17-1.Acommon,butugly,foreachloop$users=[...];
$admins=[];
foreach($usersas$user){
if($user['status']=='admin'){
$user['name']=$user['first'].''.$user['last'];
$admins[]=$user;
}
}
return$admins;
Example17-2.RefactoringtheforeachloopwithnativePHPfunctions$users=[...];
returnarray_map(function($user){
$user['name']=$user['first'].''.$user['last'];
return$user;
},array_filter($users,function($user){
return$user['status']=='admin';
}));
Here,we’vegottenridofatemporaryvariable(+$admins+)andconvertedoneconfusingforeachloopintotwodistinctactions:mapandfilter.
Theproblemis,PHP’sarraymanipulationfunctionsareawfulandconfusing.Justlookatthisexample;array_map()takestheclosurefirstandthearraysecond,butarray_filter()takesthearrayfirstandtheclosuresecond.Inaddition,ifweaddedanycomplexitytothis,we’dhavefunctionswrappingfunctionswrappingfunctions.It’samess.
Laravel’scollectionstakethepowerofPHP’sarraymanipulationmethodsandgivethemaclean,fluentsyntax—andtheyaddmanymethodsthatdon’tevenexistinPHP’sarraymanipulationtoolbox.Now,usingthecollect()helpermethodthatturnsanarrayintoaLaravelcollection,wecandowhat’sshowninExample17-3:
Example17-3.RefactoringtheforeachloopwithLaravel’scollections$users=collect([...]);
return$users->filter(function($user){
return$user['status']=='admin';
})->map(function($user){
$user['name']=$user['first'].''.$user['last'];
return$user;
});
Thisisn’tthemostextremeofexamples.Thereareplentywherethereductioninlinesofcodeandtheincreasedsimplicitywouldmakeanevenstrongercase.Butthisrighthereisso
common.
Lookattheoriginalexampleandhowmuddyitis.It’snotentirelyclearuntilyouunderstandtheentirecodesamplewhatanygivenpieceistherefor.
Thebiggestbenefitcollectionsprovide,overanythingelse,isbreakingtheactionsyou’retakingtomanipulateanarrayintosimple,discrete,understandabletasks.Youcannowdosomethinglikethis:
$users=[...]
$countAdmins=collect($users)->filter(function($user){
return$user['status']=='admin'
})->count();
Orsomethinglikethis:
$users=[...];
$greenTeamPoints=collect($users)->filter(function($user){
return$user->team=='green';
})->sum('points');
AFewMethodsThere’smuchmoreyoucandothanwhatwe’vecoveredhere.TakealookattheLaravelCollectiondocstolearnmoreaboutallthemethodsyoucanuse,buttogetyoustarted,herearejustafewofthecoremethods:
all()andtoArray()Ifyou’dliketoconvertyourcollectiontoanarray,youcandosowitheitherall()ortoArray().toArray()flattensnotjustthecollection,butalsoanyEloquentobjectsunderneathit,toarrays.all()onlyconvertsthecollectiontoanarray;anyEloquentobjectscontainedwithinthecollectionwillbepreservedasEloquentobjects.Hereareafewexamples:
$users=User::all();
$users->toArray();
/*Returns
[
['id'=>'1','name'=>'Agouhanna'],
...
]
*/
$users->all();
/*Returns
[
EloquentObject{id:1,name:'Agouhanna'},
...
]
*/
filter()andreject()Whenyouwanttogetasubsetofyouroriginalcollectionbycheckingeachitemagainstaclosure,you’llusefilter()(whichkeepsanitemiftheclosurereturnstrue)orreject()(whichkeepsanitemiftheclosurereturnsfalse):
$users=collect([...]);
$admins=$users->filter(function($user){
return$user->isAdmin;
});
$paidUsers=$user->reject(function($user){
return$user->isTrial;
});
where()
where()makesiteasytoprovideasubsetofyouroriginalcollectionwhereagivenkeyisequaltoagivenvalue.Anythingyoucandowithwhere()youcanalsodowithfilter(),butit’sashortcutforacommonscenario:
$users=collect([...]);
$admins=$users->where('role','admin');
first()andlast()Ifyouwantjustasingleitemfromyourcollection,youcanusefirst()topullfromthebeginningofthelistorlast()topullfromtheend.
Ifyoucallfirst()orlast()withnoparameters,they’lljustgiveyouthefirstorlastiteminthecollection.Butifyoupasseitheraclosure,they’llinsteadgiveyouthefirstorlastiteminthecollectionthatreturnstruewhenpassedtothatclosure.
Sometimesyou’lldothisbecauseyouwanttheactualfirstorlastitem.Butsometimesit’stheeasiestwaytogetoneitemevenifyouonlyexpecttheretobeone:
$users=collect([...]);
$owner=$users->first(function($user){
return$user->isOwner;
});
$firstUser=$users->first();
$lastUser=$users->last();
Youcanalsopassasecondparametertoeachmethod,whichisthedefaultvalueandwillbeprovidedasafallbackiftheclosuredoesn’tprovideanyresults.
each()
Ifyou’dliketodosomethingwitheachitemofacollection,butitdoesn’tincludemodifyingtheitemsorthecollectionitself,youcanuseeach():
$users=collect([...]);
$users->each(function($user){
dispatch(newEmailUserAThing($user));
});
map()
Ifyou’dliketoiterateoveralltheitemsinacollection,makechangestothem,andreturnanewcollectionwithallofyourchanges,you’llwanttousemap():
$users=collect([...]);
$users=$users->map(function($user){
return[
'name'=>$user['first'].''.$user['last'],
'email'=>$user['email']
];
});
reduce()
Ifyou’dliketogetasingleresultfromyourcollection,likeacountorastring,you’llprobablywanttousereduce().Youcandefineaninitialvalueforthe“carry,”andaclosurethatacceptsthecurrentstateofthe“carry”andtheneachitemasparameters:
$users=collect([...]);
$points=$users->reduce(function($carry,$user){
return$carry+$user['points']
},0);//Startwithacarryof0
pluck()
Ifyouwanttopulloutjustthevaluesforagivenkeyundereachiteminacollection,youcanusepluck()(formerlylists()):
$users=collect([...]);
$emails=$users->pluck('email')->toArray();
chunk()andtake()chunk()makesiteasytosplityourcollectionintogroupsofapredefinedsize,andtake()pullsjusttheprovidednumberofitems:
$users=collect([...]);
$rowsOfUsers=$users->chunk(3);//Separatesintogroupsof3
$topThree=$users->take(3);//Pullsthefirst3
groupBy()
Ifyouwanttogroupalloftheitemsinyourcollectionbythevalueofoneoftheirproperties,youcanusegroupBy():
$users=collect([...]);
$usersByRole=$users->groupBy('role');
/*Returns:
[
'member'=>[...],
'admin'=>[...]
]
*/
Youcanalsopassaclosure,andwhateveryoureturnfromtheclosurewillbewhat’susedtogrouptherecords:
$heroes=collect([...]);
$heroesByAbilityType=$heroes->groupBy(function($hero){
if($hero->canFly()&&$hero->isInvulnerable()){
return'Kryptonian';
}
if($hero->bitByARadioactiveSpider()){
return'Spidermanesque';
}
if($hero->color==='green'&&$hero->likesSmashing()){
return'Hulk-like';
}
return'Generic';
});
reverse()andshuffle()reverse()reversestheorderoftheitemsinyourcollection,andshuffle()randomizesthem:
$numbers=collect([1,2,3]);
$numbers->reverse()->toArray();//[3,2,1]
$numbers->shuffle()->toArray();//[2,3,1]
sort(),sortBy(),andsortByDesc()Ifyouritemsaresimplestringsorintegers,youcanusesort()tosortthem:
$sortedNumbers=collect([1,7,6,4])->sort()->toArray();//[1,4,6,7]
Ifthey’remorecomplex,youcanpassastring(representingtheproperty)oraclosuretosortBy()orsortByDesc()todefineyoursortingbehavior:
$users=collect([...]);
//Sortanarrayofusersbytheir'email'property
$users->sort('email');
//Sortanarrayofusersbytheir'email'property
$users->sort(function($user,$key){
return$user['email'];
});
count()andisEmpty()Youcanseehowmanyitemsthereareinyourcollectionusingcount()orisEmpty():
$numbers=collect([1,2,3]);
$numbers->count();//3
$numbers->isEmpty();//false
avg()andsum()Ifyou’reworkingwithacollectionofnumbers,avg()andsum()dowhattheirmethodnamessay,anddon’trequireanyparameters:
collect([1,2,3])->sum();//6
collect([1,2,3])->avg();//2
Butifyou’reworkingwitharrays,youcanpassthekeyofthepropertyyou’dliketopullfromeacharraytooperateon:
$users=collect([...]);
$sumPoints=$users->sum('points');
$avgPoints=$users->avg('points');
USINGCOLLECTIONSOUTSIDEOFLARAVELHaveyoufalleninlovewithcollections,anddoyouwanttousethemonyournon-Laravelprojects?WithTaylor’sblessing,IsplitoutjustthecollectionsfunctionalityfromLaravelintoaseparateprojectcalledCollect,anddevelopersatmycompanykeepitup-to-datewithLaravel’sreleases.
Justusethecomposerrequiretightenco/collectcommandandyou’llhavetheIlluminate\Support\Collectionclassreadytouseinyourcode—alongwiththecollect()helper.
TL;DRLaravelprovidesasuiteofglobalhelperfunctionsthatmakeitsimplertodoallsortsoftasks.Theymakeiteasiertomanipulateandinspectarraysandstrings,theymakeiteasiertogeneratepathsandURLs,andtheyprovidesimpleaccesstosomeconsistentandvitalfunctionality.
Laravel’scollectionsarepowerfultoolsthatbringthepossibilityofcollectionpipelinestoPHP.
Glossary
AccessorAmethoddefinedonanEloquentmodelthatcustomizeshowagivenpropertywillbereturned.Accessorsmakeitpossibletodefinethatgettingagivenpropertyfromamodelwillreturnadifferent(or,morelikely,differentlyformatted)valuethanwhatisstoredinthedatabaseforthatproperty.
ActiveRecordAcommondatabaseORMpattern,andalsothepatternthatLaravel’sEloquentuses.InActiveRecordthesamemodelclassdefinesbothhowtoretrieveandpersistdatabaserecordsandhowtorepresentthem.Additionally,eachdatabaserecordisrepresentedbyasingleentityintheapplication,andeachentityintheapplicationismappedtoasingledatabaserecord.
ApplicationtestOftencalledacceptanceorfunctionaltests,applicationteststesttheentirebehavioroftheapplication,usuallyatanouterboundary,byemployingsomethinglikeaDOMcrawler—whichisexactlywhatLaravel’sapplicationtestsuiteoffers.
Argument(Artisan)ArgumentsareparametersthatcanbepassedtoArtisanconsolecommands.Argumentsaren’tprefacedwith--orfollowedby=,butinsteadjustacceptasinglevalue.
ArtisanThetoolthatmakesitpossibletointeractwithLaravelapplicationsfromthecommandline.
AssertionIntesting,anassertionisthecoreofthetest:youareassertingthatsomethingshouldbeequalto(orlessthanorgreaterthan)somethingelse,orthatitshouldhaveagivencount,orwhateverelseyoulike.Assertionsarethethingsthatcaneitherpassorfail.
AuthenticationCorrectlyidentifyingoneselfasamember/userofanapplicationistheactofauthentication.Authenticationdoesn’tdefinewhatyoumaydo,butsimplywhoyouare(oraren’t).
Authorization
Assumingyou’veeithersucceededorfailedatauthenticatingyourself,authorizationdefineswhatyou’reallowedtodogivenyourparticularidentification.Authorizationisaboutaccessandcontrol.
AutowiringWhenadependencyinjectioncontainerwillinjectaninstanceofaresolvableclasswithoutadeveloperhavingexplicitlytaughtithowtoresolvethatclass,that’scalledautowiring.Withacontainerthatdoesn’thaveautowiring,youcan’teveninjectaplainPHPobjectwithnodependenciesuntilyouhaveexplicitlyboundittothecontainer.Withautowiring,youonlyhavetoexplicitlybindsomethingtothecontainerifitsdependenciesaretoocomplexorvagueforthecontainertofigureoutonitsown.
beanstalkdBeanstalkisaworkqueue.It’ssimpleandexcelsatrunningmultipleasynchronoustasks—whichmakesitacommondriverforLaravel’squeues.beanstalkdisitsdaemon.
BladeLaravel’stemplatingengine.
CarbonAPHPpackagethatmakesworkingwithdatesmucheasierandmoreexpressive.
CashierALaravelpackagethatmakesbillingwithStripeorBraintree,especiallyinsubscriptioncontexts,easierandmoreconsistentandpowerful.
ClosureClosuresarePHP’sversionofanonymousfunctions.Aclosureisafunctionthatyoucanpassaroundasanobject,assigntoavariable,passasaparametertootherfunctionsandmethods,orevenserialize.
CodeIgniterAnolderPHPframeworkthatLaravelwasinspiredby.
CollectionThenameofadevelopmentpatternandalsoLaravel’stoolthatimplementsit.Likearraysonsteroids,collectionsprovidemap,reduce,filter,andmanymorepowerfuloperationsthatPHP’snativearraysdon’t.
CommandThenameforacustomArtisanconsoletask.
ComposerPHP’sdependencymanager.LikeRubyGemsorNPM.
ContainerSomewhatofacatchallword,inLaravel“container”referstotheapplicationcontainerthat’sresponsiblefordependencyinjection.Accessibleviaapp()andalsoresponsibleforresolvingcallstocontrollers,events,jobs,andcommands,thecontaineristhegluethatholdseachLaravelapptogether.
ContractAnothernameforaninterface.
ControllerAclassthatisresponsibleforroutinguserrequeststhroughtotheapplication’sservicesanddata,andreturningsomeformofusefulresponsebacktotheuser.
CSRF(cross-siterequestforgery)Amaliciousattackwhereanexternalsitemakesrequestsagainstyourapplicationbyhijackingyourusers’browsers(withJavaScript,likely)whilethey’restillloggedintoyoursite.Protectedagainstbyaddingatoken(andacheckforthattokenonthePOSTside)toeveryformonthesite.
DependencyinjectionInsteadofinstantiatingdependenciesinaclass,expectthemtobeinjectedinfromtheoutside—usuallythroughtheconstructor.
DirectiveBladesyntaxoptionslike@if,@unless,etc.
DotnotationNavigatingdowninheritancetreesusing.toreferenceajumpdowntoanewlevel.Ifyouhaveanarraylike:['owner'=>['address'=>['line1'=>'123MainSt.']]],youhavethreelevelsofnesting.Usingdotnotation,youwouldrepresent“123MainSt.”as"owner.address.line1".
EagerloadingAvoidingN+1problemsbyaddingasecondsmartquerytoyourfirstquerytogetasetofrelateditems.UsuallyyouhaveafirstquerythatgetsacollectionofthingA.ButeachAhasmanyB,andsoeverytimeyougettheBfromanA,youneedanewquery.Eagerloadingmeansdoingtwoqueries:firstyougetalltheA’s,andsecondyougetalltheB’srelatedtoallthoseA’s,inasinglequery.Twoqueries,andyou’redone.
EchoALaravelproductthatmakesWebSocketauthenticationandsyncingofdatasimple.
Elixir
Laravel’sbuildtool;awrapperaroundGulp.
EloquentLaravel’sActiveRecordORM.Thetoolyou’llusetodefinesomethinglikeaUsermodel.
EnvironmentvariableVariablesthataredefinedinan.envfilethatisexpectedtobeexcludedfromversioncontrol.Thismeanstheydon’tsyncbetweenenvironmentsandthey’realsokeptsafe.
EnvoyerALaravelproductforzero-down-timedeployment.
EventLaravel’stoolforimplementingapub/suborobserverpattern.Eacheventrepresentsthataneventhappened:thenameoftheeventdescribeswhathappened(e.g.,UserSubscribed)andthepayloadallowsforattachingrelevantinformation.Designedtobe“fired”andthen“listened”for(orpublishedandsubscribed,ifyoupreferthepub/subconcept).
FacadeAtoolinLaravelforsimplifyingaccesstocomplextools.FacadesprovidestaticaccesstocoreservicesinLaravel.Sinceeveryfacadeisbackedbyaclassinthecontainer,youcouldreplaceanycalltosomethinglikeCache::put();withatwo-linecalltosomethinglike$cache=app('cache');$cache->put();.
FlagAparameteranywherethatisonoroff(boolean).
FluentMethodsthatcanbechainedoneafteranotheraresaidtobefluent.Inordertoprovideafluentsyntax,eachmethodmustreturntheinstance,preparingittobechainedagain.ThisallowsforsomethinglikePeople::where('age','>',14)->orderBy('name')->get().
FlysystemThepackagethatLaravelusestofacilitateitslocalandcloudfileaccess.
ForgeALaravelproductthatmakesiteasytospinupandmanagevirtualserversonmajorcloudproviderslikeDigitalOceanandAWS.
FQCN(fully-qualifiedclassname)Thefullnamespacednameofanygivenclass,trait,orinterface.Controlleristheclassname;Illuminate\Routing\ControlleristheFQCN.
Gulp
AJavaScript-basedbuildtool.
HelperAgloballyaccessiblePHPfunctionthatmakessomeotherfunctionalityeasier—forexample,array_get()simplifiesthelogicoflookingupresultsfromarrays.
HomesteadALaraveltoolthatwrapsVagrantandmakesiteasiertospinupForge-parallelvirtualserversforlocalLaraveldevelopment.
IlluminateThetop-levelnamespaceofallLaravelcomponents.
IntegrationtestIntegrationteststestthewayindividualunitsworktogetherandpassmessages.
IoC(inversionofcontrol)Theconceptofgiving“control”overhowtomakeaconcreteinstanceofaninterfacetothehigher-levelcodeofthepackageinsteadofthelower-levelcode.WithoutIoC,eachindividualcontrollerandclassmightdecidewhatinstanceofMaileritwantedtocreate.IoCmakesitsothatthelow-levelcode—thosecontrollersandclasses—justgettoaskforaMailer,andsomehigh-levelconfigurationcodedefinesonceperapplicationwhichinstanceshouldbeprovidedtosatisfythatrequest.
JobAclassthatintendstoencapsulateasingletask.Jobsareintendedtobeabletobepushedontoaqueueandrunasynchronously.
JSONJavaScriptObjectNotation.Asyntaxfordatarepresentation.
JWT(JSONWebToken)AJSONobjectcontainingalloftheinformationnecessarytodetermineauser ’sauthenticationstateandaccesspermissions.ThisJSONobjectisdigitallysigned,whichiswhatmakesittrustworthy,usingHMACorRSA.Usuallydeliveredintheheader.
MassassignmentTheabilitytopassmanyparametersatoncetocreateorupdateanEloquentmodel,usingakeyedarray.
MiddlewareAseriesofwrappersaroundanapplicationthatfilteranddecorateitsinputsandoutputs.
Memcached
Anin-memorydatastoredesignedtoprovidesimplebutfastdatastorage.Memcachedonlysupportsabasickey/valuestore.
MigrationAmanipulationtothestateofthedatabase,storedinandrunfromcode.
MockeryAlibraryincludedwithLaravelthatmakesiteasytomockPHPclassesinyourtests.
ModelfactoryAtoolfordefininghowtheapplicationcangenerateaninstanceofyourmodelifneededfortestingorseeding.UsuallypairedwithafakedatageneratorlikeFaker.
MultitenancyAsingleappservingmultipleclients,eachofwhichhasitscustomers.Multitenancyoftensuggeststhateachclientofyourapplicationgetsitsowntheminganddomainname,withwhichtodifferentiateitsservicetoitscustomersvis-à-visyourotherclients’potentialservices.
MutatorAtoolinEloquentthatallowsyoutomanipulatethedatabeingsavedtoamodelpropertybeforeitissavedtothedatabase.
NginxAwebserversimilartoApache.
Option(Artisan)Likearguments,optionsareparametersthatcanbepassedtoArtisancommands.They’reprefacedwith--andcanbeusedasaflag(--force)ortoprovidedata(--userId=5).
ORM(object-relationalmapper)Adesignpatternthatiscenteredaroundusingobjectsinaprogramminglanguagetorepresentdata,anditsrelationships,inarelationaldatabase.
PassportALaravelpackagethatcanbeusedtoeasilyaddanOAuthauthenticationservertoyourLaravelapp.
PHPSpecAPHPtestingframework.
PHPUnitAPHPtestingframework.ThemostcommonandconnectedtothemostofLaravel’scustomtestingcode.
PolymorphicIndatabaseterms,abletointeractwithmultipledatabasetableswithsimilarcharacteristics.Apolymorphicrelationshipwillallowentitiesofmultiplemodelstobeattachedinthesameway.
PreprocessorAbuildtoolthattakesinaspecialformofalanguage(forCSS,onespecialformisLESS)andgeneratescodewithjustthenormallanguage(CSS).Preprocessorsbuildintoolsandfeaturesthatarenotinthecorelanguage.
PrimarykeyMostdatabasetableshaveasinglecolumnthatisintendedtorepresenteachrow.Thisiscalledtheprimarykeyandiscommonlynamedid.
QueueAstackontowhichjobscanbeadded.Usuallyassociatedwithaqueueworker,whichpullsjobsoneatatimefromaqueue,worksonthem,andthendiscardsthem.
RedisLikeMemcached,adatastoresimplerthanmostrelationaldatabasesbutpowerfulandfast.Redissupportsaverylimitedsetofstructuresanddatatypesbutmakesupforitinspeedandscalability.
RESTRepresentationalStateTransfer,themostcommonformatforAPIsthesedays.UsuallysuggeststhatinteractionswithanAPIshouldeachauthenticateseparatelyandshouldbe“stateless”;alsousuallysuggeststhattheyusetheHTTPverbsforbasicdifferentiationofrequests.
RouteAdefinitionofawayorwaystheusermightvisitawebapplication.Arouteisapatterndefinition;itcanbesomethinglike/users/5,or/users,or/users/{id}.
SaaSSoftwareasaService.Web-basedapplicationsthatyoupaymoneytouse.
ScopeInEloquent,atoolfordefininghowtoconsistentlyandsimplynarrowdownaquery.
ScoutALaravelpackageforfull-textsearchonEloquentmodels.
Serialization
Theprocessofconvertingmorecomplexdata(usuallyanEloquentmodel)tosomethingsimpler(inLaravel,usuallyanarrayorJSON).
ServiceproviderAstructureinLaravelthatregistersandbootsclassesandcontainerbindings.
SoftdeleteMarkingadatabaserowas“deleted”withoutactuallydeletingit,usuallypairedwithanORMthatbydefaulthidesall“deleted”rows.
SparkALaraveltoolthatmakesiteasytospinupanewsubscription-basedSaaSapp.
SymfonyAPHPframeworkthatfocusesonbuildingexcellentcomponentsandmakingthemaccessibletoothers.Symfony’sHTTPFoundationisatthecoreofLaravelandeveryothermodernPHPframework.
TinkerLaravel’sREPL,orread–evaluate–printloop.It’satoolthatallowsyoutoperformcomplexPHPoperationswithinthefullcontextofyourappfromthecommandline.
TL;DRToolong;didn’tread.“Summary.”
TypehintPrefacingavariablenameinamethodsignaturewithaclassorinterfacename.TellsPHP(andLaravel,andotherdevelopers)thattheonlythingthat’sallowedtobepassedinthatparameterisanobjectwiththegivenclassorinterface.
UnittestUnitteststargetsmall,relativelyisolatedunits—aclassormethod,usually.
VagrantAcommand-linetoolthatmakesiteasytobuildvirtualmachinesonyourlocalcomputerusingpredefinedimages.
ValetALaravelpackage(forMacOSusers,butthereareforksforLinuxandWindows)thatmakesiteasytoserveyourapplicationsfromyourdevelopmentfolderofchoice,withoutworryingaboutVagrantorvirtualmachines.
ValidationEnsuringthatuserinputmatchesexpectedpatterns.
ViewcomposerAtoolthatdefinesthat,everytimeagivenviewisloaded,itwillbeprovidedacertainsetofdata.
ViewAtemplatefilethatdefinesHTMLtobesenttotheenduser;oftenincludesacceptingdatafromacontrollerandformattingitaspartoftheHTML.
Index
Symbols
arrow
->chainingmethods,RouteNames,Middleware
->traversingJSONstructure,JSONoperations
=>precedingTinkerresponses,Tinker
*(asterisk),followingarrayargumentsoroptions,Arrayargumentsandarrayoptions
@(atsign)
incontroller/methodreference,Auth::routes()
precedingBladedirectives,BladeTemplating
precedingBladeechosyntax,EchoingData
\(backslash),escapinginArtisancommands,ReceivingtheMessage
{}(braces)
enclosingArtisancommandarguments,Arguments,required,optional,and/orwithdefaults
enclosingrouteparameters,RouteModelBinding,FromRouteParameters
{{}},Bladeechosyntax,escaped,EchoingData,{{Versus{!!
{!!!!},Bladeechosyntax,notescaped,EchoingData,{{Versus{!!
::(colon,double),infacades,FacadesandtheContainer
=(equalsign),inArtisanargumentdefinition,Arguments,required,optional,and/orwithdefaults
--(hyphen,double),precedingArtisancommandoptions,Options,requiredvalues,value
defaults,andshortcuts
.(period),dotnotation,Glossary
?(questionmark)
followingoptionalArtisancommandarguments,Arguments,required,optional,and/orwithdefaults
followingoptionalparameters,RouteParameters
queryparameters,Parameterbindingsandnamedbindings
/(slash),escapinginArtisancommands,ControllerOrganizationandJSONReturns
A
abilities(rules)forauthorization,DefiningAuthorizationRules
abort()helper,AbortingtheRequest,Misc
abort_if()helper,AbortingtheRequest,Misc
abort_unless()helper,AbortingtheRequest,Misc
acceptancetests(seeapplicationtests)
accepts()method,Request,Userandrequeststate
accesscontrollist(ACL)(seeauthorization)
accessors,Accessors,HidingattributesfromJSON,Testing,Glossary
ACL(accesscontrollist)(seeauthorization)
action()helper,OtherRedirectMethods,URLs
ActiveRecordpattern,IntroductiontoEloquent,Glossary
(seealsoEloquent)
add()method,Cache,TheMethodsAvailableonCacheInstances
addGlobalScope()method,Globalscopes
after()method,Blueprint,Buildingextrapropertiesfluently
after()method,tasks,TaskHooks
AlgoliaSDK,InstallingScout
aliases,bindingto,BindingtoSingletons,Aliases,andInstances
all()method,collection,AFewMethods
all()method,Eloquent,RetrievingDatawithEloquent,Getmany
all()method,ParameterBag,Basicuserinput
all()method,Request,$request->all(),EloquentModelMassAssignment,Basicuserinput
all()method,Session,TheMethodsAvailableonSessionInstances
allDirectories()method,Storage,UsingtheStorageFacade
allFiles()method,Storage,UsingtheStorageFacade
allows()method,Gate,TheGateFacade(andInjectingGate)
anonymousfunctions(seeclosures)
anticipate()method,Prompts
apiguard,Guards
apimiddlewaregroup,Usingmiddlewaregroups
APIroutes,RouteDefinitions
(seealsoroutes)
api.phpfile,RouteDefinitions
APIs,WritingAPIs-TheBasicsofREST-LikeJSONAPIs
authenticationwithAPItokens,Laravel5.2+APITokenAuthentication-Laravel5.2+APITokenAuthentication
authenticationwithPassport,APIAuthenticationwithLaravelPassport-PassportScopes
filteringresults,FilteringYourAPIResults
JSONfor,TheBasicsofREST-LikeJSONAPIs,ControllerOrganizationandJSONReturns,SortingandFiltering
nestingrelationshipsbetweenresources,NestingandRelationships-NestingandRelationships
paginatingresults,EloquentPagination-EloquentPagination
requestheaders,reading,ReadingandSendingHeaders,ReadingRequestHeadersinLaravel
resourcecontrollers,ControllerOrganizationandJSONReturns-ControllerOrganizationandJSONReturns
responseheaders,sending,ReadingandSendingHeaders
RESTstyleof,TheBasicsofREST-LikeJSONAPIs-TheBasicsofREST-LikeJSONAPIs
sortingresults,SortingandFiltering-SortingYourAPIResults
testing,Testing
transformingresults,TransformingResults-WritingYourOwnTransformer
.appdomain,ConfiguringHomestead
appfolder,TheFolders
app()helper,GettingaRequestObjectinLaravel,Theapp()GlobalHelper
app-namespacedcommands,Artisan,TheGroupedCommands
app.jsfile,BringingEchointoyourproject
app.phpfileinconfig,InstallingScout,Thebroadcastserviceprovider
append()method,Storage,UsingtheStorageFacade
appendOutputTo()method,tasks,HandlingTaskOutput
application
bootstrapping,BootstrappingtheApplication-ServiceProviders
exiting,Misc
kernel,Laravel’skernel
lifecycle,Laravel’sRequestLifecycle-ServiceProviders
applicationcontainer(seecontainer)
applicationtests,Testing,ApplicationTesting-AuthenticationandSessions,Glossary
assertions,CustomApplicationTestingAssertions-JSONandNon-visit()ApplicationTestingAssertions,Glossary
clickingandforms,ClickingandForms-ClickingandForms
jobsandevents,JobsandEvents
visitingroutes,“Visiting”Routes-“Visiting”Routes
AppServiceProvider,ServiceProviders
app_path()helper,ApplicationPaths
argument()method,Artisan,argument()
arguments(Artisan),Glossary
arrays
asArtisanargumentsoroptions,Arrayargumentsandarrayoptions
collectionsasalternativeto,Introducingthebasecollection
convertingtocollections,Misc
helpersfor,Arrays-Arrays
array_filter()method,TheBasicsofCollections
array_first()helper,Arrays
array_get()helper,Arrays
array_has()helper,Arrays
array_map()method,TheBasicsofCollections
array_pluck()helper,Arrays
arrow
->chainingmethods,RouteNames,Middleware
->traversingJSONstructure,JSONoperations
=>precedingTinkerresponses,Tinker
Artisancommands,AnIntroductiontoArtisan-CallingArtisanCommandsinNormalCode,Glossary,Glossary
argumentsfor,Glossary
callingfromcode,ASampleCommand,CallingArtisanCommandsinNormalCode
callingothercommandsfrom,CallingArtisanCommandsinNormalCode
custom,WritingCustomArtisanCommands-Inputdescriptions
escapingslashesin,ControllerOrganizationandJSONReturns
listing,AnIntroductiontoArtisan
optionsfor,Options-Options,Glossary
outputduring,Output-Progressbars
progressbarsfor,Progressbars
promptingforuserinput,Prompts-Prompts
queueing,TheGroupedCommands,QueuesSupportingOtherFunctions
schedulingastasks,AvailableTaskTypes
testing,Testing,ArtisanandSeed
usinginputfrom,UsingInput-Prompts
Artisanfacade,CallingArtisanCommandsinNormalCode,Testing
artisanfile,TheLooseFiles
artisan()method,TestCase,ArtisanandSeed
ask()method,Prompts
assertEquals()method,TestCase,Cache
assertHasOldInput()method,TestCase,Session
assertions,CustomApplicationTestingAssertions-JSONandNon-visit()ApplicationTestingAssertions,Glossary
assertNotSent()method,notification,Notifications
assertPageLoaded()method,TestCase,CustomApplicationTestingAssertions
assertRedirectedTo()method,TestCase,JSONandNon-visit()ApplicationTestingAssertions
assertRedirectedToAction()method,TestCase,JSONandNon-visit()ApplicationTestingAssertions
assertRedirectedToRoute()method,TestCase,JSONandNon-visit()ApplicationTestingAssertions
assertResponseOK()method,TestCase,JSONandNon-visit()ApplicationTestingAssertions
assertResponseStatus()method,TestCase,JSONandNon-visit()ApplicationTestingAssertions
assertSent()method,notification,Notifications
assertSessionHas()method,TestCase,JSONandNon-visit()ApplicationTestingAssertions,Session
assertSessionHasAll()method,TestCase,JSONandNon-visit()ApplicationTestingAssertions,Session
assertSessionHasErrors()method,TestCase,JSONandNon-visit()ApplicationTestingAssertions,Session
assertSessionMissing()method,TestCase,Session
assertViewHas()method,TestCase,Testing,JSONandNon-visit()ApplicationTestingAssertions
assertViewHasAll()method,TestCase,JSONandNon-visit()ApplicationTestingAssertions
assertViewMissing()method,TestCase,JSONandNon-visit()ApplicationTestingAssertions
assetsfolder,ElixirFolderStructure
associate()method,Eloquent,Onetomany
asterisk(*),followingarrayargumentsoroptions,Arrayargumentsandarrayoptions
atsign(@)
incontroller/methodreference,Auth::routes()
precedingBladedirectives,BladeTemplating
precedingBladeechosyntax,EchoingData
attach()method,Eloquent,Manytomany
attach()method,mailable,MethodsAvailableinbuild()
attach()method,TestCase,ClickingandForms
attachData()method,mailable,MethodsAvailableinbuild()
attempt()method,authentication,“RememberMe”
attempts()method,jobs,Limitingthenumberoftries
attributecasting,Attributecasting
authcommands,Artisan,TheGroupedCommands
Authfacade,Usingtheauth()GlobalHelperandtheAuthFacade
authmiddleware,AuthMiddleware
authscaffold,TheAuthScaffold-TheAuthScaffold
auth()helper,Usingtheauth()GlobalHelperandtheAuthFacade,Misc
auth.basicmiddleware,AuthMiddleware
auth.phpfile,ChangingtheDefaultGuard,InstallingPassport
Auth::routes()facade,Auth::routes()
AuthController,UserAuthenticationandAuthorization
Authenticatablecontract,TheUserModelandMigration
authentication,UserAuthenticationandAuthorization-AuthEvents,Glossary
APIsfor,APIAuthenticationwithLaravelPassport-Laravel5.2+APITokenAuthentication
contracts,TheUserModelandMigration
create_users_tablemigration,TheUserModelandMigration
events,AuthEvents
ForgotPasswordController,ForgotPasswordController
guardsfor,Guards-CustomUserProvidersforNonrelationalDatabases
LoginController,LoginController-ThrottlesLoginstrait
manualauthentication,ManuallyAuthenticatingUsers
RegisterController,RegisterController-RegistersUserstrait
RegistersUserstrait,RegistersUserstrait
remembermeaccesstoken,“RememberMe”
ResetPasswordController,ResetPasswordController
routemiddlewarefor,AuthMiddleware
routesfor,Auth::routes()
settingup,TheAuthScaffold
testing,Testing-Testing,AuthenticationandSessions
Usermodel,TheUserModelandMigration-TheUserModelandMigration
viewsfor,TheAuthScaffold-TheAuthScaffold
WebSocket(seeEcho)
Authorizablecontract,TheUserModelandMigration
Authorizabletrait,CheckingontheUserInstance
authorization,TheUserModelandMigration,Authorization(ACL)andRoles-Overridingpolicies,Glossary
Authorizablecontract,TheUserModelandMigration
AuthorizesRequeststrait,ControllerAuthorization-CheckingontheUserInstance
Bladechecks,BladeChecks
checkingusercapabilities,CheckingontheUserInstance-InterceptingChecks
Gatefacade,Authorization(ACL)andRoles,TheGateFacade(andInjectingGate)
overridingchecks,InterceptingChecks
Passportpackage,Overridingpolicies
policies,Policies-Overridingpolicies
routemiddlewarefor,TheAuthorizeMiddleware
rules(abilities)for,defining,DefiningAuthorizationRules
testing,Testing-Testing
authorizationcodegrant,Passport,Authorizationcodegrant-Authorizationcodegrant
authorize()method,AuthorizesRequeststrait,ControllerAuthorization
authorize()method,formrequest,CreatingaFormRequest
authorizeForUser()method,AuthorizesRequeststrait,ControllerAuthorization
authorizeResource()method,AuthorizesRequeststrait,ControllerAuthorization
AuthorizesRequeststrait,ControllerAuthorization-CheckingontheUserInstance
AuthServiceProvider,DefiningAuthorizationRules,Generatingpolicies,ServiceProviders,PassportScopes
autowiring,HowtheContainerIsWired-HowtheContainerIsWired,Glossary
avg()method,collection,AFewMethods
avg()method,DB,Ending/returningmethods
avg()method,Eloquent,Aggregates
away()method,redirects,OtherRedirectMethods
B
back()helper,redirect()->back(),Redirectresponses,Misc
backslash(\),escapinginArtisancommands,ReceivingtheMessage
base_path()helper,ApplicationPaths
be()method,TestCase,Testing,AuthenticationandSessions
beanstalkdqueues,BasicQueueConfiguration,Glossary
before()method,tasks,TaskHooks
beginTransaction()method,DB,Transactions
Behat,Testing
belongsTo()method,Eloquent,Onetoone,Onetomany,ChildRecordsUpdatingParentRecordTimestamps
belongsToMany()method,Eloquent,Manytomany,ChildRecordsUpdatingParentRecordTimestamps
bigIncrements()method,Blueprint,Creatingcolumns
bigInteger()method,Blueprint,Creatingcolumns
billing(seeCashierpackage)
binary()method,Blueprint,Creatingcolumns
bind()method,BindingtoaClosure,BindingtoSingletons,Aliases,andInstances
binding
classestocontainer,BindingClassestotheContainer-ContextualBinding
datatoviews,BindingDatatoViewsUsingViewComposers-Class-basedviewcomposers
PDOparameterbinding,Parameterbindingsandnamedbindings
routemodelbinding,RouteModelBinding-CustomRouteModelBinding
Blade,BladeTemplating-BladeTemplating,Glossary
checksusing,BladeChecks
conditionals,Conditionals
customdirectives,CustomBladeDirectives-Example:UsingCustomBladeDirectivesforaMultitenantApp
directivesfor,BladeTemplating,Glossary
echoingPHPin,EchoingData
$expressionparameter,ParametersinCustomBladeDirectives
includedviewpartials,@include,@each
loops,Loops-@forelse,@each
multitenancyusing,Example:UsingCustomBladeDirectivesforaMultitenantApp-Example:UsingCustomBladeDirectivesforaMultitenantApp
orhelper,or
sections,DefiningSectionswith@section/@showand@yield
serviceinjection,BladeServiceInjection-BladeServiceInjection
templates,Views,TemplateInheritance-@each
Blueprintclass,Creatingcolumns-Buildingextrapropertiesfluently
boolean()method,Blueprint,Creatingcolumns
boot()method,Eloquentmodel,Globalscopes
boot()method,serviceproviders,CustomRouteModelBinding,DefiningAuthorizationRules,ServiceProviders,PassportScopes
bootstrapfolder,TheFolders
bootstrappingapplication,BootstrappingtheApplication-ServiceProviders
braces({})
enclosingArtisancommandarguments,Arguments,required,optional,and/orwithdefaults
enclosingrouteparameters,RouteModelBinding,FromRouteParameters
{{}},Bladeechosyntax,escaped,EchoingData,{{Versus{!!
{!!!!},Bladeechosyntax,notescaped,EchoingData,{{Versus{!!
broadcastnotifications,Broadcastnotifications
broadcast()helper,Excludingthecurrentuserfrombroadcastevents,Excludingthecurrentuser
broadcastAs()method,events,BroadcastinganEvent
broadcastingevents(seeWebSockets)
broadcasting.phpfile,ConfigurationandSetup
broadcastOn()method,events,FiringanEvent,BroadcastinganEvent
BroadcastServiceProvider,Thebroadcastserviceprovider
broadcastWith()method,events,BroadcastinganEvent
build()method,mailable,Basic“Mailable”MailUsage
C
cachecommands,Artisan,TheGroupedCommands
Cachefacade,AccessingtheCache
cache()helper,AccessingtheCache
cache.phpfile,Cache
caches
accessing,TheGroupedCommands,Cache-TheMethodsAvailableonCacheInstances
datastoresusedby,OtherDatabaseConfigurationOptions
forcustomdirectiveresults,CustomBladeDirectives
forroutes,RouteCaching
testing,Cache
call()method,Artisan,CallingArtisanCommandsinNormalCode
call()method,container,MethodInjection
call()method,TestCase,“Visiting”Routes,Cookies
@candirective,BladeChecks
can()method,Authorizable,CheckingontheUserInstance
@cannotdirective,BladeChecks
cannot()method,Authorizable,CheckingontheUserInstance
CanResetPasswordcontract,TheUserModelandMigration
cant()method,Authorizable,CheckingontheUserInstance
capture()method,Request,TheRequestObject
Carbonpackage,Attributecasting,TheMethodsAvailableonCacheInstances,Glossary
Cashierpackage,Glossary
chainingmethods,RouteNames,Middleware
change()method,Modifyingcolumns
channel()method,Broadcast,BindingauthorizationdefinitionsforWebSocketchannels
channel()method,Echo,UsingEchoforbasiceventbroadcasting
char()method,Blueprint,Creatingcolumns
check()method,authorization,Usingtheauth()GlobalHelperandtheAuthFacade
check()method,TestCase,ClickingandForms
choice()method,Prompts
chunk()method,collection,AFewMethods
chunk()method,Eloquent,Chunkingresponseswithchunk()
classes
autowiringtocontainer,Glossary
FQCN(fully-qualifiedclassname)for,Glossary
viewcomposersusing,Class-basedviewcomposers-Class-basedviewcomposers
clearcommand,Artisan,BasicArtisanCommands
clearInputs()method,TestCase,ClickingandForms
click()method,TestCase,ClickingandForms
closures,RouteDefinitions,Glossary
bindingto,BindingtoaClosure
definingArtisancommandsas,RegisteringCommands
definingroutegroupsusing,RouteGroups
definingroutesusing,RouteDefinitions-RouteDefinitions,RouteHandling
viewcomposersusing,Closure-basedviewcomposers
Cloudstorage(seestorage)
cloud-basedmail,Mail
CodeIgniterframework,Glossary
collect()helper,Introducingthebasecollection,Misc,TheBasicsofCollections
Collectionclass,Rawselects,Introducingthebasecollection-Introducingthebasecollection
Collectionpattern,Glossary
collections,Collections-AFewMethods
comparedtoarrays,TheBasicsofCollections-TheBasicsofCollections
convertingtoarrays,AFewMethods
returnedbyEloquent,EloquentCollections-WhatEloquentcollectionsadd
serialization,EloquentSerialization
usingoutsideLaravel,AFewMethods
colon,double(::),infacades,FacadesandtheContainer
commands,Artisan(seeArtisancommands)
comment()method,Artisan,Output
commit()method,DB,Transactions
compiled.phpfile,BootstrappingtheApplication
Composer,Composer,BootstrappingtheApplication,Glossary
commandsfor,InstallingLaravelwiththeLaravelInstallerTool,InstallingLaravelwithComposer’screate-projectFeature
newprojects,creating,InstallingLaravelwithComposer’screate-projectFeature
serviceproviderfeatureswith,ServiceProviders
composer.jsonfile,TheLooseFiles
composer.lockfile,TheLooseFiles
conditionals(Blade),Conditionals
configcommands,Artisan,TheGroupedCommands
configfolder,TheFolders,Configuration
config()helper,Misc
config/app.phpfile,InstallingScout,Thebroadcastserviceprovider
configurationfiles,TheFolders,Configuration,ChangingtheDefaultGuard,ApplicationPaths
config_path()helper,ApplicationPaths
confirm()method,Artisan,Prompts
Consolecomponent,Symfony,AnIntroductiontoArtisan
constructorinjection,AQuickIntroductiontoDependencyInjection,DependencyInjectionandLaravel,ConstructorInjection
contactinformationforthisbook,HowtoContactUs
container,InjectingDependenciesintoControllers,Glossary
accessingfacadebackingclassfrom,HowFacadesWork
accessingobjectsfrom,Theapp()GlobalHelper
autowiring,HowtheContainerIsWired-HowtheContainerIsWired
bindingclassesto,BindingClassestotheContainer-ContextualBinding
classesin,autowiring,Glossary
constructorinjection,ConstructorInjection
dependencyinjection,AQuickIntroductiontoDependencyInjection-DependencyInjectionandLaravel
methodinjection,MethodInjection
registeringbindingsfor,ServiceProviders
contextualbinding,ContextualBinding
contracts(seeinterfaces)
Contractsnamespace,TheUserModelandMigration
controllers,Controllers-Bindingaresourcecontroller,Glossary
applyingmiddlewareusing,Middleware
creating,Controllers-Controllers
gettingandhandlinguserinput,GettingUserInput-GettingUserInput
handlingroutesusing,RouteHandling
injectingdependenciesinto,InjectingDependenciesintoControllers-InjectingDependenciesintoControllers
methodreferencesyntaxfor,Auth::routes()
namespacefor,Controllers
resourcecontrollers,ResourceControllers-Bindingaresourcecontroller,ControllerOrganizationandJSONReturns-ControllerOrganizationandJSONReturns
Cookiefacade,TheCookiefacade-TheCookiefacade
cookie()helper,Thecookie()globalhelper
cookie()method,Request,Persistence,Readingcookiesfromrequestobjects
cookie()method,Response,Settingcookiesonresponseobjects
CookieJarclass,CookiesinLaravel,TheCookiefacade,Thecookie()globalhelper
cookies,Cookies-Settingcookiesonresponseobjects
accessingwithCookiefacade,TheCookiefacade-TheCookiefacade
accessingwithcookie()helper,Thecookie()globalhelper
accessingwithRequestandResponse,Cookiesonrequestandresponseobjects
configuring,TheCookiefacade
disablingencryptionfor,Cookies
locationsof,CookiesinLaravel
manuallyencryptingfor,Cookies
testing,Cookies-Cookies
copy()method,Storage,UsingtheStorageFacade
count()method,collection,AFewMethods
count()method,DB,Ending/returningmethods
count()method,Eloquent,Aggregates
count()method,ParameterBag,Basicuserinput
create()method,modelfactories,Usingamodelfactory,Inserts,Massassignment
create()method,resourcecontrollers,ThemethodsofLaravel’sresourcecontrollers
create()method,Schema,Creatingtables
create,read,update,delete(seeCRUD)
CreateFreshApiTokenmiddleware,TokensfromLaravelsessionauthentication(synchronizertokens),TokensfromLaravelsessionauthentication(synchronizertokens)
create_users_tablemigration,DefiningMigrations,TheUserModelandMigration
cronjobs,schedulerasalternativeto,Scheduler
cross-siterequestforgery(CSRF),Glossary
CRUD(create,read,update,delete),Controllers
(seealsoresourcecontrollers)
CSRF(cross-siterequestforgery),CSRFProtection-CSRFProtection,BringingEchointoyourproject,Glossary
csrf_field()helper,Misc
csrf_token()helper,Misc
CSS
preprocessorfor,Elixir-Elixir,WhatDoesElixirProvide?,Glossary
preprocessorless,inElixir,PreprocessorlessCSS
customroutemodelbinding,CustomRouteModelBinding
D
databasefolder,TheFolders
databasenotifications,Databasenotifications
DatabaseMigrationstrait,DatabaseMigrations
databases,DatabaseandEloquent
(seealsoEloquent)
connectionsto,configuring,Configuration-OtherDatabaseConfigurationOptions
customguardprovidersfor,CreatingaCustomUserProvider
databasetypessupported,DatabaseConnections,QueryBuilder
Homestead,LaravelHomestead-ConnectingtoHomesteaddatabasesfromdesktopapplications,Glossary
migrations,Migrations-RunningMigrations
paginatingresultsfrom,PaginatingDatabaseResults-ManuallyCreatingPaginators
querybuilder,QueryBuilder-Transactions
seeders,Seeding-Definingandaccessingmultiplemodelfactorytypes
testing,Testing-Testing
Tinkerinteractingwith,Tinker
DatabaseSeederclass,Seeding
DatabaseTransactionstrait,DatabaseTransactions
database_path()helper,ApplicationPaths
datemutators,Datemutators
datesandtimes(seeCarbonpackage;scheduler;timestamps)
datetime()method,Blueprint,Creatingcolumns
dbcommands,Artisan,TheGroupedCommands
DBfacade,BasicUsageoftheDBFacade
db:seedcommand,Artisan,Seeding
dd()helper,Misc
DeadMan’sSnitch,HandlingTaskOutput
decimal()method,Blueprint,Creatingcolumns
decrement()method,Cache,TheMethodsAvailableonCacheInstances
decrement()method,DB,Updates
default()method,Blueprint,Buildingextrapropertiesfluently
define()method,Gate,TheGateFacade(andInjectingGate)
define()method,modelfactories,Creatingamodelfactory
delay()method,jobs,Customizingthedelay
delay()method,notification,QueueingNotifications
DELETEmethod,AnIntroductiontoHTTPVerbs-HTTPMethodSpoofinginHTMLForms
forresourcecontrollers,ThemethodsofLaravel’sresourcecontrollers
routesbasedon,RouteVerbs
delete()method,DB,Rawdeletes,Deletes
delete()method,Eloquent,Normaldeletes
delete()method,Storage,UsingtheStorageFacade
delete()method,TestCase,“Visiting”Routes
deleteDirectory()method,Storage,UsingtheStorageFacade
deleted_atcolumn,Softdeletes
deletes,soft,Glossary
denies()method,Gate,TheGateFacade(andInjectingGate)
dependencyinjection,AQuickIntroductiontoDependencyInjection-DependencyInjectionandLaravel,Glossary
constructorinjection,AQuickIntroductiontoDependencyInjection,DependencyInjectionandLaravel,ConstructorInjection
methodinjection,AQuickIntroductiontoDependencyInjection,MethodInjection
setterinjection,AQuickIntroductiontoDependencyInjection
testingusing,Testing
destroy()method,resourcecontrollers,ThemethodsofLaravel’sresourcecontrollers
detach()method,Eloquent,Manytomany
developmentenvironments,LocalDevelopmentEnvironments-ConnectingtoHomesteaddatabasesfromdesktopapplications
DI(dependencyinjection)container(seecontainer)
directives(Blade),BladeTemplating,Glossary
directories()method,Storage,UsingtheStorageFacade
disk()method,Storage,UsingtheStorageFacade,BasicFileUploadsandManipulation
dispatch()helper,Pushingajobontoaqueue,Misc
DispatchesJobstrait,Pushingajobontoaqueue
dissociate()method,Eloquent,Onetomany
distinct()method,DB,Constrainingmethods
dnsmasqtool,LaravelValet
domains,top-level,ConfiguringHomestead
dontSee()method,TestCase,CustomApplicationTestingAssertions
dontSeeInDatabase()method,TestCase,CustomApplicationTestingAssertions
dontSeeInField()method,TestCase,CustomApplicationTestingAssertions
dontSeeIsChecked()method,TestCase,CustomApplicationTestingAssertions
dontSeeIsSelected()method,TestCase,CustomApplicationTestingAssertions
dontSeeJson()method,TestCase,JSONandNon-visit()ApplicationTestingAssertions
dontSeeLink()method,TestCase,CustomApplicationTestingAssertions
dotnotation(.),Glossary
double()method,Blueprint,Creatingcolumns
downcommand,Artisan,BasicArtisanCommands
down()method,migrations,DefiningMigrations,DefiningMigrations
downloadresponses,Downloadresponses
download()method,Response,response()->download()and->file(),Downloadresponses
E
e()helper,TheStringHelpersandPluralization,Strings
@eachdirective,Blade,@each
each()method,collection,AFewMethods
eagerloading,Eagerloading-Eagerloadingonlythecount,Glossary
Echo,BroadcastingEventsoverWebSockets,andLaravelEcho,ReceivingtheMessage,AdvancedBroadcastingTools-SubscribingtonotificationswithEcho,Glossary
authorizationforchannels,BindingauthorizationdefinitionsforWebSocketchannels-BindingauthorizationdefinitionsforWebSocketchannels
excludinguserfromevents,Excludingthecurrentuserfrombroadcastevents,Excludingthecurrentuser
JavaScriptpackagefor,LaravelEcho(theJavaScriptSide)-SubscribingtonotificationswithEcho
listeningforevents,UsingEchoforbasiceventbroadcasting
presencechannels,Presencechannels
privatechannels,Privatechannelsandbasicauthentication
serviceproviderconfiguration,Thebroadcastserviceprovider
subscribingtochannels,UsingEchoforbasiceventbroadcasting
subscribingtonotifications,SubscribingtonotificationswithEcho
edit()method,resourcecontrollers,ThemethodsofLaravel’sresourcecontrollers
Elixirbuildtool,Elixir-Elixirextensions,Glossary
directorystructurefor,ElixirFolderStructure
documentationfor,WhatDoesElixirProvide?
extensionsfor,Elixirextensions-Elixirextensions
JavaScript,concatenating,ConcatenatingJavaScript
JavaScript,processing,ProcessingJavaScript
multiplefiles,processing,Passingmultiplefiles
preprocessorlessCSS,PreprocessorlessCSS
productionmode,The--productionflag
running,RunningElixir
sourcemaps,generating,Sourcemaps
tests,running,Tests
versioning,Versioning-Versioning
versionsof,Elixir
elixir()helper,Versioning,URLs
Eloquent,Pagination,IntroductiontoEloquent-EloquentEvents,Glossary
accessors,Accessors,HidingattributesfromJSON,Testing,Glossary
aggregates,Aggregates
attributecasting,Attributecasting
collectionsreturnedby,EloquentCollections-WhatEloquentcollectionsadd
customzingroutekeyfor,ImplicitRouteModelBinding
datemutators,Datemutators
deletes,DeletingwithEloquent-Force-deletingsoft-deletedentities
eagerloading,Eagerloading-Eagerloadingonlythecount,Glossary
events,EloquentEvents-EloquentEvents
exceptionsthrownby,Getone
fillableorguardedproperties,Massassignment
filteringAPIresults,FilteringYourAPIResults
full-textsearchfor,Full-TextSearchwithLaravelScout-ManuallyTriggerIndexingviatheCLI
inserts,Inserts-Inserts,firstOrCreate()andfirstOrNew()
JSONresultsforAPIs,ControllerOrganizationandJSONReturns
massassignment,EloquentModelMassAssignment,Massassignment-Massassignment,Glossary
migration,creatingwithmodel,CreatingandDefiningEloquentModels
model,creating,CreatingandDefiningEloquentModels-Timestamps
mutators,Mutators-Mutators
paginationfor,PaginatingDatabaseResults-ManuallyCreatingPaginators,EloquentPagination-EloquentPagination
primarykeys,Primarykey
querybuilder,Pagination
relationships,EloquentRelationships-Manytomanypolymorphic
retrievingdata,RetrievingDatawithEloquent-Aggregates
scopes(filters),Scopes-Removingglobalscopes,Glossary
serialization,EloquentSerialization-HidingattributesfromJSON
sortingresults,SortingandFiltering-SortingYourAPIResults
tablenames,Tablename
timestamps,Creatingcolumns,Timestamps,Datemutators,ChildRecordsUpdatingParentRecordTimestamps-Eagerloadingonlythecount
transformingresults,TransformingResults-WritingYourOwnTransformer
updates,Updates-Massassignment
userinputfrom,EloquentModelMassAssignment
@elsedirective,Blade,@if,BladeChecks
@elseifdirective,Blade,@if
emailnotifications,Emailnotifications-Emailnotifications
emailOutputTo()method,tasks,HandlingTaskOutput
EncryptCookiesmiddleware,Cookies
encryption
disablingforcookies,Cookies
generatingkeysforapplication,TheGroupedCommands
generatingkeysforOAuthserver,InstallingPassport
manuallyencryptingcookies,Cookies
ofsessiondata,Sessions
@endcandirective,BladeChecks
@endcannotdirective,BladeChecks
@endifdirective,Blade,@if
@endsectiondirective,Blade,DefiningSectionswith@section/@showand@yield,@sectionand@endsection
ends_with()helper,TheStringHelpersandPluralization,Strings
@endunlessdirective,Blade,@unlessand@endunless
enum()method,Blueprint,Creatingcolumns
envcommand,Artisan,BasicArtisanCommands
.envfile,TheLooseFiles,Configuration
env()helper,Configuration,Misc
.env.examplefile,TheLooseFiles
.env.testfile,TheTestingEnvironment
environmentvariables,Glossary
returning,Misc
settingfortests,TheTestingEnvironment
environment()method,TheTestingEnvironment
Envoyer,HandlingTaskOutput,Glossary
equalsign(=),inArtisanargumentdefinition,Arguments,required,optional,and/orwithdefaults
errorbags,MessageBags-NamedErrorBags,TestingMessageandErrorBags
error()method,Output
errorsandexceptions
fromEloquent,Getone
fromHTTP,Misc
fromjobsinqueue,handling,HandlingErrors-Handlingfailedjobs
inmessageanderrorbags,MessageBags-NamedErrorBags,TestingMessageandErrorBags
fromsession,testingfor,JSONandNon-visit()ApplicationTestingAssertions,Session
fromuserinput,displaying,DisplayingValidationErrorMessages
$errorsvariable,MessageBags-NamedErrorBags
ES6,JavaScript,Elixir
eventcommands,Artisan,TheGroupedCommands
Eventfacade,FiringanEvent
event()helper,FiringanEvent,Misc
event-relatedtests,JobsandEvents
events,Events-Eventsubscribers,Glossary
authentication,AuthEvents
broadcastingoverWebSockets(seeWebSockets)
creating,FiringanEvent-FiringanEvent
Eloquent,EloquentEvents-EloquentEvents
firing,FiringanEvent-FiringanEvent,Misc
listenersfor,creating,ListeningforanEvent-ListeningforanEvent
Pub/Subpatternusedby,Events
subscribersfor,Eventsubscribers-Eventsubscribers
ExampleTest.phpfile,TestingBasics
except()method,Request,$request->except()and$request->only(),Basicuserinput
exceptions(seeerrorsandexceptions)
exists()method,Request,$request->has()and$request->exists(),Basicuserinput
exists()method,Storage,UsingtheStorageFacade
expectsEvents()method,TestCase,JobsandEvents
expectsJob()method,TestCase,JobsandEvents
$expressionparameter,Blade,ParametersinCustomBladeDirectives
extend()method,Storage,AddingAdditionalFlysystemProviders
@extendsdirective,Blade,@extends
F
facades,FacadesandtheContainer-HowFacadesWork,Glossary
accessingbackingclassof,HowFacadesWork
creating,HowFacadesWork
importing,GettingUserInput
importingnamespacesfor,FacadesandtheContainer
injectingbackingclassof,HowFacadesWork
mocking,MockingFacades
namespacesfor,JSONInput(and$request->json())
staticcallsusing,RouteDefinitions
factory()helper,Creatingamodelfactory,Usingamodelfactory,Misc
failed()method,jobs,Handlingfailedjobs
failing()method,Queue,Handlingfailedjobs
Faker,Testing,Returningfakefiles
Filefacade,UsingtheStorageFacade
fileresponses,Fileresponses
file()method,Faker,Returningfakefiles
file()method,Request,UploadedFiles,Files
file()method,Response,Testing,Fileresponses
files()method,Storage,UsingtheStorageFacade
filesystemstorage(seestorage)
filesystems.phpfile,ConfiguringFileAccess,UsingtheStorageFacade
file_get_contents()function,BasicFileUploadsandManipulation
fillableorguardedproperties,Massassignment
fillForm()method,TestCase,ClickingandForms
filter()method,collection,AFewMethods
filteringAPIresults,FilteringYourAPIResults
filters(seescopes(filters),Eloquent)
find()method,DB,Ending/returningmethods
find()method,Eloquent,Getone
findOrFail()method,DB,Ending/returningmethods
findOrFail()method,Eloquent,Getone
first()method,Blueprint,Buildingextrapropertiesfluently
first()method,collection,AFewMethods
first()method,DB,Ending/returningmethods,Ending/returningmethods
first()method,Eloquent,Getone
firstOrCreate()method,Eloquent,firstOrCreate()andfirstOrNew()
firstOrFail()method,DB,Ending/returningmethods
firstOrFail()method,Eloquent,Getone
firstOrNew()method,Eloquent,firstOrCreate()andfirstOrNew()
flags,Glossary
flash()method,Request,Persistence
flash()method,Session,FlashSessionStorage
flashExcept()method,Request,Persistence
flashOnly()method,Request,Persistence
float()method,Blueprint,Creatingcolumns
fluentinterface,QueryBuilder
fluentmethods,Glossary
flush()method,Cache,TheMethodsAvailableonCacheInstances
flush()method,Request,Persistence
flush()method,Session,TheMethodsAvailableonSessionInstances
flushSession()method,TestCase,AuthenticationandSessions
Flysystempackage,LocalandCloudFileManagers,AddingAdditionalFlysystemProviders,Glossary
followRedirects()method,TestCase,“Visiting”Routes
fontsusedinthisbook,HowThisBookIsStructured
forceDelete()method,Eloquent,Force-deletingsoft-deletedentities
forever()method,Cache,TheMethodsAvailableonCacheInstances
Forge,BasicQueueConfiguration,Glossary
forget()method,Cache,TheMethodsAvailableonCacheInstances
forget()method,Session,TheMethodsAvailableonSessionInstances
ForgotPasswordController,ForgotPasswordController
formencoding,UploadedFiles
formmethodspoofing,FormMethodSpoofing-HTTPMethodSpoofinginHTMLForms
formrequests,FormRequests-UsingaFormRequest,PassingParameterstoMiddleware
form-relatedtests,ClickingandForms-ClickingandForms
forUser()method,Gate,TheGateFacade(andInjectingGate),CheckingontheUserInstance
FQCN(fully-qualifiedclassname),Glossary
Fractalpackage,TransformingResults
frameworks,WhyLaravel?-Laravel5
(seealsoLaravel)
from()method,mailable,MethodsAvailableinbuild()
full-textsearch,Full-TextSearchwithLaravelScout-ManuallyTriggerIndexingviatheCLI
fully-qualifiedclassname(FQCN),Glossary
functionaltests(seeapplicationtests)
functions(seehelperfunctions)
G
Gatefacade,Authorization(ACL)andRoles,TheGateFacade(andInjectingGate)
GETmethod,AnIntroductiontoHTTPVerbs-HTTPMethodSpoofinginHTMLForms
forresourcecontrollers,ThemethodsofLaravel’sresourcecontrollers
routesbasedon,RouteVerbs
get()method,Cache,AccessingtheCache,TheMethodsAvailableonCacheInstances
get()method,Cookie,TheCookiefacade
get()method,DB,ChainingwiththeQueryBuilder,Ending/returningmethods
get()method,Eloquent,RetrievingDatawithEloquent,Getmany
get()method,ParameterBag,Basicuserinput
get()method,Route,RouteNames
get()method,Session,TheMethodsAvailableonSessionInstances
get()method,Storage,UsingtheStorageFacade
get()method,TestCase,“Visiting”Routes
getFacadeAccessor()method,HowFacadesWork
getRealPath()method,SplFileInfo,BasicFileUploadsandManipulation
.gitignorefile,TheLooseFiles
give()method,ContextualBinding
globalscopes,Globalscopes-Removingglobalscopes
granttypes,Passport,Passport’sAvailableGrantTypes-TokensfromLaravelsessionauthentication(synchronizertokens)
groupBy()method,collection,AFewMethods
groupBy()method,DB,Modifyingmethods
guard()method,UsingOtherGuardsWithoutChangingtheDefault
guardedorfillableproperties,Massassignment
guards,Guards-CustomUserProvidersforNonrelationalDatabases
adding,AddingaNewGuard
default,changing,ChangingtheDefaultGuard
driverfor,Guards,AddingaNewGuard
providerfor,Guards-CustomUserProvidersforNonrelationalDatabases
selecting,UsingOtherGuardsWithoutChangingtheDefault
guestmiddleware,AuthMiddleware
guest()method,Usingtheauth()GlobalHelperandtheAuthFacade
guest()method,redirects,OtherRedirectMethods
Gulpbuildtool,Elixir,RunningElixir,Glossary
gulpcommand,RunningElixir
gulptddcommand,Tests
gulpwatchcommand,TestingwithElixir
gulpfile.jsfile,TheLooseFiles,Elixir
H
handle()method,events,ListeningforanEvent
handle()method,jobs,Creatingajob
handle()method,requests,Laravel’skernel,CreatingCustomMiddleware-BindingMiddleware,PassingParameterstoMiddleware
has()method,Cache,TheMethodsAvailableonCacheInstances
has()method,Cookie,TheCookiefacade
has()method,Eloquent,Selectingonlyrecordsthathavearelateditem
has()method,ParameterBag,Basicuserinput
has()method,Request,$request->has()and$request->exists(),Basicuserinput
has()method,Session,TheMethodsAvailableonSessionInstances
HasApiTokenstrait,InstallingPassport
hasCookie()method,Request,Persistence
hasFile()method,Request,UploadedFiles,Files
hasMany()method,Eloquent,Onetomany
hasManyThrough()method,Eloquent,Hasmanythrough
hasOne()method,Eloquent,Onetoone
having()method,DB,Modifyingmethods
havingRaw()method,DB,Modifyingmethods
HEADmethod,AnIntroductiontoHTTPVerbs-HTTPMethodSpoofinginHTMLForms
header()method,Request,Userandrequeststate,ReadingRequestHeadersinLaravel
header()method,Response,SendingResponseHeadersinLaravel
headers(seerequestheaders;responseheaders)
helpcommand,Artisan,BasicArtisanCommands
helperfunctions,Helpers-Misc,Glossary
(seealsospecifichelpers)
forarrays,Arrays-Arrays
forpaths,ApplicationPaths
forstrings,Strings-Strings
forURLs,URLs-URLs
here()method,Echo,Presencechannels
$hiddenproperty,TransformingResults
home()method,redirects,OtherRedirectMethods
Homestead,LaravelHomestead-ConnectingtoHomesteaddatabasesfromdesktopapplications,Glossary
configuring,ConfiguringHomestead-ConfiguringHomestead
databases,connectingto,ConnectingtoHomesteaddatabasesfromdesktopapplications
databases,creating,CreatingdatabasesinHomestead
dependenciesfor,InstallingHomestead’sdependencies
initializing,ProvisioningHomestead
installing,InstallingHomestead
settingup,SettingupHomestead-ConfiguringHomestead
startingandstopping,UsingHomesteadday-to-day
toolsprovidedwith,SettingupHomestead
Homestead.yamlfile,ConfiguringHomestead-ConfiguringHomestead
.htaccessfile,Laravel’sRequestLifecycle
htmlentities()function,EchoingData,Strings
HTTPmethods(verbs),RouteDefinitions,ThemethodsofLaravel’sresourcecontrollers,AnIntroductiontoHTTPVerbs-HTTPMethodSpoofinginHTMLForms
HTTPredirects,Redirects-redirect()->with(),Misc,Misc
HTTPrequests,AbortingtheRequest,Laravel’sRequestLifecycle-BootstrappingtheApplication,TheRequestObject-Persistence
(seealsoRequestobject)
HTTPresponses,CustomResponses,TheResponseObject-Customresponsemacros,Misc
(seealsoResponseobject)
HttpFoundationclasses,TheRequestObject
hyphen,double(--),precedingArtisancommandoptions,Options,requiredvalues,valuedefaults,andshortcuts
I
iconsusedinthisbook,ConventionsUsedinThisBook
id()method,Usingtheauth()GlobalHelperandtheAuthFacade
@ifdirective,Blade,@if
Illuminatenamespace,Glossary
implicitroutemodelbinding,ImplicitRouteModelBinding
@includedirective,Blade,@include
increment()method,Cache,TheMethodsAvailableonCacheInstances
increment()method,DB,Updates
increments()method,Blueprint,Creatingcolumns
index()method,Blueprint,Buildingextrapropertiesfluently
index()method,resourcecontrollers,ThemethodsofLaravel’sresourcecontrollers
index.phpfile,BootstrappingtheApplication-ServiceProviders
info()method,Output
@injectdirective,Blade,BladeServiceInjection
Inputfacade,GettingUserInput
input()method,Request,$request->input(),JSONInput(and$request->json()),Basicuserinput
inRandomOrder()method,DB,Modifyingmethods
insert()method,DB,Rawinserts,Inserts
insertGetId()method,DB,Inserts
installertool,InstallingLaravelwiththeLaravelInstallerTool,UpandRunning
instances,bindingto,BindingtoSingletons,Aliases,andInstances
integer()method,Blueprint,Creatingcolumns
integrationtests,Testing,Glossary
intended()method,redirects,OtherRedirectMethods
InteractsWithQueuetrait,Creatingajob
interfaces(contracts),TheUserModelandMigration,BindingaConcreteInstancetoanInterface,Glossary
Interventionlibrary,BasicFileUploadsandManipulation
IoC(inversionofcontrol),AQuickIntroductiontoDependencyInjection,BindingaConcreteInstancetoanInterface,Testing,Glossary
IoCcontainer(seecontainer)
ip()method,Request,Userandrequeststate
is()method,Request,Userandrequeststate
isEmpty()method,collection,AFewMethods
isJson()method,Request,Userandrequeststate
isValid()method,File,UploadedFiles
J
JavaScript
concatenating,inElixir,ConcatenatingJavaScript
escapingbackslashesin,ReceivingtheMessage
processing,inElixir,ProcessingJavaScript
JavaScriptES6,Elixir
JavaScriptObjectNotation(seeJSON)
Jobclass,Glossary
job-relatedtests,JobsandEvents
jobs,Queues,QueuedJobs-Customizingthedelay
(seealsoqueues)
creating,Creatingajob-Creatingajob
deleting,Handlingfailedjobs
dispatching,Misc
failed,handling,HandlingErrors-Handlingfailedjobs
numberoftriesfor,Limitingthenumberoftries
pushingontoqueue,Pushingajobontoaqueue-Customizingthedelay
releasingbacktoqueue,ControllingtheQueue
retrying,Handlingfailedjobs
join()method,DB,Joins
join()method,Echo,Presencechannels
JSON(JavaScriptObjectNotation),Glossary
APIpatternfor,TheBasicsofREST-LikeJSONAPIs
APIspecfor,SortingandFiltering
assertions,JSONandNon-visit()ApplicationTestingAssertions-JSONandNon-visit()
ApplicationTestingAssertions
operations,JSONoperations,EloquentSerialization-HidingattributesfromJSON
responses,JSONresponses
testing,TestingBasics
JSONWebToken(JWT),TokensfromLaravelsessionauthentication(synchronizertokens),Glossary
json()method,Blueprint,Creatingcolumns
json()method,Request,JSONInput(and$request->json()),Basicuserinput
json()method,Response,response()->json()and->jsonp(),JSONresponses
json()method,TestCase,“Visiting”Routes
jsonb()method,Blueprint,Creatingcolumns
jsonp()method,Response,response()->json()and->jsonp()
JWT(JSONWebToken),TokensfromLaravelsessionauthentication(synchronizertokens),Glossary
K
keep()method,Session,FlashSessionStorage
kernel,Laravel’skernel
Kernel.phpfile,Bindingglobalmiddleware
keycommands,Artisan,TheGroupedCommands
keys()method,ParameterBag,Basicuserinput
L
Lambopackage,UpandRunning
Laravel
advantagesof,What’sSoSpecialAboutLaravel?-HowLaravelAchievesDeveloperHappiness
communityfor,TheLaravelCommunity-TheLaravelCommunity
documentationfor,WhatThisBookIsAbout
installer,CreatingaNewLaravelProject,UpandRunning
localdevelopmentenvironmentsfor,LocalDevelopmentEnvironments-ConnectingtoHomesteaddatabasesfromdesktopapplications
PHPversionsandextensionsfor,SystemRequirements
starting,UpandRunning
systemrequirements,WhoThisBookIsFor,SystemRequirements-Composer
versionsof(seeversionsofLaravel)
LaravelEcho(seeEcho)
LaravelEnvoyer,HandlingTaskOutput
LaravelForge(seeForge)
laravelnewcommand(Laravelinstaller),InstallingLaravelwiththeLaravelInstallerTool,UpandRunning
laravel.logfile,Thelogdriver
last()method,collection,AFewMethods
lastModified()method,Storage,UsingtheStorageFacade
later()method,Mail,later()
latest()method,DB,Modifyingmethods
lazyloading,Eagerloading,Lazyeagerloading
leftJoin()method,DB,Joins
LengthAwarePaginatorclass,ManuallyCreatingPaginators
lifecycleofapplication,Laravel’sRequestLifecycle-ServiceProviders
line()method,Output
links()method,PaginatingDatabaseResults
listcommand,Artisan,AnIntroductiontoArtisan
listen()method,Echo,UsingEchoforbasiceventbroadcasting
listeners,forevents,ListeningforanEvent-ListeningforanEvent
localdevelopmentenvironments,LocalDevelopmentEnvironments-ConnectingtoHomesteaddatabasesfromdesktopapplications
localdisk,ConfiguringFileAccess
localscopes,Localscopes
localization,Localization-Pluralizationinlocalization,TranslationandLocalization
Logfacade,FacadesandtheContainer-HowFacadesWork
logging,FacadesandtheContainer-HowFacadesWork,Thelogdriver
login()method,AuthenticatesUserstrait,ManuallyAuthenticatingUsers
LoginController,LoginController-ThrottlesLoginstrait
loginUsingId()method,ManuallyAuthenticatingUsers
longText()method,Blueprint,Creatingcolumns
$loopvariable,@forelse
loops(Blade),Loops-@forelse,@each
M
mail,Mail-Universalto
attachments,MethodsAvailableinbuild()-Queues
capturing,Mailtrap.io
classicmail,“Classic”Mail
configuring,Mail
creating,Basic“Mailable”MailUsage-Basic“Mailable”MailUsage
customizing,MethodsAvailableinbuild()
driverssupported,Mail
HTMLformat,MailTemplates
inlineimages,AttachmentsandInlineImages
logging,Thelogdriver
mailablemail,Basic“Mailable”MailUsage-Basic“Mailable”MailUsage
manuallymodifying,MethodsAvailableinbuild()
plaintextformat,MailTemplates
queuesfor,Queues
sending,Basic“Mailable”MailUsage
templates,MailTemplates
testing,LocalDevelopment,Mail
universalto,Universalto
Mailfacade,“Classic”Mail
mail.phpfile,Mail,Universalto
MailThief,Mail
Mailtrap,Mailtrap.io
makecommands,Artisan,TheGroupedCommands
make()method,app,Theapp()GlobalHelper
make()method,Cookie,TheCookiefacade,Thecookie()globalhelper
make()method,modelfactories,Usingamodelfactory
make()method,Response,response()->make()
make:authcommand,Artisan,TheAuthScaffold,TheVuecomponents
make:controllercommand,Artisan,Controllers,Controllers,Controllers,ResourceControllers
make:eventcommand,Artisan,FiringanEvent
make:jobcommand,Artisan,Creatingajob
make:mailcommand,Artisan,Basic“Mailable”MailUsage
make:middlewarecommand,Artisan,CreatingCustomMiddleware
make:migrationcommand,Artisan,Creatingamigration
make:modelcommand,Artisan,CreatingandDefiningEloquentModels,ControllerOrganizationandJSONReturns
make:notificationcommand,Artisan,Notifications
make:policycommand,Artisan,Generatingpolicies
make:requestcommand,Artisan,CreatingaFormRequest
make:seederArtisancommand,CreatingaSeeder
makeVisible()method,Eloquent,HidingattributesfromJSON
many-to-manyrelationships,EloquentRelationships,Manytomany-Gettingdatafromthepivottable
map()method,collection,AFewMethods
mapApiRoutes()method,RouteServiceProvider,Usingmiddlewaregroups
mapWebRoutes()method,RouteServiceProvider,Usingmiddlewaregroups
massassignment,EloquentModelMassAssignment,Massassignment-Massassignment,Glossary
max()method,DB,Ending/returningmethods
MbstringPHPextension,SystemRequirements
mediumInteger()method,Blueprint,Creatingcolumns
mediumText()method,Blueprint,Creatingcolumns
Memcacheddatastore,OtherDatabaseConfigurationOptions,Glossary
messagebags,MessageBags-NamedErrorBags,TestingMessageandErrorBags
MessageBagclass,MessageBags-NamedErrorBags
methodinjection,AQuickIntroductiontoDependencyInjection,MethodInjection
method()method,Request,Userandrequeststate
methods,RouteNames
(seealsospecificmethods)
chaining,RouteNames,Middleware
fluent,Glossary
HTTPmethods(verbs),RouteDefinitions,ThemethodsofLaravel’sresourcecontrollers,AnIntroductiontoHTTPVerbs-HTTPMethodSpoofinginHTMLForms
middleware,RouteDefinitions,LaravelandMiddleware-PassingParameterstoMiddleware,Glossary
binding,BindingMiddleware-Usingmiddlewaregroups
custom,creating,CreatingCustomMiddleware-BindingMiddleware
forauthentication,AuthMiddleware
forauthorization,TheAuthorizeMiddleware
groups,Usingmiddlewaregroups
passingparametersto,PassingParameterstoMiddleware
routegroupsfor,Middleware-Middleware
middleware()method,Middleware,Usingmiddlewaregroups
migratecommands,Artisan,BasicArtisanCommands,TheGroupedCommands,RunningMigrations,Seeding,ControllerOrganizationandJSONReturns
migrations,Migrations-RunningMigrations,Glossary
columns,creating,Creatingcolumns-Creatingcolumns
columns,modifying,Modifyingcolumns-Indexesandforeignkeys
creatingwithEloquentmodel,CreatingandDefiningEloquentModels
defining,DefiningMigrations-Addingandremovingforeignkeys
fieldproperties,setting,Buildingextrapropertiesfluently
foreignkeys,adding,Addingandremovingforeignkeys
foreignkeys,dropping,Addingandremovingforeignkeys
indexes,adding,Indexesandforeignkeys
indexes,removing,Removingindexes
running,RunningMigrations
tables,creating,Creatingtables
tables,dropping,Droppingtables
typesof,DefiningMigrations
min()method,DB,Ending/returningmethods
mix.browserify()method,ProcessingJavaScript
mix.phpSpec()method,Tests,TestingwithElixir
mix.phpUnit()method,Tests,TestingwithElixir
mix.rollup()method,ProcessingJavaScript
mix.sass()method,Elixir,Passingmultiplefiles
mix.scripts()method,ConcatenatingJavaScript
mix.styles()method,PreprocessorlessCSS
mix.version()method,Versioning
mix.webpack()method,ProcessingJavaScript
Mockerylibrary,Testing,Mocking-MockingFacades,Glossary
modelfactory,ModelFactories-Definingandaccessingmultiplemodelfactorytypes,Glossary
Model-View-Controller(MVC)pattern(seecontrollers)(seeviews)
modelKeys()method,collection,WhatEloquentcollectionsadd
morphedByMany()method,Eloquent,Manytomanypolymorphic
morphs()method,Blueprint,Creatingcolumns
morphsTo()method,Eloquent,Polymorphic
morphToMany()method,Eloquent,Manytomanypolymorphic
move()method,Storage,UsingtheStorageFacade
multitenancy,Example:UsingCustomBladeDirectivesforaMultitenantApp-Example:UsingCustomBladeDirectivesforaMultitenantApp,Glossary
mutators,Mutators-Mutators,Glossary
MVC(Model-View-Controller)pattern(seecontrollers;views)
N
nameprefixes,routegroupsfor,NamePrefixes
name()method,RouteNames
namespaceprefixes,routegroupsfor,NamespacePrefixes
namespaces
forcontracts,TheUserModelandMigration
forcontrollers,Controllers
defaultAppnamespace,replacing,TheGroupedCommands
escapingbackslashesinJavaScript,ReceivingtheMessage
forfacades,GettingUserInput,JSONInput(and$request->json()),FacadesandtheContainer
inFQCN(fully-qualifiedclassname),Glossary
Illuminate,BootstrappingtheApplication,Glossary
makenamespace,forArtisan,Controllers,WritingCustomArtisanCommands
needs()method,ContextualBinding
Nexmo,SMSnotifications
Nginxwebserver,Glossary
Node.js,installing,RunningElixir
Notifiabletrait,SendingNotifications
Notificationfacade,SendingnotificationswiththeNotificationfacade
notifications,Notifications-Slacknotifications
broadcastnotifications,Broadcastnotifications
channelsfor,Notifications,Definingthevia()MethodforYourNotifiables
creating,Notifications-Definingthevia()MethodforYourNotifiables
databasenotifications,Databasenotifications
driverssupported,Out-of-the-BoxNotificationTypes
emailnotifications,Emailnotifications-Emailnotifications
queueing,QueueingNotifications
recipientsof,Notifications
sending,SendingNotifications
Slacknotifications,Slacknotifications
SMSnotifications,SMSnotifications
subscribingto,SubscribingtonotificationswithEcho
testing,Notifications
notificationscommands,Artisan,TheGroupedCommands
notify()method,Notifiable,SendingnotificationsusingtheNotifiabletrait
npminstallcommand,BringingEchointoyourproject
nullable()method,Blueprint,Buildingextrapropertiesfluently
nullableTimestamps()method,Blueprint,Creatingcolumns
O
OAuth2.0,Overridingpolicies,ABriefIntroductiontoOAuth2.0
(seealsoPassportpackage)
object-relationalmapper(seeORM)
Observerpattern,eventsfor,Glossary
old()helper,redirect()->with(),Misc
old()method,Request,Persistence
oldest()method,DB,Modifyingmethods
onConnection()method,jobs,Customizingtheconnection
onConnection()method,mailable,Specifyingthequeueorconnection
one-to-manyrelationships,EloquentRelationships,Onetomany-Onetomany
one-to-onerelationships,Onetoone-Onetoone
onlineresources
Elixirdocumentation,WhatDoesElixirProvide?
facadesdocumentation,HowFacadesWork
forthisbook,HowtoContactUs
Homesteaddocumentation,InstallingHomestead
Laraveldocumentation,WhatThisBookIsAbout
SSHkeys,creating,ConfiguringHomestead
Valetdocumentation,LaravelValet
only()method,Request,$request->except()and$request->only(),EloquentModelMassAssignment,Massassignment,Basicuserinput
onlyTrashed()method,Eloquent,Queryingwithsoftdeletes
onQueue()method,events,BroadcastinganEvent
onQueue()method,jobs,Customizingthequeue
onQueue()method,mailable,Specifyingthequeueorconnection
onUserSubscription()method,events,Eventsubscribers
OpenSSLPHPextension,SystemRequirements
operatingsystemrequirements,WhoThisBookIsFor,SystemRequirements
optimizecommand,Artisan,BasicArtisanCommands,Options
option()method,option()
options(Artisan),Glossary
OPTIONSmethod,AnIntroductiontoHTTPVerbs-HTTPMethodSpoofinginHTMLForms
orhelper,Blade,or
orderBy()method,DB,Modifyingmethods
orderBy()method,Eloquent,RetrievingDatawithEloquent,SortingYourAPIResults
ORM(object-relationalmapper),Pagination,Glossary
(seealsoEloquent)
orWhere()method,DB,Constrainingmethods-Constrainingmethods
P
package.jsonfile,TheLooseFiles
paginate()method,PaginatingDatabaseResults,EloquentPagination-EloquentPagination
pagination,Pagination-ManuallyCreatingPaginators
Paginatorclass,ManuallyCreatingPaginators
parameterbinding,PDO,Parameterbindingsandnamedbindings
ParameterBagclass,Basicuserinput
@parentdirective,Blade,@parent
Passportpackage,Overridingpolicies,APIAuthenticationwithLaravelPassport-PassportScopes,Glossary
granttypesfor,Passport’sAvailableGrantTypes-TokensfromLaravelsessionauthentication(synchronizertokens)
installing,InstallingPassport-InstallingPassport
routesfor,InstallingPassport,Passport’sAPI,Theroutes
scopes,PassportScopes-PassportScopes
Vuecomponents,TheVuecomponents-TheVuecomponents
PassportServiceProvider,InstallingPassport
passwordgrant,Passport,Passwordgrant-Passwordgrant
PATCHmethod,AnIntroductiontoHTTPVerbs-HTTPMethodSpoofinginHTMLForms
forresourcecontrollers,ThemethodsofLaravel’sresourcecontrollers
routesbasedon,RouteVerbs
patch()method,TestCase,“Visiting”Routes
pathprefixes,routegroupsfor,PathPrefixes
path()method,Request,Userandrequeststate
paths
forfacades,JSONInput(and$request->json())
helpersfor,ApplicationPaths
inFQCN(fully-qualifiedclassname),Glossary
PDOparameterbinding,Parameterbindingsandnamedbindings
PDOPHPextension,SystemRequirements
period(.),dotnotation,Glossary
personalaccessclient,Personalaccesstokens
personalaccesstokens,Passport,Personalaccesstokens
PHP
versionsandextensionsfor,SystemRequirements
viewsrenderedwith,Views
PHPSpectestingframework,Glossary
PHPUnittestingframework,Testing,Testing,Glossary
phpunit.xmlfile,TheLooseFiles,TheTestingEnvironment
pingBefore()method,tasks,HandlingTaskOutput
pivottable,Manytomany-Gettingdatafromthepivottable
pjax()method,Request,Userandrequeststate
pluck()method,collection,AFewMethods
pluralization,TheStringHelpersandPluralization,Pluralizationinlocalization
policiesforauthorization,Policies-Overridingpolicies
polymorphicrelationships,EloquentRelationships,Polymorphic-Manytomanypolymorphic,Glossary
POSTmethod,AnIntroductiontoHTTPVerbs-HTTPMethodSpoofinginHTMLForms
forresourcecontrollers,ThemethodsofLaravel’sresourcecontrollers
gettinguserinputfrom,GettingUserInput
routesbasedon,RouteVerbs
post()method,TestCase,“Visiting”Routes
prepend()method,Storage,UsingtheStorageFacade
preprocessors,Glossary
press()method,TestCase,ClickingandForms
primarykeys,Glossary
primary()method,Blueprint,Buildingextrapropertiesfluently
priority()method,mailable,MethodsAvailableinbuild()
private()method,Echo,Privatechannelsandbasicauthentication
progressbars,Progressbars
progressAdvance()method,Progressbars
progressFinish()method,Progressbars
progressStart()method,Progressbars
projects
configuring,Configuration
creating,CreatingaNewLaravelProject-InstallingLaravelwithComposer’screate-projectFeature
directorystructurefor,Laravel’sDirectoryStructure-TheLooseFiles
provides()method,serviceproviders,ServiceProviders
Pub/Subpattern,Events,BroadcastingEventsoverWebSockets,andLaravelEcho,Glossary
publicdisk,ConfiguringFileAccess
publicfolder,TheFolders
pull()method,Cache,TheMethodsAvailableonCacheInstances
pull()method,Session,TheMethodsAvailableonSessionInstances
push()method,Session,TheMethodsAvailableonSessionInstances
Pusher,ConfigurationandSetup,ReceivingtheMessage-BindingauthorizationdefinitionsforWebSocketchannels
PUTmethod,AnIntroductiontoHTTPVerbs-HTTPMethodSpoofinginHTMLForms
forresourcecontrollers,ThemethodsofLaravel’sresourcecontrollers
routesbasedon,RouteVerbs
put()method,Cache,TheMethodsAvailableonCacheInstances
put()method,Session,TheMethodsAvailableonSessionInstances
put()method,Storage,UsingtheStorageFacade,BasicFileUploadsandManipulation
put()method,TestCase,“Visiting”Routes
putFile()method,Storage,UsingtheStorageFacade
Q
querybuilder,QueryBuilder-Transactions
(seealsoEloquent)
aggregates,Ending/returningmethods
chainingmethodswith,ChainingwiththeQueryBuilder-JSONoperations
constrainingqueries,Constrainingmethods-Constrainingmethods
databasetypessupported,QueryBuilder
DBfacadefor,BasicUsageoftheDBFacade
deletes,Deletes
inserts,Inserts
joins,Joins
JSONoperations,JSONoperations
modifyingqueries,Modifyingmethods-Modifyingmethods
multiplequeryresults,formatfor,Rawselects
paginationfor,PaginatingDatabaseResults-ManuallyCreatingPaginators,EloquentPagination
parameterbinding,Parameterbindingsandnamedbindings
rawSQLqueries,RawSQL-Rawdeletes,WritingrawqueriesinsidequerybuildermethodswithDB::raw
relationshipsas,Usingrelationshipsasquerybuilders
returningresults,Ending/returningmethods-Ending/returningmethods
transactions,Transactions
unions,Unions
updates,Updates
questionmark(?)
followingoptionalArtisancommandarguments,Arguments,required,optional,and/orwithdefaults
followingoptionalparameters,RouteParameters
queryparameters,Parameterbindingsandnamedbindings
question()method,Output
queuecommands,Artisan,TheGroupedCommands
queue()method,Cookie,TheCookiefacade
queue()method,Mail,queue()
queue.phpfile,BasicQueueConfiguration
queue:failedcommand,Artisan,Handlingfailedjobs
queue:failed-tablecommand,Artisan,Handlingfailedjobs
queue:flushcommand,Artisan,Handlingfailedjobs
queue:forgetcommand,Artisan,Handlingfailedjobs
queue:listencommand,Artisan,Limitingthenumberoftries
queue:retryallcommand,Artisan,Handlingfailedjobs
queue:retrycommand,Artisan,Handlingfailedjobs
queue:workcommand,Artisan,RunningaQueueWorker,Limitingthenumberoftries
Queueabletrait,Creatingajob
queues,Queues-QueuesSupportingOtherFunctions,Glossary
forArtisancommands,TheGroupedCommands,QueuesSupportingOtherFunctions
beanstalkdfor,Glossary
benefitsof,WhyQueues?
configuring,BasicQueueConfiguration
creatingjobsin,Creatingajob-Creatingajob
deletingjobsin,Handlingfailedjobs
dispatchingjobsin,Misc
errorswith,handling,HandlingErrors-Handlingfailedjobs
forjobs,QueuedJobs
formail,Queues,QueuesSupportingOtherFunctions
numberoftriesforjobs,Limitingthenumberoftries
providersanddriversfor,Queues
pushingjobsonto,Pushingajobontoaqueue-Customizingthedelay
releasingjobsbackto,ControllingtheQueue
retryingjobs,Handlingfailedjobs
workers,RunningaQueueWorker,ConfigurationandSetup
R
raw()method,DB,WritingrawqueriesinsidequerybuildermethodswithDB::raw
read-evaluate-print-loop(REPL)(seeTinker)
readme.mdfile,TheLooseFiles
Redirectfacade,Redirects
redirect()helper,Redirects-redirect()->with(),Redirectresponses,Misc
redirectPath()method,RegistersUserstrait
redirects,Redirects-redirect()->with()
Redis,OtherDatabaseConfigurationOptions,ReceivingtheMessage,ReceivingtheMessage,Glossary
reduce()method,collection,AFewMethods
reflash()method,Session,FlashSessionStorage
refresh()method,redirects,OtherRedirectMethods
regenerate()method,Session,TheMethodsAvailableonSessionInstances
register()method,RegisterUsers,RegistersUserstrait
register()method,serviceproviders,ServiceProviders,BindingtoaClosure,Service
Providers
RegisterController,RegisterController-RegistersUserstrait
RegistersUserstrait,RegistersUserstrait
regularexpressions
passingtostr_is(),Strings
routeconstraintsusing,RouteParameters
reject()method,collection,AFewMethods
relationships,EloquentRelationships-Manytomanypolymorphic
asquerybuilders,Usingrelationshipsasquerybuilders
eagerloading,Eagerloading-Eagerloadingonlythecount,Glossary
insertingrelateditems,Onetoone
lazyloading,Eagerloading,Lazyeagerloading
serializationof,HidingattributesfromJSON
release()method,jobs,ControllingtheQueue
remembermeaccesstoken,“RememberMe”
remember()method,Cache,TheMethodsAvailableonCacheInstances
rememberForever()method,Cache,TheMethodsAvailableonCacheInstances
rememberToken()method,Blueprint,Creatingcolumns
render()method,pagination,PaginatingDatabaseResults
REPL(read-evaluate-print-loop)(seeTinker)
RepresentationalStateTransfer(REST),TheBasicsofREST-LikeJSONAPIs-TheBasicsofREST-LikeJSONAPIs,Glossary
Requestfacade,InjectingaRequestObject
requestheaders,ReadingandSendingHeaders,ReadingRequestHeadersinLaravel
Requestobject,InjectingaRequestObject-JSONInput(and$request->json()),TheRequestObject-Persistence
accessing,GettingaRequestObjectinLaravel-Files
arrayinput,accessing,ArrayInput
capturingdirectly,TheRequestObject
filehandlingmethods,Files
formrequests,PassingParameterstoMiddleware
headersfor,ReadingandSendingHeaders,ReadingRequestHeadersinLaravel
JSONinput,accessing,JSONInput(and$request->json())
lifecycleof,Laravel’sRequestLifecycle-ServiceProviders
persistenceof,forsessioninteraction,Persistence
readingcookiesfrom,Readingcookiesfromrequestobjects
testing,Testing-Testing
typehintinginconstructors,InjectingDependenciesintoControllers,GettingaRequestObjectinLaravel-GettingaRequestObjectinLaravel
userandrequeststatemethods,Userandrequeststate
userinputmethods,Basicuserinput-Userandrequeststate
request()helper,InjectingaRequestObject,JSONInput(and$request->json()),GettingaRequestObjectinLaravel
reset()method,ResetPasswordController
resetPassword()method,ResetPasswordController
ResetPasswordController,ResetPasswordController
resourcecontrollerbinding,Bindingaresourcecontroller
resourcecontrollers,ResourceControllers-Bindingaresourcecontroller,ControllerOrganizationandJSONReturns-ControllerOrganizationandJSONReturns
resourcesfolder,TheFolders,ElixirFolderStructure
resources,API,TheBasicsofREST-LikeJSONAPIs,NestingandRelationships-NestingandRelationships
resources,online(seeonlineresources)
responseheaders,ReadingandSendingHeaders
Responseobject,TheResponseObject-Customresponsemacros
creating,UsingandCreatingResponseObjectsinControllers-Addingcookies
custom,CustomResponses
customresponsemacros,Customresponsemacros
downloadresponses,Downloadresponses
fileresponses,Fileresponses
headersfor,ReadingandSendingHeaders
JSONresponses,JSONresponses
lifecycleof,Laravel’sRequestLifecycle-ServiceProviders
redirectresponses,Redirectresponses-Customresponsemacros
settingcookieson,Settingcookiesonresponseobjects
testing,Testing-Testing
viewresponses,Viewresponses
response()helper,CustomResponses,UsingandCreatingResponseObjectsinControllers,Misc
REST(RepresentationalStateTransfer),TheBasicsofREST-LikeJSONAPIs-TheBasicsofREST-LikeJSONAPIs,Glossary
restore()method,Eloquent,Restoringsoft-deletedentities
reverse()method,collection,AFewMethods
rightanglebracket,triple(>>>),Tinkerprompt,Tinker
rollBack()method,DB,Transactions
Rollup,ProcessingJavaScript
routecaching,RouteCaching
routecommands,Artisan,TheGroupedCommands
routegroups,RouteGroups-NamePrefixes
(seealsocontrollers)
defining,RouteGroups
middlewareappliedto,Middleware-Middleware
nameprefixesusing,NamePrefixes
namespaceprefixesusing,NamespacePrefixes
pathprefixesusing,PathPrefixes
subdomainroutingusing,SubdomainRouting
routemiddleware,AuthMiddleware,TheAuthorizeMiddleware,Bindingroutemiddleware
routemodelbinding,RouteModelBinding-CustomRouteModelBinding
route()helper,RouteNames,RouteNames-RouteNames,URLs
route()method,redirect()->route()
route:cachecommand,Artisan,RouteCaching
route:listcommand,Artisan,Bindingaresourcecontroller
routes,Glossary
defining,RouteDefinitions-RouteNames
fluentdefinitionsof,RouteNames
handling,RouteHandling
listing,Bindingaresourcecontroller
naming,RouteNames-RouteNames
parametersfor,RouteParameters-RouteParameters,RouteModelBinding-CustomRouteModelBinding,FromRouteParameters
testing,Testing
verbsfor,RouteVerbs
routesfolder,TheFolders
routes()method,Auth,Auth::routes()
routes.phpfile,RouteDefinitions,Usingmiddlewaregroups
RouteServiceProvider,ServiceProviders,Usingmiddlewaregroups
rules(abilities)forauthorization,DefiningAuthorizationRules
rules()method,formrequest,CreatingaFormRequest
S
S3cloudstorage,ConfiguringFileAccess
s3disk,ConfiguringFileAccess
SaaS(SoftwareasaService),Glossary
save()method,Eloquent,Inserts
schedulecommands,Artisan,TheGroupedCommands
schedule:runcommand,Artisan,AvailableTaskTypes
scheduler,Scheduler-TaskHooks
Artisancommandsastasks,AvailableTaskTypes
avoidingtasksoverlapping,BlockingandOverlap
closuresastasks,AvailableTaskTypes,HandlingTaskOutput
shellcommandsastasks,AvailableTaskTypes
taskoutput,handling,HandlingTaskOutput-HandlingTaskOutput
tasktypes,AvailableTaskTypes
timeframesfor,setting,AvailableTimeFrames-AvailableTimeFrames
scopes(filters),Eloquent,Scopes-Removingglobalscopes,Glossary
scopes(privileges),OAuth,PassportScopes-PassportScopes
Scoutpackage,Full-TextSearchwithLaravelScout-ManuallyTriggerIndexingviatheCLI,Glossary
driverssupported,Full-TextSearchwithLaravelScout
installingandconfiguring,InstallingScout
manuallytriggering,ManuallyTriggerIndexingviaCode
markingmodelforindexing,MarkingYourModelforIndexing
notusingforsomeoperations,PerformOperationsWithoutIndexing
queuingactionsof,QueuesandScout
searchingindex,SearchingYourIndex
scout.phpfile,InstallingScout,QueuesandScout
scout:importcommand,Artisan,ManuallyTriggerIndexingviatheCLI
ScoutServiceProvider,InstallingScout
scriptinjection,{{Versus{!!
search()method,SearchingYourIndex
Searchabletrait,MarkingYourModelforIndexing
searchable()method,ManuallyTriggerIndexingviaCode
searchableAs()method,MarkingYourModelforIndexing
secret()method,Prompts
@sectiondirective,Blade,DefiningSectionswith@section/@showand@yield-@extends
sections,Blade,DefiningSectionswith@section/@showand@yield
secure()method,redirects,OtherRedirectMethods
secure()method,Request,Userandrequeststate
security
authentication(seeauthentication)
authorization(seeauthorization)
CSRF(cross-siterequestforgery),CSRFProtection-CSRFProtection,BringingEchointoyourproject,Glossary
encryption(seeencryption)
massassignment,EloquentModelMassAssignment,Massassignment-Massassignment,Glossary
scriptinjection,{{Versus{!!
see()method,TestCase,CustomApplicationTestingAssertions
seeCookie()method,TestCase,CustomApplicationTestingAssertions,Cookies
seed()method,TestCase,ArtisanandSeed
seeders,Seeding-Definingandaccessingmultiplemodelfactorytypes
creating,CreatingaSeeder
modelfactoriesfor,ModelFactories-Definingandaccessingmultiplemodelfactorytypes
testing,ArtisanandSeed
seeHeader()method,TestCase,CustomApplicationTestingAssertions
seeInDatabase()method,TestCase,CustomApplicationTestingAssertions
seeInField()method,TestCase,CustomApplicationTestingAssertions
seeIsChecked()method,TestCase,CustomApplicationTestingAssertions
seeIsSelected()method,TestCase,CustomApplicationTestingAssertions
seeJson()method,TestCase,JSONandNon-visit()ApplicationTestingAssertions
seeJsonEquals()method,TestCase,JSONandNon-visit()ApplicationTestingAssertions
seeLink()method,TestCase,CustomApplicationTestingAssertions
seePageIs()method,TestCase,CustomApplicationTestingAssertions
seePlainCookie()method,TestCase,Cookies
segment()method,Request,FromRequest
segments()method,Request,FromRequest
select()method,DB,Rawselects,Constrainingmethods
select()method,TestCase,ClickingandForms
selectRaw()method,DB,WritingrawqueriesinsidequerybuildermethodswithDB::raw
send()method,Mail,“Classic”Mail
send()method,Notification,SendingnotificationswiththeNotificationfacade
sendOutputTo()method,tasks,HandlingTaskOutput
serialization,EloquentSerialization-HidingattributesfromJSON,Glossary
SerializesModelstrait,Creatingajob
servecommand,Artisan,BasicArtisanCommands
server()method,Request,Userandrequeststate
server.phpfile,TheLooseFiles
servicecontainer(seecontainer)
serviceproviders,ServiceProviders-ServiceProviders,ServiceProviders,Glossary
(seealsospecificserviceproviders)
services,injectingintoaview,BladeServiceInjection-BladeServiceInjection
services.phpfile,Mail
sessioncommands,Artisan,TheGroupedCommands
Sessionfacade,AccessingtheSession
session()helper,AccessingtheSession-TheMethodsAvailableonSessionInstances
session()method,Request,AccessingtheSession
session()method,TestCase,AuthenticationandSessions
session.phpfile,Sessions
sessions,Sessions-FlashSessionStorage
accessing,AccessingtheSession-TheMethodsAvailableonSessionInstances
configuring,Sessions
driverssupported,Sessions
flashsessionstorage,FlashSessionStorage
testing,AuthenticationandSessions,Session-Session
setterinjection,AQuickIntroductiontoDependencyInjection
setUp()method,Testing
share()method,Sharingavariableglobally
shellcommands,schedulingastasks,AvailableTaskTypes
ShouldBroadcastinterface,BroadcastinganEvent
shouldHaveReceived()method,Mockery,MockingFacades
shouldIgnoreMissing()method,Mockery,Mockery
shouldReceive()method,Mockery,Mockery,MockingFacades
@showdirective,Blade,DefiningSectionswith@section/@showand@yield-@extends,@sectionand@endsection
show()method,resourcecontrollers,ThemethodsofLaravel’sresourcecontrollers
showLinkRequestForm()method,ForgotPasswordController
showLoginForm()method,AuthenticatesUserstrait
showRegistrationForm()method,RegistersUserstrait
showResetForm()method,ResetPasswordController
shuffle()method,collection,AFewMethods
singleton()method,BindingtoSingletons,Aliases,andInstances
singletons,bindingto,BindingtoSingletons,Aliases,andInstances
size()method,Storage,UsingtheStorageFacade
skip()method,DB,Modifyingmethods
Slacknotifications,Slacknotifications
slash(/),escapinginArtisancommands,ControllerOrganizationandJSONReturns
smallInteger()method,Blueprint,Creatingcolumns
SMSnotifications,SMSnotifications
softdeletes,Softdeletes-Force-deletingsoft-deletedentities,Glossary
softDeletes()method,Blueprint,Creatingcolumns,Enablingsoftdeletes
SoftwareasaService(SaaS),Glossary
sort()method,collection,AFewMethods
sortBy()method,collection,AFewMethods
sortByDesc()method,collection,AFewMethods
sortingAPIresults,SortingandFiltering-SortingYourAPIResults
sourcemaps,Elixir,Sourcemaps
Spark,Glossary
SplFileInfoclass,BasicFileUploadsandManipulation
SQLqueries,raw,RawSQL-Rawdeletes
(seealsoquerybuilder)
SQLite
dependenciesfor,Modifyingcolumns
modifyingmultiplecolumns,Modifyingcolumns
starts_with()helper,TheStringHelpersandPluralization,Strings
statelessAPIs,TheBasicsofREST-LikeJSONAPIs
statement()method,DB,RawSQL
staticcalls,RouteDefinitions
stdClassobject
returnedbyDBfacade,OtherDatabaseConfigurationOptions,Rawselects
returnedbyloops,@forelse
storage,LocalandCloudFileManagers-ConfiguringFileAccess
(seealsodatabases)
additionalproviders,adding,AddingAdditionalFlysystemProviders
cache,Cache-TheMethodsAvailableonCacheInstances
configuring,ConfiguringFileAccess-ConfiguringFileAccess
cookies,Cookies-Settingcookiesonresponseobjects
driverssupported,LocalandCloudFileManagers
Filefacadefor,UsingtheStorageFacade
fileuploads,handling,BasicFileUploadsandManipulation-BasicFileUploadsandManipulation
flashsessionstorage,FlashSessionStorage
sessionstorage,Sessions-FlashSessionStorage
Storagefacademethodsfor,UsingtheStorageFacade-UsingtheStorageFacade
testing,FileStorage-Returningfakefiles
typesof,LocalandCloudFileManagers-ConfiguringFileAccess
storagecommands,Artisan,TheGroupedCommands
Storagefacade,UsingtheStorageFacade-UsingtheStorageFacade
storagefolder,TheFolders
storage:linkcommand,Artisan,ConfiguringFileAccess
storage_path()helper,ConfiguringFileAccess,ApplicationPaths
Storeclass,AccessingtheSession
store()method,resourcecontrollers,ThemethodsofLaravel’sresourcecontrollers
store()method,UploadedFile,UploadedFiles-UploadedFiles,BasicFileUploadsandManipulation
storeAs()method,UploadedFile,UploadedFiles-UploadedFiles,BasicFileUploadsandManipulation
string()method,Blueprint,Creatingcolumns
strings
helpersfor,Strings-Strings
localization,Localization-Pluralizationinlocalization
pluralization,TheStringHelpersandPluralization,Pluralizationinlocalization
stringhelpers,TheStringHelpersandPluralization
str_contains()helper,TheStringHelpersandPluralization,Strings
str_is()helper,TheStringHelpersandPluralization,Strings
str_limit()helper,Strings
str_plural()helper,TheStringHelpersandPluralization
str_random()helper,Strings
str_slug()helper,TheStringHelpersandPluralization,Strings
subdomainrouting,SubdomainRouting
subject()method,mailable,MethodsAvailableinbuild()
submitForm()method,TestCase,ClickingandForms
subscribe()method,events,Eventsubscribers
sum()method,collection,AFewMethods
sum()method,DB,Ending/returningmethods
sum()method,Eloquent,Aggregates
SwiftMailer,Mail
Symfony,Laravel4,Glossary
Consolecomponent,AnIntroductiontoArtisan
HttpFoundationclasses,TheRequestObject
Translationcomponent,Pluralizationinlocalization
sync()method,Eloquent,Manytomany
synchronizertokens,Passport,TokensfromLaravelsessionauthentication(synchronizertokens)-TokensfromLaravelsessionauthentication(synchronizertokens)
systemrequirements,WhoThisBookIsFor,SystemRequirements-Composer
T
table()method,Tableoutput
take()method,collection,AFewMethods
take()method,DB,Modifyingmethods
take()method,Eloquent,RetrievingDatawithEloquent
Task::all()query,Views,Controllers
tasks,scheduling(seescheduler)
templates(seeBlade;views)
@testdocblock,NamingTests
TestCaseclass,TestCase
TestCase.phpfile,TestingBasics
testing,Testing-TestingBasics
APIs,Testing
applicationtests,Testing,ApplicationTesting-AuthenticationandSessions,Glossary
Artisancommands,Testing,ArtisanandSeed
assertionsin,Glossary
authenticationandauthorization,Testing-Testing,AuthenticationandSessions
cache,Cache
cookies,Cookies-Cookies
databaseoperations,Testing-Testing
dependencyinjectionin,Testing
environmentfor,TheTestingEnvironment
errorbags,TestingMessageandErrorBags
failedtestresults,TestingBasics
integrationtests,Testing,Glossary
inversionofcontrolin,Testing
JSON,TestingBasics
localization,TranslationandLocalization
mail,Mail
messagebags,TestingMessageandErrorBags
Mockerylibraryfor,Mocking-MockingFacades
namingtests,NamingTests
notifications,Notifications
requestsandresponses,Testing-Testing
routes,Testing
runningtests,Testing,Tests,TestingwithElixir
seeders,ArtisanandSeed
session,AuthenticationandSessions
sessions,Session-Session
storage,FileStorage-Returningfakefiles
traitsfor,TheTestingTraits
unittests,Testing
unittestsfor,Glossary
userinput,Testing
views,Testing-Testing
withBehat,Testing
withFaker,Testing
withMockery,Testing
withPHPUnit,Testing
writingtests,Testing
testsfolder,TheFolders,TestingBasics,NamingTests
text()method,Blueprint,Creatingcolumns
text()method,mailable,MailTemplates
thenPing()method,tasks,HandlingTaskOutput
time()method,Blueprint,Creatingcolumns
timesanddates(seeCarbonpackage;scheduler;timestamps)
timestamp()method,Blueprint,Creatingcolumns
timestamps,Creatingcolumns,Timestamps,Datemutators,ChildRecordsUpdatingParentRecordTimestamps-Eagerloadingonlythecount
timestamps()method,Blueprint,Creatingcolumns
Tinker,BasicArtisanCommands,Tinker,Tinker,Glossary
tinkercommand,Artisan,BasicArtisanCommands
tinyInteger()method,Blueprint,Creatingcolumns
TL;DR(toolong;didn’tread),HowThisBookIsStructured,Glossary
to()method,redirect()->to()
toArray()method,collection,AFewMethods
toArray()method,Eloquent,EloquentSerialization
toBroadcast()method,notification,Broadcastnotifications
toDatabase()method,notification,Databasenotifications
toJson()method,Eloquent,EloquentSerialization
TokenizerPHPextension,SystemRequirements
tokens,CSRF,CSRFProtection-CSRFProtection
toMail()method,notification,Emailnotifications-Emailnotifications
toNexmo()method,notification,SMSnotifications
toolong;didn’tread(TL;DR),HowThisBookIsStructured,Glossary
toOthers()method,events,Excludingthecurrentuserfrombroadcastevents,Excludingthecurrentuser
top-leveldomains,forlocaldevelopmentsite,ConfiguringHomestead
toSearchableArray()method,MarkingYourModelforIndexing
toSlack()method,notification,Slacknotifications
transaction()method,DB,Transactions
transactions,Transactions
translation(seelocalization)
Translationcomponent,Symfony,Pluralizationinlocalization
trashed()method,Eloquent,Queryingwithsoftdeletes
truncate()method,DB,Deletes
TwigBridgepackage,BladeTemplating
(seealsoBlade)
type()method,TestCase,ClickingandForms
typehint,InjectingDependenciesintoControllers,Glossary
typehinting,HowtheContainerIsWired,BindingaConcreteInstancetoanInterface
U
uncheck()method,TestCase,ClickingandForms
union()method,DB,Unions
unionAll()method,DB,Unions
unique()method,Blueprint,Buildingextrapropertiesfluently
unittests,Testing,Glossary
universalto,formail,Universalto
@unlessdirective,Blade,@unlessand@endunless
unsearchable()method,ManuallyTriggerIndexingviaCode
unsigned()method,Blueprint,Buildingextrapropertiesfluently
up()method,migrations,DefiningMigrations,DefiningMigrations
update()method,DB,Rawupdates,Updates
update()method,Eloquent,Updates,Massassignment
update()method,resourcecontrollers,ThemethodsofLaravel’sresourcecontrollers
updateExistingPivot()method,Eloquent,Manytomany
uploadedfiles,UploadedFiles-UploadedFiles,BasicFileUploadsandManipulation-BasicFileUploadsandManipulation,FileStorage-Returningfakefiles
UploadedFileclass,UploadedFiles,Files,Uploadingfakefiles
url()helper,RouteNames,RouteNames,URLs
url()method,Request,Userandrequeststate
URLs
helpersfor,URLs-URLs
userinputfromrouteparameters,FromRouteParameters
userinputfromURLsegments,FromRequest
userauthentication(seeauthentication)
userauthorization(seeauthorization)
userinput
Artisancommands,UsingInput-Prompts
Eloquentmodel,EloquentModelMassAssignment
formrequests,FormRequests-UsingaFormRequest
gettingandhandlingwithcontrollers,GettingUserInput-GettingUserInput
Requestobject,InjectingaRequestObject-JSONInput(and$request->json()),Basicuserinput-Userandrequeststate
routeparameters,FromRouteParameters
testing,Testing
uploadedfiles,UploadedFiles-UploadedFiles
URLs,RouteData
validating,Validation-DisplayingValidationErrorMessages
Usermodel,TheUserModelandMigration-TheUserModelandMigration
user()method,Usingtheauth()GlobalHelperandtheAuthFacade,ChangingtheDefaultGuard
username()method,AuthenticatesUserstrait
uuid()method,Blueprint,Creatingcolumns
V
Vagrant,InstallingHomestead’sdependencies,Glossary
commandsfor,UsingHomesteadday-to-day
mappingHomesteadfoldersto,ConfiguringHomestead
migrationswith,RunningMigrations
Valetpackage,LaravelValet-LaravelValet,Glossary
validate()method,controller,redirect()->with(),validate()intheControllerUsingValidatesRequests-validate()intheControllerUsingValidatesRequests
validateLogin()method,AuthenticatesUserstrait
validationofuserinput,Validation-DisplayingValidationErrorMessages,Glossary
errormessagesfrom,displaying,DisplayingValidationErrorMessages
manualvalidation,ManualValidation
validate()method,controller,validate()intheControllerUsingValidatesRequests-validate()intheControllerUsingValidatesRequests
validationrules,validate()intheControllerUsingValidatesRequests
Validatorclass,MessageBags,ManualValidation
validator()method,RegisterController
vendorcommands,Artisan,TheGroupedCommands
vendorfolder,TheFolders
vendor:publishcommand,Artisan,TheVuecomponents,InstallingScout
versioning,inElixir,Versioning-Versioning
versionsofLaravel,HowThisBookIsStructured
versionsofLaravel,priorto5.2
ACL(accesscontrollist),Authorization(ACL)andRoles
authenticationguards,ChangingtheDefaultGuard
fluentroutedefinitions,RouteNames
middlewaregroups,Usingmiddlewaregroups
render()method,pagination,PaginatingDatabaseResults
testingtraits,TestCase
versionsofLaravel,priorto5.3
APItokenauthentication,Laravel5.2+APITokenAuthentication-Laravel5.2+APITokenAuthentication
assertViewHas()method,Testing
authenticationcontrollers,UserAuthenticationandAuthorization
classicmail,“Classic”Mail
compilingJavaScript,ProcessingJavaScript
DBfacaderesults,Rawselects
Eloquentresults,Getmany
$expressionparameter,ParametersinCustomBladeDirectives
generatingresourcecontrollers,Controllers
$loopvariable,@forelse
PHPandextensions,SystemRequirements
policymethods,Generatingpolicies
routesfile,RouteDefinitions
withCookie()method,Response,Settingcookiesonresponseobjects
via()method,notification,Notifications,Definingthevia()MethodforYourNotifiables
viaRemember()method,“RememberMe”
viewcommands,Artisan,TheGroupedCommands
viewcomposers,UsingViewComposerstoShareVariableswithEveryView,BindingDatatoViewsUsingViewComposers-Class-basedviewcomposers,Glossary
viewresponses,Viewresponses
view()helper,ViewComposersandServiceInjection-Class-basedviewcomposers,Viewresponses,Misc
view()method,Response,Viewresponses
views,Views-UsingViewComposerstoShareVariableswithEveryView,Glossary,Glossary
bindingdatato,BindingDatatoViewsUsingViewComposers-Class-basedviewcomposers
loading,Views
passingvariablesto,Views,ViewComposersandServiceInjection
testing,Testing-Testing
typesof,Views
VirtualBox,InstallingHomestead’sdependencies
$visibleproperty,TransformingResults
visit()method,TestCase,“Visiting”Routes
VMWare,InstallingHomestead’sdependencies
Vuecomponents,TheVuecomponents-TheVuecomponents
W
wantsJson()method,Request,Userandrequeststate
webguard,Guards
webmiddlewaregroup,Usingmiddlewaregroups
webroutes,RouteDefinitions
(seealsoroutes)
web.phpfile,RouteDefinitions
Webpack,ProcessingJavaScript
websiteresources(seeonlineresources)
WebSocketauthentication(seeEcho)
WebSockets,BroadcastingEventsoverWebSockets,andLaravelEcho-SubscribingtonotificationswithEcho
authorizationforchannels,BindingauthorizationdefinitionsforWebSocketchannels-BindingauthorizationdefinitionsforWebSocketchannels
broadcastingevents,BroadcastinganEvent-BroadcastinganEvent
channelsfor,BroadcastinganEvent,BindingauthorizationdefinitionsforWebSocketchannels
configuring,ConfigurationandSetup
driverssupported,ConfigurationandSetup
Echofor,ReceivingtheMessage
eventstructurefor,BroadcastinganEvent
excludinguserfromevents,Excludingthecurrentuserfrombroadcastevents-Excludingthecurrentuserfrombroadcastevents
Pub/Subpatternusedby,BroadcastingEventsoverWebSockets,andLaravelEcho
queueworkerfor,ConfigurationandSetup
receivingeventmessages,ReceivingtheMessage-ReceivingtheMessage
serviceproviderconfiguration,Thebroadcastserviceprovider
when()method,ContextualBinding
where()method,collection,AFewMethods
where()method,DB,Constrainingmethods-Constrainingmethods
where()method,Eloquent,RetrievingDatawithEloquent,FilteringYourAPIResults
whereBetween()method,DB,Constrainingmethods
whereExists()method,DB,Constrainingmethods
whereIn()method,DB,Constrainingmethods
whereNull()method,DB,Constrainingmethods
whereRaw()method,DB,Constrainingmethods
with()method,redirect()->with()-redirect()->with(),Closure-basedviewcomposers
withCookie()method,Response,Settingcookiesonresponseobjects
withErrors()method,NamedErrorBags
withInput()method,redirect()->with()
withoutEvents()method,TestCase,JobsandEvents
withoutGlobalScope()method,Removingglobalscopes
withoutGlobalScopes()method,Removingglobalscopes
WithoutMiddlewaretrait,WithoutMiddleware
withoutOverlapping()method,tasks,BlockingandOverlap
withoutSyncingToSearch()method,PerformOperationsWithoutIndexing
withPivot()method,Eloquent,Gettingdatafromthepivottable
withSwiftMessage()method,mailable,MethodsAvailableinbuild()
withTrashed()method,Eloquent,Queryingwithsoftdeletes
workersforqueues,RunningaQueueWorker,ConfigurationandSetup
X
X-precedingheadernames,ReadingandSendingHeaders
Y
@yielddirective,Blade,DefiningSectionswith@section/@showand@yield
AbouttheAuthorMattStaufferisadeveloperandateacher.HeisapartnerandtechnicaldirectoratTightenCo.,blogsatmattstauffer.co,andhostsTheFive-MinuteGeekShowandtheLaravelPodcast.
ColophonTheanimalonthecoverofLaravel:UpandRunningisagemsbok(oryxgazella).ThislargeantelopeisnativetothedesertsofSouthAfrica,Botswana,Zimbabwe,andNamibia,whereitisfeaturedonthecountry’scoatofarms.
Gemsbokmeasureabout5feet7inchestallattheshoulderandcanweighfrom250to390pounds.Theyaretypicallypalegrayorbrown,withblackandwhitefacialmarkingsandlongblacktails.Ablackstripeextendsfromthechintotheloweredgeoftheneck.Thegemsbok’simpressivestraighthorns,usedindefensivemaneuvers,average33inchesinlengthandareregardedascharmsinmanycultures.InmedievalEngland,theywereoftenmarketedasunicornhorns.
Althoughthesehornsmakethegemsbokahighly-soughttrophyanimal,thepopulationremainsstablethroughoutSouthernAfrica.In1969,gemsbokwereintroducedtosouthernNewMexico,wheretheircurrentpopulationisaround3,000.
Gemsbokarewell-suitedtosuchdesertenvironments,withtheabilitytosurvivewithoutdrinkingwaterformostoftheyear.Toachievethis,theydonotpantorsweat,allowingtheirbodytemperaturetoriseseveraldegreesabovenormalonhotdays.Theirlifespanisapproximately18yearsinthewild.
ManyoftheanimalsonO’Reillycoversareendangered;allofthemareimportanttotheworld.Tolearnmoreabouthowyoucanhelp,gotoanimals.oreilly.com.
ThecoverimageisfromRiversideNaturalHistory.ThecoverfontsareURWTypewriterandGuardianSans.ThetextfontisAdobeMinionPro;theheadingfontisAdobeMyriadCondensed;andthecodefontisDaltonMaag’sUbuntuMono.
PrefaceWhatThisBookIsAbout
WhoThisBookIsFor
HowThisBookIsStructured
ConventionsUsedinThisBook
O’ReillySafari
HowtoContactUs
Acknowledgments
1.WhyLaravel?WhyUseaFramework?
“I’llJustBuildItMyself”
ConsistencyandFlexibility
AShortHistoryofWebandPHPFrameworksRubyonRails
TheInfluxofPHPFrameworks
TheGoodandtheBadofCodeIgniter
Laravel1,2,and3
Laravel4
Laravel5
What’sSoSpecialAboutLaravel?ThePhilosophyofLaravel
HowLaravelAchievesDeveloperHappiness
TheLaravelCommunity
HowItWorks
WhyLaravel?
2.SettingUpaLaravelDevelopmentEnvironmentSystemRequirements
Composer
LocalDevelopmentEnvironmentsLaravelValet
LaravelHomestead
CreatingaNewLaravelProjectInstallingLaravelwiththeLaravelInstallerTool
InstallingLaravelwithComposer ’screate-projectFeature
Laravel’sDirectoryStructureTheFolders
TheLooseFiles
Configuration
UpandRunning
Testing
TL;DR
3.RoutingandControllersRouteDefinitions
RouteVerbs
RouteHandling
RouteParameters
RouteNames
RouteGroupsMiddleware
PathPrefixes
SubdomainRouting
NamespacePrefixes
NamePrefixes
ViewsUsingViewComposerstoShareVariableswithEveryView
ControllersGettingUserInput
InjectingDependenciesintoControllers
ResourceControllers
RouteModelBindingImplicitRouteModelBinding
CustomRouteModelBinding
RouteCaching
FormMethodSpoofingAnIntroductiontoHTTPVerbs
HTTPVerbsinLaravel
HTTPMethodSpoofinginHTMLForms
CSRFProtection
Redirectsredirect()->to()
redirect()->route()
redirect()->back()
OtherRedirectMethods
redirect()->with()
AbortingtheRequest
CustomResponses
response()->make()
response()->json()and->jsonp()
response()->download()and->file()
Testing
TL;DR
4.BladeTemplatingEchoingData
ControlStructuresConditionals
Loops
or
TemplateInheritanceDefiningSectionswith@section/@showand@yield
@parent
@include
@each
ViewComposersandServiceInjectionBindingDatatoViewsUsingViewComposers
BladeServiceInjection
CustomBladeDirectivesParametersinCustomBladeDirectives
Example:UsingCustomBladeDirectivesforaMultitenantApp
Testing
TL;DR
5.FrontendComponents
ElixirElixirFolderStructure
RunningElixir
WhatDoesElixirProvide?
PaginationPaginatingDatabaseResults
ManuallyCreatingPaginators
MessageBagsNamedErrorBags
StringHelpers,Pluralization,andLocalizationTheStringHelpersandPluralization
Localization
TestingTestingwithElixir
TestingMessageandErrorBags
TranslationandLocalization
TL;DR
6.CollectingandHandlingUserDataInjectingaRequestObject
$request->all()
$request->except()and$request->only()
$request->has()and$request->exists()
$request->input()
ArrayInput
JSONInput(and$request->json())
RouteData
FromRequest
FromRouteParameters
UploadedFiles
Validationvalidate()intheControllerUsingValidatesRequests
ManualValidation
DisplayingValidationErrorMessages
FormRequestsCreatingaFormRequest
UsingaFormRequest
EloquentModelMassAssignment
{{Versus{!!
Testing
TL;DR
7.ArtisanandTinkerAnIntroductiontoArtisan
BasicArtisanCommandsOptions
TheGroupedCommands
WritingCustomArtisanCommandsRegisteringCommands
ASampleCommand
ArgumentsandOptions
UsingInput
Prompts
Output
CallingArtisanCommandsinNormalCode
Tinker
Testing
TL;DR
8.DatabaseandEloquentConfiguration
DatabaseConnections
OtherDatabaseConfigurationOptions
MigrationsDefiningMigrations
RunningMigrations
SeedingCreatingaSeeder
ModelFactories
QueryBuilderBasicUsageoftheDBFacade
RawSQL
ChainingwiththeQueryBuilder
Transactions
IntroductiontoEloquentCreatingandDefiningEloquentModels
RetrievingDatawithEloquent
InsertsandUpdateswithEloquent
DeletingwithEloquent
Scopes
CustomizingFieldInteractionswithAccessors,Mutators,andAttributeCasting
EloquentCollections
EloquentSerialization
EloquentRelationships
ChildRecordsUpdatingParentRecordTimestamps
EloquentEvents
Testing
TL;DR
9.UserAuthenticationandAuthorizationTheUserModelandMigration
Usingtheauth()GlobalHelperandtheAuthFacade
TheAuthControllersRegisterController
LoginController
ResetPasswordController
ForgotPasswordController
Auth::routes()
TheAuthScaffold
“RememberMe”
ManuallyAuthenticatingUsers
AuthMiddleware
GuardsChangingtheDefaultGuard
UsingOtherGuardsWithoutChangingtheDefault
AddingaNewGuard
CreatingaCustomUserProvider
CustomUserProvidersforNonrelationalDatabases
AuthEvents
Authorization(ACL)andRolesDefiningAuthorizationRules
TheGateFacade(andInjectingGate)
TheAuthorizeMiddleware
ControllerAuthorization
CheckingontheUserInstance
BladeChecks
InterceptingChecks
Policies
Testing
TL;DR
10.RequestsandResponsesLaravel’sRequestLifecycle
BootstrappingtheApplication
ServiceProviders
TheRequestObjectGettingaRequestObjectinLaravel
GettingBasicInformationAboutaRequest
Persistence
TheResponseObject
UsingandCreatingResponseObjectsinControllers
SpecializedResponseTypes
LaravelandMiddlewareAnIntroductiontoMiddleware
CreatingCustomMiddleware
BindingMiddleware
PassingParameterstoMiddleware
Testing
TL;DR
11.TheContainerAQuickIntroductiontoDependencyInjection
DependencyInjectionandLaravel
Theapp()GlobalHelper
HowtheContainerIsWired
BindingClassestotheContainerBindingtoaClosure
BindingtoSingletons,Aliases,andInstances
BindingaConcreteInstancetoanInterface
ContextualBinding
ConstructorInjection
MethodInjection
FacadesandtheContainerHowFacadesWork
ServiceProviders
Testing
TL;DR
12.TestingTestingBasics
NamingTests
TheTestingEnvironment
TheTestingTraitsWithoutMiddleware
DatabaseMigrations
DatabaseTransactions
ApplicationTestingTestCase
“Visiting”Routes
CustomApplicationTestingAssertions
JSONandNon-visit()ApplicationTestingAssertions
ClickingandForms
JobsandEvents
AuthenticationandSessions
ArtisanandSeed
MockingMockery
MockingFacades
TL;DR
13.WritingAPIsTheBasicsofREST-LikeJSONAPIs
ControllerOrganizationandJSONReturns
ReadingandSendingHeadersSendingResponseHeadersinLaravel
ReadingRequestHeadersinLaravel
EloquentPagination
SortingandFilteringSortingYourAPIResults
FilteringYourAPIResults
TransformingResultsWritingYourOwnTransformer
NestingandRelationships
APIAuthenticationwithLaravelPassportABriefIntroductiontoOAuth2.0
InstallingPassport
Passport’sAPI
Passport’sAvailableGrantTypes
ManagingClientsandTokenswiththePassportAPIandtheVueComponents
PassportScopes
Laravel5.2+APITokenAuthentication
Testing
TL;DR
14.StorageandRetrievalLocalandCloudFileManagers
ConfiguringFileAccess
UsingtheStorageFacade
AddingAdditionalFlysystemProviders
BasicFileUploadsandManipulation
SessionsAccessingtheSession
TheMethodsAvailableonSessionInstances
FlashSessionStorage
CacheAccessingtheCache
TheMethodsAvailableonCacheInstances
CookiesCookiesinLaravel
AccessingtheCookieTools
Full-TextSearchwithLaravelScoutInstallingScout
MarkingYourModelforIndexing
SearchingYourIndex
QueuesandScout
PerformOperationsWithoutIndexing
ManuallyTriggerIndexingviaCode
ManuallyTriggerIndexingviatheCLI
TestingFileStorage
Session
Cache
Cookies
TL;DR
15.MailandNotificationsMail
“Classic”Mail
Basic“Mailable”MailUsage
MailTemplates
MethodsAvailableinbuild()
AttachmentsandInlineImages
Queues
LocalDevelopment
NotificationsDefiningthevia()MethodforYourNotifiables
SendingNotifications
QueueingNotifications
Out-of-the-BoxNotificationTypes
TestingMail
Notifications
TL;DR
16.Queues,Jobs,Events,Broadcasting,andtheSchedulerQueues
WhyQueues?
BasicQueueConfiguration
QueuedJobs
RunningaQueueWorker
HandlingErrors
ControllingtheQueue
QueuesSupportingOtherFunctions
EventsFiringanEvent
ListeningforanEvent
BroadcastingEventsoverWebSockets,andLaravelEchoConfigurationandSetup
BroadcastinganEvent
ReceivingtheMessage
AdvancedBroadcastingTools
LaravelEcho(theJavaScriptSide)
SchedulerAvailableTaskTypes
AvailableTimeFrames
BlockingandOverlap
HandlingTaskOutput
TaskHooks
Testing
TL;DR
17.HelpersandCollectionsHelpers
Arrays
Strings
ApplicationPaths
URLs
Misc
CollectionsTheBasicsofCollections
AFewMethods
TL;DR
Glossary
Index