231

Magento 2 Development Quick Start Guide · 2020. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

  • Upload
    others

  • View
    9

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Magento 2 Development Quick Start Guide · 2020. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento
Page 2: Magento 2 Development Quick Start Guide · 2020. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

Magento2DevelopmentQuickStartGuide

BuildbetterstoresbyextendingMagento

BrankoAjzele

Page 3: Magento 2 Development Quick Start Guide · 2020. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

BIRMINGHAM-MUMBAI

Page 4: Magento 2 Development Quick Start Guide · 2020. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

mapt.io

Maptisanonlinedigitallibrarythatgivesyoufullaccesstoover5,000booksandvideos,aswellasindustryleadingtoolstohelpyouplanyourpersonaldevelopmentandadvanceyourcareer.Formoreinformation,pleasevisitourwebsite.

Page 6: Magento 2 Development Quick Start Guide · 2020. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

Whysubscribe?SpendlesstimelearningandmoretimecodingwithpracticaleBooksandVideosfromover4,000industryprofessionals

ImproveyourlearningwithSkillPlansbuiltespeciallyforyou

GetafreeeBookorvideoeverymonth

Maptisfullysearchable

Copyandpaste,print,andbookmarkcontent

Page 7: Magento 2 Development Quick Start Guide · 2020. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

Contributors

Page 9: Magento 2 Development Quick Start Guide · 2020. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

PacktissearchingforauthorslikeyouIfyou'reinterestedinbecominganauthorforPackt,pleasevisitauthors.packtpub.comandapplytoday.Wehaveworkedwiththousandsofdevelopersandtechprofessionals,justlikeyou,tohelpthemsharetheirinsightwiththeglobaltechcommunity.Youcanmakeageneralapplication,applyforaspecifichottopicthatwearerecruitinganauthorfor,orsubmityourownidea.

Page 12: Magento 2 Development Quick Start Guide · 2020. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

TableofContentsTitlePageCopyrightandCredits

Magento2DevelopmentQuickStartGuidePacktUpsell

Whysubscribe?Packt.com

ContributorsAbouttheauthorAboutthereviewerPacktissearchingforauthorslikeyou

PrefaceWhothisbookisforWhatthisbookcoversTogetthemostoutofthisbook

DownloadtheexamplecodefilesCodeinAction

ConventionsusedGetintouch

Reviews1. UnderstandingtheMagentoArchitecture

TechnicalrequirementsInstallingMagentoModesAreasRequestflowprocessingModules

CreatingtheminimalmoduleCacheDependencyinjection

ArgumentinjectionVirtualtypesProxiesFactories

PluginsThebeforepluginThearoundpluginTheafterplugin

Page 13: Magento 2 Development Quick Start Guide · 2020. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

6. DevelopingforStorefrontTechnicalrequirementsSettinguptheplaygroundCallingandinitializingJScomponentsMeetRequireJSReplacingjQuerywidgetcomponentsExtendingjQuerywidgetcomponentsCreatingjQuerywidgetscomponentsCreatingUI/KnockoutJScomponentsExtendingUI/KnockoutJScomponentsSummary

7. CustomizingCatalogBehaviorTechnicalrequirementsCreatingthesizeguideCreatingthesamedaydeliveryFlaggingnewproductsSummary

8. CustomizingCheckoutExperiencesTechnicalrequirementsPassingdatatothecheckoutAddingordernotestothecheckoutSummary

9. CustomizingCustomerInteractionsTechnicalrequirementsUnderstandingthesectionmechanismAddingcontactpreferencestocustomeraccountsAddingcontactpreferencestothecheckoutSummary

OtherBooksYouMayEnjoyLeaveareview-letotherreadersknowwhatyouthink

Page 15: Magento 2 Development Quick Start Guide · 2020. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

WhothisbookisforThisbookisintendedforPHPdevelopersgettingstartedwithMagentov2.xdevelopment.Thoughcompactintermsofpagenumbers,thebookcoversawiderangeoffunctionality,allowingthereadertomasterday-to-dayMagentoskillsinaclearandconciseway.NopreviousMagentoknowledgeisrequired.

Page 17: Magento 2 Development Quick Start Guide · 2020. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

TogetthemostoutofthisbookTogetthemostoutofthebook,thereaderisexpectedtohave:

AdegreeofPHPobject-orientedprogramming(OOP)knowledgeAbasicunderstandingofJavaScriptandXML

Page 20: Magento 2 Development Quick Start Guide · 2020. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

CodeinActionVisitthefollowinglinktocheckoutvideosofthecodebeingrun:

http://bit.ly/2D98D8q

Page 22: Magento 2 Development Quick Start Guide · 2020. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

Tipsandtricksappearlikethis.

Page 24: Magento 2 Development Quick Start Guide · 2020. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

ReviewsPleaseleaveareview.Onceyouhavereadandusedthisbook,whynotleaveareviewonthesitethatyoupurchaseditfrom?Potentialreaderscanthenseeanduseyourunbiasedopiniontomakepurchasedecisions,weatPacktcanunderstandwhatyouthinkaboutourproducts,andourauthorscanseeyourfeedbackontheirbook.Thankyou!

FormoreinformationaboutPackt,pleasevisitpacktpub.com.

Page 26: Magento 2 Development Quick Start Guide · 2020. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

MagentoadminFastestperformance

Carefullybalancingdevelopermodewithsomeofthecachetypesbeingenabled/disabledcanprovideoptimalperformanceduringdevelopment.

Page 34: Magento 2 Development Quick Start Guide · 2020. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

<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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

$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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

<MAGENTO_DIR>/framework/ObjectManager/etc/config.xsdforspecifictypedefinitions.

Argumentinjectionoftengoeshandinhandwithvirtualtypes,aswewillsoonsee.

Page 52: Magento 2 Development Quick Start Guide · 2020. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

<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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

$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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

}

Page 62: Magento 2 Development Quick Start Guide · 2020. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

//Therestofthecode...

$result=$proceed($linkId);

//Therestofthecode...

return$result;

}

Page 64: Magento 2 Development Quick Start Guide · 2020. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

observingthesamemethod.

Page 66: Magento 2 Development Quick Start Guide · 2020. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

'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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

*****/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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

Whilethisexamplewouldwork,thereisfarmoretoitthanthis.Creatingtablesmanuallyisnotaviableoptionforbuildingmodules.Magentohasjusttherightmechanismforthis,whichiscalledsetupscripts.

Page 85: Magento 2 Development Quick Start Guide · 2020. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

)->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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

dropTable:DropsthetablefromadatabasemodifyColumn:Modifiesthecolumndefinition

Page 90: Magento 2 Development Quick Start Guide · 2020. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

\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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

){

$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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

Magentocheckoutisanexcellentexampleofthat.ThewholecheckoutisafullyAJAX-enabledapponitsown,separatefromtheusualMagentostorefront,suchasitsCMS,category,andproductpages.

Page 109: Magento 2 Development Quick Start Guide · 2020. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

}

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

CRUDourwaythroughBoxyBoxthesamewaywedidwiththeCMSblock.Whiletherecertainlyisagreatdealofboilerplatecodetogoaround,ourAPIisnowbothREST-andSOAP-ready.

Page 122: Magento 2 Development Quick Start Guide · 2020. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

</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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

<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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

$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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

$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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

Likewise,theadminCreateNewOrderscreensshouldalsoshowourRoyalTrekshippingmethodsasfollows:

Finally,thesuccessfullymadeordershouldreflecttheRoyalTrek48hshippingmethodselectioninitsneworderemail,andthecustomer'sMyAccountarea,asfollows:

Withourshippingmethodsconfirmedasworking,let'sgoaheadandlookforawayofdistributingit.

Page 134: Magento 2 Development Quick Start Guide · 2020. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

waytogo.

Page 136: Magento 2 Development Quick Start Guide · 2020. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

DistributingviaPackagistPackagistisafreeonlinerepositoryserviceforComposerpackages.WecanuseittoeasilydistributeourfreeMagentomodules.ThefactthatPackagistisadefaultComposerrepository,makesitthedefactorepositoryforanyComposeruser.ThisiswhyhavingourfreeMagentomodulesavailableviaPackagistisapreferredwayofdistribution.

PushingourMagentomoduletoPackagistisquiteeasy.Assumingwehaveouraccountcreated,weshouldstartbyclickingontheSubmitbutton,whichwilllandusonthefollowingscreen:

WeneedtoprovidealinktoourGitrepositoryhere,andclicktheCheckbutton,followedbytheSubmitbutton,ifthevalidrepositorywasfound.Thisshouldcreateourpackage,asperthefollowingscreen:

Page 137: Magento 2 Development Quick Start Guide · 2020. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

SummaryInthischapter,welearnedhowtocreateasimpleshippingmodule.Wesawhoweasyitistoaddspecificshippingcalculationsaspartofofflineshippingmethods.WethenpackagedthismoduleanddistributeditviaPackagist.Thismadeiteasyfortheendconsumertouseourmodule,withjustafewsimpleconsolecommands.Likewise,anyfutureupdatestoourmoduleshouldbefrictionlessfortheendconsumer,ascomposercaneasilyhandlethoseviasimplecomposerupdatecommands.

Movingforward,wearegoingtotakealookatsomeofthespecificsofMagentoadminareadevelopment.

Page 141: Magento 2 Development Quick Start Guide · 2020. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

<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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

\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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

</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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

</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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

}

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

<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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

}

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

SummaryInthischapter,webuilttwoverydifferentscreensintheMagentoadminarea.Oneutilizedthelistingcomponent,whereastheotherutilizedtheformcomponent.Agreatdealofourworkinvolvedconfigurationratherthancoding,whichstandstoprovehowpowerfulMagentoUIcomponentscanbe.Whiletheamountofconfigurationmightseemoverwhelmingatfirst,gettingagriponindividualcomponentconfigurationsallowsustobuildcomplexinterfacesquickly.

Movingforward,wearegoingtotakealookatsomeofthespecificsbehinddevelopingforthestorefrontarea.

Page 162: Magento 2 Development Quick Start Guide · 2020. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

<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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

<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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

<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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

<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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

<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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

(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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

/*...*/

},

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

ournamespaceandredirectUrlistheactualnameofthewidgetwithinournamespace.

Oncewerefreshthestaticcontentviathephpbin/magentosetup:static-content:deploycommand,weshouldnowbeabletoseemagelicious.redirectUrlshowupinthebrowserconsolewindow.Clearly,thecurrentimplementationofredirectUrlwouldbreakthefunctionalitywehadwiththeoriginalcomponent,butitgoestoshowhoweasilywecanfullyreplacethecomponentwithanewone.

Page 176: Magento 2 Development Quick Start Guide · 2020. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

<?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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

'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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

$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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

}

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

$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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

Oncethetimerreaches0hours0minutes0seconds,itshoulddisappear.

Page 197: Magento 2 Development Quick Start Guide · 2020. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

SummaryInthischapter,wehavebuiltthreedistinctivefunctionalities,allofwhichrelatetothecatalogpartofMagento.Thoughverylightweight,theystandtoshowhoweasilyMagentocanbeextendedwithnewfeatureswithoutreallyoverridinganyofthecorefiles.UsingpluginsandJScomponentsaremerelysomeoftheapproacheswemighttake.Quiteoften,wewillfindthatasinglerequirementmightbedeliveredwithmorethanoneapproach.Themainguidingruleforourcodeshouldalwaysbe:usetheleastintrusive.Catalogfunctionalityplaysamajorroleinthecustomerconversionprocess,soourpriorityshouldalwaysbefailsafewhenpossible.

Movingforward,wearegoingtotakealookatsomeofthethingswecandotocustomizethecheckout.

Page 200: Magento 2 Development Quick Start Guide · 2020. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

CustomizingCheckoutExperiencesWhilethedefaultMagentocheckoutprovideseverythingashopneedstocompleteatransactionsuccessfully,therearedetailsspecifictotheindividualbusinessesthatoftenneedtobeaddressed.Agreatdealofthesedetailsoftenrelatetocheckoutcustomizationsthatallowforthecapturingofadditionalinformationorengagingcustomersinagreementsandsubscriptionactivities.

Movingforward,wearegoingtotakealookatthefollowing:

PassingdatatothecheckoutAddingordernotestothecheckout

Page 201: Magento 2 Development Quick Start Guide · 2020. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

canpushourserver-sidedatatoourstorefrontwhenapageloads.

Tounderstandcheckoutmodificationsbetter,weshouldfamiliarizeourselveswiththecontentofthewindow.checkoutConfigobject.

Page 204: Magento 2 Development Quick Start Guide · 2020. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

$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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

</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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

</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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

AddingcontactpreferencestocustomeraccountsNowthatweunderstandthemechanismbehindthecustomerDataobjectandthesectionload,let'sputittousebycreatingasmallmodulethataddscontactpreferencesfunctionalityunderthecustomer'sMyAccountarea,aswellasunderthecheckout.OurworkistobedoneaspartoftheMagelicious_ContactPreferencesmodule,withthefinalvisualoutcomeasfollows:

Bycontrast,thecustomer'scheckoutareawouldshowcontactpreferences,asfollows:

Page 218: Magento 2 Development Quick Start Guide · 2020. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

);

$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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

</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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

<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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

"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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

SummaryInthischapter,wehavebuiltasmallmodulethatallowedustogetagreaterinsightintoMagento'scustomerDataandsectionsmechanisms.Wemanagedtobuildasinglecomponent,thatgotusedbothonthecustomer'sMyAccountpage,aswellasonthecheckout.

Withthis,wehavereachedtheendofourbook.ThetopicswehavecoveredshouldbeenoughtogetusgoingwithMagentodevelopment,butthesheersizeoftheplatformandtheintricatespecificsofitsindividualmodulesleaveplentymoretoexplorefurtheron.Itgoeswithoutsayingthatourjourneyhasmerelybegun.

Page 229: Magento 2 Development Quick Start Guide · 2020. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

OtherBooksYouMayEnjoyIfyouenjoyedthisbook,youmaybeinterestedintheseotherbooksbyPackt:

Magento2BeginnersGuideGabrielGuarino

ISBN:9781785880766

BuildyourfirstwebstoreinMagento2MigrateyourdevelopmentenvironmenttoalivestoreConfigureyourMagento2webstoretherightway,sothatyourtaxesarehandledproperlyCreatepageswitharbitrarycontentCreateandmanagecustomercontactsandaccountsProtectMagentoinstanceadminfromunexpectedintrusionsSetupnewsletterandtransactionalemailssothatcommunicationfromyourwebsitecorrespondstothewebsite'slookandfeelMakethestorelookgoodintermsofPCIcompliance

Magento2Developer'sGuide

Page 230: Magento 2 Development Quick Start Guide · 2020. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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. 4. 16. · Branko holds several respected IT certifications, including Zend Certified PHP Engineer, Magento Certified Developer, Magento

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