magento 2 development quick start guide · 2020-04-16 · magento certified solution specialist,...

231

Upload: others

Post on 31-Jul-2020

28 views

Category:

Documents


6 download

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,
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,

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,

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,

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,

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,

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,

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,

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,

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,

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,

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,

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,

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,

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,

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,

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,

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,

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,

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,

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,

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,

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,

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,

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,

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,

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,

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,

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,

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,

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\

[email protected]\

--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,

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,

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,

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,

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,

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,

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,

<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,

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,

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,

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,

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,

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,

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,

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,

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,

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,

$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,

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,

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,

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,

<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,

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,

<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,

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,

$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,

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,

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,

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,

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,

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,

}

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,

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,

//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,

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,

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,

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,

'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,

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,

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,

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,

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,

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,

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,

*****/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,

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,

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,

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,

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,

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,

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,

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,

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,

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,

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,

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,

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,

)->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,

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,

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,

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,

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,

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,

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,

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,

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,

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,

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,

\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,

){

$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,

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,

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,

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,

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,

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,

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,

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,

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,

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,

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,

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,

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,

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,

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,

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&amp;searchCriteria[filter_groups][0][filters][0][value]=%Block%&amp;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,

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,

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,

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,

}

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,

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,

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,

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,

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,

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,

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,

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,

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,

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,

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,

</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,

<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,

$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,

$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,

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,

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,

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,

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,

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,

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,

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,

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,

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,

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,

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,

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,

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,

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,

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,

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,

<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,

\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,

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,

</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,

</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,

}

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,

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,

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,

<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,

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,

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,

}

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,

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,

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,

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,

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,

<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,

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,

<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,

<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,

<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,

<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,

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,

(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,

/*...*/

},

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,

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,

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,

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,

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,

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,

<?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,

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,

'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,

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,

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,

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,

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,

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,

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,

$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,

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,

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,

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,

}

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,

$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,

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,

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,

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,

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,

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,

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,

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,

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,

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,

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,

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,

$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,

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,

</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,

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,

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,

</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,

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,

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,

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,

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,

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,

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,

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,

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,

);

$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,

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,

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,

</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,

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,

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,

<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,

"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,

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,

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,

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,

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,

Leaveareview-letotherreadersknowwhatyouthinkPleaseshareyourthoughtsonthisbookwithothersbyleavingareviewonthesitethatyouboughtitfrom.IfyoupurchasedthebookfromAmazon,pleaseleaveusanhonestreviewonthisbook'sAmazonpage.Thisisvitalsothatotherpotentialreaderscanseeanduseyourunbiasedopiniontomakepurchasingdecisions,wecanunderstandwhatourcustomersthinkaboutourproducts,andourauthorscanseeyourfeedbackonthetitlethattheyhaveworkedwithPackttocreate.Itwillonlytakeafewminutesofyourtime,butisvaluabletootherpotentialcustomers,ourauthors,andPackt.Thankyou!