magento 2 development quick start guide · 2020-04-16 · magento certified solution specialist,...
TRANSCRIPT
![Page 1: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/1.jpg)
![Page 2: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/2.jpg)
Magento2DevelopmentQuickStartGuide
BuildbetterstoresbyextendingMagento
BrankoAjzele
![Page 3: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/3.jpg)
BIRMINGHAM-MUMBAI
![Page 4: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/4.jpg)
Magento2DevelopmentQuickStartGuideCopyright©2018PacktPublishing
Allrightsreserved.Nopartofthisbookmaybereproduced,storedinaretrievalsystem,ortransmittedinanyformorbyanymeans,withoutthepriorwrittenpermissionofthepublisher,exceptinthecaseofbriefquotationsembeddedincriticalarticlesorreviews.
Everyefforthasbeenmadeinthepreparationofthisbooktoensuretheaccuracyoftheinformationpresented.However,theinformationcontainedinthisbookissoldwithoutwarranty,eitherexpressorimplied.Neithertheauthor,norPacktPublishingoritsdealersanddistributors,willbeheldliableforanydamagescausedorallegedtohavebeencauseddirectlyorindirectlybythisbook.
PacktPublishinghasendeavoredtoprovidetrademarkinformationaboutallofthecompaniesandproductsmentionedinthisbookbytheappropriateuseofcapitals.However,PacktPublishingcannotguaranteetheaccuracyofthisinformation.
CommissioningEditor:AmarabhaBanerjeeAcquisitionEditor:ReshmaRamanContentDevelopmentEditor:KirkDsouzaTechnicalEditor:VaibhavDwivediCopyEditor:SafisEditingProjectCoordinator:HardikBhindeProofreader:SafisEditingIndexer:AishwaryaGangawaneGraphics:AlishonMendonsaProductionCoordinator:DeepikaNaik
Firstpublished:September2018
Productionreference:1180918
PublishedbyPacktPublishingLtd.LiveryPlace35LiveryStreetBirminghamB32PB,UK.
ISBN978-1-78934-344-1
www.packtpub.com
![Page 5: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/5.jpg)
mapt.io
Maptisanonlinedigitallibrarythatgivesyoufullaccesstoover5,000booksandvideos,aswellasindustryleadingtoolstohelpyouplanyourpersonaldevelopmentandadvanceyourcareer.Formoreinformation,pleasevisitourwebsite.
![Page 6: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/6.jpg)
Whysubscribe?SpendlesstimelearningandmoretimecodingwithpracticaleBooksandVideosfromover4,000industryprofessionals
ImproveyourlearningwithSkillPlansbuiltespeciallyforyou
GetafreeeBookorvideoeverymonth
Maptisfullysearchable
Copyandpaste,print,andbookmarkcontent
![Page 7: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/7.jpg)
Packt.comDidyouknowthatPacktofferseBookversionsofeverybookpublished,withPDFandePubfilesavailable?YoucanupgradetotheeBookversionatwww.packt.comandasaprintbookcustomer,youareentitledtoadiscountontheeBookcopy.Getintouchwithusatcustomercare@packtpub.comformoredetails.
Atwww.packt.com,youcanalsoreadacollectionoffreetechnicalarticles,signupforarangeoffreenewsletters,andreceiveexclusivediscountsandoffersonPacktbooksandeBooks.
![Page 8: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/8.jpg)
Contributors
![Page 9: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/9.jpg)
AbouttheauthorBrankoAjzeleisarespectedandhighlyaccomplishedsoftwaredeveloper,bookauthor,solutionspecialist,consultant,andteamleader.HecurrentlyworksforInteractiveWebSolutionsLtd(iWeb),whereheholdstheroleofseniordeveloperandisthedirectorofiWeb'sCroatiaoffice.
BrankoholdsseveralrespectedITcertifications,includingZendCertifiedPHPEngineer,MagentoCertifiedDeveloper,MagentoCertifiedDeveloperPlus,MagentoCertifiedSolutionSpecialist,Magento2CertifiedSolutionSpecialist,Magento2CertifiedProfessionalDeveloper,tomentionjustafew.
Hewascrownedthee-commerceDeveloperoftheYearbytheDigitalEntrepreneurAwardsinOctober2014forhisexcellentknowledgeandexpertiseine-commercedevelopment.
Specialthankstomysupportivewife,Ivana,forherunderstandingwhenItookquiteabitofourfamilytimeforthisendeavor.
![Page 10: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/10.jpg)
Aboutthereviewer
Andrew"Pembo"PembertonisaCertifiedMagentoDeveloperwithover20years'experiencebuildingwebsites.HeisbasedinStoke-on-Trent,UKandstartedbuildingwebsitesfromtheyoungageof13.HehasadegreeincomputersciencefromStaffordshireUniversity.
AndrewisnowthedevelopmentdirectoratiWeb(basedinStafford,UK),which,forover20years,hascreatedindustry-leadingwebsitesandnowspecializesinlargescaleMagentosolutionsandPIM-basedprojectsforawiderangeofclients.
Outsideofhisdigitallife,Andrewenjoysspendingtimewithhisfamilyofpets,travelingwithhiswife,andbeinganavidgamer.
![Page 11: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/11.jpg)
PacktissearchingforauthorslikeyouIfyou'reinterestedinbecominganauthorforPackt,pleasevisitauthors.packtpub.comandapplytoday.Wehaveworkedwiththousandsofdevelopersandtechprofessionals,justlikeyou,tohelpthemsharetheirinsightwiththeglobaltechcommunity.Youcanmakeageneralapplication,applyforaspecifichottopicthatwearerecruitinganauthorfor,orsubmityourownidea.
![Page 12: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/12.jpg)
TableofContentsTitlePageCopyrightandCredits
Magento2DevelopmentQuickStartGuidePacktUpsell
Whysubscribe?Packt.com
ContributorsAbouttheauthorAboutthereviewerPacktissearchingforauthorslikeyou
PrefaceWhothisbookisforWhatthisbookcoversTogetthemostoutofthisbook
DownloadtheexamplecodefilesCodeinAction
ConventionsusedGetintouch
Reviews1. UnderstandingtheMagentoArchitecture
TechnicalrequirementsInstallingMagentoModesAreasRequestflowprocessingModules
CreatingtheminimalmoduleCacheDependencyinjection
ArgumentinjectionVirtualtypesProxiesFactories
PluginsThebeforepluginThearoundpluginTheafterplugin
![Page 13: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/13.jpg)
EventsandobserversConsolecommandsCronjobsSummary
2. WorkingwithEntitiesTechnicalrequirementsUnderstandingtypesofmodels
CreatingasimplemodelMethodsworthmemorizing
WorkingwithsetupscriptsThe InstallSchemascriptThe UpgradeSchemascriptTheRecurringscriptThe InstallDatascriptThe UpgradeDatascriptTheRecurringDatascript
ExtendingentitiesCreatingextensionattributes
Summary3. UnderstandingWebAPIs
TechnicalrequirementsTypesofusersTypesofauthenticationTypesofAPIsUsingexistingwebAPIsCreatingcustomwebAPIsUnderstandingsearchcriteriaSummary
4. BuildingandDistributingExtensionsTechnicalrequirementsBuildingashippingextensionDistributingviaGitHubDistributingviaPackagistSummary
5. DevelopingforAdminTechnicalrequirementsUsingthelistingcomponentUsingtheformcomponentSummary
![Page 14: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/14.jpg)
6. DevelopingforStorefrontTechnicalrequirementsSettinguptheplaygroundCallingandinitializingJScomponentsMeetRequireJSReplacingjQuerywidgetcomponentsExtendingjQuerywidgetcomponentsCreatingjQuerywidgetscomponentsCreatingUI/KnockoutJScomponentsExtendingUI/KnockoutJScomponentsSummary
7. CustomizingCatalogBehaviorTechnicalrequirementsCreatingthesizeguideCreatingthesamedaydeliveryFlaggingnewproductsSummary
8. CustomizingCheckoutExperiencesTechnicalrequirementsPassingdatatothecheckoutAddingordernotestothecheckoutSummary
9. CustomizingCustomerInteractionsTechnicalrequirementsUnderstandingthesectionmechanismAddingcontactpreferencestocustomeraccountsAddingcontactpreferencestothecheckoutSummary
OtherBooksYouMayEnjoyLeaveareview-letotherreadersknowwhatyouthink
![Page 15: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/15.jpg)
PrefaceMagentoisapopularopensourcee-commerceplatformwritteninPHP.Itisusedprimarilyforbuildingwebshops,thoughitcaneasilybeusedforothertypesofwebsitesaswell.WiththehelpofitspowerfulwebAPI,wecanbuildrobustsolutionsthatsatisfymodern-dayapplicationrequirements.
Bytheendofthisbook,thereadershouldbefamiliarwithconfigurationfiles,models,collections,blocks,controllers,events,observers,plugins,UIcomponentsandotherbuildingelementsofMagento.
![Page 16: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/16.jpg)
WhothisbookisforThisbookisintendedforPHPdevelopersgettingstartedwithMagentov2.xdevelopment.Thoughcompactintermsofpagenumbers,thebookcoversawiderangeoffunctionality,allowingthereadertomasterday-to-dayMagentoskillsinaclearandconciseway.NopreviousMagentoknowledgeisrequired.
![Page 17: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/17.jpg)
WhatthisbookcoversChapter1,UnderstandingtheMagentoArchitecture,takesalookatsomeofthekeyMagentocomponents.WewillgothroughpluginsandeventobserversandlearnhowtheyprovideapowerfulwayofextendingMagento,eitherbychangingthebehaviorofexistingfunctionsorbyrunningsomefollow-upcodeinresponsetocertainevents.
Chapter2,WorkingwithEntities,demonstrateshowtodifferentiatebetweenthethreetypesofMagentomodels:non-persistable,persistablesimple,andpersistableEAV.Wewilltakealookatthesixdifferentsetupscriptsandhowtheyallowusagreatdealofflexibilityforschemaanddatamanagement.
Chapter3,UnderstandingWebAPI,showsthereaderhowtodifferentiatebetweentypesofwebAPIusers,authentication,andmethodsitprovides.WewillalsotakealookathoweasyitistocreateourownAPIswithjustafewlinesofXML.WewillseehowtheroutedefinitionallowsforeasybindingbetweenwhatarrivesviaHTTPrequestsandwhatisexecutedincode,respectingtheaccesslistpermissionsintheprocess.
Chapter4,BuildingandDistributingExtensions,discusseshowtocreateasimpleshippingmodule.Weshalltakealookathoweasyitistoaddspecificshippingcalculationsaspartofofflineshippingmethods.WewillthenpackagethismoduleanddistributeitviaPackagist.Thismakesiteasyfortheendconsumertouseourmodulewithjustafewsimpleconsolecommands.
Chapter5,DevelopingforAdmin,walksthereaderthroughbuildingtwoverydifferentscreensintheMagentoadminarea.Oneutilizesthelistingcomponent,whereastheotherutilizestheformcomponent.
Chapter6,DevelopingforStorefront,coversthebitsandpiecesinvolvedinstorefrontdevelopment,whichJScomponentsmakethemostchallengingpartof.Wewillunderstandhowtowritenewcomponents,aswellashowtooverrideorbypassexistingones–anessentialskillforanyMagentodeveloper,beitbackendorfrontend.
![Page 18: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/18.jpg)
Chapter7,CustomizingCatalogBehavior,demonstratesbuildingthreedistinctivefunctionalities,allofwhichrelatetothecatalogpartofMagento.TheydemonstratehoweasilyMagentocanbeextendedwithnewfeatureswithoutreallyoverridinganyofthecorefiles.UsingpluginsandJScomponentsarejustsomeoftheapproacheswemighttake.
Chapter8,CustomizingCheckoutExperience,demonstrateswritingasmallbutfunctionalordernotesmodule.Thiswillallowustofamiliarizeourselveswithanimportantaspectofcustomizingthecheckoutexperience,thegistofwhichliesinunderstandingthecheckout_index_indexlayouthandle,theJavaScriptwindow.checkoutConfigobject,anduiComponent.
Chapter9,CustomizingCustomerInteractions,walksthereaderthroughbuildingasmallmodulethatallowsustogetagreaterinsightintoMagento'scustomerdataandsectionsmechanism.Wewilllearnhowtomanageandbuildasinglecomponent,whichwillgetusedbothonthecustomer'sMyAccountpage,aswellasatthecheckout.
![Page 19: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/19.jpg)
TogetthemostoutofthisbookTogetthemostoutofthebook,thereaderisexpectedtohave:
AdegreeofPHPobject-orientedprogramming(OOP)knowledgeAbasicunderstandingofJavaScriptandXML
![Page 20: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/20.jpg)
DownloadtheexamplecodefilesYoucandownloadtheexamplecodefilesforthisbookfromyouraccountatwww.packtpub.com.Ifyoupurchasedthisbookelsewhere,youcanvisitwww.packtpub.com/supportandregistertohavethefilesemaileddirectlytoyou.
Youcandownloadthecodefilesbyfollowingthesesteps:
1. Loginorregisteratwww.packtpub.com.2. SelecttheSUPPORTtab.3. ClickonCodeDownloads&Errata.4. EnterthenameofthebookintheSearchboxandfollowtheonscreen
instructions.
Oncethefileisdownloaded,pleasemakesurethatyouunziporextractthefolderusingthelatestversionof:
WinRAR/7-ZipforWindowsZipeg/iZip/UnRarXforMac7-Zip/PeaZipforLinux
ThecodebundleforthebookisalsohostedonGitHubathttps://github.com/PacktPublishing/Magento-2-Quick-Start-Guide.Incasethere'sanupdatetothecode,itwillbeupdatedontheexistingGitHubrepository.
Wealsohaveothercodebundlesfromourrichcatalogofbooksandvideosavailableathttps://github.com/PacktPublishing.Checkthemout!
![Page 21: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/21.jpg)
CodeinActionVisitthefollowinglinktocheckoutvideosofthecodebeingrun:
http://bit.ly/2D98D8q
![Page 22: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/22.jpg)
ConventionsusedThereareanumberoftextconventionsusedthroughoutthisbook.
CodeInText:Indicatescodewordsintext,databasetablenames,foldernames,filenames,fileextensions,pathnames,dummyURLs,userinput,andTwitterhandles.Hereisanexample:"Thedefaultareaisthefrontend,asdefinedbythedefaultargumentundermodulestore/etc/di.xml."
Ablockofcodeissetasfollows:
constAREA_GLOBAL='global';
constAREA_FRONTEND='frontend';
constAREA_ADMINHTML='adminhtml';
constAREA_DOC='doc';
constAREA_CRONTAB='crontab';
constAREA_WEBAPI_REST='webapi_rest';
constAREA_WEBAPI_SOAP='webapi_soap';
Whenwewishtodrawyourattentiontoaparticularpartofacodeblock,therelevantlinesoritemsaresetinbold:
constAREA_GLOBAL='global';
constAREA_FRONTEND='frontend';
constAREA_ADMINHTML='adminhtml';
constAREA_DOC='doc';
constAREA_CRONTAB='crontab';
constAREA_WEBAPI_REST='webapi_rest';
constAREA_WEBAPI_SOAP='webapi_soap';
Anycommand-lineinputoroutputiswrittenasfollows:
phpbin/magentosetup:install\
--db-host="/Applications/MAMP/tmp/mysql/mysql.sock"\
--db-name=magelicious\
Bold:Indicatesanewterm,animportantword,orwordsthatyouseeonscreen.Forexample,wordsinmenusordialogboxesappearinthetextlikethis.Hereisanexample:"Thetabelementofthefile,whichisusedtoprovideasidebarmenupresenceunderMagentoadminStores|Settings|Configuration,isaniceexample."
Warningsorimportantnotesappearlikethis.
![Page 23: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/23.jpg)
Tipsandtricksappearlikethis.
![Page 24: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/24.jpg)
GetintouchFeedbackfromourreadersisalwayswelcome.
Generalfeedback:Emailfeedback@packtpub.comandmentionthebooktitleinthesubjectofyourmessage.Ifyouhavequestionsaboutanyaspectofthisbook,[email protected].
Errata:Althoughwehavetakeneverycaretoensuretheaccuracyofourcontent,mistakesdohappen.Ifyouhavefoundamistakeinthisbook,wewouldbegratefulifyouwouldreportthistous.Pleasevisitwww.packtpub.com/submit-errata,selectingyourbook,clickingontheErrataSubmissionFormlink,andenteringthedetails.
Piracy:IfyoucomeacrossanyillegalcopiesofourworksinanyformontheInternet,wewouldbegratefulifyouwouldprovideuswiththelocationaddressorwebsitename.Pleasecontactusatcopyright@packtpub.comwithalinktothematerial.
Ifyouareinterestedinbecominganauthor:Ifthereisatopicthatyouhaveexpertiseinandyouareinterestedineitherwritingorcontributingtoabook,pleasevisitauthors.packtpub.com.
![Page 25: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/25.jpg)
ReviewsPleaseleaveareview.Onceyouhavereadandusedthisbook,whynotleaveareviewonthesitethatyoupurchaseditfrom?Potentialreaderscanthenseeanduseyourunbiasedopiniontomakepurchasedecisions,weatPacktcanunderstandwhatyouthinkaboutourproducts,andourauthorscanseeyourfeedbackontheirbook.Thankyou!
FormoreinformationaboutPackt,pleasevisitpacktpub.com.
![Page 26: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/26.jpg)
UnderstandingtheMagentoArchitectureBuildingwebshopsisachallengingandtediousjob,andevenmoresoifaplatformyouareworkingonislimitedviafeatures,extensibility,andtheoverallecosystemitprovides.Choosingtherightplatformcanoftenmakethedifferencebetweenaproject'ssuccessorfailure.Theabundanceofavailablee-commercesoftware,fromSaaStoself-hostedsolutions,doesnotreallymakeitaneasychoice.
TheMagentoe-commerceplatformhasbeenaroundforover10yearsnow.WithitsfirststablereleasedatingbacktoMarch2008,itimmediatelycaughttheattentionofdevelopersasanextensibleandfeature-richopensourceplatform.Overtime,Magentoestablisheditselfasnotjustastunningtechnicalandfeature-richplatform,butasarobustecosystemaswell.Byallowingdeveloperstovalidatetheirreal-worldskillsthroughtheMagentocertificationprogram,certainstandardshavebeenputintoeffect,makingiteasierformerchantstobetterrecognizetheirsolutionpartners.Trainingcourseshavebeenfurtherprovidedforotherrolesine-commercebusinessaswell,suchasmerchants,marketers,systemadministrators,andbusinessanalysts.
Inthischapter,wewilltakealookatsomeofthekeymust-knowsaboutMagento:
InstallingMagentoModesAreasRequestflowprocessingModulesCacheDependencyinjectionPluginsEventsandobserversConsolecommandsCronjobs
![Page 27: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/27.jpg)
Tokeepthingscompactaswemoveforward,let'sassumethefollowingthroughoutthisbook:
Weareworkingonthemagelicious.locprojectWearereferringtoourprojectrootdirectoryas<PROJECT_DIR>Wearereferringtothe<PROJECT_DIR>/app/code/Mageliciousdirectoryas<MAGELICIOUS_DIR>
WearereferringtoMagento'svendor/magentodirectoryas<MAGENTO_DIR>WehavearunningLAMP/MAMP/WAMPstack(Apache,MySQL,PHP)thatiscompliantwithMagento'srequirementsWehaveaComposerpackagemanagerinstalledWehaveaccesstocrontab(Linux,MacOS)orTaskScheduler(Windows)
AMPPSisaneasytouse,allinoneLAMP/MAMP/WAMPsoftwarestackfromSoftaculous,whichenablesApache,MySQL,andPHP.WithAMPPS,youcaneveninstallMagento2.xbytheclickofabutton,whichmeansitcomesloadedwithalltherightPHPextensions.Whileitisn'tsuitedforproductionpurposes,itcomesinhandyforquicklykickingthedevelopmentenvironment.Seehttp://www.ampps.com/formoreinformation.Consultthedevdocs(https://devdocs.magento.com)forMagentotechnologystackrequirements.
![Page 28: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/28.jpg)
TechnicalrequirementsYouwillneedtohavebasicknowledgeofPHP,OOP,JavaScript,andXML.YouwillalsoneedApache,MySQL,andAMPPSinstalledonyoursystemtoexecutethecodes.
ThecodefilesofthischaptercanbefoundonGitHub:https://github.com/PacktPublishing/Magento-2-Quick-Start-Guide.
CheckoutthefollowingvideotoseetheCodeinAction:
http://bit.ly/2D8kOlF.
![Page 29: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/29.jpg)
InstallingMagentoTheMagentoplatformcomesintwoflavors:
MagentoOpenSource:Thefreeversion,targetingsmallbusinessesMagentoCommerce:Thecommercialversion,targetingsmall,medium,orenterprisebusinesses
ThedifferencebetweenthetwocomesmainlyintheformofextramodulesthatwereaddedtotheCommerceversion,whereasallthecodingconceptsandcorefeaturesremainthesame.ItgoestosaythatanyknowledgeweobtainthroughfollowingMagentoOpenSourceexamplesisfullyapplicabletoanyoneworkingonMagentoCommerce.
ThereareseveralwaysthatwecanobtainsourcefilesforMagentoOpenSource:
Sourcefilearchive(.zip,.tar.gz,.tar.bz2),availableathttps://magento.comGitrepository,availableathttps://github.com/magento/magento2Composerrepository,availableathttps://repo.magento.com
ObtainingsourcefilesviaaCLIfromthecomposerrepositoryisourpreferredmethod.Assumingwearewithintheempty<PROJECT_DIR>directory,wecankickoffthisprocessviathefollowingcommand:
composercreate-project--repository-url=https://repo.magento.com/magento/project-community-edition.
Thedot(.)attheendofthiscommandthistellsthecomposertopullthefilesintoacurrentdirectory.
OncetheComposerprocessisfinished,wecanstartinstallingMagento.TherearetwowayswecaninstallMagento:
ViatheWebSetupWizard:Thegraphical,browser-basedprocessViathecommandline:Thecommand-line-basedprocess
KnowinghowtoinstallMagentoviathecommandlineisanessentialskillinday-to-daydevelopment,asthemajorityofdevelopmentrequiresthedeveloper
![Page 30: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/30.jpg)
totacklevariousbin/magentocommands—nottomentionthecommandlineapproachissomewhatfasterandeasilyscripted.
Let'sinstallMagentowiththebuilt-inphpbin/magentosetup:installcommandandafewoftherequiredinstallationoptionsasfollows:
phpbin/magentosetup:install\
--db-host="/Applications/MAMP/tmp/mysql/mysql.sock"\
--db-name=magelicious\
--db-user=root
--db-password=root\
--admin-firstname=John\
--admin-lastname=Doe\
--admin-user=john\
--admin-password=jrdJ%0i9a69n
Aftertheprecedingcommandhasbeenexecuted,weshouldbegintoseeconsoleprogress,startingwithsomethinglikethefollowing:
StartingMagentoinstallation:
Filepermissionscheck...
[Progress:1/513]
Requiredextensionscheck...
[Progress:2/513]
EnablingMaintenanceMode...
[Progress:3/513]
Installingdeploymentconfiguration...
[Progress:4/513]
Installingdatabaseschema:
Schemacreation/updates:
Module'Magento_Store':
[Progress:5/513]
Whileitmighttakeuptoafewminutes,asuccessfulinstallationshouldendwithamessagethat'ssimilartothefollowing:
[Progress:508/513]
Installingadminuser...
[Progress:509/513]
Cachesclearing:
Cacheclearedsuccessfully
[Progress:510/513]
DisablingMaintenanceMode:
[Progress:511/513]
Postinstallationfilepermissionscheck...
Forsecurity,removewritepermissionsfromthesedirectories:'/Users/branko/Projects/magelicious/app/etc'
[Progress:512/513]
Writeinstallationdate...
[Progress:513/513]
[SUCCESS]:Magentoinstallationcomplete.
[SUCCESS]:MagentoAdminURI:/admin_mxq00c
Nothingtoimport.
![Page 31: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/31.jpg)
Rightafterinstallation,ourfirststepshouldbetosetMagentotodevelopermodebyusingthefollowingcommand:
phpbin/magentodeploy:mode:setdeveloper
WewilltakeacloserlookatMagentomodessoon;fornow,thisistobetakenasis.
MagentoautomaticallyassignsanadminURLduringconsoleinstallation,unlessexplicitlyspecifiedthroughtheinstallcommandviathe--backend-frontnameoption.Outofalltheinstallationoptionslisted,onlythefollowingareactuallyrequired:--admin-firstname,--admin-lastname,--admin-email,--admin-user,and--admin-password.ItisworthtakingsometimetoreadthroughtheofficialMagentodocumentation(https://devdocs.magento.com)andlookingatwhattherestoftheinstallationoptionshavetooffer.
IfallwentwellduringtheMagentoinstallation,weshouldbeabletoopenthestorefrontandadmininourbrowser.
![Page 32: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/32.jpg)
ModesModesplayacrucialroleinMagento'sdevelopmentanddeploymentprocesses.Theyarehandledbythedeploymodule,whichcanbefoundunderthe<MAGENTO_DIR>/module-deploydirectory.
Thebuilt-inphpbin/magentocommandprovidesuswiththefollowingdeploycommands:
deploy
deploy:mode:setSetapplicationmode.
deploy:mode:showDisplayscurrentapplicationmode.
Wealreadyusedthedeploy:mode:setdevelopercommandtoswitchfromdefaulttodevelopermode.
Magentodifferentiatesbetweenfollowingthreemodes:
default:Thedefaultafter-installmode:NotoptimizedforproductionSymlinkstostaticviewfilesarepublishedtothepub/staticdirectoryErrorsandexceptionsarenotshowntotheuser,astheyareloggedtothefilesystemShouldavoidusingit
developer:Fordevelopmentsystemsonly:Symlinkstostaticviewfilesarepublishedtothepub/staticdirectoryProvidesverboseloggingEnablesautomaticcodecompilationEnablesenhanceddebuggingSlowestperformance
production:Forproductionsystems:Errorsandexceptionsarenotshowntotheuser,astheyareloggedtothefilesystemStaticviewfilesarenotmaterialized,astheyareservedfromthecacheonlyAutomaticcodefilecompilationisdisabled,asneworupdatedfilesarenotwrittentothefilesystemEnablinganddisablingthecachetypesisnotpossiblefromthe
![Page 33: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/33.jpg)
MagentoadminFastestperformance
Carefullybalancingdevelopermodewithsomeofthecachetypesbeingenabled/disabledcanprovideoptimalperformanceduringdevelopment.
![Page 34: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/34.jpg)
AreasTheareaisalogicalcomponentthatorganizescodeforoptimizedrequestprocessing.Whilethemajorityofthetimewedon'treallyhavetocodeanythingspecificregardingareas,understandingthemiskeytounderstandingMagento.
TheMagento\Framework\App\AreaclassAREA_*constantshintatthefollowingareas:
constAREA_GLOBAL='global';
constAREA_FRONTEND='frontend';
constAREA_ADMINHTML='adminhtml';
constAREA_DOC='doc';
constAREA_CRONTAB='crontab';
constAREA_WEBAPI_REST='webapi_rest';
constAREA_WEBAPI_SOAP='webapi_soap';
Bydoingalookupforthe<argumentname="areas"stringacrossallofthe<MAGENTO_DIR>di.xmlfiles,wecanseethatfiveoftheseareashavebeenexplicitlyaddedtotheareasargumentoftheMagento\Framework\App\AreaListclass:
adminhtmlvia<MAGENTOI_DIR>/module-backend/etc/di.xmlwebapi_restvia<MAGENTOI_DIR>/module-webapi/etc/di.xmlwebapi_soapvia<MAGENTOI_DIR>/magento/module-webapi/etc/di.xmlfrontendvia<MAGENTOI_DIR>/magento/module-store/etc/di.xmlcrontabvia<MAGENTOI_DIR>/magento/module-cron/etc/di.xml
Thedefaultareaisfrontend,asdefinedbythedefaultargumentundermodule-store/etc/di.xml.Theglobalareaisusedasafallbackforfilesthatareabsentintheadminhtmlandfrontendareas.
Let'stakeacloserlookatthe<MAGENTO_DIR>/module-webapi/etc/di.xmlfile:
<typename="Magento\Framework\App\AreaList">
<arguments>
<argumentname="areas"xsi:type="array">
<itemname="webapi_rest"xsi:type="array">
<itemname="frontName"xsi:type="string">rest</item>
</item>
<itemname="webapi_soap"xsi:type="array">
<itemname="frontName"xsi:type="string">soap</item>
</item>
</argument>
</arguments>
</type>
![Page 35: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/35.jpg)
ThefrontNameiswhatsometimesappearsatthefrontoftheURL,whereastheareanameisusedinternallytorefertotheareainconfigurationfiles.DifferentareasdefinedbyMagentocancontaindifferentcodeforprocessingURLsandrequests.ThisallowsMagentotoloadonlythedependentcodeforthespecifiedarea.
Whendevelopingmodules,wedefinewhichresourcesarevisibleandaccessibleinagivenarea.Thisway,wegettocontrolthespecificareabehaviorifneeded.Anexampleofonesuchbehaviormightbethedefinitionoftheeventobserverunderthefrontendareaforcustomer_save_afterevent.Thisobserverwouldonlytriggeroncustomersaveoperationsthataretriggeredfromthestorefront,whichusuallyindicatesacustomerregisteraction.Theadminhtmlareaoperations,suchasMagentoadminmanuallycreatingacustomer,wouldfailtotriggerthisobserver,asitwasdefinedunderthefrontendarea.
Onoccasion,wemightneedtorunsomecodethatonlyexecutesundercertainareas.Insuchcases,emulationhelpsusemulateanystoreprogrammatically.TheMagento\Store\Model\App\EmulationclassprovidesthestartEnvironmentEmulationandstopEnvironmentEmulationmethods,whichwecanuseforthispurpose,asperthefollowingpartialexample:
protected$storeRepository;
protected$emulation;
publicfunction__construct(
\Magento\Store\Api\StoreRepositoryInterface$storeRepository,
\Magento\Store\Model\App\Emulation$emulation
){
$this->storeRepository=$storeRepository;
$this->emulation=$emulation;
}
publicfunctiontest(){
$store=$this->storeRepository->get('store-to-emulate');
$this->emulation->startEnvironmentEmulation(
$store->getId(),
\Magento\Framework\App\Area::AREA_FRONTEND
);
//Codetoexecuteinemulatedenvironment
$this->emulation->stopEnvironmentEmulation();
}
Whileitisnotacommonthingtodo,wecanfurtherregisternewareasourselves.Thisiseasilydonebyusingthemodule'sdi.xml.
![Page 36: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/36.jpg)
RequestflowprocessingURLsinMagentohavetheformatof<AreaFrontName>/<VendorName>/<ModuleName>/<ControllerName>/<ActionName>,butthisdoesnotmeanthatweactuallyusethearea,vendor,ormodulenameintheURLanytimewewishtoaccessacertaincontroller.Forexample,theareaforarequestisdefinedbythefirstrequestpathsegment,suchasadminforadminhtmlarea,andnoneforfrontendarea.
WeusetherouterclasstoassignaURLtoacorrespondingcontrolleranditsaction.Therouter'smatchmethodfindsamatchingcontroller,whichisdeterminedbyanincomingrequest.
Conceptually,creatinganewrouterisassimpleasdoingthefollowing:
1. InjectthenewitemundertherouterListargumentoftheMagento\Framework\App\RouterListtypeviathedi.xmlfile.
2. Createarouterfile(byusingthematchmethod,whichimplements\Magento\Framework\App\RouterInterface).
3. Returnaninstanceof\Magento\Framework\App\ActionInterface.
Bydoingalookupforthename="routerList"stringacrossallofthe<MAGENTO_DIR>di.xmlfiles,wecanseethefollowingrouterdefinitions:
Magento\Robots\Controller\Router(robots)
Magento\Cms\Controller\Router(cms)
Magento\UrlRewrite\Controller\Router(urlrewrite)
Magento\Framework\App\Router\Base(standard)
Magento\Framework\App\Router\DefaultRouter(default)
Magento\Backend\App\Router(admin)
Let'stakeacloserlookattherobotsrouterunder<MAGENTO_DIR>/module-robots.etc/frontend/di.xmlinjectsthenewitemundertherouterListargumentasfollows:
<typename="Magento\Framework\App\RouterList">
<arguments>
<argumentname="routerList"xsi:type="array">
<itemname="robots"xsi:type="array">
![Page 37: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/37.jpg)
<itemname="class"xsi:type="string">Magento\Robots\Controller\Router</item>
<itemname="disable"xsi:type="boolean">false</item>
<itemname="sortOrder"xsi:type="string">10</item>
</item>
</argument>
</arguments>
</type>
TheMagento\Robots\Controller\Routerclasshasbeenfurtherdefinedasperthefollowingpartialextract:
classRouterimplements\Magento\Framework\App\RouterInterface{
//Magento\Framework\App\ActionFactory
private$actionFactory;
//Magento\Framework\App\Router\ActionList
private$actionList;
//Magento\Framework\App\Route\ConfigInterface
private$routeConfig;
publicfunctionmatch(\Magento\Framework\App\RequestInterface$request){
$identifier=trim($request->getPathInfo(),'/');
if($identifier!=='robots.txt'){
returnnull;
}
$modules=$this->routeConfig->getModulesByFrontName('robots');
if(empty($modules)){
returnnull;
}
$actionClassName=$this->actionList->get($modules[0],null,'index','index');
$actionInstance=$this->actionFactory->create($actionClassName);
return$actionInstance;
}
}
Thematchmethodchecksiftherobots.txtfilewasrequestedandreturnstheinstanceofthematched\Magento\Framework\App\ActionInterfacetype.Byfollowingthissimpleimplementation,wecaneasilycreatetherouteofourown.
Conceptually,creatinganewcontrollerisassimpleasdoingthefollowing:
1. Registerarouteviarouter.xml.2. Createanabstractcontrollerfile(asanabstractclass,whichextends
\Magento\Framework\App\Action\Action).
3. Createanactioncontrollerfile(whichextendsthemaincontrollerfilewiththeexecutemethod,andimplements\Magento\Framework\App\ActionInterface).
4. Returnaninstanceof\Magento\Framework\Controller\ResultInterface.
Theseparationofthecontrollerintothemainandactioncontrollerfilesisnotatechnicalrequirement,butratherarecommendedorganizationalone.Magentodoesthisacrossthe
![Page 38: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/38.jpg)
majorityofitsmodules.
Bydoingalookupforthe<routestringacrossthe<MAGENTO_DIR>routes.xmlfiles,wecanseethatMagentouseshundredsofroutedefinitions,whicharespreadacrossitsmodules.Eachrouterepresentsonecontroller.
Let'stakeacloserlookatoneofMagento'scontrollers,<MAGENTO_DIR>/module-customer,whichmapstothehttp://magelicious.loc/customer/address/formURL.Therouteitselfisregisteredviafrontend/di.xmlunderthestandardrouterwithacustomerIDandacustomerfrontName,asfollows:
<routerid="standard">
<routeid="customer"frontName="customer">
<modulename="Magento_Customer"/>
</route>
</router>
TheabstractcontrollerfileController/Address.phpisdefinedpartiallyasfollows:
abstractclassAddressextends\Magento\Framework\App\Action\Action{
//Therestofthecode...
}
Theabstractcontrolleriswherewewanttoaddfunctionalityanddependenciesthataresharedacrossallofthechildactioncontrollers.
Wecanfurtherseethreedifferentactioncontrollersdefinedwithinthesubdirectorywhichhasthesamenameastheabstractclass.TheController/Addressdirectorycontainssixactioncontrollers:Delete.php,Edit.php,Form.php,FormPost.php,Index.php,andNewAction.php.Let'stakeacloserlookatthefollowingpartialForm.phpfile'scontent:
classFormextends\Magento\Customer\Controller\Address{
publicfunctionexecute(){
/**@var\Magento\Framework\View\Result\Page$resultPage*/
$resultPage=$this->resultPageFactory->create();
$navigationBlock=$resultPage->getLayout()->getBlock('customer_account_navigation');
if($navigationBlock){
$navigationBlock->setActive('customer/address');
}
return$resultPage;
}
}
TheexamplehereusesthecreatemethodoftheinjectedMagento\Framework\View\Result\PageFactorytypetocreateanewpageresult.Thevarioustypesofcontrollerresultscanbefoundwithinthe<MAGENTO_DIR>/framework
![Page 39: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/39.jpg)
directory:
Magento\Framework\Controller\Result\Json
Magento\Framework\Controller\Result\Raw
Magento\Framework\Controller\Result\Redirect
Magento\Framework\Controller\Result\Forward
Magento\Framework\View\Result\Layout
Magento\Framework\View\Result\Page
Wecantaketheunifiedwayofcreatingresultinstancesbyusingthecreatemethodof\Magento\Framework\Controller\ResultFactory.TheResultFactorydefinestheTYPE_*constantforeachofthementionedcontrollerresulttypes:
constTYPE_JSON='json';
constTYPE_RAW='raw';
constTYPE_REDIRECT='redirect';
constTYPE_FORWARD='forward';
constTYPE_LAYOUT='layout';
constTYPE_PAGE='page';
Keepingtheseconstantsinmind,wecaneasilywriteouractioncontrollercodeasfollows:
$resultRedirect=$this->resultFactory->create(ResultFactory::TYPE_REDIRECT);
$resultRedirect->setPath('adminhtml/*/index');
return$resultRedirect;
Aquicklookupforthe$this->resultFactory->createstring,acrosstheentire<MAGENTO_DIR>directory,cangiveuslotsofexamplesofhowtousetheResultFactoryforourowncode.
![Page 40: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/40.jpg)
ModulesThetop-levelMagentostructureisrathersimple.Whenwestripaway(seemingly)non-relevantfilessuchaslicenses,samplefiles,andchangelogs,whatremainslooksmuchlikethefollowing:
app/
code/
design/
etc/
config.php
env.php
bin/
composer.json
composer.lock
dev/
generated/
index.php
lib/
phpserver/
pub/
static/
adminhtml/
frontend/
setup/
update/
var/
cache/
log/
page_cache/
view_preprocessed/
pub/
static/
adminhtml/
frontend/
vendor/
composer/
magento/
symfony/
Theapp/code/<VendorName>/<ModuleName>directory,<MAGELICIOUS_DIR>forshort,iswhereourcustomcodewillreside.
Whendevelopermodeisenabled,wecanmanuallycleanthecache,compilation,andstaticfilesviatherm-rfvar/cache/*&&rm-rfvar/page_cache/*&&rm-rfvar/view_preprocessed/*&&rm-rfgenerated/*&&rm-rfpub/static/*command.Underlimitedusecases,thiscanprovideafasterdevelopmentworkflow.
Thevendor/magentodirectory,<MAGENTO_DIR>forshort,iswhereMagentosourcecoderesides,asperthefollowingpartiallisting:
![Page 41: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/41.jpg)
vendor/
magento/
composer/
framework/
language-de_de/
language-en_us/
magento-composer-installer/
magento2-base/
module-catalog/
module-checkout/
theme-adminhtml-backend/
theme-frontend-blank/
theme-frontend-luma/
Theindividualmoduledirectoryiswherethingsgetinteresting.Let'stakeaquicklookatthestructureofoneofthesimplerMagentomodules,<MAGENTO_DIR>/module-contact:
Block/
Controller/
etc/
Helper/
i18n/
Model/
Test/
view/
composer.json
LICENSE.txt
LICENSE_AFL.txt
README.md
registration.php
Thisisbynomeansthefinalstructureoftheindividualmodule.Thereareotherdirectoriesthemodulecandefine,aswewillseeaswemoveforwardthroughoutthisbook.
![Page 42: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/42.jpg)
CreatingtheminimalmoduleLet'screatethemostminimalmodulethereisinMagento.OurmodulewillbecalledCoreanditwillbelongtotheMageliciousvendor.Theformulafordeterminingthedirectoryofcustommodulesisapp/code/<VendorName>/<ModuleName>,orinourcase<MAGELICIOUS_DIR>/Core.
Westartoffbycreatingthe<MAGELICIOUS_DIR>/Core/registration.phpfilewiththefollowingcontent:
\Magento\Framework\Component\ComponentRegistrar::register(
\Magento\Framework\Component\ComponentRegistrar::MODULE,
'Magelicious_Core',
__DIR__
);
Theregistration.phpfileisessentiallytheentrypointofourmodule.TheregistermethodoftheMagento\Framework\Component\ComponentRegistrarclassprovidestheabilitytostaticallyregistercomponents,whereasacomponentcanbemorethanjustamodule,asdefinedviathefollowingconstants:
Magento\Framework\Component\ComponentRegistrar::MODULE
Magento\Framework\Component\ComponentRegistrar::LIBRARY
Magento\Framework\Component\ComponentRegistrar::THEME
Magento\Framework\Component\ComponentRegistrar::LANGUAGE
Next,wewillcreatethe<MAGELICIOUS_DIR>/Core/etc/module.xmlfilewiththefollowingcontent:
<config>
<modulename="Magelicious_Core"setup_version="1.0.0">
<sequence>
<modulename="Magento_Store"/>
<modulename="Magento_Backend"/>
<modulename="Magento_Config"/>
</sequence>
</module>
</config>
Thenameandsetup_versionattributesofamoduleelementareconsideredrequired.Thesequence,ontheotherhand,isoptional.WeuseittodefineanypotentialdependenciesaroundotherMagentomodules.
![Page 43: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/43.jpg)
Finally,weaddcomposer.jsonwiththefollowingcontent:
{
"name":"magelicious/module-core",
"description":"Thecoremodule",
"require":{
"php":"^7.0.0"
},
"type":"magento2-module",
"version":"1.0.0",
"license":[
"OSL-3.0",
"AFL-3.0"
],
"autoload":{
"files":[
"registration.php"
],
"psr-4":{
"Magelicious\\Core\\":""
}
}
}
Magentosupportsthefollowingcomposer.jsontypes:
magento2-moduleformodulesmagento2-themeforthemesmagento2-languageforlanguagepackagesmagento2-componentforgeneralextensionsthatdonotfitanyoftheothertypes
Thoughcomposer.jsonisnotrequiredforourcustommoduletobeseenbyMagento,itisrecommendedtoaddittoanycomponentwearebuilding.
Youcantriggermoduleinstallationbyrunningthephpbin/magentomodule:enableMagelicious_Corecommand,likeso:
$phpbin/magentomodule:enableMagelicious_Core
Thefollowingmoduleshavebeenenabled:
-Magelicious_Core
Tomakesurethattheenabledmodulesareproperlyregistered,run'setup:upgrade'.
Cacheclearedsuccessfully.
Generatedclassesclearedsuccessfully.Pleaserunthe'setup:di:compile'commandtogenerateclasses.
Info:Somemodulesmightrequirestaticviewfilestobecleared.Todothis,run'module:enable'withthe--clear-static-contentoptiontoclearthem.
Youcanrunthephpbin/magentosetup:upgradecommandtotriggeranyinstalland/orupdatescriptsthatneedtobetriggered:
Cacheclearedsuccessfully
Filesystemcleanup:
generated/code/Composer
![Page 44: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/44.jpg)
generated/code/Magento
generated/code/Symfony
Updatingmodules:
Schemacreation/updates:
Module'Magento_Store':
...
Module'Magento_CmsUrlRewrite':
Module'Magelicious_Core':
Module'Magento_ConfigurableImportExport':
...
Nothingtoimport.
Thisfinishesourmoduleinstallation.
Creatingthe<VendorName>/Coremoduleisoftenagoodpracticewhenworkingonaprojectwithlotsofcustom<VendorName>modules.Usedcarefully,theCoremodulecanprovidecommonbitsthataresharedacrossseveralothermodules.Thetabelementofthesystem.xmlfile,whichisusedtoprovideasidebarmenupresenceunderMagento'sadminStores|Settings|Configuration,isaniceexample.Similarly,wecanuseittoprovidetop-levelaccessresourcesforothermodulestouse.
Toconfirmourmodulewasinstalledcorrectly,performthefollowing:
Checkthe<PROJECT_DIR>/app/etc/config.phpfileforthe'Magelicious_Core'=>1entryCheckthesetup_moduletablefortheMagelicious_Core1.0.01.0.0entry
Atthemoment,ourmoduledoesabsolutelynothing,asidefromjustsittingthere.However,thesefewsimplestepsarethebasisforusmovingforwardwithMagentodevelopment,becausethemajorityofthingsinMagentoaredoneviaamodule,alongsideothertypesofcomponents,whichwehavealreadymentioned.
![Page 45: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/45.jpg)
CacheMagentomakesextensiveuseofcaching.TheSystem|Tools|CacheManagementsectionenablesustoEnable|Disable|Refreshthecachefromthecomfortofthegraphicalinterface.Duringdevelopment,theuseoftheconsoleismoreconvenientandfaster.
Thefollowingcache-relatedcommandsaresupported:
cache
cache:cleanCleanscachetype(s)
cache:disableDisablescachetype(s)
cache:enableEnablescachetype(s)
cache:flushFlushescachestorageusedbycachetype(s)
cache:statusCheckscachestatus
Outofthebox,MagentoOpenSourcecomeswith14differentcachetypes.Wecaneasilygetthestatusofeachcachetypebyrunningthephpbin/magentocache:statuscommand,whichgivesthefollowingoutput:
Currentstatus:
config:0
layout:0
block_html:0
collections:0
reflection:0
db_ddl:0
eav:0
customer_notification:0
the_custom_cache:1
config_integration:0
config_integration_api:0
full_page:0
translate:0
config_webservice:0
Wecanusetheenable|disable|cleancachecommandstoimpactoneormorecachetypesatonce.
Disabledcachetypesarenotcleaned.Usethecache:flushcommandwithcare,asflushingthecachetypepurgestheentirecachestorage.This,inturn,mightaffectotherapplicationsthatareusingthesamestorage.
Ifbuilt-incachetypesarenotenough,wecanalwayscreateourown.
CreatinganewcachetypeinMagentoisaseasyasdoingthefollowing:
![Page 46: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/46.jpg)
Createthe<MAGELICIOUS_DIR>/Core/etc/cache.xmlfilewiththefollowingcontent:
<config>
<typename="the_custom_cache"translate="label,description"instance="Magelicious\Core\Model\Cache\TheCustomCache">
<label>TheCustomCache</label>
<description>Ourcustomcachetype</description>
</type>
</config>
Createthe<MAGELICIOUS_DIR>/Core/Model/Cache/TheCustomCache.phpfilewiththefollowingcontent:
classTheCustomCacheextends\Magento\Framework\Cache\Frontend\Decorator\TagScope{
constTYPE_IDENTIFIER='the_custom_cache';
constCACHE_TAG='THE_CUSTOM_CACHE';
publicfunction__construct(\Magento\Framework\App\Cache\Type\FrontendPool$cacheFrontendPool){
parent::__construct($cacheFrontendPool->get(self::TYPE_IDENTIFIER),self::CACHE_TAG);
}
}
TheTYPE_IDENTIFIERisusedinternallyasacachetypecodethatisuniqueamongallcachetypes.TheCACHE_TAGisacachetagthat'susedtodistinguishthecachetypefromallothercaches.Runningcache:statusshouldnowshowourcustomcachetypeonthelist.
WecanusetheinstanceofMagento\Framework\App\Cache\TypeListInterfacetoinvalidatethecache,asfollows:
$this->typeList->invalidate(\Magelicious\Core\Model\Cache\TheCustomCache::TYPE_IDENTIFIER);
WecanusetheinstanceofMagento\Framework\App\Cache\Manager$cacheManagertoprogrammaticallyexecutethesameenable|disable|cleanoperationsasperthefollowingexample:
$cacheManager->setEnabled(
[\Magelicious\Core\Model\Cache\TheCustomCache::TYPE_IDENTIFIER],
true
);
$cacheManager->clean([\Magelicious\Core\Model\Cache\TheCustomCache::TYPE_IDENTIFIER]);
$cacheManager->flush([\Magelicious\Core\Model\Cache\TheCustomCache::TYPE_IDENTIFIER]);
Savingdatatocacherequiresserialization,asperthefollowingexample:
//\Magento\Framework\Config\CacheInterface$cache
//\Magento\Framework\Serialize\SerializerInterface$serializer
//\Magento\Framework\App\Cache\StateInterface$cacheState
![Page 47: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/47.jpg)
$isCacheEnabled=$cacheState->isEnabled(\Magelicious\Core\Model\Cache\TheCustomCache::TYPE_IDENTIFIER);
$cacheId='some-unique-identifier';
if($isCacheEnabled){
$cache->save(
$serializer->serialize('some-data'),
$cacheId,
[
\Magelicious\Core\Model\Cache\TheCustomCache::CACHE_TAG
]
);
}
Readingdatafromthecacheisaseasyasperthefollowingexample:
if($cacheData=$this->cache->load($cacheId);){
$someData=$this->getSerializer()->unserialize($cacheData);
}else{
$someData=$this->fetchSomeData();
}
![Page 48: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/48.jpg)
DependencyinjectionDependencyinjectionhasbecomeadefactostandardofmodern-daysoftware.Magentomakesheavyuseofthistechnique,basedonmappingsfoundindi.xmlfiles.TheworkloadofMagento'sdependencyinjectionishandledbytheMagento\Framework\ObjectManager\ObjectManagerinstance,whichimplementsthelightweightMagento\Framework\ObjectManagerInterface.
Thedi.xmlfileconfigurestheobjectmanager,tellingithowtohandlethefollowing:
ArgumentinjectionVirtualtypesProxiesFactoriesPlugins
Thesefeaturesallowforagreatdegreeofflexibilityandextensibility,aswewillsoonsee.
Everymodulecanhaveaglobalandarea-specificdi.xmlfile.
Magentoloadsconfigurationfilesinthefollowingorder:
Initial(app/etc/di.xml)Global(<ModuleDir>/etc/di.xml)Area-specific(<ModuleDir>/etc/<area>/di.xml)
WhenMagentoreadsalloftheseconfigurationfiles,itmergesthemalltogetherbyappendingallnodes.
![Page 49: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/49.jpg)
ArgumentinjectionArgumentinjectionisdoneviapreferenceandtypedefinitionswithinthedi.xml.
Byperformingalookupforthe<preferencestringacrosstheentire<MAGENTO_DIR>directory'sdi.xmlfiles,wecanseethatMagentouseshundredsofpreferencedefinitions,spreadacrossthemajorityofitsmodules.
Let'stakeaquicklookatoneofthe__constructmethod,ofthetypeMagento\Eav\Model\Attribute\Data\AbstractData:
publicfunction__construct(
\Magento\Framework\Stdlib\DateTime\TimezoneInterface$localeDate,
\Psr\Log\LoggerInterface$logger,
\Magento\Framework\Locale\ResolverInterface$localeResolver
){
$this->_localeDate=$localeDate;
$this->_logger=$logger;
$this->_localeResolver=$localeResolver;
}
Wecanfindthepreferencedefinitionsfortheseinterfacesunderthe<MAGENTO_DIR>/magento2-base/app/etc/di.xmlfile:
<preferencefor="Magento\Framework\Stdlib\DateTime\TimezoneInterface"type="Magento\Framework\Stdlib\DateTime\Timezone"/>
<preferencefor="Psr\Log\LoggerInterface"type="Magento\Framework\Logger\Monolog"/>
<preferencefor="Magento\Framework\Locale\ResolverInterface"type="Magento\Framework\Locale\Resolver"/>
Theoretically,wecanusetheobjectmanagerdirectly,asfollows:
classType{
protected$objectManager;
publicfunction__construct(
\Magento\Framework\ObjectManagerInterface$objectManager
){
$this->objectManager=$objectManager;
}
publicfunctionexample(){
$this->objectManager->create(\Fully\Qualified\Class\Name::class);
$this->objectManager->get(\Fully\Qualified\Class\Name::class);
\Magento\Framework\App\ObjectManager::getInstance()
->create(\Fully\Qualified\Class\Name::class);
\Magento\Framework\App\ObjectManager::getInstance()
->get(\Fully\Qualified\Class\Name::class);
}
}
ThedirectuseoftheobjectManagerishighlydiscouraged,asitpreventstypevalidationandtype
![Page 50: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/50.jpg)
hintingthatafactoryclassprovides.
Bydoingalookupforthe<typestringacrosstheentire<MAGENTO_DIR>directory'sdi.xmlfiles,wecanseethatMagentousesoverathousandtypedefinitions,spreadacrossthemajorityofitsmodules.
Hereisaverysimpleexample,takenfromthe<MAGENTO_DIR>/module-customer/etc/di.xmlfile:
<typename="Magento\Customer\Model\Visitor">
<arguments>
<argumentname="ignoredUserAgents"xsi:type="array">
<itemname="google1"xsi:type="string">Googlebot/1.0([email protected]://googlebot.com/)</item>
<itemname="google2"xsi:type="string">Mozilla/5.0(compatible;Googlebot/2.1;+http://www.google.com/bot.html)</item>
<itemname="google3"xsi:type="string">Googlebot/2.1(+http://www.googlebot.com/bot.html)</item>
</argument>
</arguments>
</type>
LookingintothesourceoftheMagento\Customer\Model\Visitorclass,wecanseethatithasitsconstructordefinedbythe$ignoredUserAgents=[]array.Usingthetypeelement,theprecedingexampleinjectstheignoredUserAgentsargumentwiththegivenarrayvalues.
Whenconfigurationfilesforagivenscopegetmerged,arrayargumentswiththesamenamegetmergedintoanewarray.However,ifanynewconfigurationisloadedatalatertime,eitherbyamorespecificscopeorthroughthecode,thenanyarraydefinitionsinthenewconfigurationwillreplacetheloadedconfigurationinsteadofmerging.
Thelistofavailableitemtypevaluesgoeswellbeyondjustastring,andincludesthefollowing:
boolean
string
number
null
object
const
init_parameter
array
See<MAGENTO_DIR>/framework/Data/etc/argument/types.xsdand
![Page 51: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/51.jpg)
<MAGENTO_DIR>/framework/ObjectManager/etc/config.xsdforspecifictypedefinitions.
Argumentinjectionoftengoeshandinhandwithvirtualtypes,aswewillsoonsee.
![Page 52: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/52.jpg)
VirtualtypesVirtualtypesareaveryneatfeatureofMagentothatallowustochangetheargumentsofaspecificinjectabledependencyandthuschangethebehaviorofaparticularclasstype.
The<MAGENTO_DIR>/module-checkout/etc/di.xmlfileprovidesasimpleexampleofvirtualTypeanditsusage:
<virtualTypename="Magento\Checkout\Model\Session\Storage"type="Magento\Framework\Session\Storage">
<arguments>
<argumentname="namespace"xsi:type="string">checkout</argument>
</arguments>
</virtualType>
<typename="Magento\Checkout\Model\Session">
<arguments>
<argumentname="storage"xsi:type="object">Magento\Checkout\Model\Session\Storage</argument>
</arguments>
</type>
ThevirtualTypehere(virtually)extendsMagento\Framework\Session\Storagebyrewritingitsconstructor's$namespace='default'argumentto$namespace='checkout'.However,thischangedoesnotkickinonitsown,astheMagento\Checkout\Model\Session\Storagevirtualtypemustbeusedfirst.Itisusedinthiscase,viaatypedefinition,wherethestorageargumentisreplacedentirelybythevirtualtype.
MostofthevirtualTypenameattributesacrossMagentotaketheformofanon-existingfullyqualifiedclassname,thoughthiscanbeanycharactercombinationthat'sallowedinPHParraykeys.
Bydoingalookupforthe<virtualTypestringacrosstheentire<MAGENTO_DIR>directory'sdi.xmlfiles,wecanseethatMagentouseshundredsofvirtualtypesacrossthemajorityofitsmodules.
AmorecomplexexampleofvirtualtypeusagecanbefoundundertheMagento_LayeredNavigationmodule.
The<MAGENTO_DIR>/module-layered-navigation/etc/frontend/di.xmlfiledefinestwovirtualtypes,asfollows:
<virtualTypename="Magento\LayeredNavigation\Block\Navigation\Category"type="Magento\LayeredNavigation\Block\Navigation">
![Page 53: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/53.jpg)
<arguments>
<argumentname="filterList"xsi:type="object">categoryFilterList</argument>
</arguments>
</virtualType>
<virtualTypename="Magento\LayeredNavigation\Block\Navigation\Search"type="Magento\LayeredNavigation\Block\Navigation">
<arguments>
<argumentname="filterList"xsi:type="object">searchFilterList</argument>
</arguments>
</virtualType>
Here,wehavetwovirtualtypesdefined,eachchangingthefilterListargumentoftheMagento\LayeredNavigation\Block\Navigationclass.categoryFilterListandsearchFilterListarethenamesoftwoothervirtualtypesthataredefinedin<MAGENTO_DIR>/module-catalog-search/etc/di.xml,asvisiblehere:https://github.com/magento/magento2/blob/2.2/app/code/Magento/CatalogSearch/etc/di.xml.
TheMagento\LayeredNavigation\Block\Navigation\CategoryandMagento\LayeredNavigation\Block\Navigation\Searchvirtualtypesarethenusedinlayoutfilesforblockdefinition,asfollows:
<!--view/frontend/layout/catalog_category_view_type_layered.xml-->
<referenceContainername="sidebar.main">
<blockclass="Magento\LayeredNavigation\Block\Navigation\Category"...
</referenceContainer>
<!--view/frontend/layout/catalogsearch_result_index.xml-->
<referenceContainername="sidebar.main">
<blockclass="Magento\LayeredNavigation\Block\Navigation\Search"...
</referenceContainer>
WhatthiseffectivelydoesistellMagentothat,forthecategoryviewpageandsearchpage,usethevirtualtypeforclass,thusinstructingittogothroughalltheargumentchangesspecifiedinthevirtualtype.
Thisisaninterestingexampleasitrevealsthepotentialcomplexityofusingvirtualtypes.Basically,wehaveonevirtualtype(Magento\LayeredNavigation\Block\Navigation\Search)changingthesinglefilterListargumentofarealtype(Magento\LayeredNavigation\Block\Navigation)withanothervirtualtype(categoryFilterList).Likewise,wejustlearnedhowtheclasspropertyoftheblockelementiscapableofnotjustacceptingthefullyqualifiedclassname,butthevirtualTypenameaswell.
![Page 54: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/54.jpg)
ProxiesProxyclassesareusedwhenobjectcreationisexpensiveandaclass'constructorisunusuallyresource-intensive.Toavoidunnecessaryperformanceimpact,MagentousesProxyclassestoturngiventypesintobecominglazy-loadedversionsofthem.
Aquicklookupforthe\Proxy</argument>stringacrossallMagentodi.xmlfilesrevealsoverahundredoccurrencesofthisstring.ItgoestosaythatMagentoextensivelyusesproxiesacrossitscode.
Thetypedefinitionunder<MAGENTO_DIR>/module-customer/etc/di.xmlisaniceexampleofusingproxies:
<typename="Magento\Customer\Model\Session">
<arguments>
<argumentname="configShare"xsi:type="object">Magento\Customer\Model\Config\Share\Proxy</argument>
<argumentname="customerUrl"xsi:type="object">Magento\Customer\Model\Url\Proxy</argument>
<argumentname="customerResource"xsi:type="object">Magento\Customer\Model\ResourceModel\Customer\Proxy</argument>
<argumentname="storage"xsi:type="object">Magento\Customer\Model\Session\Storage</argument>
<argumentname="customerRepository"xsi:type="object">Magento\Customer\Api\CustomerRepositoryInterface\Proxy</argument>
</arguments>
</type>
IfwelookattheconstructoroftheMagento\Customer\Model\Sessiontype,wecanseethatnoneofthefourarguments(configShare,customerUrl,customerResource,andcustomerRepository)weredeclaredasProxywithinthePHPfile.Theywhererewrittenthroughdi.xml.TheseProxytypesdonotreallyexistjustyet,astheMagentodependencyinjection(di)compilationprocesscreatesthem.Theyareautomaticallygeneratedunderthegenerateddirectory.
Onceitiscompiled,theMagento\Customer\Model\Url\Proxytypecaneasilybefoundunderthegenerated/code/Magento/Customer/Model/Url/Proxy.phpfile.Let'stakeapartiallookatit:
classProxyextends\Magento\Customer\Model\Url
implements\Magento\Framework\ObjectManager\NoninterceptableInterface{
publicfunction__construct(
\Magento\Framework\ObjectManagerInterface$objectManager,
$instanceName='\\Magento\\Customer\\Model\\Url',
$shared=true){
$this->_objectManager=$objectManager;
$this->_instanceName=$instanceName;
![Page 55: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/55.jpg)
$this->_isShared=$shared;
}
publicfunction__sleep(){
return['_subject','_isShared','_instanceName'];
}
publicfunction__wakeup(){
$this->_objectManager=\Magento\Framework\App\ObjectManager::getInstance();
}
publicfunction__clone(){
$this->_subject=clone$this->_getSubject();
}
protectedfunction_getSubject(){
if(!$this->_subject){
$this->_subject=true===$this->_isShared
?$this->_objectManager->get($this->_instanceName)
:$this->_objectManager->create($this->_instanceName);
}
return$this->_subject;
}
publicfunctiongetLoginUrl(){
return$this->_getSubject()->getLoginUrl();
}
publicfunctiongetLoginUrlParams(){
return$this->_getSubject()->getLoginUrlParams();
}
}
ThecompositionoftheProxyclassshowsthemechanismbywhichitwrapsaroundtheoriginalMagento\Customer\Model\Urltype.Thisnowmeansthat,acrossMagento,everytimetheMagento\Customer\Model\Urltypeisrequested,theMagento\Customer\Model\Url\Proxyisgoingtogetpassedinstead.Unliketheoriginaltype's__constructmethodwhichmightbeperformanceheavy,theautogeneratedProxy's__constructmethodisalightweightone.Thiseliminatespossibleperformancebottlenecks.The_getSubjectmethodisusedtoinstantiate/lazyloadtheoriginaltypewheneveranyoftheoriginaltypepublicmethodsarecalled.Forexample,thecalltothegetLoginUrlmethodwouldgothroughtheproxy.
EveryproxygeneratedbyMagentoimplementsMagento\Framework\ObjectManager\NoninterceptableInterface.Thoughtheinterfaceitselfisempty,itisusedasamarkertoidentifyproxiesforwhichwedon'tneedtogenerateinterceptors(plugins).
Whenwritingcustomtypes,suchasMagelicious\Core\Model\Customer,wecouldeasilyspecifytheproxyrightthereintheconstructor:
![Page 56: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/56.jpg)
classCustomer{
publicfunction__construct(
\Magento\Customer\Model\Url\Proxy$customerUrl
){
//...
}
}
Thisapproach,however,isabadpractice.Thewaytodothisproperlyistospecify__constructwithanoriginalMagento\Customer\Model\Urltypeandthenaddthedi.xmlasfollows:
<typename="Magelicious\Core\Model\Customer">
<arguments>
<argumentname="customerUrl"xsi:type="object">Magento\Customer\Model\Url\Proxy</argument>
</arguments>
</type>
![Page 57: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/57.jpg)
FactoriesFactoriesareclassesthatcreateotherclasses—muchliketheobjectmanager,exceptthistimeweareencouragedtousethemdirectly.Theirpurposeistoinstantiatethenon-injectableclasses—thosethatweshouldnotinjectdirectlyinto__construct.Thebeautyofusingfactoriesisthat,mostofthetime,wedon'tevenhavetowritethem,astheyareautomaticallygeneratedbyMagentounlessweneedtoimplementsomesortofspecificbehaviorforourfactoryclasses.
BydoingalookupfortheFactory$stringacrosstheentire<MAGENTO_DIR>directory's*.phpfiles,wecanseethousandsoffactoryexamples,spreadacrossthemajorityofMagento'smodules.
Whileagreatdealofthesefactoriesactuallyexist,othersareautomaticallygeneratedwhenneeded.
Let'stakeaquicklookatoneautomaticallygeneratedfactory,thatofMagento\Newsletter\Model\SubscriberFactory,whichisusedinseveralMagentomodulessuchasthenewsletter,subscriber,andreviewmodules:
classSubscriberFactory{
protected$_objectManager=null;
protected$_instanceName=null;
publicfunction__construct(
\Magento\Framework\ObjectManagerInterface$objectManager,
$instanceName='\\Magento\\Newsletter\\Model\\Subscriber'
){
$this->_objectManager=$objectManager;
$this->_instanceName=$instanceName;
}
publicfunctioncreate(array$data=array()){
return$this->_objectManager->create($this->_instanceName,$data);
}
}
Theautogeneratedfactorycodeisessentiallyjustathinwrapperontopofanobjectmanagercreatemethod.
Factoriesworkwellwiththedi.xmlpreferencemechanism,whichmeanswecaneasilypassinterfacesintotheconstructor,likeso:
![Page 58: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/58.jpg)
publicfunction__construct(
\Magento\CatalogInventory\Api\StockItemRepositoryInterface$stockItemRepository,
\Magento\CatalogInventory\Api\StockItemCriteriaInterfaceFactory$stockItemCriteriaFactory
){
$this->stockItemRepository=$stockItemRepository;
$this->stockItemCriteriaFactory=$stockItemCriteriaFactory;
}
//$criteria=$this->stockItemCriteriaFactory->create();
//$result=$this->stockItemRepository->getList($criteria);
Thepreferencemechanismmakessurethatconcreteimplementationsgetpassedtotheobjectinstancewhenitsconstructorisinvoked.
Whileindevelopermode,Magentoperformsautomaticcompilation,meaningthatchangestodi.xmlareautomaticallypickedup.Sometimes,however,ifwestumbleuponunexpectedresults,runningthebin/magentosetup:di:compileconsolecommandorevenmanuallyclearingthegeneratedfolder(rm-rfgenerated/*)mighthelpsortouttheissues.
![Page 59: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/59.jpg)
PluginsPluginsarelikelyoneofthemostpowerfulfeaturesofMagento.Theyallowustomodifythebehaviorofpublicclassfunctionsbyinterceptingafunctioncallandrunningcodebefore,after,oraroundthatfunctioncall.
Beforeweeagerlystartusingthem,itisworthemphasizinghowpluginscan'tbeusedonthefollowing:
FinalmethodsFinalclassesNon-publicmethodsClassmethods(suchasstaticmethods)__construct
VirtualtypesObjectsthatareinstantiatedbeforeMagentoorFramework\Interceptionisbootstrapped
Pluginscanbeusedonthefollowing:
ClassesInterfacesAbstractclassesParentclasses
Bydoingalookupforthe<pluginstringacrosstheentire<MAGENTO_DIR>directory'sdi.xmlfiles,wecanseehundredsofpluginexamplesspreadacrossthemajorityofMagento'smodules.
![Page 60: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/60.jpg)
ThebeforepluginThebeforeplugin,asitsnamesuggests,runsbeforetheobservedmethod.
Whenwritingabeforeplugin,thereareafewkeypointstoremember:
1. Thebeforekeywordisappendedtotheobservedinstancemethod.IftheobservedmethodiscalledgetSomeValue,thenthepluginmethodiscalledbeforeGetSomeValue.
2. Thefirstparameterofthebeforepluginmethodisalwaystheobservedinstancetype,oftenabbreviatedas$subjectordirectlybytheclasstype–whichis$processorinourexample.Wecantypecastitforgreaterreadability.
3. Allotherparametersofthepluginmethodmustmatchtheparametersoftheobservedmethod.
4. Thepluginmethodmustreturnanarraywiththesametypeandnumberofparametersastheobservedmethod'sinputparameters.
Let'stakealookatoneofMagento'sbeforepluginimplementations,theonespecifiedinthe<MAGENTO_DIR>module-payment/etc/frontend/di.xmlfile:
<typename="Magento\Checkout\Block\Checkout\LayoutProcessor">
<pluginname="ProcessPaymentConfiguration"
type="Magento\Payment\Plugin\PaymentConfigurationProcess"/>
</type>
TheoriginalmethodthispluginistargetingistheprocessmethodoftheMagento\Checkout\Block\Checkout\LayoutProcessorclass:
publicfunctionprocess($jsLayout){
//Therestofthecode...
return$jsLayout;
}
TheimplementationofthebeforepluginisprovidedviathebeforeProcessmethodoftheMagento\Payment\Plugin\PaymentConfigurationProcessclass,asperthefollowingpartialexample:
publicfunctionbeforeProcess(
\Magento\Checkout\Block\Checkout\LayoutProcessor$processor,
$jsLayout){
//Therestofthecode...
return[$jsLayout];
![Page 61: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/61.jpg)
}
![Page 62: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/62.jpg)
ThearoundpluginThearoundpluginrunsaroundtheobservedmethodinawaythatallowsustorunsomecodebeforeandaftertheoriginalmethodcall.Thisisaverypowerfulconcept,aswegettochangetheincomingparametersaswellasthereturnvalueofafunction.
Whenwritingthearoundplugin,thereareafewkeypointstoremember:
1. Thefirstparametercomingintothepluginistheobservedtypeinstance.2. Thesecondparametercomingintothepluginisacallable/Closure.Usually,
thisparameteristypedandnamedascallable$proceed.Wemustmakesuretoforwardthesameparameterstothiscallableastheoriginalmethodsignature.
3. Allotherparametersareparametersoftheoriginalobservedmethod.4. Thepluginmustreturnthesamevalueastheoriginalfunction,ideallyreturn
$proceed(…)or$returnValue=$proceed();followedby$returnValue;forcaseswhereweneedtomodifythe$returnValue.
Let'stakealookatoneofMagento'saroundpluginimplementations,theonespecifiedinthe<MAGENTO_DIR>module-grouped-product/etc/di.xmlfile:
<typename="Magento\Catalog\Model\ResourceModel\Product\Link">
<pluginname="groupedProductLinkProcessor"type="Magento\GroupedProduct\Model\ResourceModel\Product\Link\RelationPersister"/>
</type>
TheoriginalmethodofthispluginistargetingthedeleteProductLinkmethodoftheMagento\Catalog\Model\ResourceModel\Product\Linkclass:
publicfunctiondeleteProductLink($linkId){
return$this->getConnection()
->delete($this->getMainTable(),['link_id=?'=>$linkId]);
}
TheimplementationofthearoundpluginisprovidedviathearoundDeleteProductLinkmethodoftheMagento\GroupedProduct\Model\ResourceModel\Product\Link\RelationPersisterclass,asperthefollowingpartialexample:
publicfunctionaroundDeleteProductLink(
\Magento\GroupedProduct\Model\ResourceModel\Product\Link$subject,
\Closure$proceed,$linkId){
![Page 63: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/63.jpg)
//Therestofthecode...
$result=$proceed($linkId);
//Therestofthecode...
return$result;
}
![Page 64: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/64.jpg)
TheafterpluginTheafterplugin,asitsnamesuggests,runsaftertheobservedmethod.
Whenwritingtheafterplugin,thereareafewkeypointstoremember:
1. Thefirstparametercomingintothepluginisanobservedtypeinstance.2. Thesecondparametercomingintothepluginistheresultoftheobserved
method,oftencalled$resultorcalledafterthevariablereturnedfromtheobservedmethod(asinthefollowingexample:$data).
3. Allotherparametersareparametersoftheobservedmethod.4. Thepluginmustreturnthesame$result|$datavariableofthesametype,as
wearefreetomodifythevalue.
Let'stakealookatoneofMagento'safterpluginimplementations,theonespecifiedinthemodule-catalog/etc/di.xmlfile:
<typename="Magento\Indexer\Model\Config\Data">
<pluginname="indexerProductFlatConfigGet"
type="Magento\Catalog\Model\Indexer\Product\Flat\Plugin\IndexerConfigData"/>
</type>
TheoriginalmethodthispluginistargetingisthegetmethodoftheMagento\Indexer\Model\Config\Dataclass:
publicfunctionget($path=null,$default=null){
//Therestofthecode...
return$data;
}
TheimplementationoftheafterpluginisprovidedviatheafterGetmethodoftheMagento\Catalog\Model\Indexer\Product\Flat\Plugin\IndexerConfigDataclass,asperthefollowingpartialexample:
publicfunctionafterGet(Magento\Indexer\Model\Config\Data,$data,$path=null,$default=null){
//Therestofthecode...
return$data;
}
Specialcareshouldbetakenwhenusingplugins.Whiletheyprovidegreatflexibility,theyalsomakeiteasytoinducebugs,performancebottlenecks,andotherlessobvioustypesofinstabilities–evenmoresoifseveralpluginsare
![Page 65: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/65.jpg)
observingthesamemethod.
![Page 66: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/66.jpg)
EventsandobserversMagentohasaneatpublish-subscribepatternimplementationthatwecalleventsandobservers.Bydispatchingeventswhencertainactionsaretriggered,wecanrunourcustomcodeinresponsetothetriggeredevent.TheeventsaredispatchedusingtheMagento\Framework\Event\Managerclass,whichimplementsMagento\Framework\Event\ManagerInterface.
Todispatchanevent,wesimplycallthedispatchmethodoftheeventmanagerinstance,providingitwiththenameoftheeventwearedispatchingwithanoptionalarrayofdatawewishtopassontotheobservers,asperthefollowingexampletakenfromthe<MAGENTO_DIR>/module-customer/Controller/Account/CreatePost.phpfile:
$this->_eventManager->dispatch(
'customer_register_success',
['account_controller'=>$this,'customer'=>$customer]
);
Observersareregisteredviaanevents.xmlfile,asperthefollowingexamplefromthe<MAGENTO_DIR>/module-persistent/etc/frontend/events.xmlfile:
<eventname="customer_register_success">
<observername="persistent"instance="Magento\Persistent\Observer\RemovePersistentCookieOnRegisterObserver"/>
</event>
BydoingalookupfortheeventManager->dispatchstringacrosstheentire<MAGENTO_DIR>directory's*.phpfiles,wecanseehundredsofeventsexamples,spreadacrossthemajorityofMagento'smodules.Whilealloftheseeventsareofthesametechnicalimportance,wemightsaythatsomearelikelytobeusedmoreonadaytodaybasisthanothers.
Thismakesitworthtakingsometimetostudythefollowingclassesandtheeventstheydispatch:
TheMagento\Framework\App\Action\Actionclass,withthefollowingevents:controller_action_predispatch
'controller_action_predispatch_'.$request->getRouteName()
'controller_action_predispatch_'.$request->getFullActionName()
![Page 67: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/67.jpg)
'controller_action_postdispatch_'.$request->getFullActionName()
'controller_action_postdispatch_'.$request->getRouteName()
controller_action_postdispatch
TheMagento\Framework\Model\AbstractModelclass,withthefollowingevents:model_load_before
$this->_eventPrefix.'_load_before'
model_load_after
$this->_eventPrefix.'_load_after'
model_save_commit_after
$this->_eventPrefix.'_save_commit_after'
model_save_before
$this->_eventPrefix.'_save_before'
model_save_after
clean_cache_by_tags
$this->_eventPrefix.'_save_after'
model_delete_before
$this->_eventPrefix.'_delete_before'
model_delete_after
clean_cache_by_tags
$this->_eventPrefix.'_delete_after'
model_delete_commit_after
$this->_eventPrefix.'_delete_commit_after'
$this->_eventPrefix.'_clear'
TheMagento\Framework\Model\ResourceModel\Db\Collectionclass,withthefollowingevents:
core_collection_abstract_load_before
$this->_eventPrefix.'_load_before'
core_collection_abstract_load_after
$this->_eventPrefix.'_load_after'
Somemoreimportanteventscanbefoundinafewofthetypesdefinedunderthe<MAGENTO_DIR>/framework/Viewdirectory:
view_block_abstract_to_html_before
view_block_abstract_to_html_after
view_message_block_render_grouped_html_after
layout_render_before
'layout_render_before_'.$this->request->getFullActionName()
core_layout_block_create_after
![Page 68: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/68.jpg)
layout_load_before
layout_generate_blocks_before
layout_generate_blocks_after
core_layout_render_element
Let'stakeacloserlookatoneoftheseevents,theonefoundinthe<MAGENTO_DIR>/framework/Model/AbstractModel.phpfile:
publicfunctionafterCommitCallback(){
$this->_eventManager->dispatch('model_save_commit_after',['object'=>$this]);
$this->_eventManager->dispatch($this->_eventPrefix.'_save_commit_after',$this->_getEventData());
return$this;
}
protectedfunction_getEventData(){
return[
'data_object'=>$this,
$this->_eventObject=>$this,
];
}
The$_eventPrefixand$_eventObjecttypepropertiesareparticularlyimportanthere.IfweglimpseovertypessuchasMagento\Catalog\Model\Product,Magento\Catalog\Model\Category,Magento\Customer\Model\Customer,Magento\Quote\Model\Quote,Magento\Sales\Model\Order,andothers,wecanseethatagreatdealoftheseentitytypesareessentiallyextendingfromMagento\Framework\Model\AbstractModelandprovidetheirownvaluestoreplace$_eventPrefix='core_abstract'and$_eventObject='object'.Whatthismeansisthatwecanuseeventssuchas$this->_eventPrefix.'_save_commit_after'tospecifyobserversviaevents.xml.
Let'stakealookatthefollowingexample,takenfromthe<MAGENTO_DIR>/module-downloadable/etc/events.xmlfile:
<config>
<eventname="sales_order_save_commit_after">
<observername="downloadable_observer"instance="Magento\Downloadable\Observer\SetLinkStatusObserver"/>
</event>
</config>
Observersareplacedinsidethe<ModuleDir>/Observerdirectory.EveryobserverimplementsasingleexecutemethodontheMagento\Framework\Event\ObserverInterfaceclass:
classSetLinkStatusObserverimplements\Magento\Framework\Event\ObserverInterface{
publicfunctionexecute(\Magento\Framework\Event\Observer$observer){
$order=$observer->getEvent()->getOrder();
}
}
![Page 69: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/69.jpg)
Muchlikeplugins,badlyimplementedobserverscaneasilycausebugsorevenbreaktheentireapplication.Thisiswhyweneedtokeepourobserversmallandcomputationallyefficient—toavoidperformancebottlenecks.
Thecyclicaleventloopisatrapthat'seasytofallinto.Thishappenswhenanobserver,atsomepoint,isdispatchingthesameeventthatitlistensto.Forexample,ifanobserverlistenstothemodel_save_beforeevent,andthentriestosavethesameentityagainwithintheobserver,thiswouldtriggeracyclicaleventloop.
Tomakeourobserversasspecificaspossible,weneedtodeclaretheminanappropriatescope:
Forobservingfrontendonlyevents,youcandeclareobserversin<ModuleDir>/etc/frontend/events.xml
Forobservingbackendonlyevents,youcandeclareobserversin<ModuleDir>/etc/adminhtml/events.xml
Forobservingglobalevents,youcandeclareobserversin<ModuleDir>/etc/events.xml
Unlikeplugins,observersareusedfortriggeringthefollow-upfunctionality,ratherthanchangingthebehavioroffunctionsordatawhichispartoftheeventtheyareobserving.
![Page 70: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/70.jpg)
ConsolecommandsThebuilt-inbin/magentotoolplaysamajorrole–notjustinMagentodevelopment,butinproductiondeploymentsaswell.
Rightoutofthebox,itprovidesadozencommandsthatwecanusetomanagecaches,indexers,dependencycompilation,deployingstaticviewfiles,creatingCSSfromLESS,puttingourstoretomaintenance,installingmodules,andmore.
Quiteeasily,MagentoenablesustoaddourowncommandstoitsSymfony-likecommand-lineinterface(CLI).TheMagentoCLIessentiallyextendsfromSymfony\Component\Console\Command.
Therealvalueincreatingourowncommandliesintheargumentsandoptionsthatwecanmakeavailable,thuspassingdynamicinformationtothecommand.
Magentoconsolecommandsresideunderthe<ModuleName>/Consoledirectory,whichcanfurtherbeorganizedtobetteraccommodateourcommands.Magentomostlyusesthe<ModuleName>/Console/CommanddirectorytoplacetheactualCLIcommandclass,whereasvariousoptionsandotheraccompanyingclassesresideinthe<ModuleName>/Consoledirectory.
Conceptually,creatinganewCLIcommandisaseasyasdoingthefollowing:
1. Creatingthecommandclass2. Wiringitupviadi.xml3. Clearingthecacheandcompileddirectories
Let'screateourownsimpleconsolecommand.Wewillstartoffbycreatingthe<MAGELICIOUS_DIR>/Core/Console/Command/RunStockImportCommand.phpfilewiththefollowingcontent:
useSymfony\Component\Console\Command\Command;
useSymfony\Component\Console\Input\InputArgument;
useSymfony\Component\Console\Input\InputOption;
useSymfony\Component\Console\Input\InputInterface;
useSymfony\Component\Console\Output\OutputInterface;
classRunStockImportCommandextendsCommand{
constORDER_ID_ARGUMENT='order_id';
![Page 71: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/71.jpg)
constDAYS_BACK_OPTION='days_back';
protectedfunctionconfigure(){
$this->setName('magelicious:stock:import')
->setDescription('TheMageliciousStockImport.')
->setDefinition([
newInputArgument(
self::ORDER_ID_ARGUMENT,/*name*/
InputArgument::REQUIRED,/*modeREQUIREDorOPTIONAL*/
'Theargumenttoset.',/*description*/
null/*default*/
),
newInputOption(
self::DAYS_BACK_OPTION,/*name*/
null,/*shortcut*/
InputOption::VALUE_OPTIONAL,/*VALUE_NONEorVALUE_REQUIREDorVALUE_OPTIONALorVALUE_IS_ARRAY*/
'Theoptiontoset.'/*description*/
)
]);
parent::configure();
}
protectedfunctionexecute(InputInterface$input,OutputInterface$output){
try{
$output->setDecorated(true);
//$input->getArgument(self::ORDER_ID_ARGUMENT);
//$input->getOption(self::DAYS_BACK_OPTION);
//greentext
$output->writeln('<info>Theinfomessage.</info>');
//yellowtext
$output->writeln('<comment>Thecommentmessage.</comment>');
//blacktextonacyanbackground
$output->writeln('<question>Thequestionmessage.</question>');
return\Magento\Framework\Console\Cli::RETURN_SUCCESS;
}catch(\Exception$e){
//whitetextonaredbackground
$output->writeln('<error>'.$e->getMessage().'</error>');
if($output->getVerbosity()>=OutputInterface::VERBOSITY_VERBOSE){
$output->writeln($e->getTraceAsString());
}
return\Magento\Framework\Console\Cli::RETURN_FAILURE;
}
}
}
Wethenwireitupvia<MAGELICIOUS_DIR>/etc/di.xml,asfollows:
<typename="Magento\Framework\Console\CommandListInterface">
<arguments>
<argumentname="commands"xsi:type="array">
<itemname="runStockImport"xsi:type="object">Magelicious\Core\Console\Command\RunStockImportCommand</item>
</argument>
</arguments>
</type>
Wecannowclearthecacheandthecompileddirectorieseitherbyrunningthephpbin/magentocache:cleanconfigfollowedbyphpbin/magentosetup:di:compile,orbyrunningrm-rfgenerated/*andrm-rfvar/cache/*.
![Page 72: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/72.jpg)
Now,ifwerunthephpbin/magentocommand,weshouldseeourcommandonthelist:
magelicious
magelicious:stock:importTheMageliciousStockImport.
Ifwenowtestourmethodbyrunningphpbin/magentomagelicious:stock:import,thisshouldimmediatelytriggeranerror,asfollows:
[Symfony\Component\Console\Exception\RuntimeException]
Notenougharguments(missing:"order_id").
magelicious:stock:import[--days_back[DAYS_BACK]][--]<order_id>
Eitherofthefollowingcallsshouldwork:
phpbin/magentomagelicious:stock:import000000060
phpbin/magentomagelicious:stock:import000000060--days_back=7
![Page 73: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/73.jpg)
CronjobsCreatinganewcronjobisaseasyasdoingthefollowing:
1. Creatingajobdefinitionunderthe<ModuleName>/etc/crontab.xmlfile2. Creatingaclasswithapublicmethodthathandlesthejobexecution
Let'screateasimplecronjob.Wewillstartoffbycreatingthe<MAGELICIOUS_DIR>/Core/etc/crontab.xmlfilewiththefollowingcontent:
<groupid="default">
<jobname="the_job"instance="Magelicious\Core\Cron\TheJob"method="execute">
<schedule>*/15****</schedule>
</job>
</group>
Theinstanceandmethodvaluesmaptotheclassandmethodwithinthatclass,whichwillbeexecutedwhencronjobisrun.Thescheduleisacron,liketheexpressionforwhenthejobistobeexecuted.Unlesstherearespecificrequirementstellingusotherwise,wecansafelyusethedefaultgroup.
Wethencreatethe<MAGELICIOUS_DIR>/Core/Cron/TheJob.phpfilewiththefollowingcontent:
classTheJob{
publicfunctionexecute(){
//...
}
}
TheMagentoconsolecommandsupportsseveralconsolecommands:
cron
cron:installGeneratesandinstallscrontabforcurrentuser
cron:removeRemovestasksfromcrontab
cron:runRunsjobsbyschedule
Togetourcronjobrunning,weneedtomakesurethatcrontabisinstalled,byrunningphpbin/magentocron:install.Thiscommandgeneratesandinstallscrontabforthecurrentuser.Wecanconfirmthatbyfollowingupwiththecrontab-ecommand,likeso:
#~MAGENTOSTART6f7c468a10aea2972eab1da53c8d2fce
![Page 74: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/74.jpg)
*****/bin/php/magelicious/bin/magentocron:run2>&1|grep-v"Ranjobsbyschedule">>/magelicious/var/log/magento.cron.log
*****/bin/php/magelicious/update/cron.php>>/magelicious/var/log/update.cron.log
*****/bin/php/magelicious/bin/magentosetup:cron:run>>/magelicious/var/log/setup.cron.log
#~MAGENTOEND6f7c468a10aea2972eab1da53c8d2fce
Now,ifweexecutephpbin/magentocron:run,the_jobshouldfinditswayunderthecron_scheduletable.
Dependingontheschedule_generate_everyandschedule_ahead_foroptionsforaparticularcrongroup,wemightnotseesomecronjobsinstantlyshowingupinthecron_scheduletable.
MagentoOpenSourceprovidestwocrongroups:defaultandindex.Whilethemajorityoftimesourcronjobswillbeplacedunderthedefaultgroup,theremightbeaneedtocreateacompletelynewcrongroup.Luckily,thisisquiteeasy.
Tocreateanewcrongroup,allweneedisa<MAGELICIOUS_DIR>/etc/cron_groups.xmlfilewiththefollowingcontent:
<config>
<groupid="magelicious">
<schedule_generate_every>15</schedule_generate_every>
<schedule_ahead_for>20</schedule_ahead_for>
<schedule_lifetime>15</schedule_lifetime>
<history_cleanup_every>10</history_cleanup_every>
<history_success_lifetime>10080</history_success_lifetime>
<history_failure_lifetime>10080</history_failure_lifetime>
<use_separate_process>0</use_separate_process>
</group>
</config>
Whilegroupinformationisnotstoredinthecron_scheduletable,wecanuseitviatheMagentoCLItorunjobsthatarespecifictoacertaingroup:
phpbin/magentocron:run--group=default
![Page 75: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/75.jpg)
SummaryInthischapter,wetoucheduponsomeofMagento'skeyscomponents.PluginsandeventobserversprovideapowerfulwayofextendingMagento,eitherbychangingthebehaviorofexistingfunctionsorbyrunningsomefollow-upcodeinresponsetocertainevents.
Movingforward,wewilldeepenourMagentoknowledgefurtherbylookingintotheinstallandupdatescripts,theEntity–Attribute–Valuemodel(EAV),creatingnewEAVtypes,indexers,extension,andcustomattributes.
![Page 76: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/76.jpg)
WorkingwithEntitiesEveryMagentomodulehostsitsmodelswithintheModelsdirectory.Someofthesemodelsarepersistable,whileothersarenon-persistable.Agreatdealofcustom,third-party,andcoreMagentomodulespersistdatatothedatabase.DatapersistenceisoneofthekeyfunctionalitiesthatplatformslikeMagentoneedtodealwith.Terminology-wise,Magentousestermssuchasmodel,resourcemodel,andcollectionforagroupofthreeclassesthatdealwithdatapersistence,thatis,create,read,update,anddelete(CRUD)operations.
Tobetterunderstandtheoverallmechanismofentities,wearegoingtotakeacloserlookatthefollowing:
Understandingtypesofmodels:CreatingasimplemodelMethodsworthmemorizing
Workingwithsetupscripts:TheInstallSchemascriptTheUpgradeSchemascriptTheRecurringscriptTheInstallDatascriptTheUpgradeDatascriptTheRecurringDatascript
Creatingextensionattributes
![Page 77: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/77.jpg)
TechnicalrequirementsYouwillneedtohavebasicknowledgeofPHP,OOP,JavaScript,andXML.YouwillalsoneedApache,MySQL,andAMPPSinstalledonyoursystemtoexecutethecodes.
ThecodefilesofthischaptercanbefoundonGitHub:https://github.com/PacktPublishing/Magento-2-Quick-Start-Guide.
CheckoutthefollowingvideotoseetheCodeinAction:
http://bit.ly/2PKYvUx.
![Page 78: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/78.jpg)
UnderstandingtypesofmodelsTherearetwotypesofpersistencemodelsinMagento:simpleandEntity–attribute–value(EAV).Thetermentityistossedaroundinterchangeablybetweenthetwotypesofmodels.Wecanthinkofanentityasanypersistablemodel.
TheSubscriberentityoftheMagento_Newslettermoduleisanexampleofasimplemodel.Wecanseethatit'scomprisedofthefollowing:
AmodeloftypeMagento\Newsletter\Model\SubscriberextendsMagento\Framework\Model\AbstractModel
AresourcemodeloftypeMagento\Newsletter\Model\ResourceModel\SubscriberextendsMagento\Framework\Model\ResourceModel\Db\AbstractDbAcollectionoftypeMagento\Newsletter\Model\ResourceModel\Subscriber\CollectionextendsMagento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection
TheCustomerentityoftheMagento_CustomermoduleisanexampleofanEAVmodel.Wecanseethatit'scomprisedofthefollowing:
AmodeloftypeMagento\Customer\Model\CustomerextendsMagento\Framework\Model\AbstractModelAresourcemodeloftypeMagento\Customer\Model\ResourceModel\CustomerextendsMagento\Eav\Model\Entity\VersionControl\AbstractEntityAcollectionoftypeMagento\Customer\Model\ResourceModel\Customer\CollectionextendsMagento\Eav\Model\Entity\Collection\VersionControl\AbstractCollection
WhatdifferentiatesEAVfromsimplemodelsisessentiallytheirresourcemodelandcollectionclasses.Theresourcemodelisourlinktothedatabase—ourpersistor,ifyouwill.
Whenasubscriberissaved,itsdatagetssavedhorizontallyinthedatabase.Datafromthesubscribermodelgetsdumpedintothesinglenewsletter_subscribertable.
Whenacustomerissaved,itsdatagetssavedverticallyinthedatabase.Data
![Page 79: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/79.jpg)
fromthecustomermodelgetsdumpedintothefollowingtables:
customer_entity
customer_entity_datetime
customer_entity_decimal
customer_entity_int
customer_entity_text
customer_entity_varchar
Thedecisionastowheretostoreavalueforanindividualattributeiscontainedintheeav_attribute.backend_typecolumn.TheSELECTDISTINCTbackend_typeFROMeav_attribute;queryrevealsthefollowing:
Thestaticattributevaluegetsstoredinthe<entityName>_entitytableThevarcharattributevaluegetsstoredinthe<entityName>_entity_varchartableTheintattributevaluegetsstoredinthe<entityName>_entity_inttableThetextattributevaluegetsstoredinthe<entityName>_entity_texttableThedatetimeattributevaluegetsstoredinthe<entityName>_entity_datetimetableThedecimalattributevaluegetsstoredinthe<entityName>_entity_decimaltable
Nexttotheeav_attributetable,theremainingrelevantinformationisscatteredaroundthedozenofothereav_*tables,themostimportantbeingtheeav_attribute_*tables:
eav_attribute
eav_attribute_group
eav_attribute_label
eav_attribute_option
eav_attribute_option_swatch
eav_attribute_option_value
eav_attribute_set
TheSELECTentity_type_code,entity_modelFROMeav_entity_type;queryindicatesthatthefollowingMagentoentitiesarefromanEAVmodel:
customer:Magento\Customer\Model\ResourceModel\Customercustomer_address:Magento\Customer\Model\ResourceModel\Addresscatalog_category:Magento\Catalog\Model\ResourceModel\Categorycatalog_product:Magento\Catalog\Model\ResourceModel\Product
![Page 80: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/80.jpg)
order:Magento\Sales\Model\ResourceModel\Orderinvoice:Magento\Sales\Model\ResourceModel\Order\Invoicecreditmemo:Magento\Sales\Model\ResourceModel\Order\Creditmemoshipment:Magento\Sales\Model\ResourceModel\Order\Shipment
However,notallofthemusetheEAVmodeltoitsfullextent,asindicatedbytheSELECTDISTINCTentity_type_idFROMeav_attribute;query,whichpointsonlytothefollowing:
customer
customer_address
catalog_category
catalog_product
WhatthismeansisthatonlyfourmodelsinMagentoOpenSourcereallyuseEAVmodelsformanagingtheirattributesandstoringdataverticallythroughEAVtables.Therestareallflattables,asallattributesandtheirvaluesareinasingletable.
TheEAVmodelsareinherentlymorecomplextoworkwith.Theycomeinhandyforcaseswheredynamicattributecreationisneeded,ideallyviaanadmininterface,asisthecasewithproducts.Themajorityofthetime,however,simplemodelswilldothejob.
![Page 81: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/81.jpg)
CreatingasimplemodelUnlikeEAVmodels,creatingsimplemodelsisprettystraightforward.Let'sgoaheadandcreateamodel,resourcemodel,andacollectionforaLogentity.
Wewillstartoffbycreatingthe<MAGELICIOUS_DIR>/Core/Model/Log.phpfilewiththefollowingcontent:
classLogextends\Magento\Framework\Model\AbstractModel{
protected$_eventPrefix='magelicious_core_log';
protected$_eventObject='log';
protectedfunction_construct(){
$this->_init(\Magelicious\Core\Model\ResourceModel\Log::class);
}
}
Theuseof$_eventPrefixand$_eventObjectisnotmandatory,butitishighlyrecommended.ThesevaluesareusedbytheMagento\Framework\Model\AbstractModeleventdispatcherandaddtothefutureextensibilityofourmodule.WhileMagentousesthe<ModuleName>_<ModelName>conventionfor$_eventPrefixnaming,wemightbesaferusing<VendorName>_<ModuleName>_<ModelName>.The$_eventObject,byconvention,usuallybearsthenameofthemodelitself.
Wethencreatethe<MAGELICIOUS_DIR>/Core/Model/ResourceModel/Log.phpfilewiththefollowingcontent:
classLogextends\Magento\Framework\Model\ResourceModel\Db\AbstractDb{
protectedfunction_construct(){
$this->_init('magelicious_core_log','entity_id');
}
}
The_initmethodheretakestwoarguments:themagelicious_core_logvalueforthe$mainTableargumentandtheentity_idvalueforthe$idFieldNameargument.The$idFieldNameisthenameoftheprimarycolumninthedesignateddatabase.It'sworthnotingthatthemagelicious_core_logtablestilldoesn'texist,butwewilladdressthatinabit.
Wewillthencreatethe<MAGELICIOUS_DIR>/Core/Model/ResourceModel/Log/Collection.phpfilewiththefollowingcontent:
![Page 82: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/82.jpg)
classCollectionextends\Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection{
protectedfunction_construct(){
$this->_init(
\Magelicious\Core\Model\Log::class,
\Magelicious\Core\Model\ResourceModel\Log::class
);
}
}
The_initmethodheretakestwoarguments:thestringnamesof$modeland$resourceModel.Magentousesthe<FULLY_QUALIFIED_CLASS_NAME>::classsyntaxforthis,asitusesaniftysolutioninsteadofpassingclassstringsaround.
![Page 83: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/83.jpg)
MethodsworthmemorizingBothEAVandsimplemodelsextendfromtheMagento\Framework\Model\AbstractModelclass,whichfurtherextendsfromMagento\Framework\DataObject.TheDataObjecthassomeneatmethodsworthmemorizing.
Groupofthefollowingmethodsdealwithdatatransformation:
toArray:Convertsanarrayofobjectdatatoanarraywithkeysrequestedinthe$keysarraytoXml:ConvertsobjectdataintoanXMLstringtoJson:ConvertsobjectdatatoJSONtoString:Convertsobjectdataintoastringwithapredefinedformatserialize:Convertsobjectdataintoastringwithdefinedkeysandvalues
Theothergroupsofthesemethods,implementedthroughthemagic__callmethod,enablesthefollowingneatsyntax:
get<AttributeName>,forexample,$object->getPackagingOption()set<AttributeName>,forexample,$object->setPackagingOption('plastic_bag')uns<AttributeName>,forexample,$object->unsPackagingOption()has<AttributeName>,forexample,$object->hasPackagingOption()
Toquicklyputthismagicintoperspective,let'smanuallycreatethemagelicious_core_logtableasfollows:
CREATETABLE`magelicious_core_log`(
`entity_id`int(10)unsignedNOTNULLAUTO_INCREMENT,
`severity_level`varchar(24)NOTNULL,
`note`textNOTNULL,
`created_at`timestampNOTNULLDEFAULTCURRENT_TIMESTAMPONUPDATECURRENT_TIMESTAMP,
PRIMARYKEY(`entity_id`)
)ENGINE=InnoDBDEFAULTCHARSET=utf8;
WiththemagicofDataObject,ouremptyMagelicious\Core\Model\Logmodelwillstillbeabletosaveitsdata,asfollows:
$log->setCreatedAt(new\DateTime());
$log->setSeverityLevel('info');
$log->setNote('JustSomeNote');
$log->save();
![Page 84: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/84.jpg)
Whilethisexamplewouldwork,thereisfarmoretoitthanthis.Creatingtablesmanuallyisnotaviableoptionforbuildingmodules.Magentohasjusttherightmechanismforthis,whichiscalledsetupscripts.
![Page 85: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/85.jpg)
WorkingwithsetupscriptsEverytimeamoduleisinstalledviaaphpbin/magentomodule:enablecommand,Magentoshowsthefollowingmessage:Tomakesurethattheenabledmodulesareproperlyregistered,run'setup:upgrade'.Thephpbin/magentosetup:upgradecommandupgradestheMagentoapplication,databasedata,andschema.Oncetriggered,theupgradecommandinstantiatesMagento\Setup\Model\Installer,whichthengoesthroughaseriesofmethods.ItsgetSchemaDataHandlermethodrevealsthetypesofavailablesetupscripts:
InstallSchema.php
UpgradeSchema.php
Recurring.php
InstallData.php
UpgradeData.php
RecurringData.php
Thesescriptsliveunderthe<VendorName>/<ModuleName>/Setupdirectory.
Oncesuccessfullyfinished,thesetup:upgradecommandmakesanewentry,orupdatesanexistingone,inthesetup_moduletable.There,wecanseetheschema_versionanddata_versionvaluesloggedagainsteachmodule.
Whentestingoutsetupscripts,wecanmanuallydeleteandadjustourmoduleentriesunderthesetup_moduletabletotriggerindividualtypeofsetupscript.Forexample,wecanleaveschema_versionasis,whilechangingthedata_version.
Let'stakeacloserlookatwritingeachofthosescripts.
![Page 86: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/86.jpg)
TheInstallSchemascriptTheInstallSchemascriptisusedwhenwewishtoaddnewcolumnstoexistingtablesorcreatenewtables.Thisscriptisrunonlywhenamoduleisenabled.Onceenabled,themodulegetsacorrespondingentryunderthesetup_module.schema_versiontablecolumn.ThisentrypreventstheInstallSchemascriptrunningonanysubsequentsetup:upgradecommandwherethemodule'ssetup_versionremainsthesame.
Let'sgoaheadandcreatethe<MAGELICIOUS_DIR>/Core/Setup/InstallSchema.phpfilewiththefollowingcontent:
use\Magento\Framework\Setup\InstallSchemaInterface;
useMagento\Framework\Setup\ModuleContextInterface;
useMagento\Framework\Setup\SchemaSetupInterface;
classInstallSchemaimplementsInstallSchemaInterface{
publicfunctioninstall(SchemaSetupInterface$setup,ModuleContextInterface$context){
$setup->startSetup();
echo'InstallSchema->install()'.PHP_EOL;
$setup->endSetup();
}
}
Theuseof$setup->startSetup();and$setup->endSetup();isacommonpracticeamongthemajorityofsetupscripts.Theimplementationofthesetwomethodsdealswithrunningadditionalenvironmentsetupsteps,suchassettingSQL_MODEandFOREIGN_KEY_CHECKS,ascanbeseenunderMagento\Framework\DB\Adapter\Pdo\Mysql.
Tomakesomethingusefuloutofit,let'sgoaheadandreplacetheecholinewiththecodethatactuallycreatesourmagelicious_core_logtable:
$table=$setup->getConnection()
->newTable($setup->getTable('magelicious_core_log'))
->addColumn(
'entity_id',
\Magento\Framework\DB\Ddl\Table::TYPE_INTEGER,
null,
['identity'=>true,'unsigned'=>true,'nullable'=>false,'primary'=>true],
'EntityID'
)->addColumn(
'severity_level',
\Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
24,
['nullable'=>false],
'SeverityLevel'
![Page 87: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/87.jpg)
)->addColumn(
'note',
\Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
null,
['nullable'=>false],
'Note'
)->addColumn(
'created_at',
\Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP,
null,
['nullable'=>false],
'CreatedAt'
)->setComment('MageliciousCoreLogTable');
$setup->getConnection()->createTable($table);
$setup->getConnection()getsusthedatabaseadapterinstance.Fromthereon,wegetaccesstomethodsthatareneededfordatabasetablecreation.WhenitcomestoInstallSchemascripts,themajorityofthetime,thefollowingmethodswilldothejob:
newTable:RetrievesaDDLobjectforthenewtableaddColumn:AddscolumnstothetableaddIndex:AddsanindextothetableaddForeignKey:AddsaforeignkeytothetablesetComment:SetsacommentforthetablecreateTable:CreatesatablefromaDDLobject
Themagelicious_core_logtablehereisessentiallystoragebehindourMagelicious\Core\Model\Logsimplemodel.IfourmodelwasanEAVmodel,wewouldbeusingthesameInstallSchemascripttocreatetablessuchasthefollowing:
log_entity
log_entity_datetime
log_entity_decimal
log_entity_int
log_entity_text
log_entity_varchar
However,inthecaseoftheEAVmodel,theactualattributesseverity_levelandnotewouldthenlikelybeaddedviaanInstallDatascript.Thisisbecauseattributesdefinitionsareessentiallydataundertheeav_attribute_*tables—primarilytheeav_attributetable.Therefore,attributesarecreatedinsideoftheInstallDataandUpgradeDatascripts.
![Page 88: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/88.jpg)
TheUpgradeSchemascriptTheUpgradeSchemascriptisusedwhenwewishtocreatenewtablesoraddcolumnstoexistingtables.Giventhatitisrunoneverysetup:upgrade,wheresetup_module.schema_versionislowerthansetup_versionunder<VendorName>/<ModuleName>/etc/module.xml,weareinchargeofcontrollingthecodeforaspecificversion.Thisisusuallydoneviatheif-edversion_compareapproach.
Tobetterdemonstratethis,let'screatethe<MAGELICIOUS_DIR>/Core/Setup/UpgradeSchema.phpfilewiththefollowingcontent:
use\Magento\Framework\Setup\UpgradeSchemaInterface;
useMagento\Framework\Setup\ModuleContextInterface;
useMagento\Framework\Setup\SchemaSetupInterface;
classUpgradeSchemaimplementsUpgradeSchemaInterface{
publicfunctionupgrade(SchemaSetupInterface$setup,ModuleContextInterface$context){
$setup->startSetup();
if(version_compare($context->getVersion(),'2.0.2')<0){
$this->upgradeToVersionTwoZeroTwo($setup);
}
$setup->endSetup();
}
privatefunctionupgradeToVersionTwoZeroTwo(SchemaSetupInterface$setup){
echo'UpgradeSchema->upgradeToVersionTwoZeroTwo()'.PHP_EOL;
}
}
Theif-edversion_compareherereadsasfollows:ifthecurrentmoduleversionisequalto2.0.2,thenexecutetheupgradeToVersionTwoZeroTwomethod.Ifweweretoreleaseanupdatedversionofourmodule,wewouldneedtoproperlybumpupthesetup_versionof<VendorName>/<ModuleName>/etc/module.xml,orelseUpgradeSchemadoesnotmakealotofsense.Likewise,weshouldalwaysbesuretotargetaspecificmoduleversion,thusavoidingcodethatexecutesoneveryversionchange.
WhenitcomestoUpgradeSchemascripts,thefollowingmethodsofadatabaseadapterinstance,alongsidethepreviouslymentionedone,willbeofinterest:
dropColumn:DropsthecolumnfromatabledropForeignKey:DropstheforeignkeyfromatabledropIndex:Dropstheindexfromatable
![Page 89: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/89.jpg)
dropTable:DropsthetablefromadatabasemodifyColumn:Modifiesthecolumndefinition
![Page 90: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/90.jpg)
TheRecurringscriptTheRecurringscriptsexecutesoneachandeverysetup:upgradecommand,regardlessoftheschema_versionordata_versionloggedagainstthesetup_moduletable.
Let'screatethe<MAGELICIOUS_DIR>/Core/Setup/Recurring.phpfilewiththefollowingcontent:
useMagento\Framework\Setup\InstallSchemaInterface;
useMagento\Framework\Setup\ModuleContextInterface;
useMagento\Framework\Setup\SchemaSetupInterface;
classRecurringimplementsInstallSchemaInterface{
publicfunctioninstall(SchemaSetupInterface$setup,ModuleContextInterface$context){
$setup->startSetup();
echo'Recurring->install()'.PHP_EOL;
$setup->endSetup();
}
}
Thoughinteresting,theRecurringscriptsarerarelyusedinMagento.Onlyahandfulofthemareused,andthatismostlyforinstallingexternalforeignkeys.Thisisnottosaythatwecannotusethemforourpurposes–itisjustthattheirusecaseisquitelimitedwhenwethinkaboutit.
![Page 91: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/91.jpg)
TheInstallDatascriptTheInstallDatascriptisusedwhenwewishtoaddnewdatatoexistingtables.Thisscriptisrunonlywhenamoduleisenabled.Onceenabled,themodulegetsacorrespondingentryunderthesetup_module.data_versiontablecolumn.ThisentrypreventstheInstallDatascripttorunonanysubsequentsetup:upgradecommandexecution,wherethemodule'ssetup_versionremainsthesame.
Let'screatethe<MAGELICIOUS_DIR>/Core/Setup/InstallData.phpfilewiththefollowingcontent:
use\Magento\Framework\Setup\InstallDataInterface;
useMagento\Framework\Setup\ModuleContextInterface;
useMagento\Framework\Setup\ModuleDataSetupInterface;
classInstallDataimplementsInstallDataInterface{
publicfunctioninstall(ModuleDataSetupInterface$setup,ModuleContextInterface$context){
$setup->startSetup();
echo'InstallData->install()'.PHP_EOL;
$setup->endSetup();
}
}
Chancesare,wewillbeinteractingwiththistypeofscriptmoreoftenthannot.ReplacingtheecholinewithmodifiedpiecesoftheequivalentMagentoInstallDatascriptmightgiveusabetterunderstandingofthepossibilitiesbehindthesescripts.
![Page 92: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/92.jpg)
TheUpgradeDatascriptLet'screatethe<MAGELICIOUS_DIR>/Core/Setup/UpgradeData.phpfilewiththefollowingcontent:
useMagento\Framework\Setup\ModuleContextInterface;
useMagento\Framework\Setup\ModuleDataSetupInterface;
classUpgradeDataimplements\Magento\Framework\Setup\UpgradeDataInterface{
publicfunctionupgrade(ModuleDataSetupInterface$setup,ModuleContextInterface$context){
$setup->startSetup();
if(version_compare($context->getVersion(),'2.0.2')<0){
$this->upgradeToVersionTwoZeroTwo($setup);
}
$setup->endSetup();
}
privatefunctionupgradeToVersionTwoZeroTwo(ModuleDataSetupInterface$setup){
echo'UpgradeData->upgradeToVersionTwoZeroTwo()'.PHP_EOL;
}
}
Let'sgoaheadandreplacetheecholinewithsomethingpractical,likeaddinganewcolumntoanexistingtable:
$salesSetup=$this->salesSetupFactory->create(['setup'=>$setup]);
$salesSetup->addAttribute('order','merchant_note',[
'type'=>\Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
'visible'=>false,
'required'=>false
]);
Here,weusedtheinstanceofMagento\Sales\Setup\SalesSetupFactory,injectedthrough__construct.ThisfurthercreatesaninstanceoftheMagento\Sales\Setup\SalesSetupclass.WeneedthisclassinordertocreatesalesEAVattributes.Theorderentityissomewhatofastrangemix;whileitisregisteredasanEAVtypeofentityundertheeav_entity_typetable,itdoesnotreallyuseeav_attribute_*tables–itusesasinglesales_ordertabletostoreitsattributes.Wecouldhaveeasilyused(Install|Upgrade)Schemascriptstosimplyaddanewcolumnvia$setup->getConnection()->addColumn().Onceexecuted,thiscodeaddsthemerchant_notecolumntothesales_ordertable.Wewillusethiscolumnlateron,aswereachtheExtendingentitiessection.
![Page 93: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/93.jpg)
TheRecurringDatascriptMuchlikerecurringscripts,theRecurringDatascriptsarerarelyusedinMagento.Theyalsoexecuteoneachandeverysetup:upgradecommand,regardlessoftheschema_versionordata_versionloggedagainstthesetup_moduletable.MagentoOpenSourceusesmerelythreeRecurringDatascriptsthroughoutitscodebase.
Let'screatethe<MAGELICIOUS_DIR>/Core/Setup/RecurringData.phpfilewiththefollowingcontent:
useMagento\Framework\Setup\InstallDataInterface;
useMagento\Framework\Setup\ModuleContextInterface;
useMagento\Framework\Setup\ModuleDataSetupInterface;
classRecurringDataimplementsInstallDataInterface{
publicfunctioninstall(ModuleDataSetupInterface$setup,ModuleContextInterface$context){
$setup->startSetup();
echo'RecurringData->install()'.PHP_EOL;
$setup->endSetup();
}
}
Thesetupscriptsprovideawayforustomanagethedataanditsrepresentationinthedatabase.Whereasaddinganewattributetosimplemodelislikelyacaseofextendingitstablebyanextracolumn(*Schemascripts),addinganewattributetoanEAVmodelisamatterofaddingnewdataundertheeav_attributetable(*Datascripts).
![Page 94: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/94.jpg)
ExtendingentitiesWeextendentitiesbyaddingadditionalattributestothem.ReferringbacktothemagicalgetterandsettermethodsmentionedinthecontextofMagento\Framework\DataObject,thelogicalthinkingmightbe:what'sthebigdeal;can'twejustaddnewdatabasecolumnsviaUpgradeSchemaandusemagicalgetterandsettermethodstogoaroundit?Theanswerisbothyesandno,butmainlyleaningtowardno–wewillsoonlearnwhy.
Tobetterexplainthis,let'stakealookatMagento\Sales\Model\Order,theentitymodel.ThismodelimplementstheMagento\Sales\Api\Data\OrderInterfaceinterface,whichfurtherextendsMagento\Framework\Api\ExtensibleDataInterface.Here,wecanseeaconstantdefiningakeyfortheextensionattributesobject.Thisissomewhatofastartingpointforextendingentities.Sufficetosay,thereisanextraabstractionlayerontopofsomeofthemodels.Thisabstractionlayer,calledservicecontracts,isasetofPHPinterfacesthatensureawell-defined,durableAPIthatothermodulesandthird-partyextensionsmightimplement.
This,however,iseasiersaidthandone.Whenyouthinkaboutit,ifwehadamodulethat'salreadyheavilyinuse,addingevenasimpleattributetooneofitsentitymodelsmightbreakitsfunctionality.Thisiswhereextensionattributescomeintothepicture.
![Page 95: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/95.jpg)
CreatingextensionattributesCreatinganewextensionattributeforanexistingentityisusuallyacaseofdoingthefollowing:
1. Usingsetupscriptstosettheattribute,column,ortableforpersistence2. Definingtheextensionattributevia
<VendorName>/<ModuleName>/etc/extension_attributes.xml
3. Addinganafterand/orbeforeplugintothesave,get,andgetListmethodsofanentityrepository
Movingforward,wearegoingtocreateextensionattributesfortheorderentity,thatis,customer_noteandmerchant_note.
Wecanimaginecustomer_noteasanattributethatdoesnotpersistitsvalue(s)inthesales_ordertableasorderentitydoes,whereasmerchant_noteattributedoes.Thisiswhywecreatedthesales_order.merchant_notecolumnearlierviatheUpgradeData
script.
Let'sgoaheadandcreatethe<MAGELICIOUS_DIR>/Core/Api/Data/CustomerNoteInterface.phpfilewiththefollowingcontent:
interfaceCustomerNoteInterfaceextends\Magento\Framework\Api\ExtensibleDataInterface
{
constCREATED_BY='created_by';
constNOTE='note';
publicfunctionsetCreatedBy($createdBy);
publicfunctiongetCreatedBy();
publicfunctionsetNote($note);
publicfunctiongetNote();
}
Thecustomer_noteattributeisgoingtobeafull-blownobject,sowewillcreateaninterfaceforit.
Whileomittedintheexample,besuretosetthedocblocksoneachmethod,otherwisetheMagentowebAPIwillthrowanEachgettermusthaveadocblockerroroncewehookupthepluginmethods.
Wewillthencreatethe<MAGELICIOUS_DIR>/Core/Model/CustomerNote.phpfilewiththe
![Page 96: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/96.jpg)
followingcontent:
classCustomerNoteextends\Magento\Framework\Model\AbstractExtensibleModelimplements\Magelicious\Core\Api\Data\CustomerNoteInterface
{
publicfunctionsetCreatedBy($createdBy){
return$this->setData(self::CREATED_BY,$createdBy);
}
publicfunctiongetCreatedBy(){
return$this->getData(self::CREATED_BY);
}
publicfunctiongetNote(){
return$this->getData(self::NOTE);
}
publicfunctionsetNote($note){
return$this->setData(self::NOTE,$note);
}
}
Thisclassisessentiallyourcustomer_noteentitymodel.Tokeepthingsminimal,wewilljustimplementtheCustomerNoteInterface,withoutanyextralogic.
Wewillthengoaheadandcreatethe<MAGELICIOUS_DIR>/Core/etc/extension_attributes.xmlfilewiththefollowingcontent:
<?xmlversion="1.0"?>
<config>
<extension_attributesfor="Magento\Sales\Api\Data\OrderInterface">
<attributecode="customer_note"type="Magelicious\Core\Api\Data\CustomerNoteInterface"/>
<attributecode="merchant_note"type="string"/>
</extension_attributes>
</config>
Theextension_attributes.xmlfileiswhereweregisterourextensionattributes.Thetypeargumentallowsustoregistereithercomplextypes,suchasaninterface,orscalartypes,suchasastringorinteger.Withtheextensionattributesregistered,itistimetoregisterthecorrespondingplugins.Thisisdoneviathedi.xmlfile.
Let'sgoaheadandcreatethe<MAGELICIOUS_DIR>/Core/etc/di.xmlfilewiththefollowingcontent:
<?xmlversion="1.0"?>
<config>
<preferencefor="Magelicious\Core\Api\Data\CustomerNoteInterface"type="Magelicious\Core\Model\CustomerNote"/>
<typename="Magento\Sales\Api\OrderRepositoryInterface">
<pluginname="customerNoteToOrderRepository"type="Magelicious\Core\Plugin\CustomerNoteToOrderRepository"/>
<pluginname="merchantNoteToOrderRepository"type="Magelicious\Core\Plugin\MerchantNoteToOrderRepository"/>
</type>
</config>
![Page 97: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/97.jpg)
Thereasonforregisteringpluginsinthefirstplaceistohaveourcustomer_noteandmerchant_noteattributesavailableonthegetList,get,andsavemethodsoftheMagento\Sales\Api\OrderRepositoryInterfaceinterface.TherepositoryinterfacesarethemainwayofCRUD-ingentitiesunderservicecontracts.Withoutproperplugins,Magentosimplywouldnotseeourattributes.
Let'screatethe<MAGELICIOUS_DIR>/Core/Plugin/CustomerNoteToOrderRepository.phpfilewiththefollowingcontent:
classCustomerNoteToOrderRepository{
protected$orderExtensionFactory;
protected$customerNoteInterfaceFactory;
publicfunction__construct(
\Magento\Sales\Api\Data\OrderExtensionFactory$orderExtensionFactory,
\Magelicious\Core\Api\Data\CustomerNoteInterfaceFactory$customerNoteInterfaceFactory
){
$this->orderExtensionFactory=$orderExtensionFactory;
$this->customerNoteInterfaceFactory=$customerNoteInterfaceFactory;
}
privatefunctiongetCustomerNoteAttribute(
\Magento\Sales\Api\Data\OrderInterface$resultOrder
){
$extensionAttributes=$resultOrder->getExtensionAttributes()?:$this->orderExtensionFactory->create();
//TODO:Getcustomernotefromsomewhere(belowwefakeit)
$customerNote=$this->customerNoteInterfaceFactory->create()
->setCreatedBy('Mark')
->setNote('Thenote'.\time());
$extensionAttributes->setCustomerNote($customerNote);
$resultOrder->setExtensionAttributes($extensionAttributes);
return$resultOrder;
}
privatefunctionsaveCustomerNoteAttribute(
\Magento\Sales\Api\Data\OrderInterface$resultOrder
){
$extensionAttributes=$resultOrder->getExtensionAttributes();
if($extensionAttributes&&$extensionAttributes->getCustomerNote()){
//TODO:Save$extensionAttributes->getCustomerNote()somewhere
}
return$resultOrder;
}
}
Rightnow,therearenopluginmethodsdefined.getCustomerNoteAttributeandsaveCustomerNoteAttributeareessentiallyhelpermethodsthatwewillsoonuse.
Let'sextendourCustomerNoteToOrderRepositoryclassbyaddingtheafterpluginforthegetListmethod,asfollows:
publicfunctionafterGetList(
![Page 98: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/98.jpg)
\Magento\Sales\Api\OrderRepositoryInterface$subject,
\Magento\Sales\Model\ResourceModel\Order\Collection$resultOrder
){
foreach($resultOrder->getItems()as$order){
$this->afterGet($subject,$order);
}
return$resultOrder;
}
Now,let'sextendourCustomerNoteToOrderRepositoryclassbyaddingtheafterpluginforthegetmethod,asfollows:
publicfunctionafterGet(
\Magento\Sales\Api\OrderRepositoryInterface$subject,
\Magento\Sales\Api\Data\OrderInterface$resultOrder
){
$resultOrder=$this->getCustomerNoteAttribute($resultOrder);
return$resultOrder;
}
Finally,let'sextendourCustomerNoteToOrderRepositoryclassbyaddingtheafterpluginforthesavemethod,asfollows:
publicfunctionafterSave(
\Magento\Sales\Api\OrderRepositoryInterface$subject,
\Magento\Sales\Api\Data\OrderInterface$resultOrder
){
$resultOrder=$this->saveCustomerNoteAttribute($resultOrder);
return$resultOrder;
}
Withthepluginsforcustomer_notesorted,let'sgoaheadandaddressthepluginsformerchant_note.Wewillcreatethe<MAGELICIOUS_DIR>/Core/Plugin/MerchantNoteToOrderRepository.phpfilewiththefollowingcontent:
classMerchantNoteToOrderRepository{
protected$orderExtensionFactory;
publicfunction__construct(
\Magento\Sales\Api\Data\OrderExtensionFactory$orderExtensionFactory
){
$this->orderExtensionFactory=$orderExtensionFactory;
}
privatefunctiongetMerchantNoteAttribute(
\Magento\Sales\Api\Data\OrderInterface$order
){
$extensionAttributes=$order->getExtensionAttributes()?:$this->orderExtensionFactory->create();
$extensionAttributes->setMerchantNote($order->getData('merchant_note'));
$order->setExtensionAttributes($extensionAttributes);
return$order;
}
privatefunctionsaveMerchantNoteAttribute(
\Magento\Sales\Api\Data\OrderInterface$order
![Page 99: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/99.jpg)
){
$extensionAttributes=$order->getExtensionAttributes();
if($extensionAttributes&&$extensionAttributes->getMerchantNote()){
$order->setData('merchant_note',$extensionAttributes->getMerchantNote());
}
return$order;
}
}
Rightnow,therearenopluginmethodsdefined.getMerchantNoteAttributeandsaveMerchantNoteAttributeareessentiallyhelpermethodsthatwewillsoonuse.
Let'sextendourMerchantNoteToOrderRepositoryclassbyaddingtheafterpluginforthegetListmethod,asfollows:
publicfunctionafterGetList(
\Magento\Sales\Api\OrderRepositoryInterface$subject,
\Magento\Sales\Model\ResourceModel\Order\Collection$order
){
foreach($order->getItems()as$_order){
$this->afterGet($subject,$_order);
}
return$order;
}
Now,let'sextendourMerchantNoteToOrderRepositoryclassbyaddingtheafterpluginforthegetmethod,asfollows:
publicfunctionafterGet(
\Magento\Sales\Api\OrderRepositoryInterface$subject,
\Magento\Sales\Api\Data\OrderInterface$order
){
$order=$this->getMerchantNoteAttribute($order);
return$order;
}
Finally,let'sextendourMerchantNoteToOrderRepositoryclassbyaddingthebeforepluginforthesavemethod,asfollows:
publicfunctionbeforeSave(
\Magento\Sales\Api\OrderRepositoryInterface$subject,
\Magento\Sales\Api\Data\OrderInterface$order
){
$order=$this->saveMerchantNoteAttribute($order);
return[$order];
}
Theobviousdifferencehereisthat,withMerchantNoteToOrderRepository,weareusingbeforeSave,whereasweusedafterSavewithCustomerNoteToOrderRepository.Thereasonforthisisthatmerchant_noteistobesaveddirectlyontheentitywhoserepositoryweareplugginginto,thatis,itstableinthesales_orderdatabase.This
![Page 100: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/100.jpg)
way,weuseitsMagento\Framework\DataObjectpropertiesofsetDatatofetchwhatwasassuminglynotealreadysetviaextensionattributesandpassitontotheobject'smerchant_notepropertybeforeitissaved.Magento'sbuilt-insavemechanismthentakesoverandstorestheproperty,aslongasthecorrespondingcolumnexistsinthedatabase.
Withthepluginsinplace,ourattributesshouldnowbevisibleandpersistablewhenusedthroughtheOrderRepositoryInterface.WithoutgettingtoodeepintothewebAPIatthispoint,wecanquicklytestthisviaperformingthefollowingRESTrequest:
GET/index.php/rest/V1/orders?searchCriteria[filter_groups][0][filters][0][field]=entity_id&searchCriteria[filter_groups][0][filters][0][value]=1
Host:magelicious.loc
Content-Type:application/json
Authorization:Bearer0vq6d4kabpxgc5kysb2sybf3n4ct771x
WhereastheBearertokenissomethingwegetbyrunningthefollowingRESTloginaction:
POST/index.php/rest/V1/integration/admin/token
Host:magelicious.loc
Content-Type:application/json
{"username":"john","password":"grdM%0i9a49n"}
ThesuccessfulresponseofGET/V1/ordersshouldyieldaresultofthefollowingpartialstructure:
{
"items":[
{
"extension_attributes":{
"shipping_assignments":[...],
"customer_note":{
"created_by":"Mark",
"note":"NoteABC"
},
"merchant_note":"NoteXYZ"
}
}
]
}
Wecanseethatourtwoattributesarenicelynestedwithintheextension_attributeskey.
Postman,theAPIdevelopmenttool,makesiteasytotestAPIs.Seehttps://www.getpostman.comformoreinformation.
TheOrderRepositoryInterfacetowebAPIRESTrelationshipmapsoutasfollows:
![Page 101: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/101.jpg)
getList:GET/V1/orders(plusthesearchcriteriapart)get:GET/V1/orders/:idsave:POST/V1/orders/create
WewilllearnmoreaboutthewebAPIinthenextchapter.Theexamplegivenherewasmerelyforthepurposeofvisualizingtheworkwehavedonearoundplugins.Usingextensionattributes,withthehelpofplugins,wehaveessentiallyextendedtheMagentowebAPI.
![Page 102: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/102.jpg)
SummaryThroughoutthischapter,welearnedhowtodifferentiatethethreetypesofMagentomodels:non-persistable,persistablesimple,andpersistableEAV.TheinnersofEAVmodelsareleftoutofscopeduetotheirinherentlycomplexnature.Wethentookalookthroughsixdifferentsetupscripts.Thesegiveusagreatdealofflexibilityoverschemaanddatamanagement.Combinedwithextensionattributes,wegetapowerfulmechanismforextendingbuilt-inentities.Thoughsomewhattedious,theextensionattributesmechanismuseofinterfacesensuresthatintegratorscanextendthisbuilt-infunctionalitywithcomplexdatatypes.
Movingforward,wearegoingtotakealookatthepowerfulwebAPIthat'simplementedinMagento.
![Page 103: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/103.jpg)
UnderstandingWebAPIsWebapplicationprogramminginterfaces(API)playamajorroleinmodernapplicationdevelopment.Theyallowvariousthird-partyintegratorstointeractwithapplicationsthroughtheHTTPlayer.MagentosupportsbothRepresentationalStateTransfer(REST)andSimpleObjectAccessProtocol(SOAP)APIs.ItswebAPIframeworkisbasedonthecreate,read,update,delete(CRUD)andsearch(searchcriteria)models.ThescopeoffunctionalitythatAPIsofferisquitebig,allowingustousethemforawiderangeoftasks,suchascreatingacompletelynewshoppingapplication,integratingwithcustomerrelationshipmanagement(CRM)systems,enterpriseresourceplanning(ERP)systems,andcontentmanagementsystems(CMS),aswellascreatingJavaScriptwidgetsintheMagentostorefrontitself.
Movingforward,wearegoingtotakeacloserlookatthefollowingwebAPIsections:
TypesofusersTypesofauthenticationTypesofendpointsUsingexistingWebAPIsCreatingcustomWebAPIsUnderstandingsearchcriteria
![Page 104: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/104.jpg)
TechnicalrequirementsYouwillneedtohavebasicknowledgeofPHP,OOP,JavaScript,andXML.YouwillalsoneedApache,MySQL,andAMPPSinstalledonyoursystemtoexecutethecodes.
ThecodefilesofthischaptercanbefoundonGitHub:https://github.com/PacktPublishing/Magento-2-Quick-Start-Guide.
CheckoutthefollowingvideotoseetheCodeinAction:
http://bit.ly/2Oz3Gqs.
![Page 105: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/105.jpg)
TypesofusersTheMagentowebAPIframeworkdifferentiatesthreefundamentaltypesofusers:
Guest:Authorizedagainstananonymousresource:
<resources>
<resourceref="anonymous"/>
</resources>
Customer:Authorizedagainstaselfresource:
<resources>
<resourceref="self"/>
</resources>
Integrator:Authorizedagainstaspecificresourcedefinedinacl.xml:
<resources>
<resourceref="Magento_Cms::save""/>
</resources>
Tofurtherunderstandwhatthismeans,weneedtounderstandthelinkbetween<VendorName>/<ModuleName>/acl.xmland<VendorName>/<ModuleName>/webapi.xml.
Theacl.xmliswherewedefineouraccessresources.Let'stakeacloserlookatthepartialextractofonesuchresource,definedinthe<MAGENTO_DIR>/module-cms/etc/acl.xmlfile:
<config>
<acl>
<resources>
<resourceid="Magento_Backend::admin">
<resourceid="Magento_Backend::content">
<resourceid="Magento_Backend::content_elements">
<resourceid="Magento_Cms::page"title="Pages">
<resourceid="Magento_Cms::save"title="SavePage"/>
</resource>
</resource>
</resource>
</resource>
</resources>
</acl>
</config>
OurfocushereisontheMagento_Cms::saveresource.Magentomergesallofthese
![Page 106: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/106.jpg)
individualacl.xmlfilesintoonebigACLtree.WecanseethistreeintwoplacesintheMagentoadminarea:
TheRoleResourcetaboftheSystem|Permissions|UserRoles|Edit|AddNewRolescreenTheAPItaboftheSystem|Extensions|Integrations|Edit|AddNewIntegrationscreen:
ThesearethetwoscreenswherewedefineaccesspermissionsforastandardadminuserandaspecialwebAPIintegratoruser.ThisisnottosaythatastandardadminusercannotexecutewebAPIcalls.ThedifferencewillbecomemoreobviouswhenwegettotheTypesofauthenticationsection.
Tothispoint,theseresourcesdon'treallydoanythingontheirown.Simplydefiningthemwithinacl.xmlwon'tmagicallymakeaCMSpageinthiscaseaccess-protected,oranythinglikethat.Thisiswherecontrollerscomeintothemix,asoneexampleofanaccess-controllingmechanism.AquicklookupagainstMagento_Cms::savestringusage,revealsaMagento\Cms\Controller\Adminhtml\Page\EditclassusingitaspartofitsconstADMIN_RESOURCE='Magento_Cms::save'definition.
![Page 107: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/107.jpg)
TheADMIN_RESOURCEconstantisdefinedfurtherdowntheinheritancechain,onthe\Magento\Backend\App\AbstractActionasconstADMIN_RESOURCE='Magento_Backend::admin'.Thisisfurtherusedbythe_isAllowedmethodimplementationasfollows:
protectedfunction_isAllowed()
{
return$this->_authorization->isAllowed(static::ADMIN_RESOURCE);
}
TheAbstractActionclasshereisthebasisforprettymuchanyMagentoadmincontroller.Thismeansthatthecontrolleristheonethatutilizestheresourcedefinedinacl.xml,whereasdefinitionsinacl.xmlservethepurposeofbuildingtheACLtree,whichwecanmanagefromtheMagentoadmininterface.Thismeansthatanyonetryingtoaccessthecms/page/editURLinadminmusthaveaMagento_Cms::saveresourcepermissiontodoso.Otherwise,the_isAllowedmethod,readingtheADMIN_RESOURCEvalue,willreturnfalseandforbidaccesstothepage.
WebAPIs,ontheotherhand,don'tusecontrollers,sothereisnoaccesstotheADMIN_RESOURCEconstantandthe_isAllowedmethod.APIsusewebapi.xmltodefineroutes.Let'sfollowupwiththeCMSpagesaveanalogue,asperthe<MAGENTO_DIR>/module-cms/etc/webapi.xmlfile:
<routes>
<routeurl="/V1/cmsPage"method="POST">
<serviceclass="Magento\Cms\Api\PageRepositoryInterface"method="save"/>
<resources>
<resourceref="Magento_Cms::page"/>
</resources>
</route>
<routeurl="/V1/cmsPage/:id"method="PUT">
<serviceclass="Magento\Cms\Api\PageRepositoryInterface"method="save"/>
<resources>
<resourceref="Magento_Cms::page"/>
</resources>
</route>
</routes>
Theindividualroutedefinitionbindstogetherafewthings.TheurlandmethodargumentofarouteelementspecifywhatURLwilltriggerthisroute.Theclassandmethodargumentsofaserviceelementspecifywhichinterfaceandmethodonthatinterfacewillexecuteoncetherouteistriggered.Finally,therefargumentofaresourceelementspecifiesthesecuritychecktobeexecuted.IfauserexecutingawebAPIcallisunauthenticatedorauthenticatedwitharolethatdoesnothaveMagento_Cms::page,therequestwon'texecutetheservicemethodspecified.
Thecustomertypeofuseristhemostconvenientforworkingwithwidgets.The
![Page 108: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/108.jpg)
Magentocheckoutisanexcellentexampleofthat.ThewholecheckoutisafullyAJAX-enabledapponitsown,separatefromtheusualMagentostorefront,suchasitsCMS,category,andproductpages.
![Page 109: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/109.jpg)
TypesofauthenticationMagentosupportsthreedifferenttypesofauthenticationmethods:
Session-basedauthentication:BestsuitedforJavaScriptwidgetapplicationsrunningaspartoftheMagentostorefrontitself.Magentousesthelogged-instateofanadminuserorcustomertoverifytheiridentityandauthorizeaccesstotherequestedresource.Token-basedauthentication:Bestsuitedformobileorothertypesofapplicationsthatwishtoavoidthecomplexitiesoffull-blownOAuth-basedauthentication.Toobtainthetoken(withREST),oneinitiallyusesthePOST/V1/integration/customer/tokenorthePOST/V1/integration/admin/token.Asuccessfulresponsereturnsarandom32-character-longstring,forexample,8pcvbwrp97l5m1pvcdnis6e3930n4rsj.Thisisourtoken,usedforanysubsequentAPIcalls,viaaheadergivenasAuthorization:Bearer<token>.Thesimplicitybehindthisauthenticationmakesitanappealingchoicefordevelopers.OAuth-basedauthentication:Bestsuitedforthird-partyapplicationsthatintegratewithMagentoonbehalfoftheuser,withoutrevealingorstoringanyuserIDsorpasswords.ThestartingpointforsettingupOAuth-basedauthenticationisforaMagentoadminusertocreateintegration,undertheSystem|Extensions|Integration|AddNewIntegrationscreen.HerewecanprovideoptionssuchasCallbackURLandIdentitylinkURL,whichdefinetheexternalapplicationendpointthatreceivestheOAuthcredentials.Ifgiven,thevaluesoftheselinkspointtotheexternalappthatstandsastheOAuthclient.SuccessfullysavedintegrationgeneratesthekeyOAuthartefacts,suchasConsumerKey,ConsumerSecret,AccessToken,andAccessTokenSecret.
UsingOAuth-basedauthenticationexceedsthescopeofthisbook,whichiswhymovingforward,allofourexampleswillusesimplertoken-basedauthentication.
![Page 110: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/110.jpg)
TypesofAPIsMagentosupportstwotypesofAPIs:
RepresentationalStateTransfer(REST):TheendpointsforAPIsdependonwebapi.xmlandtheindividualurlargumentsofeachrouteelement,aswewillsoonsee.Theauthenticationiscarriedoverinarequest'sheaderviaaBearertoken.SimpleObjectAccessProtocol(SOAP):TheWebServicesDescriptionLanguage(WSDL)isavailableviaaURLsuchashttp://magelicious.loc/soap/default?wsdl&services=catalogProductRepositoryV1.Whereasthedefaultstringisoptional,anditmatchesthecodenameoftheMagentostoreinthiscase,ifomitted,Magentowilldefaulttoadefaultstore,whateveritscodemightbe.Likewise,theservicesparameteracceptsoneormore(comma-separated)listsofservices.ThefulllistofavailableservicescanbeobtainedviaaURLsuchashttp://magelicious.loc/soap/default?wsdl_list.Withoutgoingintothedetailsofit,sufficeittosaythatMagentogeneratestheservicenamesautomaticallybasedonmoduleandinterfacenames.MuchlikewithRESTAPIs,theauthenticationiscarriedoverinarequest'sheaderviaaBearertoken.
Thegreatthingaboutthesetwoisthatwedon'tgettowritetwodifferentAPIsinMagento.TheapproachtowritingAPIsisunified,sotospeak.Wedefinesomeinterfaces,classes,andconfigurations,andMagentothengeneratestheAPIendpointsforbothRESTandSOAPonitsown.Thus,theRESTvs.SOAPchoicereallyonlybecomesaquestionwhenweconsumeAPIs,notwhilewewritethem.
UsingSOAPservicesexceedsthescopeofthisbook,whichiswhymovingforward,allofourexampleswilluseRESTAPIs.
![Page 111: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/111.jpg)
UsingexistingwebAPIsTheCRUDandsearchmodelsofwebAPIsareimplementedthroughasetof*RepositoryInterfaceinterfaces,foundinthe<VendorName>/<ModuleName>/Api/<EntityName>RepositoryInterface.phpfiles.
Themajorityoftheserepositoryinterfacesdefineaspecificsetofcommonmethods:
save
get
getById
getList
delete
deleteById
Thedatatypethatflowsthroughthesemethodsfollowsacertainpattern,whereeachentitypassingthroughanAPIhasadatainterfacedefinedina<VendorName>/<ModuleName>/Api/Data/<EntityName>Interface.phpfile.
Let'stakeacloserlookat<MAGENTO_DIR>/module-cms/Api/BlockRepositoryInterface.php:
interfaceBlockRepositoryInterface
{
publicfunctionsave(
\Magento\Cms\Api\Data\BlockInterface$block
);
publicfunctiongetById($blockId);
publicfunctiongetList(
\Magento\Framework\Api\SearchCriteriaInterface$searchCriteria
);
publicfunctiondelete(
\Magento\Cms\Api\Data\BlockInterface$block
);
publicfunctiondeleteById($blockId);
}
Theconcreteimplementationsofrepositoryinterfacescanusuallybefoundinthe<VendorName>/<ModuleName>/Model/<EntityName>Repository.phporthe<VendorName>/<ModuleName>/Model/ResourceModel/<EntityName>Repository.phpfiles.Theexact
![Page 112: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/112.jpg)
locationisnotthatrelevant,aswebapi.xmlshouldalwaysuseaninterfaceforaclassargumentforitsserviceelementdefinition.Themappingbetweentheinterfaceandconcreteimplementationthenhappensinthemodule'sdi.xmlfileviaapreferencedefinition.Fromanintegrator'spointofview,usingAPIsdoesnotrequireanyknowledgeofconcreteimplementations.
ThePHPDoc@returntagisarequirementforeverygettermethodonanAPIinterface,otherwise,Eachgettermusthaveadocblockerroristhrown.
TheSwaggerURL,http://magelicious.loc/swagger,willgenerateaSwaggerUIinterface,thatallowsustovisualizeandinteractwiththeAPI'sresources:
Bydefault,documentationreturnedhereislimitedtoanonymoususersonly.GeneratingavalidAPIkey,viathePOST/V1/integration/customer/tokenorPOST/V1/integration/admin/tokenwillunlockthedocumentationforalltheresourcesavailabletoagivenuser.WhileSwaggercertainlyhasitsplaceindevelopmentworkflows,oftentimesthePostmantoolisamorerobustsolutionforthoseworkingextensivelywithAPIs.
Let'sgoaheadandCRUDourwaythroughthecmsBlockinterface,usingRESTendpoints:
save(createanewblock)POST/V1/cmsBlocksave(updateanexistingblockbyid)PUT/V1/cmsBlock/:idgetById(getanexistingblockbyid)GET/V1/cmsBlock/:blockIddeleteById(deleteanexistingblock)DELETE/V1/cmsBlock/:blockIdgetList(getanarrayofexistingblocks)GET/V1/cmsBlock/search
![Page 113: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/113.jpg)
Wewillbeusingtheintegratortypeofuser.ThiswillbeourMagentoadminuser,assignedeitherfullresources,oratleasttheResources|Content|Elements|BlocksresourceundertheRoleResourcetaboftheSystem|Permissions|UserRoles|Edit|AddNewRolescreen.
Westartwiththeadminloginrequest,inordertoobtainatokenforlaterrequests:
POST/index.php/rest/V1/integration/admin/tokenHTTP/1.1
Host:magelicious.loc
Content-Type:application/json
{
"username":"branko",
"password":"jrdJ%0i9a69n"
}
ThesuccessfulJSONresponseshouldcontainourAPItoken,whichwewillbeusingforanysubsequentAPIcalls.Thetokenitselfisstoredintheoauth_tokentable,underthetokencolumn.Wefurtherhaveconsumer_id,admin_id,andcustomer_idcolumnsinthattable.Thesegetfilleddependingontheusertypeweusedtologin.Bothconsumer_idandadmin_idareoftheintegratortype.Thesecolumnsgetfilledaccordinglydependingontheuserandauthenticationtypesused;asincustomerversusintegrator,andtoken-basedvsOAuth-basedvssession-basedauthentication.
Nowlet'screateanewblockviaPOST/V1/cmsBlock;thistriggersthesavemethod:
POST/rest/V1/cmsBlockHTTP/1.1
Host:magelicious.loc
Content-Type:application/json
Authorization:Bearer8pcvbwrp97l5m1pvcdnis6e3930n4rsj
{
"block":{
"identifier":"x-block",
"title":"TheXBlock",
"content":"<p>The<strong>XBlock</strong>Content...</p>",
"active":true
}
}
ThesuccessfulJSONresponseshouldreturnournewlycreatedblock:
{
"id":1,
"identifier":"x-block",
"title":"TheXBlock",
"content":"<p>The<strong>XBlock</strong>Content...</p>",
"active":true
}
![Page 114: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/114.jpg)
Nowlet'supdatetheexistingcmsBlockviaPUT/V1/cmsBlock/:id;thistriggersthesavemethod:
PUT/rest/V1/cmsBlock/1HTTP/1.1
Host:magelicious.loc
Content-Type:application/json
Authorization:Bearer8pcvbwrp97l5m1pvcdnis6e3930n4rsj
{
"block":{
"identifier":"y-block",
"title":"TheYBlock",
"content":"<p>The<strong>YBlock</strong>Content...</p>",
"active":true
}
}
ThesuccessfulJSONresponseshouldreturntheupdatedblock:
{
"id":1,
"identifier":"y-block",
"title":"TheYBlock",
"content":"<p>The<strong>YBlock</strong>Content...</p>",
"active":true
}
Let'snowfetchoneoftheexistingblocksviaGET/V1/cmsBlock/:blockId;thistriggersthegetByIdmethod:
GET/rest/V1/cmsBlock/1HTTP/1.1
Host:magelicious.loc
Content-Type:application/json
Authorization:Bearer8pcvbwrp97l5m1pvcdnis6e3930n4rsj
ThesuccessfulJSONresponseisstructurallyidenticaltothatofthesavemethod.
Now,let'strydeletingoneoftheblocksviaDELETE/V1/cmsBlock/:blockId;thistriggersthedeleteByIdmethod:
DELETE/rest/V1/cmsBlock/2HTTP/1.1
Host:magelicious.loc
Content-Type:application/json
Authorization:Bearer8pcvbwrp97l5m1pvcdnis6e3930n4rsj
ThesuccessfulJSONresponsereturnsasingletrueorfalse.
Finally,let'stryfetchingthelistofblocksviaGET/V1/cmsBlock/search;thistriggersthegetListmethod:
GET/rest/V1/cmsBlock/search?searchCriteria[filter_groups][0][filters][0][field]=title&searchCriteria[filter_groups][0][filters][0][value]=%Block%&searchCriteria[filter_groups][0][filters][0][condition_type]=likeHTTP/1.1
Host:magelicious.loc
![Page 115: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/115.jpg)
Content-Type:application/json
Authorization:Bearer8pcvbwrp97l5m1pvcdnis6e3930n4rsj
Sadly,theGETrequestdoesnotallowforthebody,so?searchCriteria...hastobepassedviaaURL.
ThesuccessfulJSONresponsereturnsanobjectcomprisedofitems,search_criteria,andtotal_counttop-levelkeys:
{
"items":[
{
"id":4,
"identifier":"x-block",
"title":"TheXBlock",
"content":"The<strong>XBlock</strong>Content...",
"creation_time":"2018-06-2307:30:06",
"update_time":"2018-06-2307:30:06",
"active":true
},
{
"id":5,
"identifier":"y-block",
"title":"TheYBlock",
"content":"The<strong>YBlock</strong>Content...",
"creation_time":"2018-06-2307:30:14",
"update_time":"2018-06-2307:30:14",
"active":true
}
],
"search_criteria":{...},
"total_count":2
}
Wewilladdressthesearch_criteriainmoredetaillateron.
![Page 116: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/116.jpg)
CreatingcustomwebAPIsLet'sgoaheadandcreateaminiature,yetfull-blownMagentomoduleMagelicious_BoxythatdemonstratestheentireflowofcreatingacustomwebAPI.
Westartoffbydefiningamodule<MAGELICIOUS_DIR>/Boxy/registration.phpasfollows:
\Magento\Framework\Component\ComponentRegistrar::register(
\Magento\Framework\Component\ComponentRegistrar::MODULE,
'Magelicious_Boxy',
__DIR__
);
Wethendefinethe<MAGELICIOUS_DIR>/Boxy/etc/module.xmlasfollows:
<config>
<modulename="Magelicious_Boxy"setup_version="2.0.2"/>
</config>
Wethendefinethe<MAGELICIOUS_DIR>/Boxy/Setup/InstallSchema.phpthataddsthefollowingtable:
$table=$setup->getConnection()
->newTable($setup->getTable('magelicious_boxy_box'))
->addColumn(
'entity_id',
\Magento\Framework\DB\Ddl\Table::TYPE_INTEGER,
null,[
'identity'=>true,
'unsigned'=>true,
'nullable'=>false,
'primary'=>true
],'EntityID'
)
->addColumn(
'title',
\Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
32,
['nullable'=>false],'Title'
)
->addColumn(
'content',
\Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
null,
['nullable'=>false],'Content'
)
->setComment('MageliciousBoxyBoxTable');
$setup->getConnection()->createTable($table);
Wethendefine<MAGELICIOUS_DIR>/Boxy/Api/Data/BoxInterface.phpasfollows:
![Page 117: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/117.jpg)
interfaceBoxInterface{
constBOX_ID='box_id';
constTITLE='title';
constCONTENT='content';
publicfunctiongetId();
publicfunctiongetTitle();
publicfunctiongetContent();
publicfunctionsetId($id);
publicfunctionsetTitle($title);
publicfunctionsetContent($content);
}
Wethendefine<MAGELICIOUS_DIR>/Boxy/Api/Data/BoxSearchResultsInterface.phpasfollows:
interfaceBoxSearchResultsInterfaceextends\Magento\Framework\Api\SearchResultsInterface
{
publicfunctiongetItems();
publicfunctionsetItems(array$items);
}
Wethenaddthe<MAGELICIOUS_DIR>/Boxy/Api/BoxRepositoryInterface.phpasfollows:
interfaceBoxRepositoryInterface
{
publicfunctionsave(\Magelicious\Boxy\Api\Data\BoxInterface$box);
publicfunctiongetById($boxId);
publicfunctiongetList(\Magento\Framework\Api\SearchCriteriaInterface$searchCriteria);
publicfunctiondelete(\Magelicious\Boxy\Api\Data\BoxInterface$box);
publicfunctiondeleteById($boxId);
}
Wethendefinethe<MAGELICIOUS_DIR>/Boxy/Model/Box.phpasfollows:
classBoxextends\Magento\Framework\Model\AbstractModelimplements\Magelicious\Boxy\Api\Data\BoxInterface
{
protectedfunction_construct(){
$this->_init(\Magelicious\Boxy\Model\ResourceModel\Box::class);
}
publicfunctiongetId(){
return$this->getData(self::BOX_ID);
}
publicfunctiongetTitle(){
return$this->getData(self::TITLE);
}
publicfunctiongetContent(){
return$this->getData(self::CONTENT);
}
publicfunctionsetId($id){
return$this->setData(self::BOX_ID,$id);
}
publicfunctionsetTitle($title){
return$this->setData(self::TITLE,$title);
![Page 118: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/118.jpg)
}
publicfunctionsetContent($content){
return$this->setData(self::CONTENT,$content);
}
}
Wethendefinethe<MAGELICIOUS_DIR>/Boxy/Model/ResourceModel/Box.phpasfollows:
classBoxextends\Magento\Framework\Model\ResourceModel\Db\AbstractDb
{
protectedfunction_construct(){
$this->_init('magelicious_boxy_box','entity_id');
}
}
Wethendefinethe<MAGELICIOUS_DIR>/Boxy/Model/ResourceModel/Box/Collection.phpasfollows:
classCollection
{
protectedfunction_construct(){
$this->_init(
\Magelicious\Boxy\Model\Box::class,
\Magelicious\Boxy\Model\ResourceModel\Box::class
);
}
}
Wethendefinethe<MAGELICIOUS_DIR>/Boxy/Model/BoxRepository.phpasfollows:
classBoxRepositoryimplements\Magelicious\Boxy\Api\BoxRepositoryInterface
{
protected$boxFactory;
protected$boxResourceModel;
protected$searchResultsFactory;
protected$collectionProcessor;
publicfunction__construct(
\Magelicious\Boxy\Api\Data\BoxInterfaceFactory$boxFactory,
\Magelicious\Boxy\Model\ResourceModel\Box$boxResourceModel,
\Magelicious\Boxy\Api\Data\BoxSearchResultsInterfaceFactory$searchResultsFactory,
\Magento\Framework\Api\SearchCriteria\CollectionProcessorInterface$collectionProcessor
)
{
$this->boxFactory=$boxFactory;
$this->boxResourceModel=$boxResourceModel;
$this->searchResultsFactory=$searchResultsFactory;
$this->collectionProcessor=$collectionProcessor;
}
//Todo...
}
Let'sgoaheadandamendtheBoxRepositorywiththesavemethodasfollows:
publicfunctionsave(\Magelicious\Boxy\Api\Data\BoxInterface$box)
{
![Page 119: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/119.jpg)
try{
$this->boxResourceModel->save($box);
}catch(\Exception$e){
thrownew\Magento\Framework\Exception\CouldNotSaveException(__($e->getMessage()));
}
return$box;
}
Let'sgoaheadandamendtheBoxRepositorywiththegetByIdmethodasfollows:
publicfunctiongetById($boxId){
$box=$this->boxFactory->create();
$this->boxResourceModel->load($box,$boxId);
if(!$box->getId()){
thrownew\Magento\Framework\Exception\NoSuchEntityException(__('Boxwithid"%1"doesnotexist.',$boxId));
}
return$box;
}
Let'sgoaheadandamendtheBoxRepositorywiththegetListmethodasfollows:
publicfunctiongetList(\Magento\Framework\Api\SearchCriteriaInterface$searchCriteria){
$collection=$this->boxCollectionFactory->create();
$this->collectionProcessor->process($searchCriteria,$collection);
$searchResults=$this->searchResultsFactory->create();
$searchResults->setSearchCriteria($searchCriteria);
$searchResults->setItems($collection->getItems());
$searchResults->setTotalCount($collection->getSize());
return$searchResults;
}
Let'sgoaheadandamendtheBoxRepositorywiththedeletemethodasfollows:
publicfunctiondelete(\Magelicious\Boxy\Api\Data\BoxInterface$box){
try{
$this->boxResourceModel->delete($box);
}catch(\Exception$e){
thrownew\Magento\Framework\Exception\CouldNotDeleteException(__($e->getMessage()));
}
returntrue;
}
Let'sgoaheadandamendtheBoxRepositorywiththedeleteByIdmethodasfollows:
publicfunctiondeleteById($boxId){
return$this->delete($this->getById($boxId));
}
Wethendefinethe<MAGELICIOUS_DIR>/Boxy/etc/di.xmlasfollows:
<config>
<preferencefor="Magelicious\Boxy\Api\Data\BoxInterface"type="Magelicious\Boxy\Model\Box"/>
<preferencefor="Magelicious\Boxy\Api\Data\BoxSearchResultsInterface"type="Magento\Framework\Api\SearchResults"/>
<preferencefor="Magelicious\Boxy\Api\BoxRepositoryInterface"type="Magelicious\Boxy\Model\BoxRepository"/>
</config>
![Page 120: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/120.jpg)
Wethendefinethe<MAGELICIOUS_DIR>/Boxy/etc/acl.xmlasfollows:
<config>
<acl>
<resources>
<resourceid="Magento_Backend::admin">
<resourceid="Magento_Sales::sales">
<resourceid="Magento_Sales::sales_operation">
<resourceid="Magento_Sales::shipment">
<resourceid="Magelicious_Boxy::box"title="BoxyBox">
<resourceid="Magelicious_Boxy::box_get"title="Get"/>
<resourceid="Magelicious_Boxy::box_search"title="Search"/>
<resourceid="Magelicious_Boxy::box_save"title="Save"/>
<resourceid="Magelicious_Boxy::box_update"title="Update"/>
<resourceid="Magelicious_Boxy::box_delete"title="Delete"/>
</resource>
</resource>
</resource>
</resource>
</resource>
</resources>
</acl>
</config>
Wethendefinethe<MAGELICIOUS_DIR>/Boxy/etc/webapi.xmlasfollows:
<routes>
<routeurl="/V1/boxyBox/:boxId"method="GET">
<serviceclass="Magelicious\Boxy\Api\BoxRepositoryInterface"method="getById"/>
<resources>
<resourceref="Magelicious_Boxy::box_get"/>
</resources>
</route>
<routeurl="/V1/boxyBox/search"method="GET">
<serviceclass="Magelicious\Boxy\Api\BoxRepositoryInterface"method="getList"/>
<resources>
<resourceref="Magelicious_Boxy::box_search"/>
</resources>
</route>
<routeurl="/V1/boxyBox"method="POST">
<serviceclass="Magelicious\Boxy\Api\BoxRepositoryInterface"method="save"/>
<resources>
<resourceref="Magelicious_Boxy::box_save"/>
</resources>
</route>
<routeurl="/V1/boxyBox/:id"method="PUT">
<serviceclass="Magelicious\Boxy\Api\BoxRepositoryInterface"method="save"/>
<resources>
<resourceref="Magelicious_Boxy::box_update"/>
</resources>
</route>
<routeurl="/V1/boxyBox/:boxId"method="DELETE">
<serviceclass="Magelicious\Boxy\Api\BoxRepositoryInterface"method="deleteById"/>
<resources>
<resourceref="Magelicious_Boxy::box_delete"/>
</resources>
</route>
</routes>
Withallthesebitsinplace,ourAPIisnowready.Weshouldnowbeableto
![Page 121: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/121.jpg)
CRUDourwaythroughBoxyBoxthesamewaywedidwiththeCMSblock.Whiletherecertainlyisagreatdealofboilerplatecodetogoaround,ourAPIisnowbothREST-andSOAP-ready.
![Page 122: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/122.jpg)
UnderstandingsearchcriteriaThesearchCriteriaparameterofaGETrequestallowsforsearchresultsfiltering.Thekeytousingitcomesdowntounderstandingitsstructureandtheavailableconditiontypes.
Observingthe\Magento\Framework\Api\SearchCriteriaInterfaceinterface,andtheMagento\Framework\Api\SearchCriteriaclassasitsconcreteimplementation,wecaneasilyconcludethefollowingsearch_criteriastructure:
"search_criteria":{
"filter_groups":[],
"current_page":1,
"page_size":10,
"sort_orders":[]
}
Whereasthemandatoryfilter_groupsparameteranditsstructureareshownasfollows:
"filter_groups":[
{
"filters":[
{
"field":"fieldOrAttrName",
"value":"fieldOrAttrValue",
"condition_type":"eq"
},
{
//LogicalOR
}
]
},
{
//LogicalAND
}
],
Conditionsnestedundertheindividualfilterskey,correspondtotheLogicalORcondition.
Thelistofcondition_typevaluesincludes:
eq:Equalsfinset:Avaluewithinasetofvaluesfrom:Thebeginningofarange,mustbeusedwithatoconditiontype
![Page 123: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/123.jpg)
gt:Greaterthangteq:Greaterthanorequalin:In,thevaluecancontainacomma-separatedlistofvalueslike:Like,thevaluecancontaintheSQLwildcardcharacterslt:Lessthanlteq:Lessthanorequaltomoreq:Moreorequaltoneq:Notequaltonin:Notin;thevaluecancontainacomma-separatedlistofindividualvaluesnotnull:Notnullnull:Null
Combiningtheseconditiontypeswillallowustofiltersearchresultsprettymuchanywaywewant.
Theoptionalsort_ordersparameteranditsstructureunfoldasfollows:
"sort_orders":[
{
"field":"fieldOrAttrName",
"direction":"ASC"
}
]
ThelistofdirectionvaluesincludesASCforascendingandDESCfordescendingsortorders.
ThesearchCriteriaisseeminglythemostcomplex,yetmostpowerfulaspectofasearchAPI.Understandinghowitworksisessentialforeffectivequerying.
![Page 124: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/124.jpg)
SummaryInthischapter,wehavecoveredvaluablewebAPIelements.WelearnedhowtodifferentiatebetweentypesofwebAPIusers,andtheauthenticationandmethodsprovidedtodoso.WealsolearnedhoweasyitistocreateourownAPIswithjustafewlinesofXML.WesawhowtheroutedefinitionallowsforeasybindingbetweenwhatcomesviaanHTTPrequesttowhatexecutesincode,respectingtheaccesslistpermissionsintheprocess.ThevalueofbuildingAPIsaspartofourdistributablemodulesliesintheirextensibility.APIsforceustoembracetheinterfacewayofthinking,thusallowingotherstouseandextendourcodeeasilyandsecurely.Thepreferencemechanismweintroducedinpreviouschapters,throughdi.xmlfiles,allowsotherstochangethebehaviorbehindtheinterfaceeasily.
Movingforward,wearegoingtotakeamorethoroughandroundedlookatbuildinganddistributingourextensionsviaComposerandPackagist.
![Page 125: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/125.jpg)
BuildingandDistributingExtensionsAttheverystartofourjourney,wementionedMagentosourcefilesbeingdistributedviathreedifferentchannels:asourcefilearchive,aGitrepository,andaComposerrepository.TheComposerapproachisthepreferredway.Whetherwearecodingamodule,library,themeorlanguagecomponent,usingtheComposerallowsforaneasyandautomateddependencymanagement,whichisnotpossibleotherwise.Magento'sbuilt-inComponentManagercanupdate,uninstall,enable,ordisableextensionsinstalledviaComposer.ThisimpliessourcesfromPackagist,MagentoMarketplace,orothercomposersources,aslongastheyhaveacomposer.jsonfile.
Movingforward,wearegoingtotakeacloserlookatthefollowingtopics:
BuildingashippingextensionDistributingviaGitHubDistributingviaPackagist
Thetermsmodule,extension,package,andcomponentareusedsomewhatinterchangeablyinMagento.Whiledeveloping,themodule.xmlimpliesmoduleterminology,andregistration.phpimpliescomponentterminology.However,distributingthemviaPackagistandMagentomarketplaceoftenimpliespackageandextensionterminologies.Magento-wise,toallintentsandpurposes,theyrefertothesamething.
![Page 126: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/126.jpg)
TechnicalrequirementsYouwillneedtohavebasicknowledgeofPHP,OOP,JavaScript,andXML.YouwillalsoneedApache,MySQL,andAMPPSinstalledonyoursystemtoexecutethecodes.
ThecodefilesofthischaptercanbefoundonGitHub:https://github.com/PacktPublishing/Magento-2-Quick-Start-Guide.
CheckoutthefollowingvideotoseetheCodeinAction:
http://bit.ly/2xoS5ms.
![Page 127: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/127.jpg)
BuildingashippingextensionOutofthebox,Magentoprovidesseveralshippingmethodsofitsown.Unlikepaymentmethods,whichtendtobelessdiverseamongdifferentwebshops,shippingmethodsareoftenanareaofcustomizationamongmerchants,whichiswhybuildingacustomizedshippingextensionisanessentialskillforeveryMagentodeveloper.
Therearetwotypesofshippingmethods:
online:Theseshippingmethodsbasetheirshippingcalculationontheshippingservicetheyconnectto.TheMagentoOpenSourceincludesfollowingmodulesthatprovideonlineshippingmethods:Magento_Ups,Magento_Usps,Magento_Fedex,Magento_Dhl.offline:Theseshippingmethodsdotheirownshippingcalculation,withoutconnectingtoanexternalservice.TheMagentoOpenSourceincludesabuilt-inMagento_OfflineShippingmodule,whichprovidesFlatRate,TableRate,Free,andStorePickupshippingmethods.
Let'sgoaheadandcreateaMagentoshippingextensionMagelicious_RoyalTrek.TheextensionassumesanimaginaryRoyalTrekcarrier,withtwoofflineshippingmethods:RoyalTrekStandardandRoyalTrek48h.
Wewillstartoffbydefining<MAGELICIOUS_DIR>/RoyalTrek/registration.phpasfollows:
\Magento\Framework\Component\ComponentRegistrar::register(
\Magento\Framework\Component\ComponentRegistrar::MODULE,
'Magelicious_RoyalTrek',
__DIR__
);
Wecanthendefinethe<MAGELICIOUS_DIR>/RoyalTrek/etc/module.xmlasfollows:
<config>
<modulename="Magelicious_RoyalTrek"setup_version="1.0.0"/>
</config>
Withthesetwofilesinplace,Magentoshouldalreadyseeourmodule,whenenabled.
![Page 128: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/128.jpg)
Wecanthengoaheadanddefinethe<MAGELICIOUS_DIR>/RoyalTrek/composer.jsonasfollows:
{
"name":"magelicious/module-royal-trek",
"description":"TheRoyalTrekshipping",
"require":{
"php":"7.0.2|7.0.4|~7.0.6|~7.1.0"
},
"type":"magento2-module",
"version":"1.0.0",
"license":[
"OSL-3.0",
"AFL-3.0"
],
"autoload":{
"files":[
"registration.php"
],
"psr-4":{
"Magelicious\\RoyalTrek\\":""
}
}
}
Wecanthendefinethe<MAGELICIOUS_DIR>/RoyalTrek/etc/adminhtml/system.xmlasfollows:
<config>
<system>
<sectionid="carriers">
<groupid="royaltrek">
<label>RoyalTrekShipping</label>
<fieldid="active"type="select">
<label>Enabled</label>
<source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
</field>
<fieldid="title"type="text">
<label>Title</label>
</field>
<fieldid="sallowspecific"type="select">
<label>ShiptoApplicableCountries</label>
<frontend_class>shipping-applicable-country</frontend_class>
<source_model>Magento\Shipping\Model\Config\Source\Allspecificcountries</source_model>
</field>
<fieldid="specificcountry"type="multiselect">
<label>ShiptoSpecificCountries</label>
<can_be_empty>1</can_be_empty>
<source_model>Magento\Directory\Model\Config\Source\Country</source_model>
</field>
<fieldid="showmethod"type="select"">
<label>ShowMethodifNotApplicable</label>
<source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
</field>
<fieldid="specificerrmsg"type="textarea">
<label>DisplayedErrorMessage</label>
</field>
<fieldid="sort_order"type="text">
<label>SortOrder</label>
<validate>validate-numbervalidate-zero-or-greater</validate>
![Page 129: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/129.jpg)
</field>
</group>
<!--todo...-->
</section>
</system>
</config>
Thissetsthegeneralconfigurationoptionsforourshippingmethods.Thesallowspecific,specificcountry,showmethod,specificerrmsgand,sort_orderarecommonconfigurationelementsofeachshippingmethod,asseenbyexaminingtheMagento\Shipping\Model\Carrier\AbstractCarrierclass.
Wecanthenextendthe<MAGELICIOUS_DIR>/RoyalTrek/etc/adminhtml/system.xmlwiththefollowinggroup:
<!--The"RoyalTrekStandard"specificoptions-->
<groupid="royaltrekstandard">
<label><![CDATA[The"RoyalTrekStandard"shippingmethod]]></label>
<fieldset_css>complex</fieldset_css>
<fieldid="title"type="text">
<label><![CDATA[Title]]></label>
</field>
<fieldid="shippingcost"type="text">
<label><![CDATA[ShippingCost]]></label>
<validate>validate-numbervalidate-zero-or-greater</validate>
</field>
</group>
Weareintroducinganadditionalsetofconfigurationoptionshere,tobeusedwithourRoyalTrekStandardmethod.
So,wethenextendthe<MAGELICIOUS_DIR>/RoyalTrek/etc/adminhtml/system.xmlwiththefollowinggroup:
<!--The"RoyalTrek48h"specificoptions-->
<groupid="royaltrek48hr">
<label><![CDATA[The"RoyalTrek48h"shippingmethod]]></label>
<fieldset_css>complex</fieldset_css>
<fieldid="title"type="text">
<label><![CDATA[Title]]></label>
</field>
<fieldid="shippingcost"type="text">
<label><![CDATA[ShippingCost]]></label>
<validate>validate-numbervalidate-zero-or-greater</validate>
</field>
</group>
Weareintroducinganadditionalsetofconfigurationoptionshere,tobeusedwithourRoyalTrek48hmethod.
Wethendefinethe<MAGELICIOUS_DIR>/RoyalTrek/etc/config.xmlasfollows:
![Page 130: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/130.jpg)
<config>
<default>
<carriers>
<royaltrek>
<!--DEFAULTSHERE-->
</royaltrek>
</carriers>
</default>
</config>
Theconfig>default>carriers>royaltreknestingpathmatchesthenestingpathofthesystem.xmlelements.Wethenreplacethe<!--DEFAULTSHERE-->withfollowing:
<active>1</active>
<title>RoyalTrekShipping</title>
<sallowspecific>0</sallowspecific>
<showmethod>0</showmethod>
<specificerrmsg>TheRoyalTrekshippingisnotavailable.</specificerrmsg>
<sort_order>10</sort_order>
<model>Magelicious\RoyalTrek\Model\Carrier\RoyalTrek</model>
<royaltrekstandard>
<title><![CDATA[RoyalTrekStandard]]></title>
<shippingcost>4.99</shippingcost>
</royaltrekstandard>
<royaltrek48hr>
<title><![CDATA[RoyalTrek48h]]></title>
<shippingcost>9.99</shippingcost>
</royaltrek48hr>
Withthis,wecansetthedefaultvaluesforeachoftheconfigurationoptionsmadeavailableviasystem.xml.
Wethendefinethe<MAGELICIOUS_DIR>/Model/Carrier/RoyalTrek.phpasfollows:
<?php
namespaceMagelicious\RoyalTrek\Model\Carrier;
classRoyalTrekextends\Magento\Shipping\Model\Carrier\AbstractCarrierimplements
\Magento\Shipping\Model\Carrier\CarrierInterface{
constCARRIER_CODE='royaltrek';
constROYAL_TREK_STANDARD='royaltrekstandard';
constROYAL_TREK_48HR='royaltrek48hr';
protected$_code=self::CARRIER_CODE;
protected$_isFixed=true;
protected$_rateResultFactory;
protected$_rateMethodFactory;
publicfunction__construct(
\Magento\Framework\App\Config\ScopeConfigInterface$scopeConfig,
\Magento\Quote\Model\Quote\Address\RateResult\ErrorFactory$rateErrorFactory,
\Psr\Log\LoggerInterface$logger,
\Magento\Shipping\Model\Rate\ResultFactory$rateResultFactory,
\Magento\Quote\Model\Quote\Address\RateResult\MethodFactory$rateMethodFactory,
array$data=[]
){
![Page 131: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/131.jpg)
$this->_rateResultFactory=$rateResultFactory;
$this->_rateMethodFactory=$rateMethodFactory;
parent::__construct($scopeConfig,$rateErrorFactory,$logger,$data);
}
publicfunctioncollectRates(\Magento\Quote\Model\Quote\Address\RateRequest$request){
if(!$this->getConfigFlag('active')){
returnfalse;
}
$result=$this->_rateResultFactory->create();
//Todo...
return$result;
}
publicfunctiongetAllowedMethods(){
return[
self::ROYAL_TREK_STANDARD=>$this->getConfigData(self::ROYAL_TREK_STANDARD.'/title'),
self::ROYAL_TREK_48HR=>$this->getConfigData(self::ROYAL_TREK_48HR.'/title'),
];
}
privatefunctiongetMethodTitle($method){
return$this->getConfigData($method.'/title');
}
privatefunctiongetMethodPrice($method){
return$this->getMethodCost($method);
}
privatefunctiongetMethodCost($method){
return$this->getConfigData($method.'/shippingcost');
}
}
ThebasicimplementationoftheMagelicious\RoyalTrek\Model\Carrier\RoyalTrekclassishighlydeterminedbytheimplementationofitsunderlyingMagento\Shipping\Model\Carrier\AbstractCarrierparentclassandMagento\Shipping\Model\Carrier\CarrierInterfaceinterface.Thebareminimumimpliessettingupthe$_codevalueandimplementingthecollectRatesmethod.The$_codevalueisanextremelyimportantbitofinformationhere.Weneedtomakesureitisuniqueamongalloftheenabledshippingextensions.ThecollectRatesmethodiswheretheactualshippingcalculationimplementationhappens.
Let'sgoaheadandextendthe<MAGELICIOUS_DIR>/Model/Carrier/RoyalTrek.phpwiththefollowing:
$method=$this->_rateMethodFactory->create();
$method->setCarrier($this->_code);
$method->setCarrierTitle($this->getConfigData('title'));
$method->setMethod(self::ROYAL_TREK_STANDARD);
$method->setMethodTitle($this->getMethodTitle($method->getMethod()));
$method->setPrice($this->getMethodPrice($method->getMethod()));
$method->setCost($this->getMethodCost($method->getMethod()));
$method->setErrorMessage(__('The%1methoderrormessagehere.'));
![Page 132: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/132.jpg)
$result->append($method);
Usingthefactory,wecancreateaninstanceofMagento\Quote\Model\Quote\Address\RateResult\Method.Thisistheindividualshippingmethodthatwewishtomakeavailableasachoiceduringcheckout.Wethensettherequiredvaluesforthecarrier:method,price,cost,andpossibleerrormessage.Withourroyaltrekstandardmethodproperlyset,wefinallypassitontothe$resultobject.
Let'sfurtherextendthe<MAGELICIOUS_DIR>/Model/Carrier/RoyalTrek.phpwiththefollowing:
$method=$this->_rateMethodFactory->create();
$method->setCarrier($this->_code);
$method->setCarrierTitle($this->getConfigData('title'));
$method->setMethod(self::ROYAL_TREK_48HR);
$method->setMethodTitle($this->getMethodTitle($method->getMethod()));
$method->setPrice($this->getMethodPrice($method->getMethod()));
$method->setCost($this->getMethodCost($method->getMethod()));
$method->setErrorMessage(__('The%1methoderrormessagehere.'));
$result->append($method);
Muchlikewiththepreviousexample,hereweshouldaddourroyaltrek48hrtothe$resultobject.
TheendresultshouldbringforthourtwoRoyalTrekshippingmethodstothestorefrontcheckoutShippingstep,asfollows:
TheOrderSummarysectionoftheReview&PaymentsstepshouldalsoreflectonthemethodselectedintheShippingstep,asfollows:
![Page 133: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/133.jpg)
Likewise,theadminCreateNewOrderscreensshouldalsoshowourRoyalTrekshippingmethodsasfollows:
Finally,thesuccessfullymadeordershouldreflecttheRoyalTrek48hshippingmethodselectioninitsneworderemail,andthecustomer'sMyAccountarea,asfollows:
Withourshippingmethodsconfirmedasworking,let'sgoaheadandlookforawayofdistributingit.
![Page 134: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/134.jpg)
DistributingviaGitHubBydefault,thePackagistrepositoryistheonlyregisteredrepositoryinComposer.WecanaddmorerepositoriestoourMagentoprojectbydeclaringthemincomposer.json.Thiswaywegettoregisterourowngitrepositoryasasourceofpackages,asfollows:
composerconfigrepositories.magelicious-royal-trekgitgit@github.com:foggyline/Magelicious_RoyalTrek.git
Thiscommandresultsinthemodifiedcomposer.jsonfile,withtherepositorieskeyamendedasfollows:
"repositories":{
"0":{
"type":"composer",
"url":"https://repo.magento.com/"
},
"magelicious-royal-trek":{
"type":"git",
"url":"[email protected]:foggyline/Magelicious_RoyalTrek.git"
}
},
Wecanseeourmagelicious-royal-trekentryaddedinthere.ThegitvalueusedforthetypekeytellstheComposerweareusingthegitrepository,locatedattheURLprovidedviatheurlkey.Thecomposerandgitarenottheonlytwovaluessupportedforthetype.Theactualtypevaluecouldhaveeasilybeenanyothertypeofsupportedversioncontrolsystem:
Git(git-scm.com)Subversion(subversion.apache.org)Mercurial(mercurial-scm.org)Fossil(fossil-scm.org)
Wecouldalsohavesimplyusedthevcsvalueforthetypekey,andreliedonComposer'sVCSdrivertoautomaticallydetectthetypebasedurlvalue.
Ifwenowexecutecomposerrequiremagelicious/royal-trek:dev-master,Composerwillinstallourshippingmodule.Whilethisnewrepositoriesapproachworkswell,itissomewhatmoresuitedfordistributingprivateMagentoextensions.Wheneverwewishtodistributeourextensionpublicly,aPackagistisamoreconvenient
![Page 135: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/135.jpg)
waytogo.
![Page 136: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/136.jpg)
DistributingviaPackagistPackagistisafreeonlinerepositoryserviceforComposerpackages.WecanuseittoeasilydistributeourfreeMagentomodules.ThefactthatPackagistisadefaultComposerrepository,makesitthedefactorepositoryforanyComposeruser.ThisiswhyhavingourfreeMagentomodulesavailableviaPackagistisapreferredwayofdistribution.
PushingourMagentomoduletoPackagistisquiteeasy.Assumingwehaveouraccountcreated,weshouldstartbyclickingontheSubmitbutton,whichwilllandusonthefollowingscreen:
WeneedtoprovidealinktoourGitrepositoryhere,andclicktheCheckbutton,followedbytheSubmitbutton,ifthevalidrepositorywasfound.Thisshouldcreateourpackage,asperthefollowingscreen:
![Page 137: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/137.jpg)
ThePackagistsaysthatourcreatedpackageisnowavailableforuseviathecomposerrequiremagelicious/module-royal-trekcommand.However,runningthiscommandnowwouldbelikelytogiveusthefollowingerror:
[InvalidArgumentException]
Couldnotfindamatchingversionofpackagemagelicious/module-royal-trek.Checkthepackagespelling,yourversionconstraintandthatthepackageisavailableinastabilitywhichmatchesyourminimum-stability(stable).
Noticethedev-masterlabelonourPackagistscreen.OurbranchesautomaticallyappearasdevversionsinPackagist.Therefore,wecanusethecomposerrequiremagelicious/module-royal-trek:dev-mastercommandtofetchthepackage.Tochangethat,weneedtospecificallytagourgitcommits,asfollows:
gitadd.
gitcommit-a-m'TheRoyalTrekshippingmodule,firstversion.'
gittag1.0.0
gitpushorigin1.0.0
![Page 138: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/138.jpg)
Oncewehavedonethat,wecangobacktothePackagistpackagescreenandhittheUpdatebutton.Thisshouldnowshowour1.0.0version:
Ifwespecifyaversionwhenrequiringthepackage,Composerfetchesthelatesttaggedversionfromthemaster.Forexample,composerrequiremagelicious/module-royal-trek:2.4.xtakesthelatest2.4taggedversionfromthemasterbranch.
Whenitcomestoversioning,itisworthnotingthatsetup_versionfoundinmodule.xml,andversionfoundincomposer.jsonaretwodifferenttypesofversioning.Magentoreferstothemasmarketingversionandcomposerversion.Marketingversionmightbethoughtofassomethingthemerchantinteractswith,whileComposerversionissomethingthatdevelopersinteractwith.TheMagento_Catalogmodule,forexample,usesthe2.2.4marketingversionformarketing,whereasitsComposerversionis102.0.4.Thisisnottosaythatwecannotusethesameversioningforboth,aslongaswerememberthatthesetup_version,foundinmodule.xml,iswhatdrivesoursetupscripts.
DistributingfuturenewversionsofourMagelicious_RoyalTrekmodulewould,therefore,comedownto:
1. Bumpingupthesetup_versionfoundinmodule.xml2. Bumpinguptheversionfoundincomposer.json
![Page 139: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/139.jpg)
3. AddressinganynecessaryMagentosetupscripts4. CommittingourchangestoGit,withproperversiontagging5. MakingsuretheUpdateistriggeredonthePackagistscreenofourmodule
editscreen
UsingthePackagist'sservicehookwecanensurethatourpackagewillalwaysbeupdatedautomatically.Seehttps://packagist.org/about#how-to-update-packagesformoreinformation.
![Page 140: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/140.jpg)
SummaryInthischapter,welearnedhowtocreateasimpleshippingmodule.Wesawhoweasyitistoaddspecificshippingcalculationsaspartofofflineshippingmethods.WethenpackagedthismoduleanddistributeditviaPackagist.Thismadeiteasyfortheendconsumertouseourmodule,withjustafewsimpleconsolecommands.Likewise,anyfutureupdatestoourmoduleshouldbefrictionlessfortheendconsumer,ascomposercaneasilyhandlethoseviasimplecomposerupdatecommands.
Movingforward,wearegoingtotakealookatsomeofthespecificsofMagentoadminareadevelopment.
![Page 141: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/141.jpg)
DevelopingforAdminAttheverybeginningofourjourney,backinChapter1,UnderstandingMagentoArchitecture,wementionedhowMagentoconsistsofdifferentareas.DevelopingforMagentoadminimpliesdevelopingfortheadminhtmlarea.Whilethemajorityofcodeisapplicableacrossdifferentareas,therearecertainsubtledifferences.UnlikefrontendwhichismostlybuiltviaHTML(.phtml,.html),theMagentoadminhtmlareaismostlybuiltviaUIcomponentswhicharereferenced,stacked,andconfiguredthrough.xmlfiles.Thisisnottosaythatthesamecomponentscannotbeusedbothforfrontendandadmin,becauseallUIcomponentscanbeconfiguredforbothoftheseareas;wejustneedtoconfigurestylesmanuallyforcomponentsonthefrontend.
TherearetwobasicUIcomponentsinMagento:listingandform.Therestaresecondarycomponents,whichserveasextensionsofbasiccomponents:listingToolbar,columns,filters,column,form,andfield.
Togetabetterunderstandingoftheadminhtmlarea,wearegoingtobuildaMagelicious_Minventorymodule,usingsomeofthesecomponents.Theideabehindthemoduleistoprovideacustomlistinginterfaceforalimitedsetofusers,wheretheycaneasilybumpuptheproductstockincertainincrementswithoutevergettingaccesstootherareasoftheMagentoadmin.
Ourworkherewillconsistoftwomajorparts:
UsingthelistingcomponentUsingtheformcomponent
Tokeepthingscompact,wewillusethe<MODULE_DIR>toreferencetheMAGELICIOUS_DIR>/Minventorydirectory.
![Page 142: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/142.jpg)
TechnicalrequirementsYouwillneedtohavebasicknowledgeofPHP,OOP,JavaScript,andXML.YouwillalsoneedApache,MySQL,andAMPPSinstalledonyoursystemtoexecutethecodes.
ThecodefilesofthischaptercanbefoundonGitHub:https://github.com/PacktPublishing/Magento-2-Quick-Start-Guide.
CheckoutthefollowingvideotoseetheCodeinAction:
http://bit.ly/2xuoFDL.
![Page 143: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/143.jpg)
UsingthelistingcomponentThelistingisabasiccomponentresponsibleforrenderinggrids,lists,andtiles,providingfiltering,pagination,sorting,andotherfeatures.ThelistingElementsgroupreferencedinthevendor/magento/module-ui/etc/ui_configuration.xsdfileprovidesanicelistofbothprimaryandsecondarylistingcomponents:
actions component file massaction select
actionsColumn container filters modal selectionsColumn
bookmark dataSource form multiline tab
boolean dataProvider hidden multiselect text
button date htmlContent nav textarea
checkbox dynamicRows input number wysiwyg
checkboxset email insertForm paging
column exportButton insertListing price
columns field listing range
columnsControls fieldset listingToolbar radioset
Thekeytousingallofthesecomponentsistounderstand:
Whatparametersindividualcomponentsaccept—furtherrevealedbydefinitionsfoundinthevendor/magento/moduleui/view/base/ui_component/etc/definitiondirectoryWhatchildcomponentsindividualcomponentsaccept—forexample,theemailcomponentcannotbenestedwithinthedataProvidercomponent
Movingforward,wewillusethelistingcomponent,andafewofitssecondarycomponentstocreatetheMicroInventoryscreenasshown:
![Page 144: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/144.jpg)
ThegriditselfistoconsistofID,SKU,Status,Quantity,andActioncolumns.TheResupplyactionwilltriggerredirectiontoacustomStockResupplyscreen,whichwewilladdressinthenextsection.TheActionsselectorintheupperleftcorneristoconsistoftwocustomactions,allowingforfixedproductstockincreases.
Assumingwehavedefinedourbasicregistration.php,composer.json,andetc/module.xmlfiles,wecanstartdealingwiththespecificsofourmodule.
Let'sstartbydefiningthe<MODULE_DIR>/etc/acl.xmlasfollows:
<config>
<acl>
<resources>
<resourceid="Magento_Backend::admin">
<resourceid="Magento_Catalog::catalog">
<resourceid="Magento_Catalog::catalog_inventory">
<resourceid="Magelicious_Minventory::minventory"title="MicroInventory"/>
</resource>
</resource>
</resource>
</resources>
</acl>
</config>
Therequirementofourmodulewastoprovideacustomlistinginterfaceforalimitedsetofusers.Theaccesslistentry,laterreferencedbyouradmincontroller,ensuresjustthat.ThechoicetonestourMagelicious_Minventory::minventoryasachildofMagento_Catalog::catalog_inventoryisbasedmerelyonlogicalgrouping,asourmoduledealswithinventorystock.We
![Page 145: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/145.jpg)
shouldnowbeabletoseeMicroInventoryunderRolesResourcesasshown:
Wethendefinethe<MODULE_DIR>/etc/adminhtml/routes.xmlasfollows:
<config>
<routerid="admin">
<routeid="minventory"frontName="minventory">
<modulename="Magelicious_Minventory"/>
</route>
</router>
</config>
Thiswillallowustoaccessourcontrolleractionslateronviahttp://magelicious.loc/index.php/<admin>/minventory/<controller>/<action>links.
Wethendefinethe<MODULE_DIR>/etc/adminhtml/menu.xmlasfollows:
<config>
<menu>
<addid="Magelicious_Minventory::minventory"
title="MicroInventory"translate="title"
module="Magelicious_Minventory"sortOrder="100"
parent="Magento_Catalog::inventory"
action="minventory/product/index"
resource="Magelicious_Minventory::minventory"/>
</menu>
</config>
ThispositionsourMicroInventorymenurightunderthemainCatalog|CATALOGUEmenu,asshown:
![Page 146: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/146.jpg)
Whenclicked,themenu'sminventory/product/indexactionwillthrowusat<MODULE_DIR>/Controller/Adminhtml/Product/Index.php,whichwillbeaddressedlateron.
Wethendefinethe<MODULE_DIR>/Model/Resupply.phpasfollows:
namespaceMagelicious\Minventory\Model;
classResupply
{
protected$productRepository;
protected$collectionFactory;
protected$stockRegistry;
publicfunction__construct(
\Magento\Catalog\Api\ProductRepositoryInterface$productRepository,
\Magento\Catalog\Model\ResourceModel\Product\CollectionFactory$collectionFactory,
\Magento\CatalogInventory\Api\StockRegistryInterface$stockRegistry
)
{
$this->productRepository=$productRepository;
$this->collectionFactory=$collectionFactory;
$this->stockRegistry=$stockRegistry;
}
publicfunctionresupply($productId,$qty)
{
$product=$this->productRepository->getById($productId);
$stockItem=$this->stockRegistry->getStockItemBySku($product->getSku());
$stockItem->setQty($stockItem->getQty()+$qty);
$stockItem->setIsInStock((bool)$stockItem->getQty());
$this->stockRegistry->updateStockItemBySku($product->getSku(),$stockItem);
}}
Thisclasswillserveasacentralizedstockupdaterforourmodule,whichwillbeupdatingstockfromtheActionsselectorfoundontheMicroInventoryscreen,aswellasfromtheSavebuttonactiontriggeredontheStockResupplyscreen.
Wethendefinethe<MODULE_DIR>/Controller/Adminhtml/Product.phpasfollows:
namespaceMagelicious\Minventory\Controller\Adminhtml;
abstractclassProductextends\Magento\Backend\App\Action
{
![Page 147: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/147.jpg)
constADMIN_RESOURCE='Magelicious_Minventory::minventory';
}
Thisisourcontrollerfile,theparentofthecontrolleractionsthatwewillsoondefine.WesetthevalueofitsADMIN_RESOURCEconstanttothatdefinedinouracl.xmlfile.Thiswillempowerourcontrollertoonlyallowaccesstouserswithproperresourceroles.
Wethendefinethe<MODULE_DIR>/Controller/Adminhtml/Product/Index.phpasfollows:
namespaceMagelicious\Minventory\Controller\Adminhtml\Product;
use\Magento\Framework\Controller\ResultFactory;
classIndexextends\Magelicious\Minventory\Controller\Adminhtml\Product
{
publicfunctionexecute()
{
$resultPage=$this->resultFactory->create(ResultFactory::TYPE_PAGE);
$resultPage->getConfig()->getTitle()->prepend((__('MicroInventory')));
return$resultPage;
}
}
Thiscontrolleractiondoesnotreallydoanythingspecial.Asidefromsettingupthescreentitle,itmerelyprovidesamechanismforloadingtheminventory_product_index.xmlthatwewilladdresslateron.
Wethendefinethe<MODULE_DIR>/Controller/Adminhtml/Product/MassResupply.phpasfollows:
namespaceMagelicious\Minventory\Controller\Adminhtml\Product;
use\Magento\Framework\Controller\ResultFactory;
classMassResupplyextends\Magelicious\Minventory\Controller\Adminhtml\Product
{
protected$filter;
protected$collectionFactory;
protected$resupply;
publicfunction__construct(
\Magento\Backend\App\Action\Context$context,
\Magento\Ui\Component\MassAction\Filter$filter,
\Magento\Catalog\Model\ResourceModel\Product\CollectionFactory$collectionFactory,
\Magelicious\Minventory\Model\Resupply$resupply
)
{
parent::__construct($context);
$this->filter=$filter;
$this->collectionFactory=$collectionFactory;
$this->resupply=$resupply;
}
![Page 148: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/148.jpg)
publicfunctionexecute()
{
$redirectResult=$this->resultFactory->create(ResultFactory::TYPE_REDIRECT);
$qty=$this->getRequest()->getParam('qty');
$collection=$this->filter->getCollection($this->collectionFactory->create());
$productResupplied=0;
foreach($collection->getItems()as$product){
$this->resupply->resupply($product->getId(),$qty);
$productResupplied++;
}
$this->messageManager->addSuccessMessage(__('Atotalof%1record(s)havebeenresupplied.',$productResupplied));
return$redirectResult->setPath('minventory/product/index');
}
}
ThiscontrolleractionwillbetriggeredbytheResupply+10andResupply+50actionsfromtheMicroInventoryscreen.WecanseeitusingtheMagento\Ui\Component\MassAction\Filtertoprocessthemassselectoptions,bindingtheminternallytoproductcollectioninordertofilterproductswehaveselectedproperly.
Wethendefinethe<MODULE_DIR>/view/adminhtml/layout/minventory_product_index.xmlasfollows:
<page>
<updatehandle="styles"/>
<body>
<referenceContainername="content">
<uiComponentname="minventory_listing"/>
</referenceContainer>
</body>
</page>
Thisisthelayoutfilethatgetstriggeredwhenwelandon<MODULE_DIR>/Controller/Adminhtml/Product/Index.php.Thenameofthefilematchesthe<routeName>/<controllerName>/<controllerActionName>path.Theactuallayoutheremerelyreferencesthecontentcontainer,towhichitaddstheminventory_listingcomponentusingtheuiComponentelement.
Wethendefinethe<MODULE_DIR>/view/adminhtml/ui_component/minventory_listing.xmlasfollows:
<listing>
<argumentname="data"xsi:type="array">
<itemname="js_config"xsi:type="array">
<itemname="provider"xsi:type="string">minventory_listing.minventory_listing_data_source</item>
</item>
</argument>
![Page 149: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/149.jpg)
<settings>
<spinner>minventory_columns</spinner>
<deps>
<dep>minventory_listing.minventory_listing_data_source</dep>
</deps>
</settings>
<!--dataSource-->
<!--listingToolbar-->
<!--columns-->
</listing>
Thisisourlistingcomponent.Theminventory_listing.minventory_listing_data_sourceisourdatasourcedefinedunderthedataSourceelement.
Wethenmodifytheminventory_listing.xmlbyreplacingthe<!--dataSource-->withthefollowing:
<dataSourcename="minventory_listing_data_source"component="Magento_Ui/js/grid/provider">
<settings>
<storageConfig>
<paramname="indexField"xsi:type="string">entity_id</param>
</storageConfig>
<updateUrlpath="mui/index/render"/>
</settings>
<dataProviderclass="Magelicious\Minventory\Ui\DataProvider\Product\ProductDataProvider"name="minventory_listing_data_source">
<settings>
<requestFieldName>id</requestFieldName>
<primaryFieldName>entity_id</primaryFieldName>
</settings>
</dataProvider>
</dataSource>
ThemostimportantpartofthedataSourcecomponentisitsdataProvider.WesetitsvaluetoMagelicious\Minventory\Ui\DataProvider\Product\ProductDataProvider.TherequestFieldNameandprimaryFieldNamearenotreallythatimportantinourcase,aswearenotreallyoperatingwithfullCRUDontheproductentity,sincewearemerelyfocusingonupdatingthequantitythroughafewlinesofcustomcode.Still,thecomponentitselfrequiresacertainminimalconfiguration,soweusewhatwewouldnormallyuseforaproductentity,butthesecanreallybeanyvaluesfoundonanentity.
Wethendefinethe<MODULE_DIR>/Ui/DataProvider/Product/ProductDataProvider.phpasfollows:
classProductDataProviderextends\Magento\Ui\DataProvider\AbstractDataProvider{
protected$collection;
publicfunction__construct(
string$name,
string$primaryFieldName,
string$requestFieldName,
![Page 150: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/150.jpg)
\Magento\Catalog\Model\ResourceModel\Product\CollectionFactory$collectionFactory,
array$meta=[],
array$data=[]
){
parent::__construct(
$name,
$primaryFieldName,
$requestFieldName,
$meta,
$data
);
$this->collection=$collectionFactory->create();
}
publicfunctiongetData(){
if(!$this->getCollection()->isLoaded()){
$this->getCollection()->load();
}
$items=$this->getCollection()->toArray();
return[
'totalRecords'=>$this->getCollection()->getSize(),
'items'=>array_values($items),
];
}
}
ThecollectionpropertyissetmandatorilybytheparentMagento\Ui\DataProvider\AbstractDataProvider,sowehavetosetitsvaluetosomekindofcollection.Sinceweareworkingwithproducts,itonlymakessensetosetittoanexistingMagento\Catalog\Model\ResourceModel\Product\Collection,thusavoidingcreatingourowncollection.ThekeymethodforourlistingcomponentisgetData.Thismethodfeedsthelistingcomponentwiththenumberofrecordsinthedatacollection,aswellasthedatacollectionitself.
WethenextendtheProductDataProvider.phpwiththefollowing:
protectedfunctionjoinQty(){
if($this->getCollection()){
$this->getCollection()->joinField(
'qty',
'cataloginventory_stock_item',
'qty',
'product_id=entity_id'
);
}
}
Theqtyfieldisnotpartofthedefaultproductscollection,sowehavetojointheqtyinformationfromthecataloginventory_stock_itemtabletoit.Wemustmakesuretocallthismethodbeforeourcollectionisloaded.
Wethenmodifytheminventory_listing.xmlbyreplacingthe<!--listingToolbar-->
![Page 151: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/151.jpg)
withthefollowing:
<listingToolbarname="listing_top">
<bookmarkname="bookmarks"/>
<columnsControlsname="columns_controls"/>
<filtersname="listing_filters"/>
<pagingname="listing_paging"/>
<--massaction-->
</listingToolbar>
ThelistingToolbarcomponentisessentiallyacontainerforthelisting-relatedelementslikepaging,massactions,filters,andbookmarks.Thebookmarkcomponentstorestheactiveandchangedstatesofdatagrids.Thepagingcomponentprovidesnavigationthroughthepagesofthecollection,otherwise,wewouldbeforcedtoviewtheentirecollectionatonce,whichwouldnotreallybeaperformance-efficientapproach.Thefilterscomponentisresponsibleforrenderingfilters'interfacesandapplyingtheactualfiltering.Thisincludesthestatesoffilters,columns'positions,appliedsorting,pagination,andsoon.
ThecolumnsControlscomponentallowsustomodifythevisibilityofthelistingcolumns,shownasfollows:
ThepossibilityoffilteringbyStoreView,asshownintheprecedingscreenshot,iseasilyaddedbymodifyingtheminventory_listing.xmlasfollows:
<filtersname="listing_filters">
<filterSelectname="store_id"provider="${$.parentName}">
<settings>
<optionsclass="Magento\Store\Ui\Component\Listing\Column\Store\Options"/>
<captiontranslate="true">AllStoreViews</caption>
<labeltranslate="true">StoreView</label>
<dataScope>store_id</dataScope>
</settings>
![Page 152: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/152.jpg)
</filterSelect>
</filters>
HereweusedthefilterSelectcomponent,withtheMagento\Store\Ui\Component\Listing\Column\Store\Optionsclasspassedasanoptionsparameter.ThisshowshoweasyitistocombinevariouscomponentsandtopulldatafromPHPclasses.
Let'smodifytheminventory_listing.xmlfurtherbyreplacingthe<--massaction-->withthefollowing:
<massactionname="listing_massaction"component="Magento_Ui/js/grid/tree-massactions">
<actionname="resupply">
<settings>
<type>resupply</type>
<labeltranslate="true">Resupply</label>
<actions>
<actionname="0">
<type>resupply_10</type>
<labeltranslate="true">Resupply+10</label>
<urlpath="minventory/product/massResupply">
<paramname="qty">10</param>
</url>
</action>
<actionname="1">
<type>resupply_50</type>
<labeltranslate="true">Resupply+50</label>
<urlpath="minventory/product/massResupply">
<paramname="qty">50</param>
</url>
</action>
</actions>
</settings>
</action>
</massaction>
Usingtheactioncomponent,wedefinetheResupply+10andResupply+50actionsusedinthescopeofthemassactioncomponent.
Wethenmodifytheminventory_listing.xmlbyreplacingthe<!--columns-->withthefollowing:
<columnsname="minventory_columns"class="Magento\Catalog\Ui\Component\Listing\Columns">
<settings>
<childDefaults>
<paramname="fieldAction"xsi:type="array">
<itemname="provider"xsi:type="string">minventory_listing.minventory_listing.minventory_columns.actions</item>
<itemname="target"xsi:type="string">applyAction</item>
<itemname="params"xsi:type="array">
<itemname="0"xsi:type="string">resupply</item>
<itemname="1"xsi:type="string">${$.$data.rowIndex}</item>
</item>
</param>
</childDefaults>
![Page 153: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/153.jpg)
</settings>
<!--columns#2-->
</columns>
Thecolumnscomponentdefinition,alongwithitschildcomponents,islikelytotakethebiggestchunkofourlistingconfiguration.Thisiswhereweaddourselectioncolumns,regularcolumns,andactioncolumns.
Todemonstratethatfurther,wereplacethe<!--columns#2-->withthefollowing:
<selectionsColumnname="ids"sortOrder="0">
<settings>
<indexField>entity_id</indexField>
</settings>
</selectionsColumn>
<columnname="entity_id"sortOrder="10">
<settings>
<filter>textRange</filter>
<labeltranslate="true">ID</label>
<sorting>asc</sorting>
</settings>
</column>
<columnname="sku"sortOrder="20">
<settings>
<filter>text</filter>
<labeltranslate="true">SKU</label>
</settings>
</column>
<columnname="qty"sortOrder="30">
<settings>
<addField>true</addField>
<filter>textRange</filter>
<labeltranslate="true">Quantity</label>
</settings>
</column>
<actionsColumnname="resupply"class="Magelicious\Minventory\Ui\Component\Listing\Columns\Resupply"sortOrder="40">
<settings>
<indexField>entity_id</indexField>
</settings>
</actionsColumn>
TheactionsColumnpointstoacustomMagelicious\Minventory\Ui\Component\Listing\Columns\Resupplyclass,whichwedefineunder<MODULE_DIR>/Ui/Component/Listing/Columns/Resupply.phpasfollows:
classResupplyextends\Magento\Ui\Component\Listing\Columns\Column{
protected$urlBuilder;
publicfunction__construct(
\Magento\Framework\View\Element\UiComponent\ContextInterface$context,
\Magento\Framework\View\Element\UiComponentFactory$uiComponentFactory,
\Magento\Framework\UrlInterface$urlBuilder,
array$components=[],
array$data=[]
){
$this->urlBuilder=$urlBuilder;
parent::__construct($context,$uiComponentFactory,$components,$data);
![Page 154: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/154.jpg)
}
publicfunctionprepareDataSource(array$dataSource){
if(isset($dataSource['data']['items'])){
$storeId=$this->context->getFilterParam('store_id');
foreach($dataSource['data']['items']as&$item){
$item[$this->getData('name')]['resupply']=[
'href'=>$this->urlBuilder->getUrl(
'minventory/product/resupply',
['id'=>$item['entity_id'],'store'=>$storeId]
),
'label'=>__('Resupply'),
'hidden'=>false,
];
}
}
return$dataSource;
}
}
TheprepareDataSourcemethodiswhereweinjectourmodifications.Wetraversethe$dataSource['data']['items']structureuntilwecomeacrossourcolumn,andthenmodifyitaccordinglywithaproperhrefvalue.This,inturn,rendersourresupplyactionscolumnaspertheMicroInventoryscreen.
WiththeMicroInventoryscreennowsortedviathelistingcomponent,let'sshiftourfocusontotheStockResupplyscreenbuiltviatheformcomponent.
![Page 155: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/155.jpg)
UsingtheformcomponentTheformisabasiccomponentresponsibleforperformingCRUDoperationsonanentity.ThelistingElementsgroupreferencedundervendor/magento/module-ui/etc/ui_configuration.xsdfileprovidesanicelistofbothprimaryandsecondaryformcomponents:
bookmark dataProvider fileUploader massaction range
boolean date form modal radioset
button dynamicRows hidden multiline select
checkbox email htmlContent multiselect tab
checkboxset exportButton input nav text
component field insertForm number textarea
container fieldset insertListing paging wysiwyg
dataSource file listing price
Movingforward,wewillusetheformcomponent,andafewofitssecondarycomponentstocreatetheStockResupplyscreenasshown:
![Page 156: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/156.jpg)
TheformitselfistoconsistofStockand+Qtyfields.TheStockfieldwillbearead-onlyfieldconsistingofanSKU+currentqtystring.TheBackbuttonwilltakeusbacktotheMicroInventorylisting,whereastheSavebuttonwillposttheformtoaspecialResupplycontrolleraction,whichwillthenincreasethestockbyagiven+Qtyamount.TheActionsselectorintheupperleftcorneristoconsistoftwocustomactions,allowingforfixedproductstockincreases.
Westartoffbydefiningthe<MODULE_DIR>/Controller/Adminhtml/Product/Resupply.phpasfollows:
use\Magento\Framework\Controller\ResultFactory;
classResupplyextends\Magelicious\Minventory\Controller\Adminhtml\Product{
protected$stockRegistry;
protected$productRepository;
protected$resupply;
publicfunction__construct(
\Magento\Backend\App\Action\Context$context,
\Magento\Catalog\Api\ProductRepositoryInterface$productRepository,
\Magento\CatalogInventory\Api\StockRegistryInterface$stockRegistry,
\Magelicious\Minventory\Model\Resupply$resupply
){
parent::__construct($context);
$this->productRepository=$productRepository;
$this->stockRegistry=$stockRegistry;
$this->resupply=$resupply;
}
publicfunctionexecute(){
if($this->getRequest()->isPost()){
$this->resupply->resupply(
$this->getRequest()->getParam('id'),
$_POST['minventory_product']['qty']
);
$this->messageManager->addSuccessMessage(__('Successfullyresupplied'));
$redirectResult=$this->resultFactory->create(ResultFactory::TYPE_REDIRECT);
return$redirectResult->setPath('minventory/product/index');
}else{
$resultPage=$this->resultFactory->create(ResultFactory::TYPE_PAGE);
$resultPage->getConfig()->getTitle()->prepend((__('StockResupply')));
return$resultPage;
}
}
}
Giventhesimplicityofourform,usingtheisPost()checkontherequestobject,weallowourselvestousethesamecontrolleractionforrenderingtheStockResupplyscreen,aswellassubmittingthesaveactiontoit.
Withcontrolleractioninplace,wethendefinethe<MODULE_DIR>/view/adminhtml/layout/minventory_product_resupply.xmlasfollows:
![Page 157: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/157.jpg)
<page>
<updatehandle="styles"/>
<body>
<referenceContainername="content">
<uiComponentname="minventory_resupply_form"/>
</referenceContainer>
</body>
</page>
Muchlikewiththeformlisting,thislayoutfilemerelycallstheminventory_resupply_formcomponent,whichiswhereallourvisualelementsoftheStockResupplyscreenreside.
Wethendefinethe<MODULE_DIR>/view/adminhtml/ui_component/minventory_resupply_form.xmlasfollows:
<form>
<argumentname="data"xsi:type="array">
<itemname="js_config"xsi:type="array">
<itemname="provider"xsi:type="string">minventory_resupply_form.minventory_resupply_form_data_source</item>
<itemname="deps"xsi:type="string">minventory_resupply_form.minventory_resupply_form_data_source</item>
</item>
<itemname="layout"xsi:type="array">
<itemname="type"xsi:type="string">tabs</item>
</item>
</argument>
<settings>
<buttons>
<buttonname="save"class="Magelicious\Minventory\Block\Adminhtml\Product\Edit\Button\Save"/>
<buttonname="back"class="Magelicious\Minventory\Block\Adminhtml\Product\Edit\Button\Back"/>
</buttons>
</settings>
<!--dataSource-->
<!--fieldset-->
</form>
Muchlikethelistingcomponent,theformcomponentalsorequiresadataprovider.
Wethenmodifytheminventory_resupply_form.xmlbyreplacingthe<!--dataSource-->withfollowing:
<dataSourcename="minventory_resupply_form_data_source">
<argumentname="data"xsi:type="array">
<itemname="js_config"xsi:type="array">
<itemname="component"xsi:type="string">Magento_Ui/js/form/provider</item>
</item>
</argument>
<dataProviderclass="Magelicious\Minventory\Ui\DataProvider\Product\Form\ProductDataProvider"name="minventory_resupply_form_data_source">
<settings>
<requestFieldName>id</requestFieldName>
<primaryFieldName>entity_id</primaryFieldName>
</settings>
</dataProvider>
</dataSource>
![Page 158: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/158.jpg)
Herewesetthedataprovider,whichpointstoourcustomclass,Magelicious\Minventory\Ui\DataProvider\Product\Form\ProductDataProvider.
Wefurthermodifytheminventory_resupply_form.xmlbyreplacingthe<!--fieldset-->withthefollowing:
<fieldsetname="minventory_product">
<argumentname="data"xsi:type="array">
<itemname="config"xsi:type="array">
<itemname="label"xsi:type="string"translate="true">General</item>
</item>
</argument>
<fieldname="stock">
<argumentname="data"xsi:type="array">
<itemname="config"xsi:type="array">
<itemname="label"xsi:type="string">Stock</item>
<itemname="visible"xsi:type="boolean">true</item>
<itemname="dataType"xsi:type="string">text</item>
<itemname="formElement"xsi:type="string">input</item>
<itemname="disabled"xsi:type="string">true</item>
</item>
</argument>
</field>
<fieldname="qty">
<argumentname="data"xsi:type="array">
<itemname="config"xsi:type="array">
<itemname="label"xsi:type="string">+Qty</item>
<itemname="visible"xsi:type="boolean">true</item>
<itemname="dataType"xsi:type="string">text</item>
<itemname="formElement"xsi:type="string">input</item>
<itemname="focused"xsi:type="string">true</item>
<itemname="validation"xsi:type="array">
<itemname="required-entry"xsi:type="boolean">true</item>
<itemname="validate-zero-or-greater"xsi:type="boolean">true</item>
</item>
</item>
</argument>
</field>
</fieldset>
HerewedefinedfieldsetwithaGeneraltitle,andtwofields:stockandqty.Thestockfieldwasdefinedasdisabled,asitspurposewillbemerelytomergethe<SKU>|<qty>valuesforinformationalpurposes.Thestructureoftheindividualfielddefinitionmightseemoverwhelmingatfirst,butwecaneasilydetermineavailableargumentsbyobservingthe<componentname="column"definitionunderthe<MAGENTO_DIR>/module-ui/view/base/ui_component/etc/definition.map.xml.
Wethendefine<MODULE_DIR>/Ui/DataProvider/Product/Form/ProductDataProvider.phpasfollows:
classProductDataProviderextends\Magento\Ui\DataProvider\AbstractDataProvider{
protected$loadedData;
protected$productRepository;
![Page 159: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/159.jpg)
protected$stockRegistry;
protected$request;
publicfunction__construct(
string$name,
string$primaryFieldName,
string$requestFieldName,
\Magento\Catalog\Model\ResourceModel\Product\CollectionFactory$collectionFactory,
\Magento\Catalog\Api\ProductRepositoryInterface$productRepository,
\Magento\CatalogInventory\Api\StockRegistryInterface$stockRegistry,
\Magento\Framework\App\RequestInterface$request,
array$meta=[],array$data=[]
){
parent::__construct($name,$primaryFieldName,$requestFieldName,$meta,$data);
$this->collection=$collectionFactory->create();
$this->productRepository=$productRepository;
$this->stockRegistry=$stockRegistry;
$this->request=$request;
}
publicfunctiongetData(){
if(isset($this->loadedData)){
return$this->loadedData;
}
$id=$this->request->getParam('id');
$product=$this->productRepository->getById($id);
$stockItem=$this->stockRegistry->getStockItemBySku($product->getSku());
$this->loadedData[$product->getId()]['minventory_product']=[
'stock'=>__('%1|%2',$product->getSku(),$stockItem->getQty()),
'qty'=>10
];
return$this->loadedData;
}
}
OurdataproviderisexpectedtoimplementthegetDatamethod.Thisreturnsanarrayofdatathatfeedstheformwithpropervalues.Thestructureofthearraymightbedifficulttograspatfirst,soithelpstoglossoversomeofMagento'sdataproviders.Thestockandqtyentriesherewillprovidevaluesforthefieldsdefinedviaminventory_resupply_form.xml.
Wethendefine<MODULE_DIR>/Block/Adminhtml/Product/Edit/Button/Back.phpasfollows:
classBackextends\Magento\Backend\Block\Templateimplements\Magento\Framework\View\Element\UiComponent\Control\ButtonProviderInterface
{
publicfunctiongetButtonData(){
return[
'label'=>__('Back'),
'on_click'=>sprintf("location.href='%s';",$this->getBackUrl()),
'class'=>'back',
'sort_order'=>10
];
}
publicfunctiongetBackUrl(){
return$this->getUrl('*/*/');
}
![Page 160: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/160.jpg)
}
TheButtonProviderInterfacerequiresthegetButtonDatamethodimplementation.ThestructureofthereturnarrayissomewhatblurryuntilweglossoversomeoftheotherbuttonsthataredefinedacrossMagento.ThisrendersourBackbuttonasfollows:
<buttonid="back"title="Back"type="button"class="action-scalableback"onclick="location.href='...strippedaway...';"data-ui-id="back-button">
<span>Back</span>
</button>
TheBackbuttonprovidesagobacktopreviouspagefunctionality,whichinourcaseisdeterminedbythevalueofthegetBackUrlmethodresponse.
Wethendefine<MODULE_DIR>/Block/Adminhtml/Product/Edit/Button/Save.phpasfollows:
classSaveextends\Magento\Backend\Block\Templateimplements\Magento\Framework\View\Element\UiComponent\Control\ButtonProviderInterface
{
publicfunctiongetButtonData(){
return[
'label'=>__('Save'),
'class'=>'saveprimary',
'data_attribute'=>[
'mage-init'=>['button'=>['event'=>'save']],
'form-role'=>'save',
],
'sort_order'=>20,
];
}
}
Muchlikewiththepreviousbutton,weuseasimilararraystructureforourbuttonhere.Thedifferenceisthatthistimewearepassingthedata_attributeaswell.ThisrendersourSavebuttonasfollows:
<buttonid="save"title="Save"type="button"class="action-scalablesaveprimaryui-buttonui-widgetui-state-defaultui-corner-allui-button-text-only"onclick="location.href='...strippedaway...';"data-form-role="save"data-ui-id="save-button"role="button"aria-disabled="false"><spanclass="ui-button-text">
<span>Save</span>
</span></button>
Themage-initpartmightseemconfusingatthemoment.Sufficeittosaythatit'sawayofinitializingaJScomponent,whichissomethingwewilladdressinmoredetailinthenextchapter.OurSaveessentiallytriggerstheform'ssubmission.
Withthiswehavefinishedourformcomponentdefinition,makingthewholeStockResupplyscreenfunctional.
![Page 161: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/161.jpg)
SummaryInthischapter,webuilttwoverydifferentscreensintheMagentoadminarea.Oneutilizedthelistingcomponent,whereastheotherutilizedtheformcomponent.Agreatdealofourworkinvolvedconfigurationratherthancoding,whichstandstoprovehowpowerfulMagentoUIcomponentscanbe.Whiletheamountofconfigurationmightseemoverwhelmingatfirst,gettingagriponindividualcomponentconfigurationsallowsustobuildcomplexinterfacesquickly.
Movingforward,wearegoingtotakealookatsomeofthespecificsbehinddevelopingforthestorefrontarea.
![Page 162: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/162.jpg)
DevelopingforStorefrontTheMagentostorefrontisthecustomer-facingviewofaMagentoe-commerceplatform.Developingforstorefrontimpliesdevelopingforthefrontendarea.WhereastheadminhtmlareaisprimarilybuiltviameansofUIcomponents,thefrontendareamakesheavyuseofJavaScript(JS)componentsthatcomeinformofjQuerywidgetsandUI/KnockoutJScomponents.AsidefromJScomponents,therearelotsofotherbitsandpiecesinvolvedinstorefrontdevelopment,suchasthemes,layouts,templates,languagepackages,andCSS/LESS.Ourfocus,however,throughoutthischapterwillbeonJScomponents,astheyseemtobethemostconfusingandchallengingpartoftheMagentofrontendtoovercome.
Movingforward,wearegoingtolookintothefollowingsections:
SettinguptheplaygroundInitializingJScomponentsMeetRequireJSReplacingjQuerywidgetcomponentsExtendingjQuerywidgetscomponentsCreatingjQuerywidgetscomponentsExtendingUI/KnockoutJScomponentsCreatingUI/KnockoutJScomponents
![Page 163: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/163.jpg)
TechnicalrequirementsYouwillneedtohavebasicknowledgeofPHP,OOP,JavaScript,andXML.YouwillalsoneedApache,MySQL,andAMPPSinstalledonyoursystemtoexecutethecodes.
ThecodefilesofthischaptercanbefoundonGitHub:https://github.com/PacktPublishing/Magento-2-Quick-Start-Guide.
CheckoutthefollowingvideotoseetheCodeinAction:
http://bit.ly/2D6oMLz.
![Page 164: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/164.jpg)
SettinguptheplaygroundTogetabetterunderstandingofthefrontendarea,wearegoingtobuildaverylightweightMagelicious_Jscomodule,toserveasaplaygroundforourJScomponentexploration.
Tothispoint,weshouldalreadybeprettyfamiliarwiththeflowofcreatinganewmodule.Assumingwehavedefinedourbasicregistration.php,composer.json,andetc/module.xmlfiles,wecanstartdealingwiththespecificsofourMagelicious_Jscomodule.
Let'sstartbydefining<MODULE_DIR>/etc/frontend/routes.xml,asfollows:
<config>
<routerid="standard">
<routeid="jsco"frontName="jsco">
<modulename="Magelicious_Jsco"/>
</route>
</router>
</config>
Wethencreate<MODULE_DIR>/Controller/Playground.php,asfollows:
namespaceMagelicious\Jsco\Controller;
abstractclassPlaygroundextends\Magento\Framework\App\Action\Action
{
}
Wethencreate<MODULE_DIR>/Controller/Playground/Index.php,asfollows:
namespaceMagelicious\Jsco\Controller\Playground;
useMagento\Framework\Controller\ResultFactory;
classIndexextends\Magelicious\Jsco\Controller\Playground
{
publicfunctionexecute(){
$resultPage=$this->resultFactory->create(ResultFactory::TYPE_PAGE);
$resultPage->getConfig()->getTitle()->set(__('Playground'));
return$resultPage;
}
}
There'snothingreallynewtothispoint.Wehavemerelycreatedaroute,controller,andcontrolleractiontosupportapagethatwecanaccessviatheURL,suchashttp://magelicious.loc/jsco/playground.ButthepageitselfisdefinedviaXMLlayout,andwefurthercreate
![Page 165: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/165.jpg)
<MODULE_DIR>/view/frontend/layout/jsco_playground_index.xml,asfollows:
<pagexmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"layout="empty"
xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<referenceContainername="content">
<blockclass="Magelicious\Jsco\Block\Test"
name="jsco_test"
template="Magelicious_Jsco::playground.phtml">
</block>
</referenceContainer>
</body>
</page>
Notelayout="empty"he
re;thisistolimitourselvestoanearlyemptypagetoworkwith.
Finally,wecreateanempty<MODULE_DIR>view/frontend/templates/playground.phtmlpage.Ifweweretonowopenalink,suchashttp://magelicious.loc/jsco/playground,thatwouldopenapagewiththePlaygroundtitleshown.playground.phtmliswhereallofoursamplecodewillgoin,aswecontinueexploringthischapter.
![Page 166: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/166.jpg)
CallingandinitializingJScomponentsCallingandinitializingJScomponentsmightseemabitchallengingatfirst.TherearetwotypesofsyntaxnotationsusedwithMagentoJScomponents:
Declarative:Usingthedata-mage-initattributeUsingthe<scripttype="text/x-magento-init"/>tag
Imperative:Usingthe<script>tag,withoutthetype="text/x-magento-init"attribute
Tobetterunderstandthedata-mage-initnotation,let'stakealookatapartial<PROJECT_DIR>/lib/web/mage/redirect-url.jsfileextract:
define([
'jquery',
'jquery/ui'
],function($){
'usestrict';
$.widget('mage.redirectUrl',{
options:{
event:'click',
url:undefined
},
_bind:function(){/*...*/},
_create:function(){/*...*/},
_onEvent:function(){/*...*/}
});
return$.mage.redirectUrl;
});
ThishereisajQuerywidgetwrappedasanAMDmodule;moreonthatlateron.data-mage-initknowshowtointerpretmage.redirectUrlasaredirectUrlcomponent.BystudyingtheredirectUrlwidgetcode,wecanseeitcanbeusednotonlywiththebuttonandthelinktypeofelementsbutwiththeselecttypeaswell.Let'sgoaheadandappendourplayground.phtmlfilewiththefollowing:
<adata-mage-init='{"redirectUrl":{"url":"http://test.url"}}'>
<span><?=__('Test')?></span>
</a>
<buttontype="button"
data-mage-init='{"redirectUrl":{"url":"http://test.url"}}'>
![Page 167: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/167.jpg)
<span><?=__('Test')?></span>
</button>
<selectdata-mage-init='{"redirectUrl":{"event":"change"}}'>
<optionvalue="http://test.url/1">Test#1</option>
<optionvalue="http://test.url/2">Test#2</option>
<optionvalue="http://test.url/3">Test#3</option>
</select>
Whiletheclickeventworksperfectlyforlinkandbuttonelements,theselectelementreliesonamorespecificchangeevent.Therefore,ourselectelementexploitsthefactthattheredirectUrlcomponentacceptstheeventconfigurationoption.Thismakesforaniceandcleanlittleexampleofreusingasinglecomponentmultipletime.
Tobetterunderstandthe<scripttype="text/x-magento-init"/>notation,let'stakealookatapartial<MAGENTO_DIR>/module-cookie/view/frontend/web/js/notices.jsfileextract:
define([
'jquery',
'jquery/ui',
'mage/cookies'
],function($){
'usestrict';
$.widget('mage.cookieNotices',{
_create:function(){
//...
}
});
return$.mage.cookieNotices;
});
Justlikeinourfirstexample,thisisjustanotherjQuerywidgetessentially.WhatthecookieNoticeswidgetdoesistakethegivencontentanddisplayitascookienoticealerttotheuser,doingsountiltheuserfinallyhitstheAllowCookiesbutton.Wecaneasilyreusethiswidgettoinjectourowncontent.WhilebothcookieNoticesandredirectUrlarejQuerywidgets,thewaytheyareusedinMagentodiffers.
Let'sgoaheadandappendourplayground.phtmlfilewiththefollowingHTMLbits:
<divid="playgroundCookieBlock"class="messageglobalcookie"
style="display:none;">
<p>
<strong><?=$block->escapeHtml(__('Weusecookiestomakeyourexperiencebetter.'))?></strong>
<span><?=$block->escapeHtml(__('Tocomplywiththenewe-Privacydirective,weneedtoaskforyourconsenttosetthecookies.'))?></span>
<?=$block->escapeHtml(__('<ahref="%1">Learnmore</a>.','http://magelicious.loc/privacy'),['a'])?>
</p>
<divclass="actions">
<buttonid="btn-cookie-allow"class="actionallowprimary">
![Page 168: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/168.jpg)
<span><?=$block->escapeHtml(__('AllowCookies'))?></span>
</button>
</div>
</div>
Thisistosimulateourintentforacustomcookiewidget,withspecialcontentandacookiename.Let'sfurtherappendtheplayground.phtmlfilewithadeclarativecalltocookieNoticesJScomponent:
<scripttype="text/x-magento-init">
{
"#playgroundCookieBlock":{
"cookieNotices":{
"cookieAllowButtonSelector":"#btn-cookie-allow",
"cookieName":"playgroundCookie",
"cookieValue":"playgroundCookieValue",
"cookieLifetime":"300",
"noCookiesUrl":"http://magelicious.loc/no-cookies"
}
}
}
</script>
UnliketheredirectUrlwidget,whichhadanicelistofoptionsdefinedattheverystartofthewidgetdefinition,thecookieNoticeswidgetdoesnothavethose.Itmerelyreferencesthoseoptionsthroughoutthecode,viathis.options.<optionPushedViaMagentoInit>calls.ThisisreallyadefaultjQuerywidgetoptionsobject.Thereasonwearebringingitupismerelytounderstandhow,mostofthetime,oneneedstotakeamoreinvolvedapproachtowardinspectingexistingJavaScriptcomponentscode,insteadofjustfocusingonthesetofpossibledefaultoptions.
Tobetterunderstandthe<script>tagnotation,let'stakealookatapartial<MAGENTO_DIR>/module-ui/view/base/web/js/modal/modal.jsfileextract:
define([
/*...*/
],function(/*...*/){
'usestrict';
//...
$.widget('mage.modal',{
//...
});
return$.mage.modal;
});
Asintheprevioustwoexamples,thisagainisjustajQuerywidget.Nowlet'sgoaheadandappendourplayground.phtmlfilewiththefollowingHTMLbits:
<div>
![Page 169: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/169.jpg)
<ahref="#"id="playgroundModalLink">Showmodal!</a>
</div>
<divid="playgroundModal">
<p>Content...</p>
</div>
Thisistosimulateourintentofcreatingamodalbox,withspecialcontent.Now,let'susethemodalwidgettoturnthisintoanactualmodal.Wefurtherappendourplayground.phtmlfile,asfollows:
<script>
require([
'jquery',
'mage/translate',
'Magento_Ui/js/modal/modal'
],function($,$t,modal){
varoptions={
title:'PlaygroundModal',
buttons:[{
text:$t('Continue'),
click:function(){
this.closeModal();
}
}]
};
modal(options,$('#playgroundModal'));
$('#playgroundModalLink').on('click',function(){
$('#playgroundModal').modal('openModal');
});
}
);
</script>
Thistimeweareusingthe<script>tagapproachtoutilizetheJScomponent.
Toensureourcodeevaluatesonpageload,wecanfurtherwrapourmodalwidgetrelatedcodeintoafunction,asfollows:
<script>
require([
/*libraries...*/
],function(/*params...*/){
$(function(){
//RawJScode...
});
}
);
</script>
Likewise,wecanuseaRequireJSdomReadymoduletoexecuteourJScodeonDOM:
![Page 170: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/170.jpg)
<script>
require([
'jquery',
'mage/translate',
'domReady!'
],function($,$t){
//RawJScode...
});
</script>
The!characterusedindomReady!isasyntaxreservedforplugins.Whilethereismoretoit,sufficetosaythatinacaseofdomReady!thepluginexistssimplyasawayofwaitinguntilDOMgetsloadedbeforeinvokingourfunction.
ThechoiceofcallingandinitializingJScomponentsdependsonhowtheyarewrittenandhowtheyareintendedtobeused.Weusethedeclarativenotationwhenourcomponentrequiresinitialization.Theconfigurationispreparedonthebackendandsimplyoutputtedtothepage.WeusetheimperativenotationonthepagesthatuserawJScode;thisallowsustoexecuteparticularbusinesslogic.
![Page 171: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/171.jpg)
MeetRequireJSTothispoint,wehavebeenusingthingslikeredirectUrlandcookieNoticesoutofthinair,buthowexactlydothesecomponentsbecomeavailabletoourcode?Theansweris,viaRequireJS,alibrarythatunderliesnearlyeveryotherJSfeaturebuiltintoMagento.TheoverallroleofRequireJSissimple;itisaJSmodulesystemthatimplementstheAsynchronousModuleDefinition(AMD)standard,whichservesasanimprovementovertheweb'scurrentglobalsandscripttags.
WehavealreadyseentheformatoftheseAMDmodulesintheprecedingexamples,whichcomesdownthefollowing:
define(['dep1','dep2'],function(dep1,dep2){
returnfunction(){
//Modulevaluetoreturn
};
});
ThegistofAMDmodulesfunctionalitycomesdowntoeachmodulebeingableto:
RegisterthefactoryfunctionviadefineInjectdependencies,insteadofusingglobalsExecutethefactoryfunctionwhenalldependenciesbecomeaccessiblePassdependentmodulesasargumentstothefactoryfunction
Thisstrategysolvesmanyoftheconventionaldependencyissues,wheredependenciesareassumedtobeimmediatelyavailablewhenthefunctionexecutes,whichisnotalwaysthecase.
IfweweretodoaViewPageSourceonourPlaygroundpageinabrowser,wewouldseethree<scripttype="text/javascript"src="...">tagswiththeirsrcattributespointingtothefollowingJSfiles:
frontend/Magento/luma/en_US/requirejs/require.js
frontend/Magento/luma/en_US/mage/requirejs/mixins.js
frontend/Magento/luma/en_US/requirejs-config.js
Aquicklookatthepartialrequirejs-config.jsfilerevealshowthesegetloaded:
![Page 172: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/172.jpg)
(function(require){
/*...*/
(function(){
varconfig={
map:{
'*':{
'redirectUrl':'mage/redirect-url',
}
}
};
require.config(config);
})();
/*...*/
(function(){
varconfig={
map:{
'*':{
cookieNotices:'Magento_Cookie/js/notices'
}
}
};
require.config(config);
})();
/*...*/
})(require);
Thesetwomappingsbreakdownasfollows:
Theleft-handsidepointstothefreelygivennameofourJScomponent,whichessentiallytellsconsumershowtoreferenceit.ThisiswhywewereabletousethesetwocomponentssimplybyreferencingthemviaredirectUrlandcookieNotices.Theright-handsidepointstothelocationofourJScomponent:
mage/redirect-url,wheremagepointstothe<PROJECT_DIR>/lib/web/magedirectory,andredirect-urlfurtherpointstotheredirect-url.jsfilewithinthatdirectoryMagento_Cookie/js/notices,whereMagento_Cookiepointstothe<MAGENTO_DIR>/module-cookie/view/frontend/webdirectory,andjs/noticesfurtherpointstothejs/notices.jsfilewithinthatdirectory
Furtherobservingtherequirejs-config.jsfile,asidefrommap,thereareafewotherimportantkeyswhoserolesareworthknowing:
varconfig={
map:{
'*':{
/*...*/
}
},
paths:{
/*...*/
},
shim:{
![Page 173: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/173.jpg)
/*...*/
},
deps:[
/*...*/
],
config:{
mixins:{
/*...*/
}
}
};
Thesebreakdownasfollows:
map:Forthegivenmoduleprefix;insteadofloadingthemodulewiththegivenID,substituteadifferentmoduleIDpaths:PathmappingsformodulenamesnotfounddirectlyunderbaseUrlshim:Configurethedependencies,exports,andcustominitializationforolderbrowserglobalsscriptsthatdonotusedefinefordeclaringthedependenciesandsettingthemodulevaluedeps:Anarrayofdependenciestoloadconfig/mixins:ListofJSclassmappings,forclasseswhosemethodsareaddedto,ormixedin,withotherJSclasses
Seehttps://requirejs.org/docs/api.htmlformoreinformationontheRequireJSAPI.
Thetakeawayhereisthatourownmodulescandefinetherequirejs-config.jsfileontheirown,underthe<MODULE_DIR>/view/frontenddirectory,allowingustohookintothefinalMagentorequirejs-config.jsfilethatgetsgeneratedforthebrowser.This,inturn,allowsustoeasilyregisterourowncomponents,overrideexistingmappings,paths,andotherthings.
![Page 174: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/174.jpg)
ReplacingjQuerywidgetcomponentsWhilethemajorityofthetime,wewouldwanttoleavetheexistingJScomponentstoworktheirmagicasis,therearetimeswhenbusinessrequirementsaredrasticenoughtomakethewholecomponentunusable.ThinkingintermsofPHPclasses,wecanimaginethatclassAimplementsX,whereaswewanttohaveacompletelydifferentimplementationofX,let'scallitB,thatsharesverylittlewithA.ThisisacasewheresimplyhavingBextendsAwouldnotsuffice,soweoptfordirectlyBimplementsX.WhiletherearenointerfacesinpureJS,thisdoesnotmeanwecannotcompletelyreplaceoneconcreteclasswithanother,aslongasweensurethosefewcrucialmethodsareavailableviathenewclass.
ReplacingJSclassesiseasywithMagento.Let'simaginewewanttofullyreplacetheredirectUrlcomponent.
Westartbycreatingthe<MODULE_DIR>/view/frontend/requirejs-config.jsfile,asfollows:
varconfig={
map:{
'*':{
redirectUrl:'Magelicious_Jsco/js/redirect-url'
}
}
};
WethenimplementtheactualMagelicious_Jsco/js/redirect-urlaspartofthe<MODULE_DIR>/view/frontend/web/js/redirect-url.jsfile,asfollows.
define([
'jquery',
],function($){
'usestrict';
$.widget('magelicious.redirectUrl',{
_create:function(){
//Newimplementation
console.log('magelicious.redirectUrl');
}
});
return$.magelicious.redirectUrl;
});
magelicious.redirectUrlmatchesthenewnameofourwidget,whereasmageliciousis
![Page 175: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/175.jpg)
ournamespaceandredirectUrlistheactualnameofthewidgetwithinournamespace.
Oncewerefreshthestaticcontentviathephpbin/magentosetup:static-content:deploycommand,weshouldnowbeabletoseemagelicious.redirectUrlshowupinthebrowserconsolewindow.Clearly,thecurrentimplementationofredirectUrlwouldbreakthefunctionalitywehadwiththeoriginalcomponent,butitgoestoshowhoweasilywecanfullyreplacethecomponentwithanewone.
![Page 176: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/176.jpg)
ExtendingjQuerywidgetcomponentsAssumingwewishtoextendtheredirectUrlcomponentinsteadofreplacingitcompletely,wecandosoinasimilarfashion.Theentryinourrequirejs-config.jsremainsthesame,whereasthedifferenceliesinhowweeditourredirect-url.jsfile:
define([
'jquery',
'jquery/ui',
'mage/redirect-url'
],function($){
'usestrict';
$.widget('magelicious.redirectUrl',$.mage.redirectUrl,{
/*Overrideofparent_onEventmethod*/
_onEvent:function(){
//Callparent's_onEvent()methodifneeded
returnthis._super();
}
});
return$.magelicious.redirectUrl;
});
Usingthe_superor_superApplyisajQuerywidgetwayofinvokingmethodsofthesamenameintheparentwidget.Whilethisapproachworks,thereisamoreelegantsolutioncalledmixins.
TheMagentomixinsforJSaremuchlikeitspluginsforPHP.Toconverttothemixinapproach,wereplaceourrequirejs-config.jswithcontent,asfollows.
varconfig={
config:{
mixins:{
'mage/redirect-url':{
'Magelicious_Jsco/js/redirect-url-mixin':true
}
}
}
};
Note,thatthistimeweareusingthefullpath'mage/redirect-url'insteadoftheredirectUrlaliasontheleftsideofthemapping,whereastherightsideofmappingpointstoourmixin.Theconventionistousethe-mixingsuffixontopoftheoriginalJSfilename.
Wethencreate<MODULE_DIR>/view/frontend/web/js/redirect-url-mixin.jswithcontent,as
![Page 177: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/177.jpg)
follows:
define([
'jquery'
],function($){
returnfunction(originalWidget){
$.widget(
'magelicious.redirectUrl',
originalWidget,{
/*Redefined_onEventmethod*/
_onEvent:function(){
console.log('_onEventviamixin');
//Callparent's_onEvent()methodifneeded
returnthis._super();
}
}
);
return$.magelicious.redirectUrl;
};
});
Theexampleheremightnotdojustice,asitmerelylooksmorecomplexthanthepreviousexampleofdirectlyextendingthewidget.ThisisbecausewecannotsimplydooriginalWidget._onEvent=function(){/*...*/};ororiginalWidget._proto._onEvent=function(){/*...*/};andthusoverridethewidgetmethod.Widgetmethodsneedtobeoverriddenontheprototype,which,inourcase,essentiallymeanscreatinganewwidgetfromtheoriginal.
Ifwewereaddingamixinforanon-widgettypeofJS,suchasMagento_Checkout/js/action/place-order,thentheapproachwouldbedifferent,asshowninMagento_CheckoutAgreements/js/model/place-order-mixin.
![Page 178: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/178.jpg)
CreatingjQuerywidgetscomponentsCreatingsimplejQuerywidgetscomponentsisprettystraightforwardfromaMagentopointofview.TheactualknowledgeofbuildingrobustjQuerywidgetsdependsonourknowledgeofjQueryitself.
Let'sassumeourwidgetwillbecalledwelcome,anditspurposeistosimplyoutputWelcome%name%totheelement,providedwepassedonthenameoptionduringwidgetinitialization.
Westartbyaddingthemappingunderour<MODULE_DIR>/view/frontend/requirejs-config.jsfile,asfollows:
varconfig={
map:{
'*':{
welcome:'Magelicious_Jsco/js/welcome'
}
}
};
Wethendefinethewidgetitself,aspartofthe<MODULE_DIR>/view/frontend/web/js/welcome.jsfile,asfollows:
define([
'jquery',
'mage/translate'
],function($,$t){
'usestrict';
$.widget('magelicious.welcome',{
_create:function(){
this.element.text($t('Welcome'+this.options.name));
}
});
return$.magelicious.welcome;
});
Wecanseethatourwidgetisquitesimple.IfwenowrunMagento'ssetup:static-content:deploycommand,ourwidgetshouldalreadybereadyforuse,aswecannowinitializeitfromtemplatefiles.
Finally,let'sinitializeourwelcomewidgetbyamendingplayground.phtml,asfollows:
![Page 179: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/179.jpg)
<?php$helper=$this->helper('Magento\Framework\Json\Helper\Data')?>
<spandata-mage-init='<?=$helper->jsonEncode(
['welcome'=>['name'=>'JohnDoe']]
)?>'></span>
Withthisinplace,weshouldnowbeabletoseetheWelcomeJohnDoemessageinourbrowser.Whilethislittlecomponentseemsquiteanoverkillforwhatitdoes,theconceptsbehinditarewhatmatters.
Seehttps://api.jqueryui.com/jquery.widget/formoreinformationoncreatingjQuerywidgets.
![Page 180: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/180.jpg)
CreatingUI/KnockoutJScomponentsTothispoint,wehaveonlybeendealingwithjQuerywidgetsascomponents.Whileextremelypowerful,jQuerywidgetsarenotbestsuitedforrenderingrobustcomponentswithcomplexHTMLstructures.TheothertypeofJScomponentsiswhatwerefertoasUI/KnockoutJScomponents.BuiltontheshouldersoftheKnockoutJSlibrary,thesecomponentsallowpowerfultemplatingofourdata,amongotherthings.Withoutgettingtoodeepintotheinsandoutsofthesetypeofcomponents,sufficetosaythatthemainconstructwearereferringtowhenwespeakofUI/KnockoutJScomponentsisuiComponent.
Asper<MAGENTO_DIR>/module-ui/view/base/requirejs-config.js,theuiComponentmapstotheMagento_Ui/js/lib/core/collectionJSfile.Inspectingthecollection.jsfile,wecanseethatuiComponentextendsuiElement,whichmapstotheMagento_Ui/js/lib/core/element/elementJSfile.TheuiComponentanduiElementmakeuseoftheko,underscore,mageUtils,uiRegistry,uiEvents,anduiClasslibraries,amongotherthings,soit'sworthgettingourselvesfamiliarwiththose.
CreatingnewUI/KnockoutJScomponentsisaslightlymoreinvolvedprocessthancreatingajQuerywidget.
Westartbycreatingthepropermappingunderour<MODULE_DIR>/view/frontend/requirejs-config.jsfile,asfollows:
varconfig={
map:{
'*':{
popularProducts:'Magelicious_Jsco/js/popular-products'
}
}
};
ThispartisthesameaswithjQuerywidgets.Herewesimplyregister,oraliasifyouwill,ourcomponentnametoitsfilelocation.
Wethendefinethecomponentitself,underthe<MODULE_DIR>/view/frontend/web/js/popular-products.jsfile,asfollows:
define([
'jquery',
![Page 181: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/181.jpg)
'uiComponent',
'ko',
'mage/translate'
],function($,Component,ko,$t){
'usestrict';
returnComponent.extend({
defaults:{
template:'Magelicious_Jsco/popular-products',
title:$t('PopularProducts'),
products:[],
},
getTitle:function(){
returnthis.title;
}
});
}
);
ThebasisofallUIcomponentsisuiComponent.WepassontheinstanceofuiComponentasaComponentparameter.WethenimplementthespecificsofourcomponentaspartoftheJSONobjectpassedontotheComponent.extendmethod.
WithourcomponentJSfilenowinplace,wefurthercreatethetemplatefilereferencedbythecomponent.Wedosounderthe<MODULE_DIR>/view/frontend/web/template/popular-products.htmlfile,asfollows:
<h4data-bind="text:getTitle()"></h4>
<uldata-bind="foreach:products">
<li>
<span>
<spandata-bind="text:title"></span>
(<spandata-bind="text:sku"></span>)
</span>
</li>
</ul>
WhathappensintheHTMLtemplatefilesisallaboutKnockoutJS,whichmeansacertainpartoftheKnockoutJSlibraryisrequiredinordertobuiltUI/KnockoutJScomponents.
Seehttp://knockoutjs.comformoreinformationontheKnockoutJSlibrary.
Wethenamendourjsco_playground_index.xmlbyaddingthefollowinglineunder<referenceContainername="content">:
<blockname="popular_products"
template="Magelicious_Jsco::popular-products.phtml"/>
popular-products.phtmliswherewewillinstantiateourUI/KnockoutJScomponent.
![Page 182: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/182.jpg)
Finally,wecreate<MODULE_DIR>/view/frontend/templates/popular-products.phtmlwithcontent,asfollows:
<?php$jsonHelper=$this->helper('Magento\Framework\Json\Helper\Data');?>
<divclass="popular-products"data-bind="scope:'popular-products-scope'">
<!--kotemplate:getTemplate()--><!--/ko-->
</div>
<scripttype="text/x-magento-init">
{
".popular-products":{
"Magento_Ui/js/core/app":{
"components":{
"popular-products-scope":{
"component":"popularProducts",
"products":<?=/*@escapeNotVerified*/$jsonHelper->jsonEncode([
['sku'=>'sku1','title'=>'Title1'],
['sku'=>'sku2','title'=>'Title2']
])?>
}
}
}
}
}
</script>
Hereweareusingthedeclarativeapproachtoinitializeourcomponent.ThestructureoftheJSONobjectunderthescripttagmightseemabitconfusingatfirst.The.popular-productskeyisessentiallyaselector,targetingwhateverHTMLelementitmightfind.Magento_Ui/js/core/appisanaliasfortheapp.jsfile,whichcreatestheUIcomponentsinstancesaccordingtotheconfigurationoftheJSONusingtheuiLayoutcomponent.componentsisakeyunderwhichwenestoneormorecomponentswewishtoinitialize.popular-products-scopeissortofascopekeyassignedtoourcomponent,whichweusetodata-bindthescopevaluetotheHTMLelement.
Clearingthecacheandredeployingthestaticfiles,weshouldnowbeabletoseeournewlycreatedcomponent.
![Page 183: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/183.jpg)
ExtendingUI/KnockoutJScomponentsExtendingUI/KnockoutJScomponentsisaprocesssimilartoextendingthejQuerywidgets.Let'sforamomentassumewehavetheMagelicious_Jsco2modulethatwantstooverrideourpopularProductscomponent.
Thewaytodoitwouldbetoaddthepropermappingunderthemapkeyofour<MODULE2_DIR>/view/frontend/requirejs-config.jsfile:
varconfig={
map:{
'*':{
popularProducts:'Magelicious_Jsco2/js/new-popular-products'
}
}
};
Wethencreatethepropernew-popular-products.jsfile,asfollows:
define([
'jquery',
'Magelicious_Jsco/js/popular-products',
'ko',
'mage/translate',
],function($,popularProductsComponent,ko,$t){
'usestrict';
returnpopularProductsComponent.extend({
getTitle:function(){
return'NEW|'+this._super();
}
});
}
);
TheexamplehereshowsthatwearenolongerpassingintheinstanceofuiComponent,rathertheinstanceoftheoriginalMagelicious_Jsco/js/popular-productsthatwewishtoextend.SimplyusingtheextendmethodonourpopularProductsComponentobjectallowsustoextenditeasily.Byredefiningthemethodsofthesamename,suchasgetTitle,weeffectivelyoverridethesamemethodonthecomponentwearerunningtheextendon.
![Page 184: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/184.jpg)
SummaryThoughtherearelotsofotherbitsandpiecesinvolvedinstorefrontdevelopment,JScomponentsmakeforthemostchallengingpartofit.Understandinghowtowritenewcomponents,aswellashowtooverrideorbypassexistingonesisanessentialskillforanyMagentodeveloper,beitbackendorfrontend.Admittedly,thischaptertookmoreofabackend/module-developertypeofanapproachonthesubject.
Wheneverthereisaneedtochangethebehavioroftheunderlyingcomponent,whetheritispureJS,ajQuerywidget,orUI/KnockoutJS,weshouldconsiderthescopeofchangesinordertodecidewhetherweshouldapproachitbyreplacing,overriding,orusingmixin.
Movingforward,wearegoingtotakealookatsomeoftheneatthingswecandoaroundcustomizingthestorefrontcatalogbehavior,mostofwhichcomedowntopluginsandJScomponents.
![Page 185: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/185.jpg)
CustomizingCatalogBehaviorRightoutofthebox,Magentoprovidesaprettyrobustcatalogfunctionality.Managingcategoriesandproductsonamulti-store,multi-currency,multi-languagelevelwithasupportforcustomattributes,catalogsearch,catalogrules,andalikearefeaturesthatarelikelytosufficeformostcustomers.Sometimes,however,certainintegrationsorlargerandsmallerfeaturesarerequested,thatbuildontopoftheexistingfunctionality.Whethertoimproveuserexperienceoraccommodateessentialbusinessrequirements,catalogcustomization'splayamajorroleineverydayMagentodevelopment.
Wearegoingtocustomizeourcatalogbehaviorby:
CreatingthesizeguideCreatingthesamedaydeliveryFlaggingnewproducts
Thesestandonlyasasmallfragmentofwhat'spossiblewithMagentocatalogcustomizations.
Movingforward,ourworkistobedoneaspartoftheMagelicious_Catalogmodule,whichwewilldevelopthroughoutthechapter.
![Page 186: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/186.jpg)
TechnicalrequirementsYouwillneedtohavebasicknowledgeofPHP,OOP,JavaScript,andXML.YouwillalsoneedApache,MySQL,andAMPPSinstalledonyoursystemtoexecutethecodes.
ThecodefilesofthischaptercanbefoundonGitHub:https://github.com/PacktPublishing/Magento-2-Quick-Start-Guide.
CheckoutthefollowingvideotoseetheCodeinAction:
http://bit.ly/2MFJaCN.
![Page 187: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/187.jpg)
CreatingthesizeguideWehavebeenaskedtoaddafunctionalitythatshowsthesizeguideonaproductviewpage.ThisistoappearasanewtabnexttotheexistingDetails,MoreInformation,andReviewstabs.Thecontentofthesizeguidetabistobethesameforallproductscontainingthesizeattribute.WealsoneedittobeeditablefromMagentoadmin.
Let'stakeamomenttothinkaboutourapproachhere:
TobethesameforallproductsandeditablefromMagentoadminneedstheCMSblockTheCMSblockneedssetupscriptforcreatingthesizeguideblockToAppearasanewtabnexttotheexistingtabsrequiresacatalog_product_view.xmllayoutupdate
Assumingwehavedefinedregistration.php,composer.json,andetc/module.xmlasbasicmodulefiles,wecandealwiththemorespecificdetailsofourMagelicious_Catalogmodule.
Westartbydefining<MODULE_DIR>/Setup/InstallData.phpwithcontent,asfollows:
namespaceMagelicious\Catalog\Setup;
classInstallDataimplements\Magento\Framework\Setup\InstallDataInterface{
protected$searchCriteriaBuilder;
protected$blockRepository;
protected$blockFactory;
publicfunction__construct(
\Magento\Framework\Api\SearchCriteriaBuilder$searchCriteriaBuilder,
\Magento\Cms\Api\BlockRepositoryInterface$blockRepository,
\Magento\Cms\Api\Data\BlockInterfaceFactory$blockFactory
){
$this->searchCriteriaBuilder=$searchCriteriaBuilder;
$this->blockRepository=$blockRepository;
$this->blockFactory=$blockFactory;
}
publicfunctioninstall(
\Magento\Framework\Setup\ModuleDataSetupInterface$setup,
\Magento\Framework\Setup\ModuleContextInterface$context
){
$setup->startSetup();
$searchCriteria=$this->searchCriteriaBuilder
->addFilter('identifier','size-guide','eq')
->create();
![Page 188: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/188.jpg)
$blocks=$this->blockRepository->getList($searchCriteria)->getItems();
if(empty($blocks)){
/*@var\Magento\Cms\Api\Data\BlockInterface$block*/
$block=$this->blockFactory->create();
$block->setIdentifier('size-guide');
$block->setTitle('SizeGuide');
$block->setContent('Sizeguide!');
$this->blockRepository->save($block);
}
$setup->endSetup();
}
}
TheInstallDatascriptensuresthatthesize-guideCMSblockiscreatedduringmoduleinstallationifitdoesnotalreadyexist.Withthisinplace,wecanalreadyrunthesetup:upgradecommand.Thisshouldinstallourmoduleandcreatethesize-guideCMSblock.
Wethendefine<MODULE_DIR>/Block/SizeGuide.phpwithcontent,asfollows:
namespaceMagelicious\Catalog\Block;
classSizeGuideextends\Magento\Cms\Block\Blockimplements\Magento\Framework\DataObject\IdentityInterface{
protected$product;
protected$coreRegistry;
publicfunction__construct(
\Magento\Framework\View\Element\Context$context,
\Magento\Cms\Model\Template\FilterProvider$filterProvider,
\Magento\Store\Model\StoreManagerInterface$storeManager,
\Magento\Cms\Model\BlockFactory$blockFactory,
\Magento\Framework\Registry$coreRegistry,
array$data=[]
){
$this->coreRegistry=$coreRegistry;
parent::__construct($context,$filterProvider,$storeManager,$blockFactory,$data);
}
publicfunction_toHtml(){/*...*/}
publicfunctiongetProduct(){
if(!$this->product){
$this->product=$this->coreRegistry->registry('product');
}
return$this->product;
}
}
Thisistheactualblockclassthatwewilloutputontheproductviewpage.Theregistry'sobjectproductkeyisalreadysetbytheparentclassupthelayouttree.Thisallowsustoeasilyfetchtheinstanceofthecurrentproduct.
The_toHtmlmethodisfurtherimplemented,asfollows:
protectedfunction_toHtml()
{
if($this->getProduct()->getTypeId()==\Magento\ConfigurableProduct\Model\Product\Type\Configurable::TYPE_CODE){
$configurableAttributes=$this->getProduct()->getTypeInstance()->getConfigurableAttributesAsArray($this->getProduct());
![Page 189: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/189.jpg)
foreach($configurableAttributesas$attribute){
if(isset($attribute['attribute_code'])&&$attribute['attribute_code']=='size'){
returnparent::_toHtml();
}
}
}
return'';
}
Thisisthegistofoursizeguidefunctionality.Theconfigurabletypeandsizeattributecodechecksensurethattheoutputof_toHtmlrendersthesize-guideblockonlyforcertaingroupsofproducts.
Wefinallydefine<MODULE_DIR>/view/frontend/layout/catalog_product_view.xmlwithcontent,asfollows:
<page>
<body>
<referenceBlockname="product.info.details">
<blockclass="Magelicious\Catalog\Block\SizeGuide"name="size-guide"after="-"group="detailed_info">
<arguments>
<argumentname="block_id"xsi:type="string">size-guide</argument>
<argumentname="css_class"xsi:type="string">description</argument>
<argumentname="at_label"xsi:type="string">none</argument>
<argumentname="title"translate="true"xsi:type="string">SizeGuide</argument>
</arguments>
</block>
</referenceBlock>
</body>
</page>
ThisisthegluethatbindsourSizeGuideblocktoaproductviewpage,and,morespecifically,theproduct.info.detailsblockthatneatlycontainstheDetails,MoreInformation,andReviewstabs.
Thefinalproductviewpageresultshouldlooklikethis:
![Page 190: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/190.jpg)
CreatingthesamedaydeliveryWehavebeenaskedtoaddafunctionalitythatshowsanactivecountdownwithaYouhave%h%min%sectocatchoursamedaydeliveryoffermessageonaproductviewpage,whereasthecountdownisbasedonanoptionallyassigneddailycutoffAttime,setforeveryproductindividually,foreverydayofaweekindependently.
Let'stakeamomenttothinkaboutourapproachhere:
EveryproductandeverydayofaweekimplyMondaytoSunday_[Cutoff_At]productattributesProductattributesimplysetupscriptActivecountdownimpliesJScomponents
Westartbybumpingupthesetup_versionvalueofour<MODULE_DIR>/etc/module.xmlfilefrom1.0.0to1.0.1.Thisallowsustointroducethe<MODULE_DIR>/Setup/UpgradeData.phpfilewithanupgrade,asfollows:
protectedfunctionupgradeToVersionOneZeroOne(
\Magento\Framework\Setup\ModuleDataSetupInterface$setup
){
$eavSetup=$this->eavSetupFactory->create(['setup'=>$setup]);
$days=[
'monday','tuesday','wednesday','thursday',
'friday','saturday','sunday'
];
$sortOrder=100;
foreach($daysas$day){
$eavSetup->addAttribute(
\Magento\Catalog\Model\Product::ENTITY,
$day.'_cutoff_at',
[
'type'=>'varchar',
'label'=>ucfirst($day).'CutoffAt',
'input'=>'text',
'required'=>false,
'sort_order'=>$sortOrder++,
'global'=>\Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface::SCOPE_STORE,
'group'=>'Cutoff',
]
);
}
}
![Page 191: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/191.jpg)
TheaddAttributemethodhereisrunforeachdayoftheweek,thuscreatingmonday_cutoff_attosunday_cutoff_atproductattributes.If,atthispoint,weweretoruntheMagento'ssetup:upgradecommand,ourUpgradeDatascriptwouldgetexecutedandschema_versionanddata_versionnumbersfromwithinthesetup_moduletablewouldgetbumpedtothe1.0.1version.Likewise,goingintotheMagentoadminareaandeditingorcreatinganewproduct,wouldshowthefollowingscreen.Thisiswhereweenabletheusertoenterthetimeofthedayinan<hour>:<minute>format,suchas15:30.Thistime,ifentered,willlaterbeusedbytheJScomponenttorenderthecountdownfunctionalityonthestorefrontproductviewpage:
Wethencreate<MODULE_DIR>/Block/Product/View/Cutoff.php,asfollows:
namespaceMagelicious\Catalog\Block\Product\View;
classCutoffextends\Magento\Framework\View\Element\Templateimplements\Magento\Framework\DataObject\IdentityInterface
{
private$product;
protected$coreRegistry;
protected$localeDate;
publicfunction__construct(
\Magento\Framework\View\Element\Template\Context$context,
\Magento\Framework\Registry$coreRegistry,
\Magento\Framework\Stdlib\DateTime\TimezoneInterface$localeDate,
array$data=[]
){
$this->coreRegistry=$coreRegistry;
$this->localeDate=$localeDate;
parent::__construct($context,$data);
![Page 192: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/192.jpg)
}
publicfunctiongetProduct(){/*...*/}
publicfunctiongetCutoffAt(){/*...*/}
publicfunctiongetIdentities(){/*...*/}
}
Wewillusethisclasswhenwereachourlayoutupdate.
ThegetProductmethodisfurtherimplemented,asfollows:
publicfunctiongetProduct()
{
if(!$this->product){
$this->product=$this->coreRegistry->registry('product');
}
return$this->product;
}
Asmentionedpreviously,theregistry'sproductkeyisalreadysetbytheparentclassupthelayouttree,soweexploitthatfacttofetchthecurrentproduct.
ThegetCutoffAtmethodisfurtherimplemented,asfollows:
publicfunctiongetCutoffAt()
{
$timezone=new\DateTimeZone($this->localeDate->getConfigTimezone());
$now=new\DateTime('now',$timezone);
$day=strtolower($now->format('l'));
$cutoffAt=$this->getProduct()->getData($day.'_cutoff_at');
if($cutoffAt){
$timeForDay=\DateTime::createFromFormat(
'Y-m-dH:i',
$now->format('Y-m-d').''.$cutoffAt,
$timezone
);
if($timeForDayinstanceof\DateTime){
return$timeForDay->format(DATE_ISO8601);
}
}
return0;
}
ThisisthegistofoursamedaydeliveryfunctionalityfromthePHPsideofthings.Weensureweproperlyreturnthefulldateandtimebasedontheproduct's$day.'_cutoff_at'attributevalue;thiswilllaterbepassedontotheJScomponent.
Finally,thegetIdentitiesmethodisfurtherimplemented,asfollows:
publicfunctiongetIdentities()
{
![Page 193: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/193.jpg)
$identities=$this->getProduct()->getIdentities();
$timezone=new\DateTimeZone($this->localeDate->getConfigTimezone());
$now=new\DateTime('now',$timezone);
$day=strtolower($now->format('l'));
returnarray_push($identities,$day);
}
ThegetIdentitiesmethodhasbeenimplementedinawaytoensurecachingofthisblockisconsideredinarelationtoproductidentityaswellasthedayoftheweek.
Wethencreatethe<MODULE_DIR>/view/frontend/requirejs-config.jsfile,asfollows:
varconfig={
map:{
'*':{
cutoffAt:'Magelicious_Catalog/js/cutoff'
}
}
};
ThisregistersthecutoffAtcomponentwithMagento,whichpointstoourmodule'scutoff.jsfile.
Wethencreatethe<MODULE_DIR>/view/frontend/web/js/cutoff.jsfile,asfollows:
define([
'jquery',
'uiComponent',
'ko',
'moment'
],function($,Component,ko,moment){
'usestrict';
returnComponent.extend({
defaults:{
template:'Magelicious_Catalog/cutoff',
expiresAt:null,
timerHide:false,
timerHours:null,
timerMinutes:null,
timerSeconds:null,
},
initialize:function(){
this._super();
this.countdown(this);
returnthis;
},
initObservable:function(){
this._super()
.observe('timerHidetimerHourstimerMinutestimerSeconds');
returnthis;
},
countdown:function(self){/*...*/}
});
}
);
![Page 194: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/194.jpg)
OurJScomponenttemplatevaluepointsto<MODULE_DIR>/view/frontend/web/template/cutoff.html,whichwewillsoonaddress.expiresAtistheonlyrealoptionthatisexpectedtobepassedonwhenthecomponentisinitialized.Theobservabletimer*optionswillbeusedinternallytocontrolthefunctionalityofourcomponent.
Thecountdownfunctionisfurtherimplemented,asfollows:
countdown:function(self){
vartoday=moment(newDate());
setInterval(function(){
self.expiresAt=moment(self.expiresAt).subtract(1,'seconds');
varmilliseconds=moment(self.expiresAt,'DD/MM/YYYYHH:mm:ss').diff(moment(today,'DD/MM/YYYYHH:mm:ss'));
varduration=moment.duration(milliseconds);
self.timerHours(duration.hours()>=0?duration.hours():0);
self.timerMinutes(duration.minutes()>=0?duration.minutes():0);
self.timerSeconds(duration.seconds()>=0?duration.seconds():0);
if(self.timerHours()==0
&&self.timerMinutes()==0
&&self.timerSeconds()==0
){
self.timerHide(true);
}
},1000);
}
Thishereisthegistofoursamedaydeliveryfunctionality.UsingthecoreJSsetIntervalmethod,wesetupasimpleper-secondcounter.WiththefewlinesofcodewrappedwithinsetInterval,wecontrolourobservabletimer*optionsboundtoourcutoff.htmltemplate.This,inturn,resultsinthevisualcountdowneffect.
Wethencreatethe<MODULE_DIR>/view/frontend/web/template/cutoff.htmlfile,asfollows:
<spanclass="cutoff-component"data-bind="ifnot:timerHide">
<spantranslate="'Youhave'"></span>
<spanclass="timer">
<spanclass="timer-parttimer-part-hours">
<spanclass="numeric"data-bind="text:timerHours"></span>
<spanclass="label"data-bind="i18n:'hours'"></span>
</span>
<spanclass="timer-parttimer-part-minutes">
<spanclass="numeric"data-bind="text:timerMinutes"></span>
<spanclass="label"data-bind="i18n:'minutes'"></span>
</span>
<spanclass="timer-parttimer-part-seconds">
<spanclass="numeric"data-bind="text:timerSeconds"></span>
<spanclass="label"data-bind="i18n:'seconds'"></span>
</span>
</span>
<spantranslate="'tocatchoursamedaydeliveryoffer.'"></span>
</span>
![Page 195: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/195.jpg)
ThisisthetemplatefilebehindourJScomponent.Weseeallthosetimer*optionsbeingboundedtoproperspanelements.Wrappingeverytimer*optioninitsownspanallowsforpotentialflexibilityaroundstylinglateron.
Seehttps://devdocs.magento.com/guides/v2.2/ui_comp_guide/concepts/knockout-bindings.htmlforalistofMagentocustomKnockout.jsbindings.
Wethencreatethe<MODULE_DIR>/view/frontend/templates/product/view/cutoff.phtmlfile,asfollows:
<?php/*@var\Magelicious\Catalog\Block\Product\View\Cutoff$block*/?>
<?php$jsonHelper=$this->helper('Magento\Framework\Json\Helper\Data');?>
<divclass="cutoff"data-bind="scope:'cutoff-scope'">
<!--kotemplate:getTemplate()--><!--/ko-->
</div>
<scripttype="text/x-magento-init">
{
".cutoff":{
"Magento_Ui/js/core/app":{
"components":{
"cutoff-scope":{
"component":"cutoffAt",
"expiresAt":<?=/*@escapeNotVerified*/$jsonHelper->jsonEncode($block->getCutoffAt())?>
}
}
}
}
}
</script>
ThisisthetemplatefilethatinitializesourJScomponent.Withthisfileinplace,wecanfinallygluethingstogetherbyamendingthebodyelementofthe<MODULE_DIR>/view/frontend/layout/catalog_product_view.xmlfile,asfollows:
<referenceBlockname="product.info.extrahint">
<blockname="cutoff"
class="Magelicious\Catalog\Block\Product\View\Cutoff"
template="Magelicious_Catalog::product/view/cutoff.phtml">
</block>
</referenceBlock>
Thefinalproductviewpageresultshouldlooklikethis:
![Page 196: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/196.jpg)
Oncethetimerreaches0hours0minutes0seconds,itshoulddisappear.
![Page 197: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/197.jpg)
FlaggingnewproductsWehavebeenaskedtoaddafunctionalitythatflagseverynewproductshownonthestorefrontcategoryviewandproductviewpageswitha[NEW]prefixinfrontofitsname.Newimpliesanythingwithinthe5daysoftheproduct'screated_atvalue.
Luckilyforus,wecaneasilycontrolaproduct'snameviaanafterpluginonaproduct'sgetNamemethod.AllittakesistodefineanafterGetNamepluginwithacategoryviewandproductviewpagesconstraint,furtherfilteredbyacreated_atconstraint.
Toregistertheplugin,westartbycreatingthe<MODULE_DIR>/etc/frontend/di.xmlfilewithcontent,asfollows:
<config>
<typename="Magento\Catalog\Api\Data\ProductInterface">
<pluginname="newProductFlag"type="Magelicious\Catalog\Plugin\NewProductFlag"/>
</type>
</config>
Wethencreatethe<MODULE_DIR>/Plugin/NewProductFlag.phpfilewithcontent,asfollows:
namespaceMagelicious\Catalog\Plugin;
classNewProductFlag
{
protected$request;
protected$localeDate;
publicfunction__construct(
\Magento\Framework\App\RequestInterface$request,
\Magento\Framework\Stdlib\DateTime\TimezoneInterface$localeDate
)
{
$this->request=$request;
$this->localeDate=$localeDate;
}
publicfunctionafterGetName(\Magento\Catalog\Api\Data\ProductInterface$subject,$result)
{
$pages=['catalog_product_view','catalog_category_view'];
if(in_array($this->request->getFullActionName(),$pages)){
$timezone=new\DateTimeZone($this->localeDate->getConfigTimezone());
$now=new\DateTime('now',$timezone);
$createdAt=\DateTime::createFromFormat('Y-m-dH:i:s',$subject->getCreatedAt(),$timezone);
![Page 198: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/198.jpg)
if($now->diff($createdAt)->days<5){
return__('[NEW]').$result;
}
}
return$result;
}
}
TheafterGetNameisourafterplugintargetingtheproduct'sgetNamemethod.Usingtherequest'sgetFullActionNamemethod,wemakesureourpluginisconstrainedtoonlycatalog_product_viewandcatalog_category_viewpages,orelsetheoriginalproductnameisreturned.Theuseofthepropertimezoneanddiffmethodassuresthatwefurtherfilterdowntoonlythoseproductsthatweconsidernew.Clearingthecacheatthispointshouldallowourfunctionalitytokickin.
Thefinalresultshouldlooklikethis:
![Page 199: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/199.jpg)
SummaryInthischapter,wehavebuiltthreedistinctivefunctionalities,allofwhichrelatetothecatalogpartofMagento.Thoughverylightweight,theystandtoshowhoweasilyMagentocanbeextendedwithnewfeatureswithoutreallyoverridinganyofthecorefiles.UsingpluginsandJScomponentsaremerelysomeoftheapproacheswemighttake.Quiteoften,wewillfindthatasinglerequirementmightbedeliveredwithmorethanoneapproach.Themainguidingruleforourcodeshouldalwaysbe:usetheleastintrusive.Catalogfunctionalityplaysamajorroleinthecustomerconversionprocess,soourpriorityshouldalwaysbefailsafewhenpossible.
Movingforward,wearegoingtotakealookatsomeofthethingswecandotocustomizethecheckout.
![Page 200: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/200.jpg)
CustomizingCheckoutExperiencesWhilethedefaultMagentocheckoutprovideseverythingashopneedstocompleteatransactionsuccessfully,therearedetailsspecifictotheindividualbusinessesthatoftenneedtobeaddressed.Agreatdealofthesedetailsoftenrelatetocheckoutcustomizationsthatallowforthecapturingofadditionalinformationorengagingcustomersinagreementsandsubscriptionactivities.
Movingforward,wearegoingtotakealookatthefollowing:
PassingdatatothecheckoutAddingordernotestothecheckout
![Page 201: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/201.jpg)
TechnicalrequirementsYouwillneedtohavebasicknowledgeofPHP,OOP,JavaScript,andXML.YouwillalsoneedApache,MySQL,andAMPPSinstalledonyoursystemtoexecutethecodes.
ThecodefilesofthischaptercanbefoundonGitHub:https://github.com/PacktPublishing/Magento-2-Quick-Start-Guide.
CheckoutthefollowingvideotoseetheCodeinAction:
http://bit.ly/2PHMwqX.
![Page 202: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/202.jpg)
PassingdatatothecheckoutUnlikethemostlystaticCMS,category,andproductpages,thecheckoutpagehasamoredynamicnature.Itisanapplicationonitsown,primarilyconstructedoutofJScomponents,whichfurtherutilizeMagento'sAPIendpointstomoveusthroughthecheckoutsteps.Magento'sMagento\Checkout\Model\CompositeConfigProvidertypeallowsustopushthenecessaryserver-sideinformationeasilytotheuiComponentofthestorefronts.
Aquicklookupforthename="configProviders"stringacrossthecontentofdi.xmlinthe<MAGENTO_DIR>directoryrevealsdozenofdefinitions.Acloserlookatthe<MAGENTO_DIR>/module-tax/etc/frontend/di.xmlrevealsthefollowing:
<typename="Magento\Checkout\Model\CompositeConfigProvider">
<arguments>
<argumentname="configProviders"xsi:type="array">
<itemname="tax_config_provider"xsi:type="object">Magento\Tax\Model\TaxConfigProvider</item>
</argument>
</arguments>
</type>
WeareessentiallyinjectingnewitemsundertheconfigProvidersargumentoftheMagento\Checkout\Model\CompositeConfigProvidertype.Theimplementationofacustomconfigprovider,suchastheMagento\Tax\Model\TaxConfigProvider,mustimplementtheMagento\Checkout\Model\ConfigProviderInterface.TheunderlyinggetConfigmethodreturnsanarrayofkey-valuemappings,suchas:
return[
'isDisplayShippingPriceExclTax'=>$this->isDisplayShippingPriceExclTax(),
'isDisplayShippingBothPrices'=>$this->isDisplayShippingBothPrices(),
'reviewShippingDisplayMode'=>$this->getDisplayShippingMode(),
/*...*/
];
These,inturn,becomeavailabletotheuiComponent,asobservedin<MAGENTO_DIR>/module-tax/view/frontend/web/js/view/checkout/shipping_method/price.js:
isDisplayShippingPriceExclTax:window.checkoutConfig.isDisplayShippingPriceExclTax,
isDisplayShippingBothPrices:window.checkoutConfig.isDisplayShippingBothPrices,
WecanseethevaluesreturnedbythegetConfigmethodnowavailableundertheJavaScriptwindow.checkoutConfigobject.Thisisasimplemechanismbywhichwe
![Page 203: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/203.jpg)
canpushourserver-sidedatatoourstorefrontwhenapageloads.
Tounderstandcheckoutmodificationsbetter,weshouldfamiliarizeourselveswiththecontentofthewindow.checkoutConfigobject.
![Page 204: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/204.jpg)
AddingordernotestothecheckoutNowthatweunderstandthemechanismbehindthewindow.checkoutConfigobject,let'sputittousebycreatingasmallmodulethataddsordernotesfunctionalitytothecheckout.OurworkistobedoneaspartoftheMagelicious_OrderNotesmodule,withthefinalvisualoutcome,asfollows:
Theideabehindthemoduleistoprovideacustomerwithanoptionofputtinganoteagainsttheirorder.Ontopofthat,wealsoprovideastandardrangeofpossiblenotestochoosefrom.
Assumingwehavedefinedregistration.php,composer.json,andetc/module.xmlasbasicmodulefiles,wecandealwiththemorespecificdetailsofourMagelicious_OrderNotesmodule.
Westartbydefiningthe<MODULE_DIR>/Setup/InstallSchema.phpwithcontent,asfollows:
namespaceMagelicious\OrderNotes\Setup;
classInstallSchemaimplements\Magento\Framework\Setup\InstallSchemaInterface
{
publicfunctioninstall(
\Magento\Framework\Setup\SchemaSetupInterface$setup,
\Magento\Framework\Setup\ModuleContextInterface$context
){
$connection=$setup->getConnection();
![Page 205: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/205.jpg)
$connection->addColumn(
$setup->getTable('quote'),
'order_notes',
[
'type'=>\Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
'nullable'=>true,
'comment'=>'OrderNotes'
]
);
$connection->addColumn(
$setup->getTable('sales_order'),
'order_notes',
[
'type'=>\Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
'nullable'=>true,
'comment'=>'OrderNotes'
]
);
}
}
OurInstallSchemascriptcreatesthenecessaryorder_notescolumninboththequoteandsales_ordertables.Thisiswherewewillstorethevalueofthecustomer'scheckoutnote,ifthereisany.
Wethendefinethe<MODULE_DIR>/etc/frontend/routes.xmlwithcontent,asfollows:
<config>
<routerid="standard">
<routeid="ordernotes"frontName="ordernotes">
<modulename="Magelicious_OrderNotes"/>
</route>
</router>
</config>
TheroutedefinitionhereensuresthatMagentowillrecognizeHTTPrequestsstartingwithordernotes,andlookforcontrolleractionswithinourmodule.
Wethendefinethe<MODULE_DIR>/Controller/Index.phpwithcontent,asfollows:
namespaceMagelicious\OrderNotes\Controller;
abstractclassIndexextends\Magento\Framework\App\Action\Action
{
}
Thisismerelyanemptybaseclass,foroursoon-to-followcontrolleraction.
Wethendefinethe<MODULE_DIR>/Controller/Index/Process.phpwithcontent,asfollows:
namespaceMagelicious\OrderNotes\Controller\Index;
![Page 206: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/206.jpg)
classProcessextends\Magelicious\OrderNotes\Controller\Index
{
protected$checkoutSession;
protected$logger;
publicfunction__construct(
\Magento\Framework\App\Action\Context$context,
\Magento\Checkout\Model\Session$checkoutSession,
\Psr\Log\LoggerInterface$logger
)
{
$this->checkoutSession=$checkoutSession;
$this->logger=$logger;
parent::__construct($context);
}
publicfunctionexecute()
{
//implement...
}
}
ThiscontrolleractionshouldcatchanyHTTPordernotes/index/processrequests.Wethenextendtheexecutemethod,asfollows:
publicfunctionexecute()
{
$result=[];
try{
if($notes=$this->getRequest()->getParam('order_notes',null)){
$quote=$this->checkoutSession->getQuote();
$quote->setOrderNotes($notes);
$quote->save();
$result[$quote->getId()];
}
}catch(\Exception$e){
$this->logger->critical($e);
$result=[
'error'=>__('Somethingwentwrong.'),
'errorcode'=>$e->getCode(),
];
}
$resultJson=$this->resultFactory->create(\Magento\Framework\Controller\ResultFactory::TYPE_JSON);
$resultJson->setData($result);
return$resultJson;
}
Thisiswherewearestoringtheordernotesonourquoteobject.Lateron,wewillpullthisontooursalesorderobject.Wefurtherdefinethe<MODULE_DIR>/etc/frontend/di.xmlwithcontent,asfollows:
<config>
<typename="Magento\Checkout\Model\CompositeConfigProvider">
<arguments>
<argumentname="configProviders"xsi:type="array">
<itemname="order_notes_config_provider"xsi:type="object">
Magelicious\OrderNotes\Model\ConfigProvider
</item>
![Page 207: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/207.jpg)
</argument>
</arguments>
</type>
</config>
Weareregisteringourconfigurationproviderhere.Theorder_notes_config_providermustbeunique.Wethendefinethe<MODULE_DIR>/Model/ConfigProvider.phpwithcontent,asfollows:
namespaceMagelicious\OrderNotes\Model;
classConfigProviderimplements\Magento\Checkout\Model\ConfigProviderInterface
{
publicfunctiongetConfig()
{
return[
'orderNotes'=>[
'title'=>__('OrderNotes'),
'header'=>__('Headercontent.'),
'footer'=>__('Footercontent.'),
'options'=>[
['code'=>'ring','value'=>__('Ringlonger')],
['code'=>'backyard','value'=>__('Trybackyard')],
['code'=>'neighbour','value'=>__('Pingneighbour')],
['code'=>'other','value'=>__('Other')],
]
]
];
}
}
Thisistheimplementationofourorder_notes_config_providerconfigurationprovider.Wecanprettymuchreturnanyarraystructurewewish.Thetop-levelorderNoteswillbeaccessiblelaterviaJScomponentsaswindow.checkoutConfig.orderNotes.Wefurtherdefinethe<MODULE_DIR>/view/frontend/layout/checkout_index_index.xmlwithcontent,asfollows:
<page>
<body>
<referenceBlockname="checkout.root">
<arguments>
<argumentname="jsLayout"xsi:type="array">
<itemname="components"xsi:type="array">
<itemname="checkout"xsi:type="array">
<itemname="children"xsi:type="array">
<itemname="steps"xsi:type="array">
<itemname="children"xsi:type="array">
<itemname="order-notes"xsi:type="array">
<itemname="component"xsi:type="string">
Magelicious_OrderNotes/js/view/order-notes
</item>
<itemname="sortOrder"xsi:type="string">2</item>
<!--closingtags-->
Thereisquiteanestingstructurehere.Ourordernotescomponentisbeinginjectedunderthechildrencomponentofthecheckout'sstepscomponent.
![Page 208: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/208.jpg)
Wethendefinethe<MODULE_DIR>/view/frontend/web/js/view/order-notes.jswithcontent,asfollows:
define([
'ko',
'uiComponent',
'underscore',
'Magento_Checkout/js/model/step-navigator',
'jquery',
'mage/translate',
'mage/url'
],function(ko,Component,_,stepNavigator,$,$t,url){
'usestrict';
letcheckoutConfigOrderNotes=window.checkoutConfig.orderNotes;
returnComponent.extend({
defaults:{
template:'Magelicious_OrderNotes/order/notes'
},
isVisible:ko.observable(true),
initialize:function(){
//TODO
},
navigate:function(){
//TODO
},
navigateToNextStep:function(){
//TODO
}
});
});
ThisisouruiComponent,poweredbyKnockout.Thetemplateconfigurationpointstothephysicallocationofthe.htmlfilethatisusedasacomponent'stemplate.ThenavigateandnavigateToNextStepareresponsiblefornavigationbetweenthecheckoutstepsduringcheckout.Let'sextendtheinitializefunctionfurther,asfollows:
initialize:function(){
this._super();
stepNavigator.registerStep(
'order_notes',
null,
$t('OrderNotes'),
this.isVisible,
_.bind(this.navigate,this),
15
);
returnthis;
}
Weusetheinitializemethodtoregisterourorder_notesstepwiththestepNavigator.
Let'sextendthenavigateToNextStepfunctionfurther,asfollows:
navigateToNextStep:function(){
![Page 209: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/209.jpg)
if($(arguments[0]).is('form')){
$.ajax({
type:'POST',
url:url.build('ordernotes/index/process'),
data:$(arguments[0]).serialize(),
showLoader:true,
complete:function(response){
stepNavigator.next();
}
});
}
}
WeusethenavigateToNextStepmethodtopersistourdata.TheAJAXPOSTordernotes/index/processactionshouldgrabtheentireformandpassitsdataalong.
Finally,let'saddthehelpermethodsforour.htmltemplate,asfollows:
getTitle:function(){
returncheckoutConfigOrderNotes.title;
},
getHeader:function(){
returncheckoutConfigOrderNotes.header;
},
getFooter:function(){
returncheckoutConfigOrderNotes.footer;
},
getNotesOptions:function(){
returncheckoutConfigOrderNotes.options;
},
getCheckoutConfigOrderNotesTime:function(){
returncheckoutConfigOrderNotes.time;
},
setOrderNotes:function(valObj,event){
if(valObj.code=='other'){
$('[name="order_notes"]').val('');
}else{
$('[name="order_notes"]').val(valObj.value);
}
returntrue;
},
Thesearejustsomeofthehelpermethodswewillbindtowithinour.htmltemplate.Theymerelypullthedataoutfromthewindow.checkoutConfig.orderNotesobject.
Wethendefinethe<MODULE_DIR>/view/frontend/web/template/order/notes.htmlwithcontent,asfollows:
<liid="order_notes"data-bind="fadeVisible:isVisible">
<divdata-bind="text:getTitle()"data-role="title"></div>
<divid="step-content"data-role="content">
<divdata-bind="text:getHeader()"data-role="header"></div>
<!--form-->
<divdata-bind="text:getFooter()"data-role="footer"></div>
</div>
![Page 210: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/210.jpg)
</li>
Thisisourcomponenttemplate,whichgivesitavisualstructure.Weexpanditfurtherbyreplacingthe<!--form-->withthefollowing:
<formdata-bind="submit:navigateToNextStep"novalidate="novalidate">
<divdata-bind="foreach:getNotesOptions()"class="fieldchoice">
<inputtype="radio"name="order[notes]"class="radio"
data-bind="value:code,click:$parent.setOrderNotes"/>
<labeldata-bind="attr:{'for':code}"class="label">
<spandata-bind="text:value"></span>
</label>
</div>
<textareaname="order_notes"></textarea>
<divclass="actions-toolbar">
<divclass="primary">
<buttondata-role="opc-continue"type="submit"class="buttonactioncontinueprimary">
<span><!--koi18n:'Next'--><!--/ko--></span>
</button>
</div>
</div>
</form>
Theformitselfisrelativelysimple,thoughitrequiressomeknowledgeofKnockout.Understandingthedatabindingisquiteimportant.ItallowsustobindnotjusttextandtheHTMLvaluesofHTMLelements,butotherattributesaswell,suchastheclick.
Wethendefinethe<MODULE_DIR>/etc/webapi_rest/events.xmlwithcontent,asfollows:
<config>
<eventname="sales_model_service_quote_submit_before">
<observername="orderNotesToOrder"
instance="Magelicious\OrderNotes\Observer\SaveOrderNotesToOrder"
shared="false"/>
</event>
</config>
Thesales_model_service_quote_submit_beforeeventischosenbecauseitallowsustogainaccesstobothquoteandorderobjectseasilyattherighttimeintheordercreationprocess.
Wethendefinethe<MODULE_DIR>/Observer/SaveOrderNotesToOrder.phpwithcontent,asfollows:
namespaceMagelicious\OrderNotes\Observer;
classSaveOrderNotesToOrderimplements\Magento\Framework\Event\ObserverInterface
{
publicfunctionexecute(\Magento\Framework\Event\Observer$observer)
{
$event=$observer->getEvent();
![Page 211: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/211.jpg)
if($notes=$event->getQuote()->getOrderNotes()){
$event->getOrder()
->setOrderNotes($notes)
->addStatusHistoryComment('Customernote:'.$notes);
}
return$this;
}
}
Here,wearegrabbingtheinstanceofanorderobjectandsettingtheordernotestothevaluefetchedfromtheordernotesvalueofapreviouslystoredquote.ThismakesthecustomernoteappearundertheCommentsHistorytaboftheMagentoadminorderViewscreen,asfollows:
Withthis,wehavefinalizedourlittlemodule.Eventhoughthemodule'sfunctionalityisquitesimple,thestepsforgettingitupandrunningweresomewhatinvolved.
![Page 212: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/212.jpg)
SummaryInthischapter,wehavebuiltasmall,butfunctional,ordernotesmodule.Thisallowedustofamiliarizeourselveswithanimportantaspectofcustomizingthecheckoutexperience.Thegistofthisliesinunderstandingthecheckout_index_indexlayouthandle,theJavaScriptwindow.checkoutConfigobject,andtheuiComponent.
Failuretodeliverconsistentandstablecheckoutexperiencesisboundtoresultinalossofconversions.Giventhenumberandcomplexityofthecomponentsinvolved,itisbesttokeepthenumberofcheckoutcustomizationstoaminimum.
Movingforward,wearegoingtotakealookatsomeofthethingswecandoregardingthecustomizationofcustomerinteractions.
![Page 213: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/213.jpg)
CustomizingCustomerInteractionsAlongwiththecatalogandcheckout,customer-relatedfunctionalityiscentraltoMagento.Thecustomer'sMyAccountareaallowscontroloveraddresses,orders,billingagreements,productwishlists,productreviews,newslettersubscriptions,andmore.CustomizingcustomerfunctionalityoftenincludeschangestotheSignInandCreateanAccountprocesses,aswellasmodifyingexisting,oraddingnewfunctionalityundertheMyAccountarea.
Dependingonthedynamicsandintricacyofourfunctionality,JScomponentsareoftenfriendliersolutionsthanserver-sidePHTMLtemplates.Theyallowustoengagethecustomerwithoutnecessarilyreloadingentirepages,thusimprovingtheoverallcustomerexperience.Aswithanyclienttoserver-sidecommunication,thequestionofpassingandupdatingthedataremainstobeaddressed.ThisiswhereweturnourfocustoMagento'ssectionmechanism.
Movingforwardwearegoingtotakealookatthefollowing:
UnderstandingthesectionmechanismAddingcontactpreferencestocustomeraccountsAddingcontactpreferencestothecheckout
![Page 214: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/214.jpg)
TechnicalrequirementsYouwillneedtohavebasicknowledgeofPHP,OOP,JavaScript,andXML.YouwillalsoneedApache,MySQL,andAMPPSinstalledonyoursystemtoexecutethecodes.
ThecodefilesofthischaptercanbefoundonGitHub:https://github.com/PacktPublishing/Magento-2-Quick-Start-Guide.
CheckoutthefollowingvideotoseetheCodeinAction:
http://bit.ly/2NQFB1f.
![Page 215: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/215.jpg)
UnderstandingthesectionmechanismOurpreviouschaptertoucheduponconfigprovidersandthewindow.checkoutConfigobject;amechanismbywhichwecanpushourserver-sidedatatoourstorefrontwhenapageloads.ThesectionmechanismallowsustopushdatatoabrowserpageuponanynamedHTTPPOSTrequest.
Let'stakeaquicklookatthe<MAGENTO_DIR>/module-review/etc/frontend/sections.xmlfile:
<actionname="review/product/post">
<sectionname="review"/>
</action>
Thedefinitionprovidedhereistobeinterpretedas:"anystorefrontHTTPPOSTreview/product/postrequestistotriggerareviewsectionload,"wherereviewsectionloadmeansMagentotriggeringanadditionalAJAXrequestfollowingthecompletionofanobservedHTTPPOST.Theresultofthissectionloadaction,inthiscase,istherefreshofsectiondata,retrievableviacustomerData.get('review'),aswewillsoonsee.
Nowlet'stakealookatthe<MAGENTO_DIR>/module-review/etc/frontend/di.xmlfile:
<typename="Magento\Customer\CustomerData\SectionPoolInterface">
<arguments>
<argumentname="sectionSourceMap"xsi:type="array">
<itemname="review"xsi:type="string">Magento\Review\CustomerData\Review</item>
</argument>
</arguments>
</type>
WeareessentiallyinjectingnewitemsunderthesectionSourceMapargumentoftheMagento\Customer\CustomerData\SectionPoolInterfacetype.Theimplementationofacustomsection,suchastheMagento\Review\CustomerData\Review,mustimplementtheMagento\Customer\CustomerData\SectionSourceInterface.TheunderlyinggetSectionDatamethodreturnsanarrayofkey-valuemappings,suchas:
return[
'nickname'=>'',
'title'=>'',
'detail'=>''
]
![Page 216: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/216.jpg)
These,inturn,becomeavailabletotheuiComponent,asobservedinthepartial<MAGENTO_DIR>/module-review/view/frontend/web/js/view/review.jsfile:
define([
'uiComponent',
'Magento_Customer/js/customer-data',
'Magento_Customer/js/view/customer'
],function(Component,customerData){
'usestrict';
returnComponent.extend({
initialize:function(){
this.review=customerData.get('review')...
},
nickname:function(){
returnthis.review().nickname...
}
});
});
ThegetmethodofthecustomerDataobjectcanbeusedtofetchthesectionSourceMapdata,suchascustomerData.get('review').ThisdataisrefreshedeverytimeanHTTPPOSTismadetothereview/product/postroute.ThisisbecausefollowinganyHTTPPOSTreview/product/post,MagentowilltriggeranHTTPGETcustomer/section/load/?sections=review&update_section_id=true&_=1533836467415,whichinturnupdatescustomerDataaccordingly.
![Page 217: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/217.jpg)
AddingcontactpreferencestocustomeraccountsNowthatweunderstandthemechanismbehindthecustomerDataobjectandthesectionload,let'sputittousebycreatingasmallmodulethataddscontactpreferencesfunctionalityunderthecustomer'sMyAccountarea,aswellasunderthecheckout.OurworkistobedoneaspartoftheMagelicious_ContactPreferencesmodule,withthefinalvisualoutcomeasfollows:
Bycontrast,thecustomer'scheckoutareawouldshowcontactpreferences,asfollows:
![Page 218: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/218.jpg)
Theideabehindthemoduleistoprovideacustomerwithanoptionofchoosingpreferredcontactpreferences,sothatamerchantmayfollowupwiththedeliveryprocessaccordingly.
Assumingwehavedefinedregistration.php,composer.json,andetc/module.xmlasbasicmodulefiles,wecandealwiththemorespecificdetailsofourMagelicious_ContactPreferencesmodule.
Westartbydefiningthe<MODULE_DIR>/Setup/InstallData.php,asfollows:
$customerSetup=$this->customerSetupFactory->create(['setup'=>$setup]);
$customerSetup->addAttribute(
\Magento\Customer\Model\Customer::ENTITY,
'contact_preferences',
[
'type'=>'varchar',
'label'=>'ContactPreferences',
'input'=>'multiselect',
'source'=>\Magelicious\ContactPreferences\Model\Entity\Attribute\Source\Contact\Preferences::class,
'required'=>0,
'sort_order'=>99,
'position'=>99,
'system'=>0,
'visible'=>1,
'global'=>\Magento\Catalog\Model\ResourceModel\Eav\Attribute::SCOPE_GLOBAL,
]
![Page 219: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/219.jpg)
);
$contactPreferencesAttr=$customerSetup
->getEavConfig()
->getAttribute(
\Magento\Customer\Model\Customer::ENTITY,
'contact_preferences'
);
$contactPreferencesAttr->setData('used_in_forms',['adminhtml_customer']);
$contactPreferencesAttr->save();
WeareinstructingMagentotocreateamultiselecttypeofattribute.TheattributebecomesvisibleundertheMagentoadminarea,withacustomereditingscreenasfollows:
Wethendefinethe<MODULE_DIR>/Model/Entity/Attribute/Source/Contact/Preferences.php,asfollows:
namespaceMagelicious\ContactPreferences\Model\Entity\Attribute\Source\Contact;
classPreferencesextends\Magento\Eav\Model\Entity\Attribute\Source\AbstractSource
{
constVALUE_EMAIL='email';
constVALUE_PHONE='phone';
constVALUE_POST='post';
constVALUE_SMS='sms';
publicfunctiongetAllOptions()
{
return[
['label'=>__('Email'),'value'=>self::VALUE_EMAIL],
['label'=>__('Phone'),'value'=>self::VALUE_PHONE],
['label'=>__('Post'),'value'=>self::VALUE_POST],
['label'=>__('SMS'),'value'=>self::VALUE_SMS],
];
}
}
![Page 220: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/220.jpg)
Thesearethecontactpreferenceoptionswewanttoprovideasourattributesource.Wewillusethisclassnotjustforinstallation,butlateronaswell.
Wethendefinethe<MODULE_DIR>/etc/frontend/routes.xml,asfollows:
<config>
<routerid="standard">
<routeid="customer"frontName="customer">
<modulename="Magelicious_ContactPreferences"before="Magento_Customer"/>
</route>
</router>
</config>
Unlikeourroutedefinitionsinpreviouschapters,hereweareusinganalreadyexistingroutenamecustomer.TheattributebeforeitallowsustoinsertourmodulebeforetheMagento_Customermodule,allowingustorespondtothesamecustomer/*routes.Weshouldbeverycarefulwiththisapproach,nottodetachsomeoftheexistingcontrolleractions.Inourcase,weareonlydoingthissothatwemightusethecustomer/contact/preferencesURLlateron.
Wethendefinethe<MODULE_DIR>/Controller/Contact/Preferences.php,asfollows:
namespaceMagelicious\ContactPreferences\Controller\Contact;
classPreferencesextends\Magento\Customer\Controller\AbstractAccount
{
publicfunctionexecute()
{
if($this->getRequest()->isPost()){
$resultJson=$this->resultFactory->create(\Magento\Framework\Controller\ResultFactory::TYPE_JSON);
if($this->getRequest()->getParam('load')){
//Merelyfortriggering"contact_preferences"section
}else{
//SAVEPREFERENCES
}
return$resultJson;
}else{
$resultPage=$this->resultFactory->create(\Magento\Framework\Controller\ResultFactory::TYPE_PAGE);
$resultPage->getConfig()->getTitle()->set(__('MyContactPreferences'));
return$resultPage;
}
}
}
Thisistheonlycontrolleractionwewillhave.Wewillusethesameactionforhandlingthreedifferentintents.Thisisnotanidealexampleofhowoneshouldwritecodeinthisscenario,butitisacompactone.Thefirstintentwewillhandleisthesectionloadtrigger,thesecondistheactualpreferencesave,and
![Page 221: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/221.jpg)
thethirdisthepageload.Thesewillbecomeclearaswemoveforward.
WethenreplacetheSAVEPREFERENCEScommentwiththefollowing:
//\Magento\Framework\App\Action\Context$context
//\Magento\Customer\Model\Session$customerSession
//\Magento\Customer\Api\CustomerRepositoryInterface$customerRepository
//\Psr\Log\LoggerInterface$logger
try{
$preferences=implode(',',
array_keys(
array_filter($this->getRequest()->getParams(),function($_checked,$_preference){
returnfilter_var($_checked,FILTER_VALIDATE_BOOLEAN);
},ARRAY_FILTER_USE_BOTH)
)
);
$customer=$this->customerRepository->getById($this->customerSession->getCustomerId());
$customer->setCustomAttribute('contact_preferences',$preferences);
$this->customerRepository->save($customer);
$this->messageManager->addSuccessMessage(__('Successfullysavedcontactpreferences.'));
}catch(\Exception$e){
$this->logger->critical($e);
$this->messageManager->addErrorMessage(__('Errorsavingcontactpreferences.'));
}
Herewearehandlingtheactualsavingofthechosencontactpreferences.Therequestparametersareexpectedtobeinthe<preference_name>=<true|false>format.Weusetheimplodetoturntheincomingrequestandpassitontotherepository'ssetCustomAttributemethod.Thisisbecause,bydefault,Magentostoresthemultiselectattributeasacomma-separatedstringinthedatabase.TheaddSuccessMessageandaddErrorMessagecallsareinterestinghere.OnemightexpectthatwewouldreturnthesemessagesaspartofaJSONresponse.But,wedon'treallyneedaJSONresponsebodyhere.ThisisbecauseMagentohasthemessagessectiondefinedunder<MAGENTO_DIR>/module-theme/etc/frontend/sections.xmlas<actionname="*">.Whatthismeansisthatmessagesgetrefresheduponeverysectionloadand,sinceourcontrolleractionismappedinourownsections.xml,theloadofoursectionwillalsoloadmessages.
Wethendefinethe<MODULE_DIR>/view/frontend/layout/customer_account.xml,asfollows:
<page>
<body>
<referenceBlockname="customer_account_navigation">
<blockclass="Magento\Customer\Block\Account\SortLinkInterface"name="customer-account-navigation-contact-preferences-link">
<arguments>
<argumentname="path"xsi:type="string">customer/contact/preferences</argument>
<argumentname="label"xsi:type="string"translate="true">MyContactPreferences</argument>
<argumentname="sortOrder"xsi:type="number">230</argument>
</arguments>
</block>
![Page 222: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/222.jpg)
</referenceBlock>
</body>
</page>
Thedefinitionshereinjectanewmenuitemunderthecustomer'sMyAccountscreen.Thecustomer_account_navigationblock,originallydefinedunder<MAGENTO_DIR>/module-customer/view/frontend/layout/customer_account.xml,isinchargeofrenderingthesidebarmenu.ByinjectingthenewblockofMagento\Customer\Block\Account\SortLinkInterfacetype,wecaneasilyaddnewmenuitems.
Wethendefinethe<MODULE_DIR>/view/frontend/layout/customer_contact_preferences.xml,asfollows:
<page>
<updatehandle="customer_account"/>
<body>
<referenceContainername="content">
<blockname="contact_preferences"
template="Magelicious_ContactPreferences::customer/contact/preferences.phtml"cacheable="false"/>
</referenceContainer>
</body>
</page>
Thisistheblockthatwillgetloadedintothecontentareaofapage,onceweclickonournewlyaddedMyContactPreferenceslink.Sincetheonlyroleofthecontact_preferencesblockwillbetoloadtheJScomponent,weomittheclassdefinitionthatwewouldnormallyhaveoncustomblocks.
Wethendefinethe<MODULE_DIR>/view/frontend/templates/customer/contact/preferences.phtml,asfollows:
<divclass="contact-preferences"data-bind="scope:'contact-preferences-scope'">
<!--kotemplate:getTemplate()--><!--/ko-->
</div>
<scripttype="text/x-magento-init">
{
".contact-preferences":{
"Magento_Ui/js/core/app":{
"components":{
"contact-preferences-scope":{
"component":"contactPreferences"
}
}
}
}
}
</script>
![Page 223: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/223.jpg)
TheonlypurposeofthetemplatehereistoloadtheJScontactPreferencescomponent.Wecanseethatnodataispassedfromtheserver-side.phtmltemplatetotheJScomponent.WewillusethesectionandcustomerDatamechanismslateronforthat.
Wethendefinethe<MODULE_DIR>/view/frontend/requirejs-config.js,asfollows:
varconfig={
map:{
'*':{
contactPreferences:'Magelicious_ContactPreferences/js/view/contact-preferences'
}
}
};
Herewemapthecomponentname,contactPreferences,toitsphysicallocationinourmoduledirectory.
Wethendefinethe<MODULE_DIR>/view/frontend/web/js/view/contact-preferences.js,asfollows:
define([
'uiComponent',
'jquery',
'mage/url',
'Magento_Customer/js/customer-data'
],function(Component,$,url,customerData){
'usestrict';
letcontactPreferences=customerData.get('contact_preferences');
returnComponent.extend({
defaults:{
template:'Magelicious_ContactPreferences/contact-preferences'
},
initialize:function(){/*...*/},
isCustomerLoggedIn:function(){
returncontactPreferences().isCustomerLoggedIn;
},
getSelectOptions:function(){
returncontactPreferences().selectOptions;
},
saveContactPreferences:function(){/*...*/}
});
});
ThisisourJScomponent,thecoreofourclient-sidefunctionality.WeinjecttheMagento_Customer/js/customer-datacomponentasacustomerDataobject.ThisgivesusaccesstodatawearepushingfromtheserversideviathegetSectionDatamethodoftheMagelicious\ContactPreferences\CustomerData\Preferencesclass.Thestringvaluecontact_preferencespassedtothegetmethodofthecustomerDataobjectmustmatchtheitemnameunderthesectionSourceMapofourdi.xmldefinition.
![Page 224: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/224.jpg)
Let'sextendtheinitializefunctionfurther,asfollows:
initialize:function(){
this._super();
$.ajax({
type:'POST',
url:url.build('customer/contact/preferences'),
data:{'load':true},
showLoader:true
});
}
TheadditionofanAJAXrequestcallwithinthecomponent'sinitializemethodismoreofatricktotriggerthecontact_preferencessectionloadinourcase.WearedoingitsimplybecausesectionsdonotloadonHTTPGETrequests,asthatmightloadthesamecustomer/contact/preferencespage.Rather,theyloadonHTTPPOSTevents.Thiswayweensurethatthecontact_preferencessectionwillloadwhenourcomponentisinitialized,thusprovidingitwiththenecessarydata.WearefarfromsayingthatthisisarecommendedapproachforgeneralJScomponentdevelopment,though.
Let'sextendthesaveContactPreferencesfunctionfurther,asfollows:
saveContactPreferences:function(){
letpreferences={};
$('.contact_preference').children(':checkbox').each(function(){
preferences[$(this).attr('name')]=$(this).attr('checked')?true:false;
});
$.ajax({
type:'POST',
url:url.build('customer/contact/preferences'),
data:preferences,
showLoader:true,
complete:function(response){
//someactions...
}
});
returntrue;
}
ThesaveContactPreferencesmethodwillbetriggeredeverytimeacustomerclicksonthecontactpreferenceonthestorefront,whetheritisanactofcheckingoruncheckingindividualcontactpreferences.
Wethendefinethe<MODULE_DIR>/view/frontend/web/template/contact-preferences.html,asfollows:
![Page 225: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/225.jpg)
<divdata-bind="if:isCustomerLoggedIn()">
<divdata-role="title"data-bind="i18n:'ContactPreferences'"></div>
<divdata-role="content">
<divclass="contact_preference"repeat="foreach:getSelectOptions(),item:'$option'">
<inputtype="checkbox"
click="saveContactPreferences"
ko-checked="$option().checked"
attr="name:$option().value"/>
<labeltext="$option().label"attr="for:$option().value"/>
</div>
</div>
</div>
TheHTMLdefinedherevisuallysetsourcomponent.AbasicknowledgeofKnockoutJSisrequiredinordertoutilizetherepeatdirective,fedwiththearrayofdatacomingfromthegetSelectOptionsmethod,whichbynowweknoworiginatesfromtheserverside.
Wethendefinethe<MODULE_DIR>/etc/frontend/sections.xml,asfollows:
<config>
<actionname="customer/contact/preferences">
<sectionname="contact_preferences"/>
</action>
</config>
Withthis,wemakethenecessarymappingbetweenHTTPPOSTcustomer/contact/preferencesrequestsandthecontact_preferencessectionweexpecttoload.
Wethendefinethe<MODULE_DIR>/etc/frontend/di.xml,asfollows:
<config>
<typename="Magento\Customer\CustomerData\SectionPoolInterface">
<arguments>
<argumentname="sectionSourceMap"xsi:type="array">
<itemname="contact_preferences"xsi:type="string">Magelicious\ContactPreferences\CustomerData\Preferences</item>
</argument>
</arguments>
</type>
</config>
Hereweinjectourcontact_preferencessection,instructingMagentowheretoreaditsdatafrom.Withthisinplace,anyHTTPPOSTcustomer/contact/preferencesrequestisexpectedtotriggerafollow-upAJAXPOSTcustomer/section/load/?sections=contact_preferences%2Cmessages&update_section_id=true&_=1533887023603requestthat,inturn,returnsdatamuchlikethefollowing:
{
"contact_preferences":{
![Page 226: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/226.jpg)
"selectOptions":[
{
"label":"Email",
"value":"email",
"checked":true
},
{...}
],
"isCustomerLoggedIn":true,
"data_id":1533875246
},
"messages":{
"messages":[
{
"type":"success",
"text":"Successfullysavedcontactpreferences."
}
],
"data_id":1533875246
}
}
Ifweweretoenableourmoduleatthispoint,weshouldbeabletoseeitworkingunderthecustomer'sMyAccountscreen.Thoughsimple,thestepsofgettingeverythinglinkedweresomewhatinvolved.Thebenefitofthisapproach,wheredataissentviathesectionsmechanism,isthatourcomponentplaysnicelywithfull-pagecaching.Theneededcustomer-relateddataissimplyfetchedbyadditionalAJAXcalls,insteadofcachingitonaper-customerbasis,andthusthisbypassesthepurposeoffull-pagecaching.
![Page 227: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/227.jpg)
AddingcontactpreferencestothecheckoutWithourcomponentnowworkingonthecustomer'sMyAccountpage,let'sgoaheadandaddittothecheckout'sReview&Paymentsstepaswell.
Bytappingintothecheckout_index_indexlayouthandle,andnestingourcomponentunderthedesiredchildrenelement,wecaneasilyaddittothecheckoutpage.Wedosowiththe<MODULE_DIR>/view/frontend/layout/checkout_index_index.xmlfile,asfollows:
<page>
<body>
<referenceBlockname="checkout.root">
<arguments>
<argumentname="jsLayout"xsi:type="array">
<itemname="components"xsi:type="array">
<itemname="checkout"xsi:type="array">
<itemname="children"xsi:type="array">
<itemname="steps"xsi:type="array">
<itemname="children"xsi:type="array">
<itemname="billing-step"xsi:type="array">
<itemname="children"xsi:type="array">
<itemname="payment"xsi:type="array">
<itemname="children"xsi:type="array">
<itemname="afterMethods"xsi:type="array">
<itemname="children"xsi:type="array">
<itemname="contact-preferences"xsi:type="array">
<itemname="component"xsi:type="string">Magelicious_ContactPreferences/js/view/contact-preferences</item>
<!--closingtags-->
Thenestingstructureofcheckout_index_index.xmlisquiterobust.Thereareseveralplaceswherewecanactuallyinsertourowncomponent.Mostofthetime,thismightbetrialanderror.Inthiscase,weoptedforthechildrenareaofafterMethods.Thisshouldpositionitunderthecheckout'sReview&Paymentsstep,rightafterthepaymentsmethodlist.
![Page 228: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/228.jpg)
SummaryInthischapter,wehavebuiltasmallmodulethatallowedustogetagreaterinsightintoMagento'scustomerDataandsectionsmechanisms.Wemanagedtobuildasinglecomponent,thatgotusedbothonthecustomer'sMyAccountpage,aswellasonthecheckout.
Withthis,wehavereachedtheendofourbook.ThetopicswehavecoveredshouldbeenoughtogetusgoingwithMagentodevelopment,butthesheersizeoftheplatformandtheintricatespecificsofitsindividualmodulesleaveplentymoretoexplorefurtheron.Itgoeswithoutsayingthatourjourneyhasmerelybegun.
![Page 229: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/229.jpg)
OtherBooksYouMayEnjoyIfyouenjoyedthisbook,youmaybeinterestedintheseotherbooksbyPackt:
Magento2BeginnersGuideGabrielGuarino
ISBN:9781785880766
BuildyourfirstwebstoreinMagento2MigrateyourdevelopmentenvironmenttoalivestoreConfigureyourMagento2webstoretherightway,sothatyourtaxesarehandledproperlyCreatepageswitharbitrarycontentCreateandmanagecustomercontactsandaccountsProtectMagentoinstanceadminfromunexpectedintrusionsSetupnewsletterandtransactionalemailssothatcommunicationfromyourwebsitecorrespondstothewebsite'slookandfeelMakethestorelookgoodintermsofPCIcompliance
Magento2Developer'sGuide
![Page 230: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/230.jpg)
BrankoAjzele
ISBN:9781785886584
SetupthedevelopmentandproductionenvironmentofMagento2UnderstandthenewmajorconceptsandconventionsusedinMagento2Buildaminiatureyetfully-functionalmodulefromscratchtomanageyoure-commerceplatformefficientlyWritemodelsandcollectionstomanageandsearchyourentitydataDiveintobackenddevelopmentsuchascreatingevents,observers,cronjobs,logging,profiling,andmessagingfeaturesGettothecoreoffrontenddevelopmentsuchasblocks,templates,layouts,andthethemesofMagento2Usetoken,session,andOauthtoken-basedauthenticationviavariousflavorsofAPIcalls,aswellascreatingyourownAPIsGettogripswithtestingMagentomodulesandcustomMagentothemes,whichformsanintegralpartofdevelopment
![Page 231: Magento 2 Development Quick Start Guide · 2020-04-16 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer,](https://reader034.vdocuments.net/reader034/viewer/2022042515/5f84196137d4cf5d4e1f9947/html5/thumbnails/231.jpg)
Leaveareview-letotherreadersknowwhatyouthinkPleaseshareyourthoughtsonthisbookwithothersbyleavingareviewonthesitethatyouboughtitfrom.IfyoupurchasedthebookfromAmazon,pleaseleaveusanhonestreviewonthisbook'sAmazonpage.Thisisvitalsothatotherpotentialreaderscanseeanduseyourunbiasedopiniontomakepurchasingdecisions,wecanunderstandwhatourcustomersthinkaboutourproducts,andourauthorscanseeyourfeedbackonthetitlethattheyhaveworkedwithPackttocreate.Itwillonlytakeafewminutesofyourtime,butisvaluabletootherpotentialcustomers,ourauthors,andPackt.Thankyou!