magento 2 development quick start guide · magento certified solution specialist, magento 2...

231

Upload: others

Post on 31-Jul-2020

34 views

Category:

Documents


7 download

TRANSCRIPT

Page 1: Magento 2 Development Quick Start Guide · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just
Page 2: Magento 2 Development Quick Start Guide · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

Magento2DevelopmentQuickStartGuide

BuildbetterstoresbyextendingMagento

BrankoAjzele

Page 3: Magento 2 Development Quick Start Guide · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

BIRMINGHAM-MUMBAI

Page 4: Magento 2 Development Quick Start Guide · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

mapt.io

Maptisanonlinedigitallibrarythatgivesyoufullaccesstoover5,000booksandvideos,aswellasindustryleadingtoolstohelpyouplanyourpersonaldevelopmentandadvanceyourcareer.Formoreinformation,pleasevisitourwebsite.

Page 6: Magento 2 Development Quick Start Guide · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

Whysubscribe?SpendlesstimelearningandmoretimecodingwithpracticaleBooksandVideosfromover4,000industryprofessionals

ImproveyourlearningwithSkillPlansbuiltespeciallyforyou

GetafreeeBookorvideoeverymonth

Maptisfullysearchable

Copyandpaste,print,andbookmarkcontent

Page 7: Magento 2 Development Quick Start Guide · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

Packt.comDidyouknowthatPacktofferseBookversionsofeverybookpublished,withPDFandePubfilesavailable?YoucanupgradetotheeBookversionatwww.packt.comandasaprintbookcustomer,youareentitledtoadiscountontheeBookcopy.Getintouchwithusatcustomercare@packtpub.comformoredetails.

Atwww.packt.com,youcanalsoreadacollectionoffreetechnicalarticles,signupforarangeoffreenewsletters,andreceiveexclusivediscountsandoffersonPacktbooksandeBooks.

Page 8: Magento 2 Development Quick Start Guide · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

Contributors

Page 9: Magento 2 Development Quick Start Guide · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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

Page 12: Magento 2 Development Quick Start Guide · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

TableofContentsTitlePageCopyrightandCredits

Magento2DevelopmentQuickStartGuidePacktUpsell

Whysubscribe?Packt.com

ContributorsAbouttheauthorAboutthereviewerPacktissearchingforauthorslikeyou

PrefaceWhothisbookisforWhatthisbookcoversTogetthemostoutofthisbook

DownloadtheexamplecodefilesCodeinAction

ConventionsusedGetintouch

Reviews1. UnderstandingtheMagentoArchitecture

TechnicalrequirementsInstallingMagentoModesAreasRequestflowprocessingModules

CreatingtheminimalmoduleCacheDependencyinjection

ArgumentinjectionVirtualtypesProxiesFactories

PluginsThebeforepluginThearoundpluginTheafterplugin

Page 13: Magento 2 Development Quick Start Guide · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

6. DevelopingforStorefrontTechnicalrequirementsSettinguptheplaygroundCallingandinitializingJScomponentsMeetRequireJSReplacingjQuerywidgetcomponentsExtendingjQuerywidgetcomponentsCreatingjQuerywidgetscomponentsCreatingUI/KnockoutJScomponentsExtendingUI/KnockoutJScomponentsSummary

7. CustomizingCatalogBehaviorTechnicalrequirementsCreatingthesizeguideCreatingthesamedaydeliveryFlaggingnewproductsSummary

8. CustomizingCheckoutExperiencesTechnicalrequirementsPassingdatatothecheckoutAddingordernotestothecheckoutSummary

9. CustomizingCustomerInteractionsTechnicalrequirementsUnderstandingthesectionmechanismAddingcontactpreferencestocustomeraccountsAddingcontactpreferencestothecheckoutSummary

OtherBooksYouMayEnjoyLeaveareview-letotherreadersknowwhatyouthink

Page 15: Magento 2 Development Quick Start Guide · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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

Page 17: Magento 2 Development Quick Start Guide · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

TogetthemostoutofthisbookTogetthemostoutofthebook,thereaderisexpectedtohave:

AdegreeofPHPobject-orientedprogramming(OOP)knowledgeAbasicunderstandingofJavaScriptandXML

Page 20: Magento 2 Development Quick Start Guide · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

CodeinActionVisitthefollowinglinktocheckoutvideosofthecodebeingrun:

http://bit.ly/2D98D8q

Page 22: Magento 2 Development Quick Start Guide · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

Tipsandtricksappearlikethis.

Page 24: Magento 2 Development Quick Start Guide · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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

FormoreinformationaboutPackt,pleasevisitpacktpub.com.

Page 26: Magento 2 Development Quick Start Guide · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

MagentoadminFastestperformance

Carefullybalancingdevelopermodewithsomeofthecachetypesbeingenabled/disabledcanprovideoptimalperformanceduringdevelopment.

Page 34: Magento 2 Development Quick Start Guide · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

<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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

$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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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

Argumentinjectionoftengoeshandinhandwithvirtualtypes,aswewillsoonsee.

Page 52: Magento 2 Development Quick Start Guide · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

<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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

$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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

}

Page 62: Magento 2 Development Quick Start Guide · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

//Therestofthecode...

$result=$proceed($linkId);

//Therestofthecode...

return$result;

}

Page 64: Magento 2 Development Quick Start Guide · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

observingthesamemethod.

Page 66: Magento 2 Development Quick Start Guide · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

'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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

*****/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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

SummaryInthischapter,wetoucheduponsomeofMagento'skeyscomponents.PluginsandeventobserversprovideapowerfulwayofextendingMagento,eitherbychangingthebehaviorofexistingfunctionsorbyrunningsomefollow-upcodeinresponsetocertainevents.

Movingforward,wewilldeepenourMagentoknowledgefurtherbylookingintotheinstallandupdatescripts,theEntity–Attribute–Valuemodel(EAV),creatingnewEAVtypes,indexers,extension,andcustomattributes.

Page 76: Magento 2 Development Quick Start Guide · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

Whilethisexamplewouldwork,thereisfarmoretoitthanthis.Creatingtablesmanuallyisnotaviableoptionforbuildingmodules.Magentohasjusttherightmechanismforthis,whichiscalledsetupscripts.

Page 85: Magento 2 Development Quick Start Guide · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

)->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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

dropTable:DropsthetablefromadatabasemodifyColumn:Modifiesthecolumndefinition

Page 90: Magento 2 Development Quick Start Guide · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

\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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

){

$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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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

Page 109: Magento 2 Development Quick Start Guide · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

}

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

CRUDourwaythroughBoxyBoxthesamewaywedidwiththeCMSblock.Whiletherecertainlyisagreatdealofboilerplatecodetogoaround,ourAPIisnowbothREST-andSOAP-ready.

Page 122: Magento 2 Development Quick Start Guide · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

SummaryInthischapter,wehavecoveredvaluablewebAPIelements.WelearnedhowtodifferentiatebetweentypesofwebAPIusers,andtheauthenticationandmethodsprovidedtodoso.WealsolearnedhoweasyitistocreateourownAPIswithjustafewlinesofXML.WesawhowtheroutedefinitionallowsforeasybindingbetweenwhatcomesviaanHTTPrequesttowhatexecutesincode,respectingtheaccesslistpermissionsintheprocess.ThevalueofbuildingAPIsaspartofourdistributablemodulesliesintheirextensibility.APIsforceustoembracetheinterfacewayofthinking,thusallowingotherstouseandextendourcodeeasilyandsecurely.Thepreferencemechanismweintroducedinpreviouschapters,throughdi.xmlfiles,allowsotherstochangethebehaviorbehindtheinterfaceeasily.

Movingforward,wearegoingtotakeamorethoroughandroundedlookatbuildinganddistributingourextensionsviaComposerandPackagist.

Page 125: Magento 2 Development Quick Start Guide · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

</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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

<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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

$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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

$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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

Likewise,theadminCreateNewOrderscreensshouldalsoshowourRoyalTrekshippingmethodsasfollows:

Finally,thesuccessfullymadeordershouldreflecttheRoyalTrek48hshippingmethodselectioninitsneworderemail,andthecustomer'sMyAccountarea,asfollows:

Withourshippingmethodsconfirmedasworking,let'sgoaheadandlookforawayofdistributingit.

Page 134: Magento 2 Development Quick Start Guide · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

waytogo.

Page 136: Magento 2 Development Quick Start Guide · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

DistributingviaPackagistPackagistisafreeonlinerepositoryserviceforComposerpackages.WecanuseittoeasilydistributeourfreeMagentomodules.ThefactthatPackagistisadefaultComposerrepository,makesitthedefactorepositoryforanyComposeruser.ThisiswhyhavingourfreeMagentomodulesavailableviaPackagistisapreferredwayofdistribution.

PushingourMagentomoduletoPackagistisquiteeasy.Assumingwehaveouraccountcreated,weshouldstartbyclickingontheSubmitbutton,whichwilllandusonthefollowingscreen:

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

Page 137: Magento 2 Development Quick Start Guide · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

3. AddressinganynecessaryMagentosetupscripts4. CommittingourchangestoGit,withproperversiontagging5. MakingsuretheUpdateistriggeredonthePackagistscreenofourmodule

editscreen

UsingthePackagist'sservicehookwecanensurethatourpackagewillalwaysbeupdatedautomatically.Seehttps://packagist.org/about#how-to-update-packagesformoreinformation.

Page 140: Magento 2 Development Quick Start Guide · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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

Movingforward,wearegoingtotakealookatsomeofthespecificsofMagentoadminareadevelopment.

Page 141: Magento 2 Development Quick Start Guide · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

<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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

\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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

</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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

</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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

}

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

<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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

}

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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

Movingforward,wearegoingtotakealookatsomeofthespecificsbehinddevelopingforthestorefrontarea.

Page 162: Magento 2 Development Quick Start Guide · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

<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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

<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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

<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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

<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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

<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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

(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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

/*...*/

},

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

ournamespaceandredirectUrlistheactualnameofthewidgetwithinournamespace.

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

Page 176: Magento 2 Development Quick Start Guide · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

<?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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

'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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

$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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

}

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

$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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

Oncethetimerreaches0hours0minutes0seconds,itshoulddisappear.

Page 197: Magento 2 Development Quick Start Guide · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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

Movingforward,wearegoingtotakealookatsomeofthethingswecandotocustomizethecheckout.

Page 200: Magento 2 Development Quick Start Guide · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

CustomizingCheckoutExperiencesWhilethedefaultMagentocheckoutprovideseverythingashopneedstocompleteatransactionsuccessfully,therearedetailsspecifictotheindividualbusinessesthatoftenneedtobeaddressed.Agreatdealofthesedetailsoftenrelatetocheckoutcustomizationsthatallowforthecapturingofadditionalinformationorengagingcustomersinagreementsandsubscriptionactivities.

Movingforward,wearegoingtotakealookatthefollowing:

PassingdatatothecheckoutAddingordernotestothecheckout

Page 201: Magento 2 Development Quick Start Guide · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

canpushourserver-sidedatatoourstorefrontwhenapageloads.

Tounderstandcheckoutmodificationsbetter,weshouldfamiliarizeourselveswiththecontentofthewindow.checkoutConfigobject.

Page 204: Magento 2 Development Quick Start Guide · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

$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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

</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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

</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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

SummaryInthischapter,wehavebuiltasmall,butfunctional,ordernotesmodule.Thisallowedustofamiliarizeourselveswithanimportantaspectofcustomizingthecheckoutexperience.Thegistofthisliesinunderstandingthecheckout_index_indexlayouthandle,theJavaScriptwindow.checkoutConfigobject,andtheuiComponent.

Failuretodeliverconsistentandstablecheckoutexperiencesisboundtoresultinalossofconversions.Giventhenumberandcomplexityofthecomponentsinvolved,itisbesttokeepthenumberofcheckoutcustomizationstoaminimum.

Movingforward,wearegoingtotakealookatsomeofthethingswecandoregardingthecustomizationofcustomerinteractions.

Page 213: Magento 2 Development Quick Start Guide · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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

Bycontrast,thecustomer'scheckoutareawouldshowcontactpreferences,asfollows:

Page 218: Magento 2 Development Quick Start Guide · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

);

$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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

</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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

<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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

"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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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

Withthis,wehavereachedtheendofourbook.ThetopicswehavecoveredshouldbeenoughtogetusgoingwithMagentodevelopment,butthesheersizeoftheplatformandtheintricatespecificsofitsindividualmodulesleaveplentymoretoexplorefurtheron.Itgoeswithoutsayingthatourjourneyhasmerelybegun.

Page 229: Magento 2 Development Quick Start Guide · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

OtherBooksYouMayEnjoyIfyouenjoyedthisbook,youmaybeinterestedintheseotherbooksbyPackt:

Magento2BeginnersGuideGabrielGuarino

ISBN:9781785880766

BuildyourfirstwebstoreinMagento2MigrateyourdevelopmentenvironmenttoalivestoreConfigureyourMagento2webstoretherightway,sothatyourtaxesarehandledproperlyCreatepageswitharbitrarycontentCreateandmanagecustomercontactsandaccountsProtectMagentoinstanceadminfromunexpectedintrusionsSetupnewsletterandtransactionalemailssothatcommunicationfromyourwebsitecorrespondstothewebsite'slookandfeelMakethestorelookgoodintermsofPCIcompliance

Magento2Developer'sGuide

Page 230: Magento 2 Development Quick Start Guide · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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 · Magento Certified Solution Specialist, Magento 2 Certified Solution Specialist, Magento 2 Certified Professional Developer, to mention just

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