Upload
syed-haider-hassan
View
774
Download
339
Embed Size (px)
DESCRIPTION
With the challenges of growing an online business, Magento 2 is an open source e-commerce platform with innumerable functionalities that gives you the freedom to make on-the-fly decisions. It allows you to customize multiple levels of security permissions and enhance the look and feel of your website, and thus gives you a personalized experience in promoting your business.What you will learnInstall a Magento 2 shop with sample dataUpgrade the data in a Magento 1 shop to a Magento 2 shopManage the look and feel of the shop with custom themesExtend the shop with custom functionality such as forms, grids, and moreAccelerate your store with some performance toolsBuild and structure your own shipping moduleTest your shop with automated tests and manage your product displayAbout the AuthorBart Delvaux is an experienced web developer with several years of experience in the PHP world. He has worked with the most important frameworks in PHP, such as Drupal and Zend Framework, but Magento is his specialization.Bart has obtained all the Magento developer certifications: Front End Developer, Developer, as well as Developer Plus. He currently works for ISAAC Software Solutions, a company that specializes in software solutions such as web shops, apps, system integrations, and more.Bart finished a large variety of Magento projects in his Magento career that started in 2010 with the principle "quality above quantity". Having gone from handling a basic shop to shipping modules and large, complex Magento stores, Magento holds no secrets from him.Bart has also worked on Magento 1.8 Development Cookbook, Packt Publishing. Now that Magento 2 is out, it is time for the next one!Table of ContentsUpgrading from Magento 1Working with ProductsThemingCreating a ModuleDatabases and ModulesMagento BackendEvent Handlers and CronjobsCreating a Shipping ModuleCreating a Product Slider WidgetPerformance OptimizationDebugging and Unit Testing
Citation preview
Magento2DevelopmentCookbook
TableofContents
Magento2DevelopmentCookbook
Credits
AbouttheAuthor
AbouttheReviewers
www.PacktPub.com
Supportfiles,eBooks,discountoffers,andmore
Whysubscribe?
FreeaccessforPacktaccountholders
Preface
Whatthisbookcovers
Whatyouneedforthisbook
Whothisbookisfor
Sections
Gettingready
Howtodoit…
Howitworks…
There’smore…
Seealso
Conventions
Readerfeedback
Customersupport
Downloadingtheexamplecode
Downloadingthecolorimagesofthisbook
Errata
Piracy
Questions
1.UpgradingfromMagento1
Introduction
CreatingaMagento1websitewithsampledata
Gettingready
Howtodoit…
Howitworks…
CreatingaMagento2website
Gettingready
Howtodoit…
Howitworks…
There’smore…
PreparinganupgradefromMagento1
Gettingready
Howtodoit…
Howitworks…
Upgradingthedatabase
Gettingready
Howtodoit…
Howitworks…
There’smore…
Seealso
UsinganIDE
Gettingready
Howtodoit…
There’smore…
WritingcleancodewithPHPMDandPHPCS
Gettingready
Howtodoit…
Howitworks…
There’smore…
2.WorkingwithProducts
Introduction
Configuringthecatalogdefaults
Gettingready
Howtodoit
Howitworks
Workingwithattributesets
Gettingready
Howtodoit
Howitworks
Workingwithproducttypes
Gettingready
Howtodoit
Howitworks…
There’smore…
Asimpleproduct
Aconfigurableproduct
Abundleproduct
Agroupedproduct
Avirtualproduct
Adownloadableproduct
Addingsocialmediabuttons
Gettingready
Howtodoit
Howitworks
EmbeddinganHTMLobject
Gettingready
Howtodoit
Howitworks
ChangingtheURLofaproductpage
Gettingready
Howtodoit
Howitworks
There’smore
3.Theming
Introduction
ExploringthedefaultMagento2themes
Gettingready
Howtodoit…
Howitworks…
CreatingaMagento2theme
Gettingready
Howtodoit…
Howitworks…
There’smore…
CustomizingtheHTMLoutput
Gettingready
Howtodoit…
Howitworks…
Addingextrafilestothetheme
Gettingready
Howtodoit…
Howitworks…
There’smore…
WorkingwithLESS
Gettingready
Howtodoit…
Howitworks…
There’smore…
Changingapagetitle
Howtodoit…
Howitworks…
Workingwithtranslations
Gettingready
Howtodoit…
Howitworks…
Addingwidgetstothelayout
Gettingready
Howtodoit…
Howitworks…
Customizingemailtemplates
Gettingready
Howtodoit…
Howitworks…
4.CreatingaModule
Introduction
Creatingthemodulefiles
Gettingready
Howtodoit…
Howitworks…
Creatingacontroller
Gettingready
Howtodoit…
Howitworks…
There’smore…
Addinglayoutupdates
Gettingready
Howtodoit…
Howitworks…
Addingatranslationfile
Gettingready
Howtodoit…
Howitworks…
Addingablockofnewproducts
Gettingready
Howtodoit…
Howitworks…
Addinganinterceptor
Gettingready
Howtodoit…
Howitworks…
Seealso
Addingaconsolecommand
Gettingready
Howtodoit…
Howitworks…
Seealso…
5.DatabasesandModules
Introduction
Creatinganinstallandupgradescript
Gettingready
Howtodoit…
Howitworks…
Creatingaflattablewithmodels
Gettingready
Howtodoit…
Howitworks…
WorkingwithMagentocollections
Gettingready
Howtodoit…
Howitworks…
Programmaticallyaddingproductattributes
Gettingready
Howtodoit…
Howitworks…
Repairingthedatabase
Gettingready
Howtodoit…
Howitworks…
6.MagentoBackend
Introduction
Registeringabackendcontroller
Gettingready
Howtodoit…
Howitworks…
Extendingthemenu
Gettingready
Howtodoit…
Howitworks…
AddinganACL
Gettingready
Howtodoit…
Howitworks…
Addingconfigurationparameters
Gettingready
Howtodoit…
Howitworks…
Creatingagridofadatabasetable
Gettingready
Howtodoit…
Howitworks…
Workingwithbackendcomponents
Gettingready
Howtodoit…
Howitworks…
Addingcustomerattributes
Gettingready
Howtodoit…
Howitworks…
Workingwithsourcemodels
Gettingready
Howtodoit…
Howitworks…
7.EventHandlersandCronjobs
Introduction
Understandingeventtypes
Gettingready
Howtodoit…
Howitworks…
Seealso
Creatingyourownevent
Gettingready
Howtodoit…
Howitworks…
Addinganeventobserver
Gettingready
Howtodoit…
Howitworks…
Introducingcronjobs
Gettingready
Howtodoit…
Howitworks…
Creatingandtestinganewcronjob
Gettingready
Howtodoit…
Howitworks…
8.CreatingaShippingModule
Introduction
Initializingmoduleconfigurations
Gettingready
Howtodoit…
Howitworks…
Seealso
Writinganadaptermodel
Gettingready
Howtodoit…
Howitworks…
Extendingtheshippingmethodfeatures
Gettingready
Howtodoit…
Howitworks…
Addingthemoduleinthefrontend
Gettingready
Howtodoit…
Howitworks…
9.CreatingaProductSliderWidget
Introduction
Creatinganemptymodule
Gettingready
Howtodoit…
Howitworks…
Creatingawidgetconfigurationfile
Gettingready
Howtodoit…
Howitworks…
Creatingtheblockandtemplatefiles
Gettingready
Howtodoit…
Howitworks…
Creatingacustomconfigurationparameter
Gettingready
Howtodoit…
Howitworks…
There’smore…
Finalizingthetheming
Gettingready
Howtodoit…
Howitworks…
10.PerformanceOptimization
Introduction
Benchmarkingawebsite
Gettingready
Howtodoit…
Howitworks…
Optimizingthefrontendofthewebsite
Gettingready
Howitworks…
Howitworks…
There’smore…
OptimizingthedatabaseandMySQLconfigurations
Gettingready
Howtodoit…
Howitworks…
OptimizingtheApachewebserver
Howtodoit…
Howitworks…
FindingperformanceleaksinMagento
Gettingready
Howtodoit…
Howitworks…
ConfiguringOPcache,Redis,andMemcached
Gettingready
ZendOPcache
Memcached
Redis
Howtodoit…
Howitworks…
OptimizingthePHPconfigurations
Gettingready
Howtodoit…
Howitworks…
11.DebuggingandUnitTesting
Introduction
LoggingintoMagento2
Gettingready
Howtodoit…
Howitworks…
GettingstartedwithXdebug
Gettingready
Howtodoit…
Howitworks…
RunningautomatedtestsfromMagento
Gettingready
Howtodoit…
Howitworks…
CreatingaMagentotestcase
Gettingready
Howtodoit…
Howitworks…
There’smore…
Index
Magento2DevelopmentCookbook
Magento2DevelopmentCookbookCopyright©2015PacktPublishing
Allrightsreserved.Nopartofthisbookmaybereproduced,storedinaretrievalsystem,ortransmittedinanyformorbyanymeans,withoutthepriorwrittenpermissionofthepublisher,exceptinthecaseofbriefquotationsembeddedincriticalarticlesorreviews.
Everyefforthasbeenmadeinthepreparationofthisbooktoensuretheaccuracyoftheinformationpresented.However,theinformationcontainedinthisbookissoldwithoutwarranty,eitherexpressorimplied.Neithertheauthor,norPacktPublishing,anditsdealersanddistributorswillbeheldliableforanydamagescausedorallegedtobecauseddirectlyorindirectlybythisbook.
PacktPublishinghasendeavoredtoprovidetrademarkinformationaboutallofthecompaniesandproductsmentionedinthisbookbytheappropriateuseofcapitals.However,PacktPublishingcannotguaranteetheaccuracyofthisinformation.
Firstpublished:December2015
Productionreference:1171215
PublishedbyPacktPublishingLtd.
LiveryPlace
35LiveryStreet
BirminghamB32PB,UK.
ISBN978-1-78588-219-7
www.packtpub.com
CreditsAuthors
BartDelvaux
Reviewers
KarenKilroy
PankajPareek
DavidParloir
MariusStrajeru
CommissioningEditor
VeenaPagare
AcquisitionEditor
PrachiBisht
ContentDevelopmentEditor
AparnaMitra
TechnicalEditor
AbhishekR.Kotian
CopyEditor
PranjaliChury
ProjectCoordinator
IzzatContractor
Proofreader
SafisEditing
Indexer
MariammalChettiyar
Graphics
DishaHaria
ProductionCoordinator
ConidonMiranda
CoverWork
ConidonMiranda
AbouttheAuthorBartDelvauxisanexperiencedwebdeveloperwithseveralyearsofexperienceinthePHPworld.HehasworkedwiththemostimportantframeworksinPHP,suchasDrupalandZendFramework,butMagentoishisspecialization.
BarthasobtainedalltheMagentodevelopercertifications:FrontEndDeveloper,Developer,aswellasDeveloperPlus.HecurrentlyworksforISAACSoftwareSolutions,acompanythatspecializesinsoftwaresolutionssuchaswebshops,apps,systemintegrations,andmore.
BartfinishedalargevarietyofMagentoprojectsinhisMagentocareerthatstartedin2010withtheprinciple“qualityabovequantity”.Havinggonefromhandlingabasicshoptoshippingmodulesandlarge,complexMagentostores,Magentoholdsnosecretsfromhim.
BarthasalsoworkedonMagento1.8DevelopmentCookbook,PacktPublishing.NowthatMagento2isout,itistimeforthenextone!
Iwanttothankeveryonewhomadeitpossibleformetocompletethisbook.IwouldliketoextendthankstothepeopleatPacktPublishingforthesupportandtomycolleaguesfortheirvisionandsupport.
Lastly,IwanttothankthepeoplewhocontributedtoMagento2.TheydidagoodjobcreatinganewversionofthepopularMagentosystem,whichisfuture-proof!
AbouttheReviewersKarenKilroyisahighlyexperienceddeveloper,administrator,andinstructor.SheisaMagento-certifiedFrontEndDeveloper.Asahands-ondeveloperandsystemsadministratorwithmorethan25yearsofexperienceinIT,whichincludes20yearsinwebdevelopment,KarenhasfocusedprimarilyonMagentoforthepast7years.Currently,sheisemployedatAmplifiasaMagentotechnicalleadandworksonseveralwell-knowncommercesites.
KarengotherstartinMagentoatadirectmarketingcompanysellingEdenPUREHeaters(edenpure.com),asitethatgeneratesmillionsofdollarsinsales.Additionally,shewasacoursewareauthorandinstructorforMagento’sofficialtrainingarm,MagentoU,between2010and2014.KarenisalsoareviewerofMasteringMagento,2ndEdition,PacktPublishing.
PriortobecominginvolvedwithMagento,shecustomizedLAMPcontentmanagementsystems,suchasJoomla,Drupal,andWordPress.Intheearlydaysofwebdevelopment,Karenledherowncompany,wheresheemployed20developersdoingJavaandLotusNotes/Dominoworkforlargeclients.
Inhersparetime,sheisalsoaprofessionaldragonboatcoachandsteersperson.
PankajPareekisacertifiedsoftwareprofessionalwhohasexpertiseinMagento,PHP,andotherframeworks.Hehasprovidedhisprofessionalservicesinthisfieldformorethan7years.
Atrueprofessional,Pankajworkswiththemottothatknowledgeincreaseswhenyoushareitwithothers.Heisapersonwhohasexploreddifferentaspectsofthesoftwarefieldsuomoto.Pankajisaquick,curiouslearnerwhoreceivedvariousrecognizedcertificationsintheITfieldinaveryshortspanoftime,namelyMagentoDeveloper(2013),MagentoSolutionSpecialist(2015),andZendCertifiedEngineer(2014).
Iwouldliketoexpressmygratitudetowardmylovinggrandmother,family,colleagues,andthealmightygod.
DavidParloirhasbeenafreelanceMagentodevelopersincethefirstversionwasreleasedin2008,andthroughthis,hehasalsobeentheleaddeveloperforseverallargeglobalprojects.Priortothis,Davidworkedforseveralcompaniesthatfocusedonthedevelopmentofe-commercewebsitesandevenworkedasateacherofMagentoforashortperiod.Heisaself-taughtdeveloperwhoseeswebdevelopmentasmorethanajob—heseesitasapassion.Davidconsidershimselfacraftsman,keepinguptodatewiththelatesttrendsinthisareawhilebalancingthenewskillshedevelops,withadesireforhiscodetobeefficient,simple,andelegant.
MariusStrajeruis32yearsoldandfinishedasafacultyofcomputerscienceinIasi,Romania,in2006.Sincethen,hehasworkedasaPHPdeveloperforvarioussoftwarecompanies.
Marius’areaofexpertiseisMagento;hehasbeenworkingwithMagentosinceversion1.0
cameoutin2008.HestartedlookingatMagento2assoonasheheardthatthesourcecodeisavailableinadevversion.
www.PacktPub.com
Supportfiles,eBooks,discountoffers,andmoreForsupportfilesanddownloadsrelatedtoyourbook,pleasevisitwww.PacktPub.com.
DidyouknowthatPacktofferseBookversionsofeverybookpublished,withPDFandePubfilesavailable?YoucanupgradetotheeBookversionatwww.PacktPub.comandasaprintbookcustomer,youareentitledtoadiscountontheeBookcopy.Getintouchwithusat<[email protected]>formoredetails.
Atwww.PacktPub.com,youcanalsoreadacollectionoffreetechnicalarticles,signupforarangeoffreenewslettersandreceiveexclusivediscountsandoffersonPacktbooksandeBooks.
https://www2.packtpub.com/books/subscription/packtlib
DoyouneedinstantsolutionstoyourITquestions?PacktLibisPackt’sonlinedigitalbooklibrary.Here,youcansearch,access,andreadPackt’sentirelibraryofbooks.
Whysubscribe?FullysearchableacrosseverybookpublishedbyPacktCopyandpaste,print,andbookmarkcontentOndemandandaccessibleviaawebbrowser
FreeaccessforPacktaccountholdersIfyouhaveanaccountwithPacktatwww.PacktPub.com,youcanusethistoaccessPacktLibtodayandview9entirelyfreebooks.Simplyuseyourlogincredentialsforimmediateaccess.
PrefaceMagentoisoneofthemostpopulare-commerceplatformsonthemarket.Itcontainsalotofe-commercefunctionality,itisstable,anditisfree.ThismeansthatalotofpeoplechooseMagentofortheironlinebusiness.
ThefirststableversionofMagentowasreleasedin2008.ThelaterreleaseswerebasedonthefirstversionofMagento.TechnologychangesquicklyandMagentoneededabigupdate—abigreleaseMagento2isnowready.
DevelopinginMagentoisnotaseasyasyouwouldexpect.EvenifyouhaveknowledgeofMagento1,agoodguidewithpracticalexamplesthatshowsyouthebestpracticeisamusthave,andthisisexactlywhatthisbookwilldo.
WithMagento2DevelopmentCookbook,wewillcoverthemostimportanttopicsthatwillhelpyoubecomeagoodMagento2developer.Wewillstartwiththebasicsandwewillendwiththemoreadvancedtopics.
Thisbookisdividedintoseveralrecipes,whichshowyouwhichstepstotaketocompleteaspecificaction.Ineachrecipe,wehaveasectionthatexplainshoweverythingworks.
Wewillstartthisbookwiththecreationofagooddevelopmentenvironment.Foragooddevelopmentenvironment,weneedtherighttools.WewillinstallMagentoandwewilldiscusshowwecanmigratedatafromaMagento1toaMagento2shop.Next,wewillseesomefunctionalstuff.Youwilllearnhowthecatalogsystemworks,whichproducttypesareavailable,andalotmore.
Afterthis,youwilllearnhowwecancreateaMagentothemetochangethelookandfeeloftheMagentoshop.Butthemainfocusofthisbookwillbethedevelopmentpart.WewillcreateacustommodulethatwewillextendwithalotofcommonfeaturesthatareusedinMagentoprojects,suchasextracontrollerpages,databaseintegrations,customshippingmethods,andextrabackendinterfaces.
Attheendofthisbook,wewillseehowwecanimprovetheperformanceofaMagentoshop.Finally,wewillseesomedebuggingtechniques,suchasXdebugandcreatingunittestsusingtheMagentotestframework.
WhatthisbookcoversChapter1,UpgradingfromMagento1,providesanintroductiontohowyoucaninstallandmigratethedatafromaMagento1toaMagento2shop.Wewillalsoprepareourdevelopmentenvironmentinthischapter.
Chapter2,WorkingwithProducts,givesyouamorefunctionalinformationaboutthepossibilitiesofdisplayingproductsinyourMagentoshop.
Chapter3,Theming,explainshowyoucancustomizethelookandfeelofyourwebshopusingacustomMagentotheme.
Chapter4,CreatingaModule,describeshowtocreateabasicMagentomodule;howtoextendthatmodulewithcustomconfigurations,suchasacustompage,translations,andblocks;andhowtochangebehaviorofstandardMagentoclasses.
Chapter5,DatabasesandModules,demonstrateshowyoucanextendaMagentomodulewithdatabaseinteractions,suchasinstallandupgradescripts,acustomentitythatrepresentsadatabasetable.
Chapter6,MagentoBackend,showsyouhowtointegrateaMagentomodulewiththebackend,suchasaddingconfigurationpages,creatingoverviewpages,andextendingtheadminmenu.
Chapter7,EventHandlersandCronjobs,describeshowtheevent-drivenarchitectureisimplementedinMagentoandhowtointegratethisinyourmodule.Laterinthischapter,youwilllearnhowtocreatecronjobsandhowtotestthem.
Chapter8,CreatingaShippingModule,showsyouhowtocreateamodulewiththeconfigurationsthatarerequiredforanewshippingmethod.
Chapter9,CreatingaProductSliderWidget,willcoverhowtocreateamodulewithacustomwidget,howtobuildthebackendinterface,andhowtoprovideagoodUIinthefrontendofthatwidget.
Chapter10,PerformanceOptimization,describeshowtobenchmarkasitetoexplorethelimitsandhowtoimprovetheperformanceusingdifferenttechniquessuchasRedisandMemcached.
Chapter11,DebuggingandUnitTesting,showsyouhowtousethePHPdebuggerXdebugandhowwecancreateautomatedtestsusingtheMagento2testingframework.
WhatyouneedforthisbookMagento2sourcecodeAvirtualLinuxserver(Ubuntu15.10orhigher)Onthatvirtualserver,youneedthefollowing:
Apache2.4PHP5.5orhigherMySQLServer5.6orhigherSSHaccess
NetBeansIDE(oranyothergoodPHPeditorlikePhpStorm)Adatabaseclient(suchaphpMyAdmin)AstandardwebbrowserXdebugGitSCM
WhothisbookisforThisbookisforwebprogrammerswhoarefamiliarwithPHPandwanttostartwithMagento2.ThisbookisalsoforMagento1developerswhowanttoknowhoweverythingworksinMagento2.
ThisbookwillstartwiththebasicsofMagento2developmentandwillendwiththemoreadvancedtopics.EvenifyouknowledgeaboutMagentodevelopment,thisbookisagoodreferenceifyouwanttomoreaboutaparticulartopicinMagento.
SectionsInthisbook,youwillfindseveralheadingsthatappearfrequently(Gettingready,Howtodoit,Howitworks,There’smore,andSeealso).
Togiveclearinstructionsonhowtocompletearecipe,weusethesesectionsasfollows:
GettingreadyThissectiontellsyouwhattoexpectintherecipe,anddescribeshowtosetupanysoftwareoranypreliminarysettingsrequiredfortherecipe.
Howtodoit…Thissectioncontainsthestepsrequiredtofollowtherecipe.
Howitworks…Thissectionusuallyconsistsofadetailedexplanationofwhathappenedintheprevioussection.
There’smore…Thissectionconsistsofadditionalinformationabouttherecipeinordertomakethereadermoreknowledgeableabouttherecipe.
SeealsoThissectionprovideshelpfullinkstootherusefulinformationfortherecipe.
ConventionsInthisbook,youwillfindanumberoftextstylesthatdistinguishbetweendifferentkindsofinformation.Herearesomeexamplesofthesestylesandanexplanationoftheirmeaning.
Codewordsintext,databasetablenames,foldernames,filenames,fileextensions,pathnames,dummyURLs,userinput,andTwitterhandlesareshownasfollows:“Thewidget.xmlfileisusedtodefinewidgetsintheMagentoinstallation.”
Ablockofcodeissetasfollows:
<?xmlversion="1.0"?>
<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
<routerid="standard">
<routeid="helloworld"frontName="helloworld">
<modulename="Packt_HelloWorld"/>
</route>
</router>
</config>
Newtermsandimportantwordsareshowninbold.Wordsthatyouseeonthescreen,forexample,inmenusordialogboxes,appearinthetextlikethis:“ClickingtheNextbuttonmovesyoutothenextscreen.”
NoteWarningsorimportantnotesappearinaboxlikethis.
TipTipsandtricksappearlikethis.
ReaderfeedbackFeedbackfromourreadersisalwayswelcome.Letusknowwhatyouthinkaboutthisbook—whatyoulikedordisliked.Readerfeedbackisimportantforusasithelpsusdeveloptitlesthatyouwillreallygetthemostoutof.
Tosendusgeneralfeedback,simplye-mail<[email protected]>,andmentionthebook’stitleinthesubjectofyourmessage.
Ifthereisatopicthatyouhaveexpertiseinandyouareinterestedineitherwritingorcontributingtoabook,seeourauthorguideatwww.packtpub.com/authors.
CustomersupportNowthatyouaretheproudownerofaPacktbook,wehaveanumberofthingstohelpyoutogetthemostfromyourpurchase.
DownloadingtheexamplecodeYoucandownloadtheexamplecodefilesfromyouraccountathttp://www.packtpub.comforallthePacktPublishingbooksyouhavepurchased.Ifyoupurchasedthisbookelsewhere,youcanvisithttp://www.packtpub.com/supportandregistertohavethefilese-maileddirectlytoyou.
DownloadingthecolorimagesofthisbookWealsoprovideyouwithaPDFfilethathascolorimagesofthescreenshots/diagramsusedinthisbook.Thecolorimageswillhelpyoubetterunderstandthechangesintheoutput.Youcandownloadthisfilefromhttp://www.packtpub.com/sites/default/files/downloads/1234OT_ColorImages.pdf.
ErrataAlthoughwehavetakeneverycaretoensuretheaccuracyofourcontent,mistakesdohappen.Ifyoufindamistakeinoneofourbooks—maybeamistakeinthetextorthecode—wewouldbegratefulifyoucouldreportthistous.Bydoingso,youcansaveotherreadersfromfrustrationandhelpusimprovesubsequentversionsofthisbook.Ifyoufindanyerrata,pleasereportthembyvisitinghttp://www.packtpub.com/submit-errata,selectingyourbook,clickingontheErrataSubmissionFormlink,andenteringthedetailsofyourerrata.Onceyourerrataareverified,yoursubmissionwillbeacceptedandtheerratawillbeuploadedtoourwebsiteoraddedtoanylistofexistingerrataundertheErratasectionofthattitle.
Toviewthepreviouslysubmittederrata,gotohttps://www.packtpub.com/books/content/supportandenterthenameofthebookinthesearchfield.TherequiredinformationwillappearundertheErratasection.
PiracyPiracyofcopyrightedmaterialontheInternetisanongoingproblemacrossallmedia.AtPackt,wetaketheprotectionofourcopyrightandlicensesveryseriously.IfyoucomeacrossanyillegalcopiesofourworksinanyformontheInternet,pleaseprovideuswiththelocationaddressorwebsitenameimmediatelysothatwecanpursuearemedy.
Pleasecontactusat<[email protected]>withalinktothesuspectedpiratedmaterial.
Weappreciateyourhelpinprotectingourauthorsandourabilitytobringyouvaluablecontent.
QuestionsIfyouhaveaproblemwithanyaspectofthisbook,youcancontactusat<[email protected]>,andwewilldoourbesttoaddresstheproblem.
Chapter1.UpgradingfromMagento1Inthischapter,wewillcover:
CreatingaMagento1websitewithsampledataCreatingaMagento2websitePreparinganupgradefromMagento1UpgradingthedatabaseUsinganIDEWritingcleancodewithPHPMDandPHPCS
IntroductionMagentoisoneofthemostcompletee-commerceplatformsontheopensourcemarket.WithadefaultMagentoinstallation,allthecommone-commercefeatures,suchascatalognavigation,promotionrules,taxsettings,onlinepayments,andsoonareavailable.
ThefirstversionofMagentowasreleasedin2008afteroneyearofdevelopment.Magentowasinitiallydesignedasane-commercesystemthatcouldbeusedforawiderangeofuses.Inlateryears,Magentobecameverypopularasanout-of-the-boxe-commercesystemandalotofminorversionsofthe1.xserieshavebeenreleasedinthelastfewyears.
Tobefutureproof,Magentostartedthedevelopmentofamajorupgradeofthesystem,alsoknownasMagento2.Magento2isabigimprovementoneverypartofMagento.Everyaspectisanalyzedandrewrittenwithup-to-datetechnologiestobereadyforthefuture.Everything,includingthedeveloperexperience,maintainability,performance,andtechnologieswillbeimproved.
Inthischapter,wewillupgradethedataofaMagento1installationtoaMagento2installation.Wewillalsopreparesometoolsthatwecanuseinthefollowingchaptersofthisbook.
CreatingaMagento1websitewithsampledataTostartaMagento2upgrade,weneedaMagento1webshopwithsomedata.Inthisrecipe,wewillinstallthelatestMagentoversion,1.9,withthesampledataforthenewresponsivetheme.
GettingreadyToinstallaMagento1website,weneedthefollowingstuff:
Awebserver(Linux,Apache2,PHP,orMySQL)TheMagento1.9codebaseTheMagento1.9sampledata
NoteTheMagento1.9codebaseandsampledatacanbedownloadedfromtheMagentositeathttp://www.magentocommerce.com/download.
Thefollowingstuffisrecommendedfortheinstallation:
Command-lineaccessAvirtualhost(domainname)thatisgoingtobeyourwebroot
NoteWerecommendthatyouuseatestserverthatisonyourdevelopmentmachine.IfyouuseaLinuxoraMacoperatingsystem,youcaninstallthewebserveronyourlocalmachine.IfyouhaveaWindowsmachine,youcanuseavirtualLinuxserverforyourdevelopment.
Howtodoit…1. ExtracttheMagentocodearchiveinyourwebroot(thedirectoryofthevirtualhost).
Anls-lacommandshouldgiveyouthefollowingoutput:
api.php
app
cron.php
cron.sh
downloader
errors
favicon.ico
get.php
includes
index.php
index.php.sample
install.php
js
lib
LICENSE_AFL.txt
LICENSE.html
LICENSE.txt
mage
media
php.ini.sample
pkginfo
RELEASE_NOTES.txt
shell
skin
var
2. Extractthesampledataarchivetoadifferentfolderfromthewebroot.Copythecontentsofthemediaandskinfolderstothemediaandskinfoldersinyourwebroot.Wecandothisbyusingthefollowingcpcommand:
cp–R<path_to_sampledata_folder>/media/*
<path_to_magento_folder>/media/
cp–R<path_to_sampledata_folder/skin/*<path_to_magento_folder>/skin/
3. CreateadatabasefortheMagento1installationandnameitmagento1.Wecandothisbyrunningthefollowingcommands:
mysql-u<username>-p
createdatabasemagento1;
exit;
4. Importthesqlfilethatisinthesampledatadirectory.Thisfilecontainsadatabasethatwewillimportintothemagento1database.Wecandothisbyrunningthefollowingcommand:
mysql-u<username>-pmagento1<"path_to_sample_data.sql"
TipToavoidpermissionproblems,ensurethatallfilesandfoldershavetheright
permissions.Forsecurityreasons,itisrecommendedthatallfileshavejustenoughpermissionssothatonlytherightuserscanaccesstherightfiles.Whenyougivealltherights(777),youdon’thavepermissionproblemsbecauseeachusercanread,writeand,executeeachfileofyourapplication.Moreinformationaboutfilepermissionscanbefoundathttp://devdocs.magento.com/guides/m1x/install/installer-privileges_after.html.
5. Whenthefilesareintherightplaceandthedatabaseisimported,wecanruntheMagentoinstaller.Openyourbrowserandgotothedomainthatisconfiguredforyourwebsite.Youshouldseetheinstallerasinthefollowingscreenshot:
6. Continuewiththeinstallationprocessbyacceptingthetermsandconditions.7. Onthenextscreen,choosethecorrectlanguage,locale,andcurrencyforyourstore.8. Ontheconfigurationpage,fillintheformwiththerightdata:
DatabaseType:MySQL.Host:EnterthehostnameorIPaddressofyourdatabaseserver(localhostifitisonthesamemachine).Databasename:Entermagento1inthisfield(oranothernameifyouhaveadifferentnameforyourdatabase).Username:Enteryourdatabaseusername.Userpassword:Enteryourdatabasepassword.Tablesprefix:Leavethisfieldempty(thestringinthisfieldwillbeusedtoprefixalltablesofyourdatabase).BaseURL:EntertheURLofyourwebsiteinthisfield.Adminpath:Enteradmininthisfield.Thiswillbethepathofthebackend.Enablecharts:Fordevelopment,itisrecommendedthatthisbeunchecked.SkipBaseURLValidationBeforetheNextStep:Whenchecked,thewizard
willcheckforavalidURLwhenprocessingthisform.UseWebServer(Apache)rewrites:Checkthiswhentheapachemodulemod_rewriteisenabled.UseSecureURL’s(SSL):Thischeckboxmustbeuncheckedifyoudon’tuseHTTPS.
9. Submitthisformandwewillbeforwardedtothenextstep.Inthisstep,youcanconfiguretheadministratoraccount.Fillintherightdataandremembertheusernameandpasswordbecausethisisrequiredtomanagethestore.Leavetheencryptionkeyfieldempty.
10. Aftersubmittingthisform,theinstallationiscomplete.Optionally,youcansubmittheMagentosurvey.Atthebottomofthepage,therearebuttonstonavigatetothefrontendandbackend.Whengoingtothefrontend,youcanseeademoshopwithsampledataasinthefollowingscreenshot:
11. Thelayoutisresponsive.Whenscalingyourbrowsertoasmallerwidth,thewebsitewillswitchtothemobilelayoutlikeinthefollowingscreenshot:
Howitworks…WehavejustcreatedafullyfunctionalMagento1store.Thewebshopisfullyconfiguredandfilledwithdataaboutproducts,customers,andorders,justthedataweneedtomigratetoMagento2(intheupcomingrecipes).
Wheninstallinganewshop,youhavetofollowtheinstaller.Thisinterfacecreatesaconfigurationfileapp/etc/local.xml.Ifthefiledoesn’texist,Magentowilllaunchtheinstallerwizard.Ifthefileisthere,Magentowillruntheshop.
Withavalidlocal.xmlfile,itistechnicallypossibletoinstallanewMagentoshop,butthisisnotrecommendedbecausesomesettingssuchasabackenduser,timezone,andcurrencyarenotset.Theseareactionsthatyouhavetodomanuallywhenchoosingforthismethod.
CreatingaMagento2websiteInthepreviousrecipe,wecreatedaMagento1websitewithsampledatathatwewilluseforanupgrade.Inthisrecipe,wewilldothesame,butwewillcreateaMagento2websitewiththesampledataforMagento2.
GettingreadyToinstallMagento2,weneedthenewesttoolstorunthatapplication.Makesureyourwebserverhasthefollowingstuffinstalled:
PHP5.5orhigherMySQL5.6orhigherApache2.2orhigherCommandlineaccessComposer
WecaninstallMagento2indifferentways.Inthisrecipe,wewillinstallMagento2usingComposer.TheadvantageofthisisthatwecanuseGITtoaddversioncontroltoourcustomdevelopment.
Howtodoit…1. WewillinstallMagento2withComposer.Forthis,weneedauthenticationkeys.
Withanaccountonthemagento.comsite,gotoDevelopers|SecurekeysintheMyAccountsection.Onthispage,youcangeneratepublicandprivatekeysthatwillbeyourusernameandpasswordinthenextstep.
2. ToinstallMagento2withcomposer,wehavetorunthefollowingcommand:
composercreate-project--repository-url=https://repo.magento.com
magento/project-community-edition<installation_dir>
3. Youwillbepromptedforausernameandpassword.Theusernameisthepublickeyandthepasswordistheprivatekeythatwegeneratedinthepreviousstep.Whenthecommandhasrun,theinstallationdirectorywillhavethefollowingstructure:
app
bin
CHANGELOG.md
composer.json
composer.lock
CONTRIBUTING.md
CONTRIBUTOR_LICENSE_AGREEMENT.html
COPYING.txt
dev
.gitignore
Gruntfile.js
.htaccess
.htaccess.sample
index.php
lib
LICENSE_AFL.txt
LICENSE.txt
nginx.conf.sample
package.json
.php_cs
php.ini.sample
pub
README.md
setup
.travis.yml
update
var
vendor
TipCheckthattheuserandgroupofthesefilesarethesameasyourApacheuser.Onerecommendationistoexecuteallthecommandsasyourapacheuser.
4. Wehaveinstalledthecodebasewithcomposer.Nowwecanruntheinstallationwizard.OpenyourbrowserandentertheURLofyoursite.Youshouldseethefollowingwelcomescreen:
5. HittheAgreeandSetupMagentobuttonandstarttheenvironmentcheck.6. ClickonNextandenteryourdatabaseinformationasfollows:
DatabaseServerHost:ThehostnameorIPaddressofthedatabaseserverDatabaseServerUsername:TheusernameofthedatabaseaccountDatabaseServerPassword:ThepasswordfortheaccountDatabaseName:ThenameofthedatabaseTablePrefix:Optionally,youcangiveaprefixforeachtable
7. GotothenextstepandcheckiftherightinformationisfilledfortheURLpart.Intheadvancedsection,youcanoptionallyconfigureHTTPS,apacherewrites,andyourencryptionkey.Forourtestenvironment,wecanleavethesesettingsastheyareconfigured.
NoteMakesurethatthemod_rewriteoptionisenabledfortheapacheserver.Whennotenabled,theURLrewriteswillnotworkcorrectly.
8. Inthenextstep,youcanconfigureyourtimezone,currency,anddefaultlanguage.9. Inthelaststep,youcanconfigureyouradministrationaccount.Afterclickingonthe
Nextbutton,youarereadytoinstall.ClickontheInstallNowbuttonandtheinstallerwillstart.Thiswilltakesometimebecausetheinstallerwilladdthesampledataduringtheinstallation.YoucanopentheConsoleLogtoseewhatiscurrentlyhappening.
10. Whentheinstallerisready,youwillseethefollowingsuccessmessage:
11. RunthefollowingcommandsinyourMagentoinstallationdirectorytoconfigurethesampledata:
phpbin/magentosampledata:deploy
composerupdate
phpbin/magentosetup:upgrade
12. Theprecedingcommandswilldownloadandinstallthesampledatapackages.Becausetheycontainalotofimages,thiscouldtakesometime.Thesetup:upgradecommandwillinstallthesampledata,andthisalsotakessometime.
13. Theinstallationofthewebshopisnowcomplete.Younowhaveanup-and-runningMagento2webshop.WhenyounavigatetothecategoryGear|Bags,youshouldseesomethinglikeinthefollowingscreenshot:
Howitworks…WehavenowinstalledaMagento2website.LikewedidinthepreviousrecipeforMagento1.9,wedownloadedthecodebase(usingcomposer),createdadatabase,andinstalledMagento.
ForMagento2,weusedcomposertodownloadthecodebase.ComposerisaPHPdependencymanager.Allthedependenciesaresetinthecomposer.jsonfile.Forthisrecipe,therearetheMagentoandthemagento-sample-datadependenciesinthecomposer.jsonfile.Thereisalsoacomposer.lockfilegenerated.Inthatfile,theversionsoftheinstalleddependenciesarestored.
NoteWhenworkingwithGIT,weonlyhavetocommitthecomposer.json,composer.lock,and.gitignorefilesforaworkingMagento2project.WhenanotherpersondoesaGitcloneoftherepositoryandrunsthecomposer’sinstallcommand,Magento2willbeinstalledwiththeversionthatisinthecomposer.lockfile.
ThesampledataforMagento2isnowascriptthatwillbeexecutedaftertheinstallationofMagento.Thatscriptwilladdproducts,customers,orders,CMSdata,andmoreconfigurationstopopulatetheshop.
Theshopisinstalledandtheconfigurationsettings(database,encryptionkey,andsoon)arenowstoredinapp/etc/env.phpinsteadofintheapp/etc/local.xmlfileinMagento1.
There’smore…WheninstallingMagento2,herearesomecommonissuesthatcanoccurandtheirfixes:
Whenyoudon’tseeCSSinyourbrowser,youhavetocheckthefollowingthings:
Makesurethepub/folderiswritableRunthecommandphpbin/magentosetup:static-content:deploytogeneratethestaticcontent
Youforgettoinstallthesampledata:
YoucaninstallthesampledataaftertheinstallationofMagentowiththecommandphpbin/magentosampledata:deploy
Theinstallationisnotrespondinganymore:
ThiscouldbecausedbyanApachetimeout.Ifthisoccurs,youcanmaybetrythecommand-lineinstallation.Thisworksasfollows:
ToruntheMagentoinstallerfromthecommandline,wecanusethecommandphpbin/magentosetup:install.Wehavetoaddthefollowingrequiredparameterstothecommandtoconfiguretheinstallation:
base-url:ThebaseURL,forexamplehttp://magento2.local/db-host:ThedatabasehostorIPaddressdb-user:Thedatabaseusernamedb-name:Thedatabasenamedb-password:Thedatabasepasswordadmin-firstname:Thefirstnameoftheadministratoruseradmin-lastname:Thelastnameoftheadminuseradmin-email:Thee-mailaddressoftheadminuseradmin-user:Theusername(loginname)oftheadminuseradmin-password:Thepasswordfortheadminuserlanguage:Thelanguageoftheshopcurrency:Thecurrencycodeoftheshoptimezone:Thetimezoneoftheshopuse-rewrites:Whethertousetheapacherewritesornotuse-sample-data:Installthesampledata(optional)
Lookatthefollowingcodeforaworkingexampleoftheinstallcommand:
phpbin/magentosetup:install--base-url=http://magento2.local/--db-
host=localhost--db-user=magento2--db-name=magento2--db-
password=yourpassword--admin-firstname=John--admin-lastname=Doe--admin-
[email protected]=admin--language=en_US--
currency=USD--timezone=UTC--use-rewrites=1
PreparinganupgradefromMagento1ThedifferencesbetweenMagento1andMagento2arehuge.Thecodehasawholenewstructurewithalotofimprovementsbutthereisonebigdisadvantage.WhatdoIdoifIwanttoupgrademyMagento1shoptoaMagento2shop?
MagentocreatedanupgradetoolthatmigratesthedatafromaMagento1databasetotherightstructureforaMagento2database.
ThecustommodulesinyourMagento1shopwillnotworkinMagento2.ItispossiblethatsomeofyourmoduleswillhaveaMagento2version,anddependingonthemodule,themoduleauthorwillhaveamigrationtooltomigratethedatathatisinthemodule.
GettingreadyBeforewegetstarted,makesureyouhaveanempty(withoutsampledata)Magento2installationwiththesameversionastheMigrationtoolthatisavailableat:
https://github.com/magento/data-migration-tool-ce.
Howtodoit…1. InyourMagento2version(withthesameversionasthemigrationtool),runthe
followingcommands:
composerconfigrepositories.data-migration-toolgit
https://github.com/magento/data-migration-tool-ce
composerrequiremagento/data-migration-tool:2.0.0
2. InstallMagento2withanemptydatabasebyrunningtheinstaller.Makesureyouconfigureitwiththerighttimezoneandcurrencies.
3. Whenthesestepsaredone,youcantestthetoolbyrunningthefollowingcommand:
phpbin/magentomigrate:data--help
4. Thenextthingiscreatingtheconfigurationfiles.Examplesoftheconfigurationfilesareinvendor/magento/data-migration-tool/etc/<version>.Wecancreateacopyofthisfolderwherewecansetourcustomconfigurationvalues.ForaMagento1.9installation,wehavetorunthefollowingcpcommand:
cp–Rvendor/magento/data-migration-tool/etc/ce-to-ce/1.9.1.0/
vendor/magento/data-migration-tool/etc/ce-to-ce/packt-migration
5. Openthevendor/magento/data-migration-tool/etc/ce-to-ce/packt-migration/config.xml.distfileandsearchforthesource/databaseanddestination/databasetags.Changethevaluesofthesedatabasesettingstoyourdatabasesettingslikeinthefollowingcode:
<source>
<databasehost="localhost"name="magento1"user="root"/>
</source>
<destination>
<databasehost="localhost"name="magento2_migration"user="root"/>
</destination>
6. Renamethatfiletoconfig.xmlwiththefollowingcommand:
mvvendor/magento/data-migration-tool/etc/ce-to-ce/packt-
migration/config.xml.distvendor/magento/data-migration-tool/etc/ce-to-
ce/packt-migration/config.xml
Howitworks…Byaddingacomposerdependency,weinstalledthedatamigrationtoolforMagento2inthecodebase.ThismigrationtoolisaMagentoconsolecommandthatwillhandlethemigrationstepsfromaMagento1shop.
Intheetcfolderofthemigrationmodule,thereisasampleconfigurationofanemptyMagento1.9shop.
IfyouwanttomigrateanexistingMagento1shop,youhavetocustomizetheseconfigurationfilessoitmatchesyourpreferredstate.
Inthenextrecipe,wewilllearnhowwecanusethescripttostartthemigration.
UpgradingthedatabaseInthepreviousrecipe,weconfiguredthedatabasemigrationtool.Inthisrecipe,wewillrunthemigrationtoolsothatwecanmigratepartsfromaMagento1shoptoaMagento2shop.
GettingreadyYouneedaMagento1websiteandaMagento2website.TheMagento2websiteneedstohavethedatabasemigrationtoolinstalledandconfiguredasdescribedinthepreviousrecipe.
Inthisrecipe,wewilldoamigrationfromacleanMagento1site,toaMagento2sitewithoutsampledata.
WedidamigrationfromacleanMagento1databasewithsometestproducts.MakesureyouhaveacleanlyinstalledMagento1shopwithsometestdata(products,orders,andsoon)init.
Howtodoit…1. Firstweneedtomakesurethatthedatabasesettingsarecorrectinthe
vendor/magento/data-migration-tool/etc/ce-to-ce/packt-
migration/config.xmlfile.Openthatfileandcheckthatthedatabasecredentialsarecorrect.Wecreatedthisfileinthepreviousrecipe:
<sourceversion="1.9.1">
<databasehost="localhost"name="magento1_migration"user="root"/>
</source>
<destinationversion="2.0.0.0">
<databasehost="localhost"name="magento2_migration"user="root"/>
</destination>
NoteIfyouhaveadatabaseprefixinyoursourceordestinationdatabase,youcanoptionallyconfiguresource_prefixanddest_prefixinthe<options>sectionofthesameconfigurationfile.
TipTestthemigrationfirstwithacleanMagento1.9database.ThemappingthatwewilluseinthisrecipeisforacleanMagento1.9installation.Withanexistingshop,youwillhavecustomattributesandentitiesthatneedmoreconfigurationtomakethemigrationwork.
2. Ifthesesettingsarecorrect,wecanruntheupgradetool.Runthefollowingcommand:
phpbin/magentomigrate:data--help
3. Thisgivesusthefollowingoutput:
4. Tostartortestamigration,wehavetorunthefollowingcommand:
phpbin/magentomigrate:datavendor/magento/data-migration-tool/etc/ce-
to-ce/packt-migration/config.xml
5. Themigrationwillstartandwillgivethefollowingoutput:
6. Themigrationisnowcomplete.IfyoucheckyourdatabasefortheMagento2website,youwillseethatthedata(products,categories,andsoon)ismigratedfromMagento1.
TipIfyouwanttorerunthemigrationtool,youhavetoremovethevar/migration-tool-progress.lockfile.
7. WecanalsomigratethesettingsfromtheMagento1website.Todothis,youhavetoreplacethedataparameterinthecommandusingsettings.
8. Tocheckiftheupgradeworks,youhavetolookatthedataoftheMagento2installation.Wecancheckthefollowingthingsinthebackend:
Theorders(Sales|Orders)Theproducts(Products|Catalog)Thecustomers(Customers|AllCustomers)
9. Youcanalsocheckinthedatabaseifyoulookatthefollowingtables:
sales_order
customer_entity
catalog_product_entity
url_rewrite
Howitworks…Whenthemigrationtoolstarts,itstartscheckingalltheconfigurationsthatareintheconfigurationfilesofthemigrationtool.IftherearemorethingsavailableintheMagento1databasethanthethingsthatareconfigured,themigrationtoolwillgiveanotificationandstopthemigration.
It’slikelythateveryexistingMagento1shopworkswithcustomattributes,customentities,andsoon.Eachentity,attribute,andsoonneedstobedeclaredintheconfigurationfiles.
Themosttime-consumingpartofamigrationistocreateagoodconfigurationfilesothatthemigrationtoolwon’tfailonmissingstuff.Itisonyoutodecidewhattoignoreandwhattomigrate.Iftheconfigurationfilesarevalid,themigrationwillstartandthedatawillcomeintotheMagento2database.Thesameprincipleapplieswhenmigratingthesettings,butyouhavetothinkaboutwhetheryouwantit.
NoteWiththemigrationtool,itisonlypossibletomigratedataandsettings.ThecodeofMagento1moduleswillnotworkinMagento2.Soforyourmodules,youneedtoseeifthereisaMagento2version/alternativeavailable.
There’smore…Inthisrecipe,wedidamigrationofacleanMagento1installationtoacleanMagento2installation.HoweveralmosteveryrunningMagento1shopisnotclean.Itcontainscustomattributes,custommodules,andacustomconfiguration.
Whenmigratingsuchashoptoanewshop,themigrationisabitmorecomplex.Thefirstquestionis:Whatneedstobemigrated?Withthetool,youcanmigrateeveryentity,fromproducts,customers,andorderstoreviews,settings,andmore.
Ifyouwanttoskipdatathatmustbemigrated,youcanusethemap.xmlfile.Ifyouopenthefilevendor/magento/data-migration-tool/etc/ce-to-ce/packt-migration/map.xml,youseethatalotofentitiesareignoredinthemap/source/document_rulestag.
TipIfyouwanttochangesomethinginthemap.xmlfile,youhavetomakesurethattherightmap.xmlfileisloaded.Thisfileisconfiguredintheconfig.xmlfile(whereyoudidyourdatabaseconfiguration).Inthatfile,youhavetolookfortheXMLtagconfig/options/map_file.
IfyouhaveanerrorsuchasSourcedocumentsnotmapped,youhavetoaddtheconfigurationfortheseentitiesinthemap/source/document_rulestagofthemap.xmlfile.IftheerrorissomethinglikeDestinationdocumentsnotmapped,youhavetoaddconfigurationinthemap/destination/documenttagofthemap.xmlfile.
TosolveerrorssuchasSourcefieldsnotmappedyouhavetoaddconfigurationinthemap-eav.xmlfile.
SeealsoMigratingconfigurationfilesisthemosttimeconsumingpartofadatamigration.Ifyouwantmoreinformationonthemigrationtool,youcanhavealookattheMagentoMigrationWhitepaper,availableathttp://magento.com/resources/magento-2-migration-whitepaper.
UsinganIDEWritinggoodcodestartswithagooddevelopmentenvironment.AnIntegratedDevelopmentEnvironment(IDE)isthemainpartofagooddevelopmentenvironment.NetBeansisafreeandopensourcePHPeditorthatcanbeusedforMagentodevelopment.Inthisrecipe,wewillsetupaMagento2projectinNetBeans.
GettingreadyInstallthelatestversionofNetBeansIDEonyourcomputer.YoucandownloaditfromthefollowingURL:
https://netbeans.org/downloads/
ForPHPdevelopment,youneedtodownloadtheHTML5&PHPbundle.
Howtodoit…1. Tocreateanewproject,openNetBeansandnavigatetoFile|NewProject.2. Awindowliketheoneinthefollowingscreenshotwillappearonyourscreen.Click
onPHPandPHPApplicationwithExistingSources.
3. ClickonNextandconfigurethefollowingsettings:
SourceFolder:ThisfieldissettothelocationofyourMagentocode(like/var/www/html/magento2/)ProjectName:TheNetBeansprojectnameisenteredinthisfieldPHPVersion:ThisfieldissettoPHP5.5DefaultEncoding:ThisfieldissettoUTF-8
4. Inthenextscreenshot,youcanseehoweverythingisconfigured:
TipWhenyouareworkingwithaversioncontrolsystemlikeGIT,itisrecommendedthatyoucheckthecheckbox.PutNetBeansmetadataintoaseparatedirectory.Ifnotchecked,a.nbprojectfolderiscreatedinyourMagentoroot,andyoudon’twanttohavethatfolderinyourversioncontrolsystem.Anotherpossibilityistoaddthe.nbprojectfolderinthe.gitignorefile.
5. ClickonNextandconfigurethefinalsettings:
Runas:IfyouaredevelopingonalocalPC,chooseLocalWebServerProjectURL:TheURLofyourwebsiteIndexfile:Setthistoindex.php
Thesettingsareshowninthefollowingscreenshot:
6. ClickontheFinishbuttonandyourNetBeansprojectisready.Youcannowstartdeveloping.
There’smore…Inthisrecipe,weusedthefreecodeeditorNetBeans,buttherearealsosomeothergoodalternativesonthemarket,suchas:
PHPStormEclipsewithPDT(PHPDevelopmentTools)ZendStudio
WritingcleancodewithPHPMDandPHPCSMaintainingcleancodeismuchmoreefficientthanmaintainingspaghetticode,butwritingcleancodeisnotaseasyasitsounds.Thesedaystherearesometoolsthathelpyouwithwritingcleancode,suchasPHPMDandPHP_CodeSniffer.
PHPMDstandsforPHPMessDetector;thistoolwillcheckyourcodeoncomplexityandhowvariablesareusedandwilldetectsomepossiblebugs.ItgoesabitfurtherthanthesyntaxcheckinyourIDE.
PHP_CodeSnifferorPHPCSchecksyourcodeoncodingstandardssuchasPSR-1andPSR-2.
GettingreadyWewillinstallPHPMDandPHP_CodeSnifferinourdevelopmentenvironment.Makesureyouhavecommand-lineaccesstoyourdevelopmentenvironment.
Howtodoit…1. BeforeinstallingPHPMDandPHP_CodeSniffer,wehavetomakesurethatPHPis
installedonourdevelopmentmachine.Especiallyifyouaredevelopingonaremoteserver,itcouldbethatPHPisnotinstalled.
2. DownloadandinstallPHPMD.DependingonyourOS,theprotocolcouldbedifferent.Youcanfindinstructionsat:
http://phpmd.org/download/index.html
3. DownloadandinstallPHP_CodeSniffer.Youcanfindtheinstallationinstructionsat:
https://github.com/squizlabs/PHP_CodeSniffer
4. Everythingisinstalled,sowecanrunatestforPHPMD.ForthePHPMDcommand,thesearetherequiredoptions:
FilenameordirectoryTheformatofthereportTheruleset
5. Let’srunthefollowingcommandtocheckthefileoncleancodeandoutputtext:
phpmdapp/code/Magento/Cms/Model/Observer.phptextcleancode
6. Itgivesusthefollowingoutput:
/var/www/magento2/app/code/Magento/Cms/Model/Observer.php:70Avoid
usingstaticaccesstoclass'\Magento\Cms\Helper\Page'inmethod
'noCookies'
/var/www/magento2/app/code/Magento/Cms/Model/Observer.php:71Avoid
usingstaticaccesstoclass'\Magento\Store\Model\ScopeInterface'in
method'noCookies'.
/var/www/magento2/app/code/Magento/Cms/Model/Observer.php:77The
methodnoCookiesusesanelseexpression.Elseisnevernecessaryand
youcansimplifythecodetoworkwithoutelse.
7. Therearealotoferrors,butMagento2definesitsownrulesforPHPMD.Torunatestwiththeserules,wecanrunthefollowingcommand:
phpmdapp/code/Magento/Cms/Model/Observer.phptext
dev/tests/static/testsuite/Magento/Test/Php/_files/phpmd/ruleset.xml
8. Thiscommandgivesemptyoutput,whichmeansthatthisfileisvalid.9. WewillnowrunatestonthesamefilewithPHP_CodeSniffer.Withthenext
command,wewillrunatestonthesamefileweusedforPHPMD.
phpcsapp/code/Magento/Cms/Model/Observer.php
10. Thistestgivesusthefollowingoutput:
FILE:/var/www/magento2/app/code/Magento/Cms/Model/Observer.php
----------------------------------------------------------------------
FOUND22ERRORSAND2WARNINGSAFFECTING12LINES
----------------------------------------------------------------------
5|WARNING|[]PHPversionnotspecified
5|ERROR|[]Missing@categorytaginfilecomment
5|ERROR|[]Missing@packagetaginfilecomment
5|ERROR|[]Missing@authortaginfilecomment
5|ERROR|[]Missing@licensetaginfilecomment
5|ERROR|[]Missing@linktaginfilecomment
10|ERROR|[]Missing@categorytaginclasscomment
10|ERROR|[]Missing@packagetaginclasscomment
10|ERROR|[]Missing@authortaginclasscomment
10|ERROR|[]Missing@licensetaginclasscomment
10|ERROR|[]Missing@linktaginclasscomment
18|ERROR|[]Protectedmembervariable"_cmsPage"mustnotbe
||prefixedwithanunderscore
25|ERROR|[]Protectedmembervariable"_scopeConfig"mustnot
||beprefixedwithanunderscore
27|ERROR|[]Missingshortdescriptionindoccomment
28|ERROR|[]Missingparametercomment
28|ERROR|[x]Expected27spacesafterparametertype;1found
29|ERROR|[]Missingparametercomment
42|ERROR|[]Missingparametercomment
42|ERROR|[x]Tagvalueindentedincorrectly;expected2spaces
||butfound1
43|ERROR|[]Tagcannotbegroupedwithparametertagsina
||doccomment
62|ERROR|[]Missingparametercomment
62|ERROR|[x]Tagvalueindentedincorrectly;expected2spaces
||butfound1
63|ERROR|[]Tagcannotbegroupedwithparametertagsina
||doccomment
78|WARNING|[]Lineexceeds85characters;contains94
||characters
----------------------------------------------------------------------
PHPCBFCANFIXTHE3MARKEDSNIFFVIOLATIONSAUTOMATICALLY
----------------------------------------------------------------------
Time:28ms;Memory:3.75Mb
NoteIfthephpmdcommandisnotworking,youhavetofindthepathtothephpmdexecutableandrunitfromthere.
11. WhenwespecifytherulesetofMagento2,wehavethefollowingcommand:
phpcsapp/code/Magento/Cms/Model/Observer.php--
standard=dev/tests/static/testsuite/Magento/Test/Php/_files/phpcs/rules
et.xml
12. Thiscommandgivesusthefollowingoutput:
FILE:/var/www/magento2/app/code/Magento/Cms/Model/Observer.php
----------------------------------------------------------------------
FOUND5ERRORSAFFECTING5LINES
----------------------------------------------------------------------
18|ERROR|Missingvariabledoccomment
25|ERROR|Missingvariabledoccomment
31|ERROR|Missingfunctiondoccomment
45|ERROR|Missingfunctiondoccomment
65|ERROR|Missingfunctiondoccomment
----------------------------------------------------------------------
Time:35ms;Memory:3.75Mb
Howitworks…PHPMDandPHP_CodeSnifferaretoolsthatchecksPHPfilesoncodestyle.Thesetoolshavedefinedtheirdefaultrulesetsforcommonusage.
Magentohascreateditsownrulesets;theycanbefoundinthedirectorydev/tests/static/testsuite/Magento/Test/Php/_files/phpcs/ruleset.xml.
WhendevelopingcustomcodeinMagento2,itisrecommendedthatyouconfiguretheserulesetswhenworkingwithPHPMDandPHP_CodeSniffer.
There’smore…SomeIDE’shavebuilt-insupportforPHPMDandPHP_CodeSniffer.Thesepluginswillrunatestwhensavingafile.
InNetBeans,youhavethephpcsmdpluginthatallowsyoutointegratethesetoolsinyourIDE.FormoredetailsvisitthefollowingURL:
http://plugins.netbeans.org/plugin/40282/phpmd-php-codesniffer-plugin
InPHPStorm,thereisbuilt-insupportforPHPMDandPHP_CodeSniffer.Ifitisconfigured,thereisacolorindicatorthatsayshowcleanyourcodeis.Moreinformationcanbefoundathttps://www.jetbrains.com/phpstorm/help/using-php-mess-detector.html.
TipWhenconfiguringPHPMDandPHP_CodeSnifferinanIDE,thesetoolsandPHPneedtobeinstalledonthemachineonwhichtheIDEisrunning.
Chapter2.WorkingwithProductsInthischapter,wewillcoverthefollowingrecipes:
ConfiguringthecatalogdefaultsWorkingwithattributesetsWorkingwithproducttypesAddingsocialmediabuttonsEmbeddinganHTMLobjectChangingtheURLofaproductpage
IntroductionHowproductsaredisplayedinthefrontendisveryimportantformakingawebshopwithgoodusability.Convincingthevisitorstobuysomethingfromtheshopisthemaintargetofeveryshopowner.
Productsneedtobesetinsuchawaythatavisitorcanquicklyfindwhatheislookingfor.Withgoodproductcontent,ashoplooksreliable,duetowhichavisitorismorelikelytobuysomething.
Inthischapter,wewillexplainwhatyoucandotodisplayproductsinyourshop,andwewillseesomeextrathings,suchasavideoandaddtocartlinks,toraiseconversionfromaprospectivebuyertoanactualbuyer.
Thegoalofthischapteristomakeyourshopmoreuser-friendlywithjustalittledevelopment.
ConfiguringthecatalogdefaultsOneofthefirstthingsistoconfiguresomedefaultcatalogsettingstothepreferredvalues.WewillcoveralltheconfigurationvaluesthatarepossibleinaMagento2installation.
Wewillgothroughtheavailableconfigurationsandchangesomevaluestotherecommendedsettings.
GettingreadyOpenyourfrontendandlogintothebackendinaseparatebrowsertab.Wewillmodifysomeconfigurationvaluestotherecommendedsettings.Whenchangingaconfigurationvalue,wecancheckwhathappensinthefrontend.
HowtodoitInthenextsteps,wewilltakealookatthecatalogsettings:
1. Inthebackend,navigatetoStores|Settings|Configuration.OpentheCatalogmenu,aswecanseeinthefollowingscreenshot:
2. OpentheProductFieldsAuto-Generationsection.Inthissection,wecanconfigurethebehaviorofthegenerationofSKUandmetadata.WhenweaddthefollowingvaluesfortheMaskforMetaKeywords,theSKU,name,andthestring“Magento”willbegeneratedwhensavingaproduct:
{{sku}},{{name}},Magento
3. OpentheStorefrontsectionandsetthefollowingvalues:
Listmode:grid(bydefault,thisshowstheproductsinagridorlist)ProductsperpageonGridallowedvalues:12,24,36ProductsperpageonGriddefaultvalue:24
NoteWhenchangingtheallowedanddefaultvalueforagridpage,ensurethatyoucandividethenumbersbythenumberofproductsthatfitinarowinyourtheme.Otherwise,thelastrowofproductswillnotbecomplete.
ProductsperpageonListallowedvalues:10,20,30,40ProductsperpageonListdefaultvalue:10
Allowallproductsperpage:No
NoteWhenyouhavealargenumberofproducts,itisnotrecommendedtosettheAllowallproductsperpageoptiontoYes.Whenyouhave2000productsandyouwanttoshowalltheproductsonasinglepage,youwillgenerateanenormousHTMLoutputthatcancausememoryissues.
ProductlistingSortby:priceUseFlatCatalogCategory:NoUseFlatCatalogProduct:NoAllowDynamicMediaURL’sinProductsandCategories:Yes
4. Enabletheproductreviewsforguests.Thisallowseveryonetowriteareviewaboutaproduct.Whenthisisenabled,areviewformwillappearontheproductreviewpage.
5. OpentheProductAlertssectiontoconfigureproductalerte-mailsthatwillbesentwhenthepriceorstockchanges.
6. Wewillconfigureastockalertwiththefollowingsettings:
Allowalertwhenproductpricechanges:NoAllowalertwhenproductcomesbackinstock:Yes
NoteThepreviousconfigurationswillsendstockalerte-mails(astockalertistriggeredwhenaproductbecomesavailableinstock)tothesubscribede-mailaddresses.
7. WecansetthevaluesforProductAlertsRunsettingsinthenextsection.Wewillconfigureadailytaskat04:00hourstosendthealerte-mails:
Frequency:DailyStarttime:04:00:00
8. LeavetheProductImagePlaceholdersoptionsastheyare.Ifwewant,wecansetadefaultimagethatwillbeshownwhenaproducthasnoimageortheimageisnotfound.Thebestwayistosettheplaceholderimagesinthetheme.
9. IntheRecentlyViewed/ComparedProductstab,setthefollowingvalues:
Showforcurrent:Website
NoteThiswillshowtherecentproductsyouviewedoverallstoresandstoreviewsinthewebsite.
Defaultrecentlyviewedcount:5Defaultrecentlycomparedcount:5
10. InthePricetab,settheCatalogPriceScopeoptionasGlobal.Forthistutorial,wedon’tneeddifferentpricesforeachstoreview.WhenPriceScopeissettoGlobal,wecanonlyconfigureoneglobalpriceforaproduct,whichwillbethesameinallstoreviews.
11. IntheLayeredNavigationsection,wewillmodifysomesettingstocustomizetheleftnavigationforthecategorypages:
Displayproductcount:YesPricenavigationstepcalculation:Automatic(thiswillequalizepriceranges)
12. Bymakingthesesettings,thepricestepswillalwayshavethesameincrement.13. OpentheCategoryTopNavigationsection,andsetMaximalDepthto3.This
meansthatthenavigationwillbeshownwithamaximumofthreelevels.14. IntheChangingtheURLofaproductpagerecipe,wewilllookattheSearch
EngingeOptimizationstep.15. ConfiguretheCatalogSearchsectionasfollows:
MinimalQueryLength:3MaximumQueryLength:128SearchEngine:MySQLApplyLayeredNavigationifSearchResultsareLessThan:0
NoteIfasearchresultshowsalotofproducts,thegenerationofthelayerednavigationslowsdownthepageload.Withthissetting,youcandisablethelayerednavigationiftheresultscountishigherthantheconfiguredvalue.
16. Don’tforgettosavetheconfigurationbyclickingontheSaveConfigbutton.
HowitworksAllthesesettingsaresavedintheconfigurationtableofMagento.Thefrontendfilesforthecatalogpageswillpickupthesesettingsandrendertheoutputbasedonthesesettings.
Whenyouaddextrafunctionalitytothecategorypage,youcaneasilyextendtheconfigurationwithextraparameters.MoreinformationaboutextendingtheconfigurationsisgivenintheExtendingthesystemconfigurationrecipeofChapter6,MagentoBackend.
WorkingwithattributesetsMagentohasaflexiblesystemtoworkwithproducts.Whenyousell,forexample,aboardgameoracomputer,thespecificationsofeachproductaredifferent.Foraboardgame,informationsuchasageanddurationisrelevant.Foracomputer,alotoftechnicalspecificationsarerelevant,suchastheCPUpower,discsize,andsoon.
Tocoverthis,Magento2comeswithasystemcalledproducttemplates,whichcanbecomparedwithattributesetsinMagento1.
Aproducttemplateisaspecificationofproductattributesthatyoucanassigntoproducts.
GettingreadyInthebackend,wewillusethepagesStores|Attributes|ProductandStores|Attributes|AttributeSet.
Wewillcreateanewproductattributeandanewproducttemplate(suchasanattributeset)thatwecanuseinnewproducts.
HowtodoitInthefollowingsteps,wewillcreateanextraproductattributethatwecanuseinaproducttemplate:
1. NavigatetoStores|Attributes|Productinthebackend,andclickontheAddNewAttributebutton.
2. Populatetheformwiththefollowinginstructions:
Defaultlabel:Availablefrom(Thislabelwillbeusedtoidentifytheattribute)CatalogInputTypeforStoreOwner:Date(thisisthetypeoftheattribute)
3. ClickonSaveandContinueEditandtheattributewillbesaved.YouwillseethattheAttributeCodefieldisprepopulatedwithacodethatisgeneratedfromthelabel.
4. Additionally,wecansetthefollowingvalues:
Valuesrequired:NoScope:StoreView(withthissetting,wecreatethepossibilitytospecifyseparatevaluesforeachstoreview)Defaultvalue:LeavethisfieldemptyUniquevalue:No
5. IntheManageLabelstab,wecansetthelabelthatwillbedisplayedontheproductdetailpage.Ifleftempty,theattributelabelwillbeused.
6. IntheStorefrontPropertiestab,wecansetthefollowingproperties:
Useinsearch:NoComparableonStorefront:NoUseforPromoRuleConditions:NoAllowHTMLTagsonfrontend:NoVisibleonCatalogPagesonStorefront:NoUsedinProductListing:NoUsedforSortinginProductListing:No
7. ClickonSaveAttribute;thiswillsavetheattribute.8. ThenextstepistocreateanAttributeSettowhichwillassignaproductto.Inthe
backend,navigatetoStores|Attributes|AttributeSet.9. ClickontheAddAttributeSetbuttonandfillintheform,asfollows:
10. ClickingonSavewillopentheoverviewpage.11. CreateagroupnamedGamespecificdataanddragtheavailable_fromand
manufacturerattributestoit.Theoverviewwilllookasfollows:
12. SavetheAttributeSet.13. NavigatetoProduct|CatalogandcreateanewproductbyclickingontheNew
Productbutton.14. SelecttherightAttributeSetintheform,asshowninthefollowingscreenshot:
15. Whenselectingtheproducttemplate,youwillseethattheGamespecificdatatabappearsintheproducttab,asshowninthefollowingscreenshot:
HowitworksProductattributesandAttributeSetsareusedwhenyouworkwithmultiplefamiliesofproducts.Inthesampledataofourshop,therearemoreattributesetsavailableforbags,clothing,andmore.
WithAttributeSets,youcanmakegroupsofattributesforeveryproductfamily.Whencreatingaproductattribute,youhavetochoosethetypeoftheattribute,whichcanbeoneofthefollowing:
TextfieldTextareaDateYes/NoMultipleselectDropdownPriceMediaimageFixedProductTax
TipWhenyouwanttouseanattributeasafilterintheleftnavigationonthecategorypages,thisattributemusthavethetypeDropdown,MultipleSelectorPrice.
WorkingwithproducttypesInMagento2,itisstillpossibletoworkwithdifferenttypesofproducts.Thestandardproductisasimpleproduct,whichisusedtosellbasicproducts,buttherearemoretypesavailable,suchasproducts,whereyoucanchooseasizeandotheroptions,ordownloadproducts,virtualproducts(suchasalicense),andproductcombinations.
GettingreadyInthisrecipe,wewillcreateaconfigurableproduct,forexample,youwanttobuyapairofshoeswhereyoucanchoosetheirsizeandcolor.OpentheMagentobackendandnavigatetoProducts|Catalog.
HowtodoitInthefollowingsteps,wewillcreateaproductwherewecanspecifyasizeontheproductdetailpage:
1. NavigatetoProducts|Catalog,clickonthearrowneartheAddProductbutton,andchooseConfigurableProductasshowninthefollowingscreenshot:
2. Chooseaname,SKU,andpricefortheproduct.3. Thenextstepistodecidetheattributeonwhichwewanttoconfigureourproduct.
ScrolldownthroughtheNewProductspage,andclickontheCreateConfigurationsbutton.Inthegrid,selecttheSizeattributeandclickonNext.
4. SelectthesizesforyourproductsandclickonNext.5. Onthenextscreen,youcanaddimages,prices,andquantitiesfortheproducts,or
youcandothislater.6. WhenyouclickonNext,youwillgetanoverview.WhenyouclickonGenerate
Products,theproductswillbedisplayedasshowninthefollowingscreenshot:
7. ClickontheSavebuttonandapopupwillshowuptocreateaspecificattributesetforthisconfiguration.SelecttheAddconfigurableattributestothenewsetbasedoncurrentoptionandselectaSizeforthename.
8. ClickonConfirmtosavetheproductsinthedatabase.9. OntheProductEditpage,selectthecategorywhereyouwanttoaddtheproduct,
clickonSave,andsearchfortheproductintherightcategoryinthefrontend.Theproductdetailpagewilllookasfollows:
Howitworks…Aconfigurableproductisaproductwhereyoucanselectoneormoreoptionsontheproductdetailpage.Eachcombinationofselectionsleadstoasimpleproductinthedatabase.Thecustomerchoosestheoptions,andwhenaddingtheconfigurableproducttothecart,asimpleproductisalsoaddedinthebackground.
Thisisthereasonwhywehavegeneratedsimpleproductstobeshownasoptionsinourconfigurableproduct.Theconfigurableproductisaparentwrapperthatisusedtodisplayinthefrontend.ThesimpleproductsarehiddeninthefrontendbytheVisibilityattribute.
Whenaproductissold,theSKUoftheselectedchildproductwillbeusedtoprocesstheorder.That’sthereasonwhywehavetoconfigureastockonthechildproducts.
There’smore…InMagento,wecancreatesixtypesofproduct.Thefollowingoverviewgivesashortdescriptionofwhatispossiblewiththedifferentproducttypes.
AsimpleproductAsimpleproductisjustaproductthatyoucansellinyourwebshop.EveryproductinMagentohasauniqueID(SKU)thatmostlyhasthesamevalueasthearticlecodeofthesuppliers.
AconfigurableproductInthisrecipe,wecreatedaconfigurableproduct.Thisproducthaschildproductsthatyoucanconfigureontheproductpage(forexample,toconfigurethesize).Thechildproductsaresimpleproducts.
AbundleproductAbundledproductislikeaconfigurableproduct,butwiththisone,youcan(optionally)specifymoreoptions.
Inthesampledata,youcanfindagoodexamplebynavigatingtoGear|FitnessEquipment|SpriteYogaCompanionKit.
NoteEveryoptionofabundlerepresentsanotherproductintheshop.Whenyouaddabundleproducttothecart,theproductsofthechosenconfigurationwillalsobeaddedtothecartinthebackground.
AgroupedproductAgroupedproductisaproductthatrepresentsasetinwhichyoucanspecifythenumberofchildproducts.AgoodexampleofthiscanbefoundbynavigatingtoGear|FitnessEquipment|SetofSpriteYogaStraps.
Inagroupedproduct,youassignsimpleproducts.Whenaddingagroupedproducttothecart,thechildproductswillbeaddedasseparateproductswiththeconfiguredquantity.
AvirtualproductAvirtualproductislikeasimpleproductbutitisnotphysical.Ithasnoinventoryandcan’tbeshipped.Agoodexampleofavirtualproductisasoftwarelicense.
AdownloadableproductAdownloadableproductisaproductthatisnotphysical.Whenacustomerbuysadownloadableproduct,adownloadlinkwillbesenttothecustomersothattheycandownloadtheirproduct,whichisintheformofaPDF,MP3,ZIPfile,oranyothertypeoffile.
AddingsocialmediabuttonsThesedays,anincreasingnumberofpeoplearesharingtheirmindsthroughdifferentsocialmedia,suchasFacebook,Twitter,andmanymore.
Everysocialmediaplatformhastheoptiontosharepagesontheirplatform.Themostfamousexamplesofthisarethesharebuttons,suchastheLikebuttonofFacebook,theTweetbutton,theGooglePlusbutton,andmanymore.
GettingreadyInthisrecipe,wewilladdsharebuttonsforthefollowingplatforms.Youcantakealookatthedeveloperdocumentationofeachplatformasapreparatorystep:
Facebook(https://developers.facebook.com/docs/plugins/like-button)Twitter(https://about.twitter.com/resources/buttons)GooglePlus(https://developers.google.com/+/web/+1button/)
Toshowabuttononeveryproductpage,wehavetodosomecodechanges.EnsurethatyouhaveaccesstothecodewithyourIDE.
HowtodoitThefollowingstepsshowyouhowtoaddsocialmediabuttonstothedescriptionofyourproduct:
1. Openthepageoftheproductwhereyouwanttoaddthesocialmediabuttons.2. WewillstartwiththeLikebuttonofFacebook.Visit
https://developers.facebook.com/docs/plugins/like-buttonandconfigurethefollowingformwithyourdata:
3. WhenclickingtheGetCodebutton,youwillseethecodeofthebutton.Placethiscodeintheproductdescriptionfield.
4. SavetheproductandopentheProductDetailpageofthatproductinthefrontend.YouwillseeaLikebuttoninthedescriptionfieldoftheproduct.
5. Wecanalsoaddbuttonsforsharingonothersocialmediausingthesameprinciple.ForTwitter,wecangetthecodeofabuttonathttps://about.twitter.com/resources/buttons.Fromhere,copythecodeandpasteitafterthecodeoftheFacebookLikebutton.
6. Similarly,forGooglePlus,wecangetabuttonathttps://developers.google.com/+/web/+1button/fromwherewecancopythecodeandpasteitinthedescriptionfieldoftheproduct.
7. Whenreloadingthefrontend,wecanseethebuttonsonthedetailpageofthatproduct.Ifwewanttoshowitoneverypage,wewillhavetomakeachangeinthecodeoftheproductdetailtemplate.
8. Ifyouhaveacustomtheme,copyandpastetheapp/code/Magento/Catalog/view/frontend/templates/product/view/description.phtml
filetotheconfiguredapp/design/frontend/<package>/<theme>/Magento_Catalog/templates/product/view/description.phtml
theme.
Note
Ifyouhavenocustomthemeinstalled,takealookattheCreatingaMagento2themerecipeofChapter3,Theming.Inthatrecipe,allthestepsareexplainedonhowtodothis.
9. Addthecodeofthesocialmediabuttonsattheendofthefile.YougeneratetheproductURLwiththefollowingcode:$block->getProduct()->getProductUrl().UsethiscodetogeneratetheURLforthebutton.ThecodefortheFacebookLikebuttoncodewilllookasfollows:
<divclass="fb-like"
data-href="<?phpecho$block->getProduct()->getProductUrl()?>"
data-width="450"
data-layout="standard"
data-action="like"
data-show-faces="true"
data-share="true">
</div>
10. Savethefile,cleanthecache,andyouwillseetheLikebuttononeverypage.
HowitworksAsocialmediabuttonismostlyapieceofexternalHTMLthatwillrenderinyourwebsite.Withthisbutton,apagecanbeshared.
Torenderthesebuttons,externalstaticresourcesfromthesocialmediawebsitewillbeloaded.Whenyoureadthecodeofthebuttons,youcanseethatadditionalJavaScriptlibrariesareincludedinthecode.
Whensharingapage,thesocialmediacrawlsthepagetolookforatitle,image,anddescriptionforthepost.Inthefirstcase,thecrawlerwillsearchfortheOpenGraphmetatags.
ThesetagsaregeneratedbyMagentoonaproductdetailpage,soagoodtitle,image,anddescriptionwillbedisplayedforthepost.
EmbeddinganHTMLobjectInaproductdescription,wecanaddHTMLtagssothatwecanusethe<object>tag.Withan<object>tag,wecanembedwidgets,suchasaYouTubevideo,socialwidgets,andmore.
Inthisrecipe,wewillfocusonhowtoaddaYouTubevideoinaproductdescription.
GettingreadyGotohttp://www.youtube.com,andchooseavideothatyouwanttoshowontheproductdetailpage.
HowtodoitThenextstepsshowsyouhowtoembedaYouTubevideoonaproductdetailpage.
1. OntheYouTubevideopage,clickontheEmbedbutton.Whenyouclickthisbutton,thefollowingscreenshowsup:
2. CopytheHTMLcodeandpasteitinthedescriptionoftheproduct.3. Savetheproduct.4. Gototheproductinthefrontend.YouwillseethevideoontheProductpage.
HowitworksTheabilitytouseHTMLtagsinproductdescriptionsgivesalotofflexibilityforthisfield.ItispossibletouseaWYSIWYGeditorforthecontentbecausethisallowsustousewidgets,suchasaYouTubevideoorotherthird-partywidgets.
OnaYouTubevideopage,weopenedtheembedoptionswherewecanconfigurethecodethatwewillincludeinoursite.Wecanspecifyoptionssuchaswidth,height,colorandmore.
Wheneverythingisconfigured,wecanpastetheEmbedcodeintheHTMLoftheproductdescription,andthevideowillbevisibleinthedescriptionoftheproduct.
ChangingtheURLofaproductpageWhenyouareonaproductpage,theURLofeveryproductalwayslooksclean.ThenameintheURLmakesitverySEOfriendly.
Inthisrecipe,wewillexplorethepossibilitiesofURLrewritesinMagento.
GettingreadyInthebackend,navigatetoProducts|Inventory|CatalogandlookforasimpleproductwithvisibilityCatalog,Search.ThisrecipeisbasedontheEndeavorDaytripBackpackproductfromthesampledata.
HowtodoitInthefollowingsteps,wewillseetheprocedureforchangingaURLofaproductdetailpage.
1. Findtheappropriateproductinthefrontend.YoucanfindbynavigatingtoGear|Bags.Ifyouopentheproductdetailpage,youwillseetheURL/endeavor-daytrip-backpack.html.
2. Inthebackend,changetheURLkeyattributetobuy-now-endeavor-daytrip-backpack.
3. Reloadtheproductinthefrontend.TheURLwillchangetotheonewehavejustenteredinthebackend.
TipWhenyouselecttheCreatePermanentRedirectoptionforanoldURLcheckbox,Magentowillcreateapermanent301redirectresponsefortheoldURLoftheproduct.ThecheckboxislocatedintheproducteditpageinthebackendundertheURLKeyattribute.
4. EmptytheURLkeyattributeatthebackendandsavetheproductagain.YouwillseethatMagentoautogeneratestheURLkeyattributebasedonthenameoftheproduct.
5. Atthebackend,navigatetoStores|Configuration|Catalog|SearchEngineOptimizations.CleartheproductURLSuffixfieldandsavetheconfiguration.
6. ClearthecachebynavigatingtoSystem|CacheManagement.7. Reloadtheproductinthefrontend,andyouwillseethatthe.htmlsuffixisgone.
NoteInMagento1,therewasaURLrewriteindex,inMagento2,thisindexisreplacedbyanewsystemtogeneratetheURLs.
HowitworksInMagento,thereisaURLrewritesystemthatmapsanSEO-friendlyURLtothesystem’sURL.Intechnicalterms,thisisalsocalledrouting.Inthebackend,youcanseealltheURLrewritesthatareavailableintheinstallation.
YoucanseethisbynavigatingtoMarketing|SEO&Search|URLRewritesinthebackend.Onthispage,youcanseethecompletelistoftheURLsthatareavailableinthewebshop.Ifwesearchforendeavor-daytrip-backpack,wewillseealistofalltheURLs,whichlookslikethis.
Whatweseeisthefollowing:
Permanent301redirectresponses(rowswheretheRedirectTypecolumnisPermanent(301))TheProductURLTheURLofaproductineverycategory
AlltheURLsaregeneratedseparatelyforeachstoreviews.Whenaproductisenabledinmultiplestores,itisnormalthataproducthasmorethanoneURL.
There’smoreOntheURLrewritepage,itisalsopossibletoaddcustomURLrewrites.Forexample,aURLrewriteforthecontactpage.
WhenaddingtheAddnewURLRewritebutton,aformshowsup.Thefollowingscreenshotshowsushowwecancreateanaliasfromthecontact-us.htmlURLtothe/contactpage:
IntheStoreoption,youcanconfigurethestorefortheURLrewrite.
ThevalueintheRequestPathfieldisthepaththatyouwanttorewrite;inthiscase,wewanttorewritesomethingonthe/contact-us.htmlpath.
ThevalueintheTargetPathfieldisthepathwheretherequestwillend;inthiscase,itisthe/contactpage.
IfthevalueinRedirectTypeissettoNo,thetargetpathwillberenderedontherequestpath(so,theURLdoesn’tchange).Youalsohavethechoicetoredirectthepagewithapermanent(301)ortemporary(302)redirect.
Chapter3.ThemingInthischapter,wewillcover:
ExploringthedefaultMagento2themesCreatingaMagento2themeCustomizingtheHTMLoutputAddingextrafilestothethemeWorkingwithLESSChangingapagetitleWorkingwithtranslationsAddingwidgetstothelayoutCustomizingemailtemplates
IntroductionThemostcommoncustomizationonaMagentoshopisthetheming.Makingagoodfirstimpressiontoyourcustomersisanimportantpointtoraisetheconversion.Almosteverystoreownerwantsalookandfeeloftheircompanyintheirshop.
Inthischapter,wewillcoverallthethingsyouneedtocustomizeaMagentotheme.Wewillseehowwecancustomizethetemplates,CSS,JavaScript,translations,andmore.
ExploringthedefaultMagento2themesWhenMagento2isinstalled,therearetwothemesavailable.YouhavetheLumathemethatyoucanseeinthesampledata,andyouhavetheBlankthemethatisdevelopedasastartingpointtocustomizeyourtheme.
GettingreadyLogintothebackendandopenthedesignconfiguration.WecanfindthisinStores|Configuration|Design.
Howtodoit…ThefollowinginstructionsdescribehowwecanconfigurethethemesettingsforaMagento2store:
1. Whenyoulookatthethemesettingsontheconfigurationpage,weseethattheMagentoLumathemeisselectedintheDesignThemesection.SelecttheMagentoBlankoptioninthedropdown,savetheconfiguration,andreloadthefrontend.Youshouldseesomethinglikethefollowingscreenshot:
2. WehavenowconfiguredtheMagentoBlankthemeforthestore.TheMagentoBlankthemeisthedefaultthemewheretheMagentoLumathemewillextendfrom.
3. OpenthepageContent|ThemesinthebackendandyouwillseeanoverviewoftheavailablethemesinMagento.WhenMagento2isinstalledwithoutmodules,thefollowingthemesareavailable:
MagentoBlank(usedasthedefaulttheme)
MagentoLuma(usedinthesampledatastore)
4. WhenclickingonMagentoLuma,wewillseethedetailsofthethemelikethefollowingscreenshot:
5. WeseethattheParentThemeisMagentoBlank.ThismeansthatthisthemewillextendtheMagentoBlanktheme.
6. Inthebackend,openthepageContent|Schedule.WhenclickingtheAddDesignChangebutton,wecanconfigureadesignchangewithafromandtodate.
Howitworks…InMagento2theconceptofthemesismucheasierthaninMagento1.Therearenopackagesanymore,andnoskins.
InMagento2,athemecanhaveaparentthemeandthat’sall.Whenathemehasaparenttheme,itwillinheriteverythingfromthatparentthemeexceptyoucanoverrideitinyourtheme.Thismeansthatifyourthemeisempty,allthesettingsoftheparentthemewillbeused.
LikeinMagento1,youcanoverridethethemesettingsindifferentwayssuchasthefollowing:
ByaUserAgentExceptionontheStores|Configuration|DesignpageByascheduledthemesettingontheContent|SchedulepageOnaspecificCMSpage(canbeconfiguredontheDesignsectionofaCMSeditpage)Onaspecificproductdetailpage(thiscanbeconfiguredontheDesignsectionofaproducteditpage)Onaspecificcategorypage(thiscanbeconfiguredontheCustomDesignstepofacategory)
CreatingaMagento2themeWewillstartcustomizingthelookandfeeloftheshopbycreatingacustomtheme.Thepurposeofacustomthemeisthatwedon’thavetomodifythecorefilesdeliveredbyMagento.
GettingreadyOpenyourMagentowebrootinyourfavoriteIDE.Wewillcreateathemeand,forthis,wewillworkintheapp/design/frontendfolder.
Beforeyoustart,disablethefull-pagecachebecausethiswillsaveyoualotoftrouble.YoucandothisinthebackendonthepageSystem|CacheManagement.ClickontheFlushMagentoCachebuttonafteryouhavedisabledtheFullpagecaching.
Howtodoit…Thefollowingprocedureshowsyouwhichactionsarerequiredtocreateacustomtheme:
1. Forourtheme,wewillcreateavendornamespace.Wecandothisbycreatingthefolderapp/design/frontend/Packt.
NoteAthemenamespaceisalwayswritteninCamelCasesuchasMagento,Packt.
2. Inthatnamespace,wewillcreateathemecalledcookbook.First,createthefolderapp/design/frontend/Packt/cookbook.
NoteAthemeisalwayswritteninsmallletterssuchasluma,cookbook,blank.
3. Toregisterthetheme,wehavetocreatethefilewiththefollowingcontent:
<?php
\Magento\Framework\Component\ComponentRegistrar::register(
\Magento\Framework\Component\ComponentRegistrar::THEME,
'frontend/Packt/cookbook',
__DIR__
);
4. Atthispoint,wehavetocreateathemeconfigurationfile.Wecandothisbycreatingthefileapp/design/frontend/Packt/cookbook/theme.xmlwiththefollowingcontent:
<themexmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Config/etc/theme.x
sd">
<title>PacktCookbook</title>
<parent>Magento/blank</parent>
</theme>
5. Itisalsopossibletoaddapreviewimagetothetheme.Inthethemefolder,createamediafolderinthethemefolderandaddanimageofyourchoicethatwewanttouseasapreview.
6. Tosetthepreviewimage,wehavetoaddthehighlightedXMLconfigurationtothetheme.xmlfile:
...
<title>PacktCookbook</title>
<parent>Magento/blank</parent>
<media>
<preview_image>media/preview.png</preview_image>
</media>
</theme>
7. Clearthecacheandrunthecomposerinstalltoregisterthetheme.8. Totestwhetherthethemeexists,navigatetothepageContent|Themesinthe
Magentobackend.Youshouldseethethemeinthelist.Whenweclickonthetheme,wecanseethedetailsthatwehaveconfiguredinthetheme.xmlfile.
9. Thelaststepistoconfigurethetheme.GotothepageStores|Configuration|Designandconfigurethethemelikethefollowingscreenshot:
10. Flushthecacheandreloadthefrontend.YourshopwillnowhavethelookandfeeloftheBlanktheme.
NoteIfyouwanttouninstallatheme,youcanusethecommandphpbin/magentotheme:uninstall.
Howitworks…WehavejustcreatedanemptythemethatinheritseverythingfromtheMagentobasetheme.Whenwewanttochangestuffonthetheme,wecandoitinthisthemewithoutoverridingthecore.
Whenwewanttocustomizesomeaspectsofthetheme,wecancopyafilefromtheMagento/Blankthemeandpasteitinourtheme.Whencopyingafile,thedirectorystructureneedstobethesameasthestructureoftheparenttheme.
NoteChangingcodeintheMagentocorewillalsowork,butthisisnotrecommended.Whenyouwanttoupgradeyourstore,allthecorefileswillbeoverwrittenandthenallyourchangeswillbelost.
Wecreatedatheme.xmlfileinourthemewherewehavesetthedefaultsettingsofthethemesuchasthenameandparenttheme.Magentowillstorethisdatainthethemetableofthedatabase.
NoteAlltheXMLconfigurationswillbecachedbyMagento.Sowhenthecachesareenabledandyouchangesomethingintheconfigfiles,makesureyoucleanandflushthecaches.Youcanalsodisablethecacheswhendeveloping.Youcancleanthecacheusingthecommandphpbin/magentocache:cleanoryoucandoitinthebackend.
There’smore…InMagento,itispossibletoenabletemplatehintsinthefrontend.Whenthisisenabled,containerswillbeshownaroundblocksofHTMLwiththereferencetothefileandclassthatisloaded.
Toenablethis,navigatetoStores|Configuration|Advanced|DeveloperandchangetheconfigurationscopedropdowntoMainWebsitelikethefollowingscreenshot:
IntheDebugsection,youcanenablethehintsbysettingthedropdownvaluestoYesforthefollowingfields:TemplatePathHintsandAddBlockNamestoHints.
Whenthisisdone,reloadyourfrontendandyouwillseeredbordersaroundeachblockofHTML.
CustomizingtheHTMLoutputYoucancustomizeathemeintwoways.Wecanonlychangesomestylestomakeashoplookdifferent.ThesecondwayistocustomizetheHTMLoutput,whichiswhatwewillcoverinthisrecipe.
ItisverycommontowanttochangesomestuffontheHTMLstructuretomakeyourshoplookunique.
GettingreadyMakesureyouhavethethemeinstalledandconfiguredlikewehavedoneinthepreviousrecipe.
Ifyoudon’thavethethemeinstalled,youcaninstallthestartfilesforthisrecipe.
Howtodoit…Inthenextsteps,wewillchangethelogo,changeatemplate,andwewilladdextrablockstothefooter:
1. Firstwewillchangethelogo.Ifyoulookatthefrontendofthewebshop,youwillseethatthedefaultMagentologoisused.ThisisanSVGimagebutwhatifIhaveaPNGimage?Createalogo.pngfileinthefolderapp/design/frontend/Packt/cookbook/web/images.Ifthisfolderdoesn’texist,createone.
2. Thesecondstepistocreatealayoutconfigurationfileinthefolderapp/design/frontend/Packt/cookbook/Magento_Theme/layout.
3. Inthatfolder,createadefault.xmlconfigurationwiththefollowingcontent:
<?xmlversion="1.0"?>
<pagexmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/pa
ge_configuration.xsd">
<body>
<referenceBlockname="logo">
<actionmethod="setLogoFile">
<argumentname="logo_file"
xsi:type="string">images/logo.png</argument>
</action>
</referenceBlock>
<referenceBlockname="logo"remove="true"/>
</body>
</page>
4. Flushthecacheandreloadthefrontend.Youshouldseethatthelogoischangedtothespecifiedfile.
5. Nextwewillchangethetoolbaronthecategorypages.Inthefrontend,navigatetoacategorypagewithproductssuchasWomen|Tops.
6. Tochangetheoutputofacategorypage,wehavetooverridethetemplate.Toknowwhattemplateisused,wecanenablethetemplatehints.EnablingthetemplatehintsisdescribedintheThere’smoresectionofthepreviousrecipeCreatingaMagento2theme.
7. Withthetemplatehintsenabled,weseethetemplatelocatedinapp/code//Magento/Catalog/view/frontend/templates/product/list.phtml.Tooverridethistemplate,copyandpastethatfileinapp/design/frontend/Packt/cookbook/Magento_Catalog/templates/product/.Ifthatfolderdoesn’texist,createit.
8. Cleanthecacheandreloadthepage.Inthetemplatehints,youwillseethatthefilewejustcopiedisusedinsteadofthedefaultone.
TipIfyoudon’tseeanychangestothefrontend,youhavetoflushtheMagentocache.Alsodisablethefull-pagecachewhendeveloping.
9. Inthisfile,wecanchangewhatwewantwithoutoverridingthecore.10. Atlast,wewilladdanextramenuwithlinksinthefooter.Forthis,weneedtoedit
thefile:app/design/frontend/Packt/cookbook/Magento_Theme/layout/default.xml.
11. PastethefollowinghighlightedcodeinthatXMLfile:
...
</action>
</referenceBlock>
<referenceBlockname="footer">
<blockclass="Magento\Framework\View\Element\Html\Links"
name="footer_links_account"after="footer_links">
<arguments>
<argumentname="css_class"xsi:type="string">footer
links</argument>
</arguments>
<block
class="Magento\Framework\View\Element\Html\Link\Current"name="my-
account-link">
<arguments>
<argumentname="label"xsi:type="string">My
account</argument>
<argumentname="path"
xsi:type="string">customer/account</argument>
</arguments>
</block>
<block
class="Magento\Framework\View\Element\Html\Link\Current"name="my-cart-
link">
<arguments>
<argumentname="label"xsi:type="string">My
cart</argument>
<argumentname="path"
xsi:type="string">checkout/cart</argument>
</arguments>
</block>
</block>
</referenceBlock>
</body>
</page>
12. Youhavetopastethehighlightedcodeasachildofthe<body>tag.13. Cleanthecacheandreloadthefrontend.Youshouldnowseeanextracolumninthe
footerwithtwolinksinit.14. Toendthisrecipe,wewilladdalinktothemenuwehavejustcreatedbutthatmenu
itemisonlyvisibleonthecartpage.Torealizethis,wehavetoaddthefileapp/design/frontend/Packt/cookbook/Magento_Theme/layout/checkout_cart_index.xml
15. Inthatfile,addthefollowingcontent:
<?xmlversion="1.0"?>
<pagexmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
layout="1column"
xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/pa
ge_configuration.xsd">
<body>
<referenceContainername="footer_links_account">
<block
class="Magento\Framework\View\Element\Html\Link\Current"
name="checkout-link">
<arguments>
<argumentname="label"xsi:type="string">Goto
checkout</argument>
<argumentname="path"
xsi:type="string">checkout/onepage</argument>
</arguments>
</block>
</referenceContainer>
</body>
</page>
16. Cleanthecacheandgotothecartpage.Youwillseethatthereisanextralinkinthemenu.
Howitworks…Withthetemplatehintson,weseethatalotofHTMLblocksareusedtobuildthepage.AlltheseblocksareconfiguredinthelayoutXMLfiles.
Everyblockinthepageisfromaspecifictype.Thistypeistheclassthatisusedtogeneratetheblock.Theblockclasscontainsfunctionsthatcouldbecalledinthetemplatebycallingthe$blockvariable.
InthelayoutXMLfiles,thetypeoftheblockisspecifiedwiththeclassattribute.
Forchangingthelogofile,wehavechangedaparameteroftheblockclassthatisusedforthelogo.WiththeXMLconfigurationinthedefault.xmlpage,wehavemodifiedthelogo_fileparameterofthatclassforallpages.Everyblockhasanameandbyusingthe<referenceBlockname="block_name">tag,wecanmodifythecontentsoftheblock.Wecanmodifypageargumentslikewehavedonewiththelogoorwecanaddchildblockslikewehavedonewiththeextrafootermenu.
Theextrafootermenuisanewblockthatwehavespecifiedasachildofthefooterblock.Inthefootermenublock,wehavespecifiedthelinksthatarealsoimplementedasablock.
Weaddthenewblockinthedefault.xmlfile.Thismeansthattheconfigurationisloadedonallpages.
Ifyouwanttoaddaconfigurationthatisonlyforaspecificpage,youhavetoputthatconfigurationinadifferentfile.Thenameofthefileisthelayouthandlethatisusedsuchascheckout_cart_indexforthecartpage.
AddingextrafilestothethemeInthepreviousrecipe,welearnedhowwecanmakestructuralchangestotheHTMLoutput.HoweveranHTMLoutputisnotacompletedwebsite.WithJavaScriptandCSS,wecanthemetheHTMLoutput.
GettingreadyWewilllearnhowwecanaddCSSandJavaScriptfilestoourtheme.ThisisacommoncasewhenintegratinganexternalJavaScriptplugin.Todothis,wehavetoworkinthethemefolderthatwecreatedintherecipeCreatingaMagento2themeearlierinthischapter.
Howtodoit…Thefollowingstepsdescribehowwecanaddextrafilestoatheme:
1. OpenthefrontendandopentheHTMLsourceofapage,andhavealookatthe<head>section.WeseethattheincludedJSandCSSfilesarelocatedinthepub/staticmap.
2. IfwewanttoaddanextraCSSfilewehavetoconfigurethatinthelayoutXMLfiles.Openorcreatethefileapp/design/frontend/Packt/cookbook/Magento_Theme/layout/default_head_blocks.xml
3. Inthatfileaddthefollowingcontent:
<?xmlversion="1.0"?>
<pagexmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/pa
ge_configuration.xsd">
<head>
<csssrc="css/cookbook.css"/>
</head>
</page>
4. Createthefileapp/design/frontend/Packt/cookbook/web/css/cookbook.cssandaddthefollowingcontentinit:
body{
background-color:#dcdcdc;
}
5. Cleanthecacheandreloadthefrontend.Youwillseethatthebackgroundcolorischangedtolightgrayandthatthecookbook.cssfileisincludedinthe<head>section.
NoteInthenextrecipe,wewillseehowwecangenerateCSSusingtheLESSpre-processorinMagento2.Thismethodisrecommendedwhenyouwanttoadda3rdpartyCSSsuchastheCSSofajQueryplugin.
6. ThenextthingistoaddaJavaScriptfiletothe<head>section.ThisworksthesamewayaswiththeCSSfile.
7. Openthefileapp/design/frontend/Packt/cookbook/Magento_Theme/layout/default_head_blocks.xml
andaddthehighlightedcodeasachildofthe<head>tag:
<head>
<csssrc="css/cookbook.css"/>
<scriptsrc="js/cookbook.js"/>
</head>
8. Createthefilecookbook.jsinthefolderapp/design/frontend/Packt/cookbook/web/js/.
9. Cleanthecacheandreloadthesourceofthefrontend.Youwillseethatthe
cookbook.jsfileisaddedinthe<head>.
Howitworks…Likeinthepreviousrecipe,wedidsomelayoutXMLconfigurationstoaddtheCSSandJavaScriptfile.Intheconfigurationfiledefault_head_blocks.xml,weaddedXMLconfigurationstoaddaCSSfileandaJSfile.
WhenrenderingtheHTMLoutput,Magentowilllookforall<head>configurationentriesandwillgeneratealistofCSSandJSfiles.
WecreatedtheCSSandJavaScriptfilesinthethemefolder.ButifwelookattheHTMLsource,weseethatMagentoloadsthefilefromadifferentlocation.Thefilesareloadedfromthepub/staticfolder.
Magentobuildsasymboliclinkfromthepubfoldertothewebfolderinthetheme.WhenwehavestaticfilessuchasCSS,JavaScript,images,webfonts,andmore,wehavetoplacethesefilesinthewebfolderofthetheme.
There’smore…InthisrecipewelearnedhowwecanaddCSSandJavaScriptfilestotheHTMLhead,butwiththesamesystemwecanalsoaddmetalinkandtitletagstothehead.Thefollowingcodesnippetshowsyouhowthisworks.Thisisanexampleofthedefault_head_blocks.xml:
<?xmlversion="1.0"?>
<pagexmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Fr
amework/View/Layout/etc/page_configuration.xsd">
<head>
<csssrc="css/cookbook.css"/>
<scriptsrc="js/cookbook.js"/>
<linkrel="publisher"src="https://www.packtpub.com/"/>
<metaname="author"content="PacktPublishing"/>
</head>
</page>
The<title>configurationismissinginthisconfiguration.HowtochangeapagetitleisexplainedintherecipeChangingapagetitleinthischapter.
TipIfyouwanttoexplorewhichconfigurationsarepossibleinaparticularXMLfile,havealookattheXSDfilethatisdeclaredatthetopofthefileandyouwillseewhichconfigurationsyoucanuseinyourconfigfile.
WorkingwithLESSInMagento1,theCSSofaMagentothemewasstoredinonebigCSSfile(thestyles.css)butinMagento2,itiscompletelydifferent.ThebigCSSfilehasbeenreplacedbyacollectionofLESSfiles.
LESSisalanguagethatisusedtogenerateCSS.CSSisverystatic.Youcan’tusefunctions,variables,nesting,andsoonbutwithLESS,youcan.
MagentohasaLESSpre-processorthatgeneratesaCSSfilefromtheLESSfilesinthetheme.
GettingreadyOpenyourfavoriteIDEandnavigatetothethemefolderofthePackt/cookbookthemethatwehavecreatedinthepreviousrecipes.
AccesstoacommandlineisalsousefultoworkwithGrunt.
Howtodoit…Thefollowingstepsdescribehowwecanchangethelayout,suchashowMagento2doesit:
1. Copythefileapp/design/frontend/Magento/blank/web/css/source/_theme.lesstothefolderapp/design/frontend/Packt/cookbook/web/css/source/_theme.less.
NoteIfyouinstalledMagento2usingcomposer,youhavetocopythe_theme.lessfilefromthefoldervendor/magento/theme-frontend-blank/web/css/source/.
2. Inthatfile,addthefollowingcontent:
@cookbook_primary_color:#FECA5C;
@cookbook_dark_color:#373C40;
@text__color:@cookbook_dark_color;
@navigation__background:@cookbook_primary_color;
@button-primary__background:@cookbook_primary_color;
@button-primary__border:1pxsolid@cookbook_dark_color;
@button-primary__color:@cookbook_dark_color;
3. Reloadthepage.Normallyyouwillseenolayoutchangesbecausewehavetoclearthepre-generatedfile.Removethefollowingfolderswithpre-generatedCSS:
rm–rfpub/static/*
rm–rfvar/view_preprocessed
Alsodon’tforgettoflushthecachebeforerenderingthepage.
4. Reloadthepageandyourpagewilllooklikethefollowingscreenshot.TheloadtimeofthepageisabitlongerbecausetheCSSneedstobegenerated:
NoteIfyougetapagewithoutstyling,anerroroccurredwhenrenderingtheCSSfile.Ifthishappens,flushthecacheandremovethefoldersagain,asdescribedinstep3.
Anotherthingthatcanhappenisthatthepubandvarfoldersdonothaveenoughfilepermissions.
5. WhenwewanttoaddsomeCSSentriestotheheader,wehavetocopythefileapp/design/frontend/Magento/blank/Magento_Theme/web/css/source/_module.less
toapp/design/frontend/Packt/cookbook/Magento_Theme/web/css/source/_module.less
6. Addthefollowinghighlightedcodeinthatfilearoundline293.Thiswillgivethetopmenuadarkercolor:
...
.page-header{
border:0;
margin-bottom:0;
.panel.wrapper{
background-color:@cookbook_dark_color;
li{
a{
color:@color-white;
}
}
border-bottom:1pxsolid@secondary__color;
}
.header.panel{
padding-top:@indent__s;
padding-bottom:@indent__s;
&:extend(.abs-add-clearfix-desktopall);
}
.switcher{
display:inline-block;
}
}
...
7. Cleanthecacheandremovethefolderspub/static/*andvar/view_preprocessed,andreloadthepage.Youwillseethatthetopbarisnowinadarkcolor.
Howitworks…WhenworkingwithLESS,thereareafewwaysofdoingit.ThegoalistoavoidduplicateCSScodethatisloadedinthebrowser.
First,wedidsomestylingbychangingsomevariablesthataredefinedbyMagento.Thepurposeofthetheme.lessfileintheweb/css/sourcedirectoryistooverridethedefaultvariablesoftheMagentotheme.TheLESScompilerconvertstherightvaluestotherightCSSentries.
Ifyouwanttoknowwhichvariablesareavailable,havealookinthefolderlib/web/css/source/lib/variables.Inthisfolder,thevariablesofthedefaultMagentocomponentsareinitialized.Onefolderaboveintheweb/css/source/lib,theCSSstructureofthesecomponentsisdefined.
ThesecondthingwedidwastooverrideathemeLESSfilefromtheMagento_Thememodule.Wecopiedtheoriginalfiletoourtheme.Thismeansthatthefileofourthemeisloadedinsteadofthedefaulttheme.
Thisprocessiscalledthefallbackmechanism.Whenafileisloaded,Magentowilllookforitinthefollowingorder:
Themefolder(app/design/frontend/<Vendor>/<Theme>/web/css)Parentthemefolders(app/design/frontend/<Vendor>/<Theme>/web/css)Modulefolder(app/code/<Vendor>/<Module>/view/frontend/web/css)
NoteIfyouinstalledMagento2usingcomposer,thedefaultandLumathemeofMagentoareinthevendor/magentofolder.
WhenthereisachangeinaLESSfile,wehavetocleartwofolders.Thefirstoneisthevar/view_preprocessedfolder.Inthisfolder,alltheparticularLESSfilesaremergedinalargefile.
Thatlargefilewillbecompiledinthefolderpub/static/frontend.Sothat’sthereasonwhywehadtoclearboththefolders.
WhenMagentoloadsfilesfromthepub/static/frontendfolderthatdoesn’texist,Magentowilllookinitscorefolderstomakethosefilesavailableinthisfolder.ForCSSfiles,Magentowillstartthegenerationofthem.Forotherfilessuchasimages,Magentowillcreatesymlinkstothesourcefile.
Changingfilesinthepubfolderisnotagoodideabecauseyouhavetodoitintheappfolderandregeneratethefilesinpub.
TipItisalsopossibletousetheJavaScriptcompiler.Inthebackend,youcanenableitonthepageStores|Configuration|Advanced|Developer|Front-enddevelopmentworkflow.
There’smore…WhenworkinginLESS,itisnotconvenienttoalwaysclearthecontentsofthepub/staticandvar/view_preprocessedfolderswhentestingtheresultofachange.
FornormalLESS,youcaninstallaLESSwatcher,butwiththemodulararchitectureofMagento,wecanusetheJavaScripttaskrunnerGrunt.
WithGrunt,wecanconfigureawatcherforourtheme.WhenthereisachangeinaLESSfile,GruntwillregeneratethepublicfilefollowingtherulesofMagento.
TouseGrunt,wefirsthavetoinstallnodejsandgrunt-cli.Wecandothiswiththefollowingcommands(onaUbuntuserver):
sudoapt-getupdate
apt-getinstallnodejs
sudoapt-getinstallnpm
sudonpminstall-ggrunt-cli
WhenGruntisinstalled,wecanconfigureitforourdemoshop.OpenyourterminalandchangetotheMagentoprojectroot.Inthisdirectory,runthefollowingcommands:
npminstallgrunt--save-dev
npminstall
npmupdate
npminstallgrunt-contrib-less
NoteTheseinstallationinstructionsaretestedonanUbuntuServer.Ifyouwanttoinstallthisonotheroperatingsystems,youcanfindmoreinformationonthefollowingURL:http://gruntjs.com/installing-grunt.
Addyourthemebyaddingthefollowingconfigurationtothefiledev/tools/grunt/configs/themes.js:
cookbook:{
area:'frontend',
name:'Packt/cookbook',
locale:'en_US',
files:[
'css/styles-m',
'css/styles-l'
],
dsl:'less'
},
Whenrunningthecommandgruntexec:cookbook,thefileswillbegeneratedforthecookbooktheme.
Whenrunninggruntwatch,thesefileswillautomaticallybegeneratedafterafilechange.
ChangingapagetitleChangingapagetitlehelpsyoutoimprovetheSEOofyourwebsite.Forproductpages,wecanmanagethatwiththebackendbutonotherpages,suchasthecontactpage,wecan’tdothat.
Inthisrecipe,wewillchangethepagetitleofthecontactpagethatisavailableatthe/contactpath.
Howtodoit…Tochangethepagetitleofthecontactpage,havealookatthefollowingsteps:
1. Gotothecontactpageinthefrontend.Thisisavailableatthepath/contact.2. YouseethatthepagetitleissettoContactUs.WewillchangethistoGiveusa
message.3. Createthefilecontact_index_index.xmlinthefolder
app/design/frontend/Packt/cookbook/Magento_Contact/layout.Ifthatfolderdoesn’texist,createit.
4. Inthecontact_index_index.xmlfile,pastethefollowingcontent:
<?xmlversion="1.0"?>
<pagexmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
layout="1column"
xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/pa
ge_configuration.xsd">
<head>
<title>Giveusamessage!</title>
</head>
</page>
5. Cleanthecacheandreloadthefrontend.YouwillseethatthepagetitleischangedtoGiveusamessage!.
Howitworks…The<title>andothertagsintheheadaregeneratedintheclasslib/internal/Magento/Framework/Page/Config/Renderer.php.WhenyoulookfortherenderTitle()function,youseethefollowingcode:
publicfunctionrenderTitle()
{
return'<title>'.$this->pageConfig->getTitle()->get().'</title>'.
"\n";
}
ThisfunctionlooksinthepageConfigforatitletag.ThepageConfiglooksinthe<head>tagoftheXMLconfigurationfile.
Forothertypesofpages,suchasaproductdetailpagewherewecansetthetitleinthebackend,thepageConfigvalueswillbeoverwrittenafterloadingthelayoutXMLconfiguration.Butforthecontactpage,thisisnotthecase.
WorkingwithtranslationsMagentohastheabilitytorunastoreinmultiplelanguages.Everystoreviewhasalanguage,andMagentohasasystemtotranslatetheinterfaceintothatparticularlanguage.
GettingreadyOpenthebackendandgotothestoreconfigurationatStores|Configuration|General.WewillconfiguretheFrenchlanguageforthedefaultstoreview.
Howtodoit…ThefollowingstepsshowhowwecantranslatetheinterfaceofMagentoinaspecificlanguage:
1. First,wewillconfigurethedefaultlanguageofthestoreview.Inthebackend,gotoStores|Configuration|Generalandchangethelocaletothepreferredvalue.InMagento,thefollowinglanguagepacksareinstalled:
en_US—English(UnitedStates)de_DE—German(Germany)es_ES—Spanish(Spain)fr_FR—French(France)nl_NL—Dutch(Netherlands)pt_BR—Portuguese(Brazil)zh_CN—Chinese(China)
ThisrecipeworkswithashopintheFrenchlanguage.
2. Whencleaningthecacheandreloadingthefrontend,youwillseethattheinterfacetextsaretranslatedintoFrench.
3. Whenyouwanttotranslateexistingstrings,wecandothisbytheInlineTranslationtoolandwecandoitinthetheme.WecanenabletheInlineTranslationtoolinthebackendonthepageStores|Configuration|Advanced|Developer|TranslateInline.
4. Reloadthefrontendandyouwillseeredframesaroundeachinterfacetext.Whenhoveringoveraframe,aniconwillappear.Clickonitandapop-upwindowwillbeshownwiththetranslationform:
5. Whensubmittingthisform(byclickingSOUMETTRE),andafterclearingthecache,youwillseethatthevalueyouenteredinthisformisused.
6. Thesecondwayoftranslatingtheinterfaceistouseathemetranslation.Foraddingcustomtranslationsforatheme,wehavetocreatethefollowingfile:app/design/frontend/Packt/cookbook/i18n/fr_FR.csv.
7. WhenwewanttotranslatethestringPanierontheshoppingcartpage,wehavetoknowtheoriginalstring.YoucanfindtheoriginalstringintheInlineTranslationpopuporinthetemplates:
TheoriginalstringforPanierisShoppingCart.Whenweaddthe
followinglineintheCSVfilewehavejustcreated,thestringwillbe
translatedtothatvalueShoppingCart,Votrepanier.
8. Cleanthecacheandreloadtheshoppingcartpage.YouwillseethatthetitleischangedfromPaniertoVotrepanier.
Howitworks…Magentohasapowerfultranslatefunction.Tomakeastringtranslatablethroughthatfunction,wehavetousethefollowingsyntax:
__('Translatablestring')
Whenusingthissyntax,thefollowingfallbackmechanismwillbeused:
First,Magentowilllookinthedatabasetabletranslation.ThistableisusedtosavethevaluestranslatedwithInlineTranslation.Ifthestringisnotfoundinthetranslationtable,Magentowilllookforitinthetheme.Itwillsearchtoseewhetherthestringispresentinthefileapp/design/frontend/<VendorName>/<ThemeName>/i18n/<locale>.csv.Thelastfallbackisthetranslationfilesofthemodules.Thesearelocatedini18n/<locale.csvofthemodule.Ifnomatchingstringisfound,thetranslatefunctionwillreturntheoriginalstringthatispassedasanargumenttothetranslatefunction.
AddingwidgetstothelayoutMagentohasasetofpredefinedwidgetsthatyoucanconfigureandshowonthedifferentpages.WiththeMagentowidgetinterface,wecanconfiguredifferentwidgetsondifferentpages.
GettingreadyWewilladdablockofproductsonthecontentareaofthehomepage.GotothebackendandnavigatetoContent|Widgets.
Howtodoit…Inthefollowingsteps,wewillconfigureawidgetforthecategorypages:
1. ClickonthebuttonAddWidget.2. Intheform,setthefollowingvalues:
Type:CatalogProductsListDesignTheme:Packtcookbook
3. Clickcontinueandthefollowingscreenshowsup:
4. CompletetheStorefrontPropertiestabwiththefollowingvalues:
WidgetInstanceTitle:Widget-home-productsThisisthetitleofthewidgetinthebackend.Astructuralnameiseasywhen
workingwithalotofwidgetsAssigntoStoreViews:AllStoreViews
5. CompletetheWidgetOptionstabwiththefollowingvalues:
Title:FeaturedproductsNumberofProductstoDisplay:10Conditions:Chooseaspecificcategorylikethefollowingscreenshot:
6. SavethewidgetbyclickingSaveandContinueEdit.Thewidgetinstanceisnowsavedbutnothingwillshowupinthefrontendbecausethereisnolayoutupdateset.
7. Wehavetocreatealayoutupdatebeforethewidgetwillshowupinthefrontend.Onthewidgetpage,opentheFrontendPropertiesandcompletetheLayoutUpdatessectionasfollows:
8. Clearthecacheandreloadthehomepage.Youwillseealistwithproductsfromtheconfiguredcategory.
Howitworks…IntheMainContentArea,anewblockisaddedtothefrontend.Likeanyotherblockinthefrontend,thisblockhasablockclassandatemplatesimilartootherblocks.
TheonlydifferenceisthatthisblockisnotgeneratedbyanXMLfilebutbyanXMLlayoutinstructioninthedatabase.
ThewidgetinterfacewillgeneratealayoutXMLthatisstoredinthedatabase.TheblockclassandtemplatearesimilartootherblocksintheXMLfiles.
CustomizingemailtemplatesMagentohasalotoffunctionalitythatsendsemailsonparticularactions,suchasaneworder,customerinformation,newsletter,andmanymore.
Whencustomizingthelookandfeelofyourwebsite,itisnicethatyouremailtemplateshavethesamelookandfeel.
Inthisrecipe,wewilllearnhowwecandothis.Wewillcustomizethenewaccountemailandwewilllearnhowwecaneditthegenericheaderandfooterofthetransactionalemails.
GettingreadyWewillcustomizeemailtemplates.Totestthis,makesureyourdevelopmentenvironmentcansendemails.
Likethepreviousrecipes,wewillbuildthecodefurtheronthethingsthatwehavedoneinthepreviousrecipes.Ifyoudon’thavethecompletecode,youcaninstallthestartfilesforthisrecipe.
Howtodoit…Thefollowingstepsshowhowwecancustomizetheemailtemplateswithoutoverridingthestandardtemplates:
1. Theemailtemplatesarelocatedintheviewfolderofeachmodule.Ifwewanttochangethenewaccountemail,wehavetocopytheoriginalfiletoyourtheme.Copythefileapp/code/Magento/Customer/view/email/account_new.htmlandpastethisinthefolderapp/design/frontend/Packt/cookbook/Magento_Customer/email/.
NoteIfyouinstalledMagentowithcomposer,youhavetocopythefilevendor/magento/module-customer/view/frontend/email/account_new.html.
2. Whenopeningthenewfile,youcanmodifythecontentoftheemailinanywayyouwant.Wecanusethefollowingvariablestomakethefiledynamic:
{{storeurl='<path>'}}TheURLofthestore.Youcanalsogiveapathbetweenthequotes.{{varcustomer.<field>}}Thecustomerobject.Afterthedot,youcanspecifytheattributeofthecustomerobject.{{varstore.<field>}}Thestoreobjectwheretheemailissentfrom.{{configpath='<configpath>'}}Avalueoftheconfigurationtablecore_config_data.
3. Savethefileandcreateanewaccountinthefrontendwithyouremailaddress.Youwillreceiveanemailofanewaccount.Ifyouopenit,youwillseethatthevariablesarereplacedbytheinformationofMagento.
4. Whenwelookinthetemplate,weseethatanemailheaderisincludedinthetemplate.Ifwewanttochangetheheader,wehavetocopythefileapp/code/Magento/Email/view/frontend/email/header.htmltothefolderapp/design/frontend/Packt/cookbook/Magento_Email/email/.
5. Tomodifythefooter,wehavetocopytheapp/code/Magento/Email/view/frontend/email/footer.htmlfiletothesamefolder.
NoteIfyouinstalledMagentowithcomposer,youcanfindtheheaderandfooterfilesinthefoldervendor/magento/module-email/view/frontend/email/.
Howitworks…WhenanemailissentinMagento,thegeneralemailfunctionisused.Thisfunctionsendsanemailwiththerighttemplateandemailheaderssuchassubject,sender.
TheemailfunctionneedsatemplateandthattemplateisconfiguredintheconfigurationXMLfilesandusesafilefromtheemailfolder.
WhenanemailtemplateisconfiguredthroughtheconfigurationXML,itisalsopossibletooverwritethisemailtemplateinthebackend.ThiscanbemanagedonthepageMarketing|EmailTemplates.
NewinMagento2istheabilitytoworkwithaheaderandfootertemplatethatisusedinallthetransactionalemails.IntheolderversionsofMagento1,thiswasnotpossibleandwhenyouwantedtochangesomethingintheemailheader,youhadtoeditalltheemailtemplatefiles.
AlsonewinMagento2isthatyoudon’thaveanemailtemplateforeachlocale.Withthe{{trans"Yourstringtotranslate"}}code,youcanusetheMagentotranslationfilestomakeyouremailsmultilingual.
ThesetwofeaturesmakeitaloteasiertodevelopthetransactionalemailsinMagento2.
Emailtemplatesarenowpartofamodule.Intheview/<area>/emailfolderaretheemailtemplatesstored.Tomodifyatemplate,youcancopythefileandpasteitinthe<modulename>/emailfolderinthetheme.
Chapter4.CreatingaModuleInthischapter,wewillcoverthefollowingrecipes:
CreatingthemodulefilesCreatingacontrollerAddinglayoutupdatesAddingatranslationfileAddingablockofnewproductsAddinganinterceptorAddingaconsolecommand
IntroductionWhenyoulookintheapp/codefolder(thecoreofMagento),youseethemodulararchitecture.Everyconceptinthee-commerceflowisstoredinamodule.TheMagentoapplicationisacombinationofallthesemodules.
Oneoftheadvantagesofamodulararchitectureistheextendibility.ItiseasytoaddmodulesthataddtoormodifythenativebehaviorofMagento.
Inthischapter,wewillcreateamodulewiththemostimportantthingsyouneedtoknowwhenwritingcodeinMagento.
CreatingthemodulefilesWhencreatingamodule,thefirststepistocreatethefilesandfolderstoregisterthemodule.Attheendofthisrecipe,wewillhavearegisteredmodulebutwithoutfunctionality.
Inthenextrecipes,wewilladdextrafeaturestothatmodule.
GettingreadyOpentherootfolderofyourMagento2website.Theapp/code/folderisthefolderwhereallthemoduledevelopmentneedstobedone.
AccesstoacommandlineisalsorecommendedbecauseMagento2hasabuilt-inconsoletoolwithalotofcommandsthatwecanuseduringthedevelopment.
Howtodoit…Inthefollowingsteps,wewillcreatetherequiredfilestoregisteraMagentomodule:
1. WewillcreateaHelloWorldmoduleinthePacktnamespace.InyourMagentoroot,createthefollowingfolders:
app/code/Packt
app/code/Packt/HelloWorld
app/code/Packt/HelloWorld/etc
2. Intheetcfolderofthemodule,createafilecalledmodule.xmlwiththefollowingcontent:
<?xmlversion="1.0"?>
<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.
xsd">
<modulename="Packt_HelloWorld"setup_version="2.0.0">
<sequence>
<modulename="Magento_Catalog"/>
</sequence>
</module>
</config>
NoteInMagento2,thereareXMLStyleDefinition(XSD)filesthatdescribesthestructureoftheconfigurationXMLfiles.Inthe<config>tag,thecorrectXSDfileisconfigured.
3. Toregisterthemodule,wehavetocreatearegistration.phpfileintheapp/code/Packt/HelloWorld/folderwiththefollowingcontent:
<?php
\Magento\Framework\Component\ComponentRegistrar::register(
\Magento\Framework\Component\ComponentRegistrar::MODULE,
'Packt_HelloWorld',
__DIR__
);
4. OpenyourterminalandgototheMagentodirectory.Inthisdirectory,runthefollowingcommands:
composerinstall
phpbin/magentocache:clean
phpbin/magentosetup:upgrade
5. WheneverythingisOK,youcanseethenameofthemoduleintheoutputofthelastcommand.
6. Totestthatthemoduleisinstalled,openthebackendandnavigatetoStores|Configuration|Advanced|Advanced,andcheckthatthemoduleispresentinthe
list.EnsurethatyouhavecleanedtheMagentocaches.
Howitworks…ModuledevelopmentinMagento2ismucheasierthaninMagento1.Theconceptofcodepoolsisgone,everythingisstoredinasinglefolder(code,translations,templates,CSS,andmore).ThesethingsmakeitaloteasiertodevelopandmaintainaMagentomodule.
Toinitialize,wehavetocreatethefoldersandthemodule.xmlfileintheetcfolderofthemodule.Inthemodule.xmlfile,weinitializethePackt_HelloWorldname,theversionnumber,andthesequence.
Whenwecreatedthemodulefiles,weexecutedthesetup:upgradecommand.Byrunningthiscommand,wewillruntheinstallorupgradeprocedureofallthemodules.Inthisprocess,alotofgeneratedclassesarecreatedinthevar/generationfolder.
Weusedthebin/magentotoolforcleaningthecacheandrunningtheupgradescripts.ThistoolwasintroducedinMagento2andisareplacementofthird-partytoolsfromMagento1(suchasn98magerunandwiz).
Whenrunningthephpbin/magentocommand,youcanseealistofallavailablecommands.Itiseasytoaddyourowncommandsinamodule.
CreatingacontrollerInyourMagentoroot,createthefollowingfolders:Wewilladdanextrapagethatwecanuseforseveralpurposes.
GettingreadyWebuildfurtheronthePackt_HelloWorldmodulethatwecreatedinthepreviousrecipe.EnsurethatyouhavethismoduleinyourMagentoinstance.Also,ensurethatthefullpagecacheisdisabledwhenyouaredeveloping.YoucandisablethisinthebackendbynavigatingtoSystem|CacheManagement.
Howtodoit…Thefollowingstepsshowhowtoaddextrapagesusingcontrollersandcontrolleractions:
1. Createthefollowingfolders:
app/code/Packt/HelloWorld/etc/frontend
app/code/Packt/HelloWorld/Controller
app/code/Packt/HelloWorld/Controller/Index
2. Intheapp/code/Packt/HelloWorld/etc/frontendfolder,createaroutes.xmlfilewiththefollowingcontent:
<?xmlversion="1.0"?>
<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd
">
<routerid="standard">
<routeid="helloworld"frontName="helloworld">
<modulename="Packt_HelloWorld"/>
</route>
</router>
</config>
3. Inthelastfolder,thatis,app/code/Packt/HelloWorld/Controller/Index,createtheIndex.phpfilewiththefollowingcontent:
<?php
namespacePackt\HelloWorld\Controller\Index;
classIndexextends\Magento\Framework\App\Action\Action
{
/**
*Indexaction
*
*@return$this
*/
publicfunctionexecute()
{
}
}
4. Cleanthecacheusingthephpbin/magentocache:cleancommand.5. Openyourbrowserandnavigatetothe/helloworldURLoftheshop.Youwillseea
whitepage.Thisisnormalbecausethecontrolleractionisempty.6. Toloadthelayoutoftheshop,addthefollowingcodeintheindex.phpfile:
/**@var\Magento\Framework\View\Result\PageFactory*/
protected$resultPageFactory;
publicfunction__construct(
\Magento\Framework\App\Action\Context$context,
\Magento\Framework\View\Result\PageFactory$resultPageFactory
){
$this->resultPageFactory=$resultPageFactory;
parent::__construct($context);
}
publicfunctionexecute()
{
$resultPage=$this->resultPageFactory->create();
return$resultPage;
}
NoteIfyoustillseeawhitepage,thepageiscached.Youhavetoflushthecacheusingthephpbin/magentocache:flushcommand.ItisrecommendedthatyoudisabletheFullPageCache,asexplainedinthebeginningofthisrecipe.
7. WewillnowcreateanextraactionthatredirectsustotheHelloWorldpageandcreatetheapp/code/Packt/HelloWorld/Controller/Index/Redirect.phpfile.
8. Inthisfile,addthefollowingcontent:
<?php
namespacePackt\HelloWorld\Controller\Index;
classRedirectextends\Magento\Framework\App\Action\Action
{
publicfunctionexecute()
{
$this->_redirect('helloworld');
}
}
9. CleanthecacheandgototheURL/helloworld/index/redirect.Wewillberedirectedtotheindexaction.
10. Wecanalsochangethecontentoftheexecute()methodto$this->_forward('index').WewillseethesameoutputbuttheURLdoesn’tchangeinthebrowserbar.
Howitworks…AllpagesinMagentoareexecutedbycontrolleractions.Allthecontrollersareplacedinmodules,andeachcontrollercanhavemultiplecontrolleractions.ThisgivesusthefollowingstructureoftheURL:<modulenameorfrontname>/<controllerName>/<actionName>.
WhenyoucomparethecontrollerpartwithMagento1,alotofthingshavebeenchangedandmadeeasier.
InMagento2,everycontrolleractioniswritteninaseparateclass.ThisclassextendstheMagento\Framework\App\Action\Actionclass.Thecontrolleristhefolderwheretheactionsareplaced.
NoteItisalsopossiblethatthecontrollerisinaseparateclass,butthisisonlydonewhentherearegenericfunctionsthattheactionswilluse.AgoodexamplecanbefoundintheProductControlleroftheMagento_Catalogmodule.
Inacontrolleraction,theexecute()methodisusedtostarttherenderingofthepage.Whenwehavenothinginthismethod,thepagewillhaveanemptyoutput(blankscreen).
Ifwewanttorenderthelayout,wewillinitializetheresultPageFactoryinstanceinthe__construct()methodofthecontroller.Thisfactoryclassisusedtostartthelayoutrenderingofthepage.
Thesecondcontrolleractionwecreatedwasonethatdoesaredirecttoanotherpage.Whencallingthe_redirect()methodinacontrolleraction,a301redirectwillbereturnedtothegivenURL.
The_forward()methoddoeslikelythesame,butthisinternallyforwardstheactiontoanothercontroller.ThismeansthattheoutputofanothercontrolleractionwillberenderedonthepagebuttheURLwon’tchange.ThismethodisusedtotranslateanSEO-friendlyURL(suchasaproductURL)totherightcontrolleractionwiththerightparameters.
There’smore…Whenthingsarenotworkingasyouexpect,youcanusethefollowingtipstomakeitwork:
Cleanthecache.Youcandothisusingthephpbin/magentocache:cleancommand.Flushthecache.Youcandothisusingthephpbin/magentocache:flushcommand.Removethevar/generationfolder.Sometimes,thethegeneratedclassesinthisfolderneedstoberegenerated.
AddinglayoutupdatesInthepreviousrecipe,wecreatedapagewithoutcontent.Inthisrecipe,wewillmodifythecontentofthatpagewithlayoutupdates.
Withlayoutupdates,wecanarrangethestructureofthepageaswehaveseenintheCustomizingtheHTMLoutputrecipeofChapter3,Theming.Buthere,wewillseehowwecandothatinamodule.
GettingreadyThisrecipebuildsfurtheronthepreviousrecipe.Youneedtheinstallthemodulethatwecreatedinthepreviousrecipesofthischapter.
Howtodoit…Inthenextsteps,wewillseehowwecanmodifytheblocklayoutwithourmodule:
1. Createtheapp/code/Packt/HelloWorld/view/frontend/layoutfolder.2. Inthisfolder,createafilecalleddefault.xmlwiththefollowingcontent:
<?xmlversion="1.0"?>
<pagexmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/pa
ge_configuration.xsd">
<body>
<referenceBlockname="footer_links">
<block
class="Magento\Framework\View\Element\Html\Link\Current"
name="helloworld-link">
<arguments>
<argumentname="label"translate="true"
xsi:type="string">Helloworldlanding</argument>
<argumentname="path"
xsi:type="string">helloworld/index/index</argument>
</arguments>
</block>
</referenceBlock>
</body>
</page>
3. Cleanthecacheusingthephpbin/magentocache:cleancommandandreloadthefrontend.Inthefooter,youwillseeanextralinkleadingtothepagethatwecreatedinthepreviousrecipe.
4. Thelayoutupdatewejustcreatedisappliedtoallpages.Ifwewantupdatesonthehelloworldindexpage,wehavetocreatetheapp/code/Packt/HelloWorld/view/frontend/layout/helloworld_index_index.xml
file.5. Inthisfile,pastethefollowingcontent:
<?xmlversion="1.0"?>
<pagexmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
layout="2columns-left"
xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/pa
ge_configuration.xsd">
<head>
<title>HelloworldLandingspage</title>
</head>
<body>
<removename="wishlist_sidebar"/>
</body>
</page>
6. Wealsoneedtoregisterthepage.Forthis,createtheapp/code/Packt/HelloWorld/etc/frontend/page_types.xmlfilewiththefollowingcontent:
<?xmlversion="1.0"?>
<page_typesxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/pa
ge_types.xsd">
<typeid="helloworld_index_index"label="HelloWorldlandingpage"/>
</page_types>
7. Cleanthecacheandreloadthe/helloworldpage.YouwillseethatthetitleissimilartowhatweconfiguredintheXMLfileandthewishlistblockisnotpresentintheleft-handsidecolumn.
8. Tofinishthisrecipe,wewilladdacustomtemplatewithacustomBlockclass.Createtheapp/code/Packt/HelloWorld/Block/Landingspage.phpfilewiththefollowingcontent:
<?php
namespacePackt\HelloWorld\Block;
useMagento\Framework\View\Element\Template;
classLandingspageextendsTemplate
{
publicfunctiongetLandingsUrl()
{
return$this->getUrl('helloworld');
}
publicfunctiongetRedirectUrl()
{
return$this->getUrl('helloworld/index/redirect');
}
9. Now,wehavetocreatethetemplatewherewewillcallthemethodfromtheLandingspageclass.Createtheapp/code/Packt/HelloWorld/view/frontend/templates/landingspage.phtmlfilewiththefollowingcontent:
<h2>HelloWorld</h2>
<p>
<ahref="<?phpecho$block->getLandingsUrl();?>">Gotolandings
URL</a>
</p>
<p>
<ahref="<?phpecho$block->getRedirectUrl();?>">Gotoredirect
URL</a>
</p>
10. Asthelaststep,wehavetoaddtheblockwithourlayoutXML.Addthefollowingconfigurationtotheapp/code/Packt/HelloWorld/view/frontend/layout/helloworld_index_index.xml
fileasachildofthe<body>tag:
<referenceContainername="content">
<blockclass="Packt\HelloWorld\Block\Landingspage"
name="landingsblock"template="Packt_HelloWorld::landingspage.phtml"/>
</referenceContainer>
11. Cleanthecacheandreloadthe/helloworldURL.Youwillseesomethinglikethefollowing:
Howitworks…Layoutupdatescanbeplacedinmodulesandthemes.IntheCustomizingtheHTMLoutputofChapter3,Theming,weexplainedhowtolayoutupdatesworkinMagentothemes,butitisalsopossibletodothesameprincipleinamodule.
EveryMagento2folderhasaviewfolder.Intheviewfolder,allthestufftorenderthepageisstored,suchasLESS(CSS),JavaScript,templates,andlayoutfiles.
Intheviewfolder,wecanhavethefollowingsubfolders:
adminhtml
base
frontend
Asthenamesuggests,theadminhtmlfolderisusedfortheMagentobackend,thefrontendfolderisusedforthefrontend,andthebasefolderisusedforboth(frontendandbackend).
Inthesefolders,thefollowingstructureistheinternalfolderstructurethatisused:
layout(forlayoutupdateXMLfiles)templates(for.phtmltemplates)web(forstaticfiles,suchasLESS,JavaScript,andimages)
Inthelayoutfolder,wecanplacelayoutXMLfiles.Foreverylayouthandle,wecanapplylayoutupdatesinaseparatefile.
Wehaveplacedalayoutfileforthedefaulthandle(theseinstructionsareloadedonallpages).Everypagealsohasitsownhandleinthestructure<modulefrontname>_<controllername>_<actionname>.Forthehelloworldlandingspage,isthishelloworld_index_indexfile.Inthehelloworld_index_index.xmlfile,wehaveplacedthelayoutinstructionsofthatpage.Thedefaulthandle,default.xml,isloadedonallpages.
Inthatfile,wecreatedalayoutinstructionthatdefinesacustomblockwithtemplateonapage.Thelandingspage.phtmltemplateofthePackt_HelloWorldmoduleisusedtorendertheoutput.Withthe$blockvariable,wecancallthemethodsofthePackt\HelloWorld\Block\Landingspageclass.
TipInMagento1,weusedthe$thiscommandtocallmethodsfromtheblockclass.InMagento2,wewillusethe$blockvariableforthis.
Theguidelineistousethe.phtmlfilesfortherenderingoftheHTMLoutput.ThesefilesmaynotcontainalogofthePHPcode.ThePHPcodeiswrittenintheblockfilesandtheHTMLcodeinthe.phtmlfiles.Inthe.phtmlfiles,wecancallmethodsfromtheblockclass.
AddingatranslationfileMagentoismadetoruninmultiplelanguages.Thismeansthattheinterfaceandcontentneedstobetranslatableintheconfiguredlanguages.
Inthisrecipe,youwilllearnhowtomakethestringsinourmoduletranslatableindifferentlanguages.
GettingreadyWewillcreatetranslationfilesforthemodulethatwecreatedinthepreviousrecipesofthischapter.EnsurethatyouhavethecodeinyourMagentoinstance.
Howtodoit…Thefollowingproceduredemonstrateshowwecanmanagetranslationsinourmodule:
1. Tomakeatesttranslation,wecancreateatesttranslationinthetemplatefilethatwecreatedinthepreviousrecipe.Addthefollowingcodeattheendofthefileapp/code/Packt/HelloWorld/view/frontend/templates/landingspage.phtml:
<p>
<?phpecho__('Testtranslation')?>
</p>
2. Gotothe/helloworldpageandyouwillseethatthetextTesttranslationisaddedonthepage.
3. Totranslatethisstring,wehavetocreatetheapp/code/Packt/HelloWorld/i18nfolder.
4. Inthisfolder,createtheen_US.csvfile.5. AddthefollowinglineintheCSVfile:
"Testtranslation","Translationtotest"
6. Cleanthecacheandreloadthepage.IfthelanguageofyourshopissettoEnglish(UnitedStates),youwillseethattheoutputissettoTranslationtotest.
7. Ifwewant,forexample,aFrenchtranslation,wehavetocreatethefr_FR.csvfilewiththefollowingcontent:
"Testtranslation","Testtraduction"
8. ChangethelanguageofthestoretoFrench,cleanthecache,andyouwillseetheFrenchtranslation.
TipIfyouwanttoknowallthetranslationsofamodule,youcanrunthephpbin/magentoi18n:collect-phrasesapp/code/<Vendorname>/<Modulename>
commandandyouwillgetaCSVlistofallthetranslations.
Howitworks…Whencallingthe__('translatestring')function,Magentowillsearchforatranslationforthatstringinthecurrentlanguage.Magentowilllookforthestringsinthefollowingorder:
ThedatabasetranslationtableThethemetranslationfiles(app/design/fronted/<Package>/<theme>/i18n/<locale_code>.csv)Themoduletranslationfiles(app/code/<Vendor>/<Module>/i18n/<locale_code>.csv)
Whenastringisfound,Magentodoesn’tlookfurtherforothermatchingstrings.Ifnomatchingstringisfoundforthecurrentlanguage,Magentowillreturnthestringthatispresentinthefirstparameterofthetranslatefunction(thatis,theuntranslatedstring).
TheimplementationoftranslationsinMagento2ismucheasierthaninMagento1.Everythingisstoredinthemodulefolder,andyoudon’thavetoaddconfigurationXMLinstructionstothemodulewhereyoucandomistakeswith.
Also,thetranslatefunctionhasnowbeenmovedtoaglobalfunction.Youdon’tneedahelperclasstocallthe__()function.The__()functionisimplementedasaglobalfunctionthatisavailableeverywhereintheapplication.
AddingablockofnewproductsInthepreviousrecipes,wepreparedthemodulefortherealwork.Weaddedthemostcommonfeaturestothemodulesothatwecaneasilyextenditwiththenewfunctionality.
Inthisrecipe,wewillcreateablockofnewproductstothepagewecreatedinthepreviousrecipes.
GettingreadyInourmodule,wewillcreateablockthatwillloadaproductcollection.Thisproductcollectionwillbeusedinthetemplate,whichwillshowthenewestproductsoftheshop.
Ensurethatyouhavethemoduleofthepreviousrecipeinstalled.
Howtodoit…Thefollowingstepsdemonstratehowtostartwithaddingtheblockwithnewproducts:
1. Tocreatetheblockclass,wehavetocreatetheNewproducts.phpfileintheapp/code/Packt/HelloWorld/Block/folder.
2. Addthefollowingcontenttothatfile:
<?php
namespacePackt\HelloWorld\Block;
useMagento\Framework\View\Element\Template;
classNewproductsextendsTemplate
{
}
3. Createatemplateinthemodulefolder.Wecandothisbycreatingthenewproducts.phtmlfileintheapp/code/Packt/HelloWorld/view/frontend/templates/folder.
4. AddsomeHTMLcontenttothattemplatefilesuchas<h2>NewProducts</h2>.5. Toaddtheblocktothepage,wehavetocreatealayoutupdate.Inthe
app/code/Packt/HelloWorld/view/frontend/layout/helloworld_index_index.xml
file,addthefollowingcodeasachildof<referenceContainername="content">:
<blockclass="Packt\HelloWorld\Block\Newproducts"name="new_products"
template="Packt_HelloWorld::newproducts.phtml"/>
6. Cleanthecacheandreloadthe/helloworldpage.YouwillseethattheNewProductstitleisvisible.
7. Createaconstructorintheblockclassthatinitializestheproductcollectionfactory.Wecandothisbyaddingthefollowingcodeinthatclass(theapp/code/Packt/HelloWorld/Block/Newproducts.phpfile):
private$_productCollectionFactory;
publicfunction__construct(
Template\Context$context,
\Magento\Catalog\Model\ResourceModel\Product\CollectionFactory
$productCollectionFactory,
array$data=[])
{
parent::__construct($context,$data);
$this->_productCollectionFactory=$productCollectionFactory;
}
8. CreatethegetProducts()methodinthesameblockclass.Thismethodwillreturnthefivelatestproductsoftheshop.ThecodeforthegetProducts()methodwilllookasfollows:
publicfunctiongetProducts(){
$collection=$this->_productCollectionFactory->create();
$collection
->addAttributeToSelect('*')
->setOrder('created_at')
->setPageSize(5);
return$collection;
}
9. ThelaststepistocallthemethodinthetemplateandgenerateanHTMLfileforit.Thecodeofthetemplateisasfollows:
<h2>Newproducts</h2>
<ul>
<?phpforeach($block->getProducts()as$product):?>
<li><?phpecho$product->getName()?></li>
<?phpendforeach;?>
</ul>
10. Reloadthe/helloworldpageandyouwillseealistwiththenamesofthelatestproducts.
Howitworks…WhatwehavedoneinthisrecipeisabasicextensionofMagento.WeaddedacustomblockthatusestheMagentoframeworktorenderthecontent.
WecreatedablockclassthathasthegetProducts()method.Thismethodreturnsthelatestfiveproductsofthewebshop.Inthismethod,wecreatedaquerythatusestheMagentocollections.WithMagentocollections,wecangetdatafromthedatabase.AcollectionbuildsaSQLqueryinthebackground.
Thepurposeofcollectionsisthatthereisaneasyinterfacetogettherightentities.AproductisnotstoredinonedatabasetablebecauseitusestheEntityAttributeValuesystem(EAV).TheMagentocollectionsgenerateanSQLquerythatreturnsthevaluesofthattables.ThissavesustheprogrammingofverycomplexSQLqueries.
Toworkwiththecollections,weusedtheCollectionFactoryproducttoworkwiththecollectionfunctions.WeinitializedthisclassintheconstructoranduseditinthegetProducts()method.
Whenwerunthecreate()functiononCollectionFactory,aproductcollectionwillbereturned.ItislikedoingacollectionusingthegetCollection()methodonaMagentomodel,butbecausethismethodisdeprecated,wehavetouseCollectionFactory.
AddinganinterceptorOneofthemajorthingsthathaschangedinMagento2isthatthereisnoMageclass.Toreplacethis,allobjectsarepassedtotheclasseswithdependencyinjection.
DependencyinjectionisapowerfultoolthataddsalotofflexibilitytoaddorchangebehaviorinMagento.
GettingreadyToexplorethepossibilitiesofdependencyinjectionofMagento2,weneedthemodulethatwecreatedinthepreviousrecipes.
Howtodoit…Thefollowingstepsdescribehowwecanmodifythebehaviorofsomeclasses,whichisanewconceptinMagento2thatreplacestherewritingofclassesinMagento1:
1. Createtheapp/code/Packt/HelloWorld/etc/di.xmlfileandpastethefollowingcontentinit:
<?xmlversion="1.0"?>
<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/
config.xsd">
<typename="Magento\Catalog\Model\Product">
<pluginname="Packt_HelloWorld::productName"
type="Packt\HelloWorld\Plugin\Catalog\ProductAround"sortOrder="1"/>
</type>
</config>
NoteThelettersdiinthedi.xmlfilestandsforDependencyInjection.
2. Createapluginclassbycreatingtheapp/code/Packt/HelloWorld/Plugin/Catalog/ProductAround.phpfilewiththefollowingcontent:
<?php
namespacePackt\HelloWorld\Plugin\Catalog;
useMagento\Catalog\Model\Product;
classProductAround
{
publicfunctionaroundGetName($interceptedInput)
{
return"Nameofproduct";
}
}
NoteItishighlyrecommendedthatyouusearoundinthemethodnamebecauseyoucanalsowriteinterceptorsthatareexecutedbeforeorafteramethod.
3. Cleanthecachesandregeneratetheclassesbyremovingthevar/generationfolder.4. ReloadaproductpageandyouwillseethateveryproductnameischangedtoName
ofproduct.5. Toundothis,commentthe<type>tagandcontentsofthedi.xmlfileandregenerate
theclassesbyremovingthevar/generationfolder.Also,don’tforgettocleanthecache.
6. Reloadtheproductpageandyouwillseethenormalproductnames.
Howitworks…Inthisrecipe,weaddedadependencyinjectionintotheMagento\Catalog\Model\Productclass.WedidanoverrideofanexistingmethodinMagento.
Withinterception,wecanexecutethecodebefore,after,andaroundanymethodofaclass.ThisgivesalotofpossibilitiestoaddbehaviortoMagento.
Inthedi.xmlfile,weinitializedapluginthatcouldoverridemethodsoftheMagento\Catalog\Model\Productclass.
TheoverridesaredoneinthePackt\HelloWorld\Plugin\Catalog\ProductAroundclass.Inthisclass,wedidamodificationofthegetName()methodoftheoriginalclassusingthearoundGetName()method.
Totestourcode,wehadtocreatethegeneratedclasses.Wecandothisbyremovingthevar/generationfolderorbyrunningthephpbin/magentosetup:di:compilecommand.ThecachealsoneedstobecleanedbecausewechangedthingsintheconfigurationXMLfiles.
Thiscommandcreatesgeneratedclassesthatwillbeplacedinthevar/generationfolder.Withoutgeneratingtheclasses,theconfigurationinthedi.xmlfilewillnotload.Thisisalsothereasonwhyyouhavetodothiswheninstallingorupgradinganewmodule.
DependencyinjectionreplacestheclassrewritesysteminMagento1.Withdependencyinjection,youcanintercepteverymethodthatiscalledinaclass.WiththerewritesystemofMagento1,youcouldnotdothiswithabstractclasses.
Itisalsopossibletoexecutecodebeforeandafteramethodiscalled.
SeealsoAlotofthingsarepossiblewithDependencyInjection.FormoreinformationhowitisintegratedinMagento,youcanreadthedocumentationontheMagentosite:
http://devdocs.magento.com/guides/v2.0/extension-dev-guide/depend-inj.html.
NoteMoreinformationaboutthedependencyinjectiondesignpatterncanbefoundonthefollowingURL:
https://en.wikipedia.org/wiki/Dependency_injection.
AddingaconsolecommandAnothernewthinginMagento2isthebuilt-incommand-linetool.Inthischapter,weusedthistooltocleanthecache,forexample.
Withinamodule,itispossibletoextendthistoolwithcustomcommands,andthisisthethingthatwewilldointhisrecipe.
GettingreadyThisrecipewillbuildfurtheronthemodulethatwehavecreatedinthischapter.Ifyoudon’thavethecode,youcaninstallthestarterfiles.
Howtodoit…Inthenextsteps,wewillcreateasimpleconsolecommandthatwillprintsomeoutputtotheconsole.Usingthisprinciple,youcancreateyourowncommandstoautomatesometasks:
1. Foracustomconsolecommand,wehavetoaddthefollowingconfigurationinthedi.xmlfileofthemodule.Pastethefollowingcodeinthatfileaschildofthe<config>tag:
<typename="Magento\Framework\Console\CommandList">
<arguments>
<argumentname="commands"xsi:type="array">
<itemname="helloWorldCommand"
xsi:type="object">Packt\HelloWorld\Console\Command\HelloWorldCommand</i
tem>
</argument>
</arguments>
</type>
2. Next,wewillcreatetheapp/code/Packt/HelloWorld/Console/Command/HelloWorldCommand.phpfilewiththefollowingcontent:
<?php
namespacePackt\HelloWorld\Console\Command;
useSymfony\Component\Console\Command\Command;
useSymfony\Component\Console\Input\InputInterface;
useSymfony\Component\Console\Output\OutputInterface;
useSymfony\Component\Console\Input\InputOption;
classHelloWorldCommandextendsCommand
{
}
3. Inthepreviousstep,wecreatedtheclassforthecommand.Toinitializethecommand,wehavetoaddthefollowingcontenttoit:
constINPUT_KEY_EXTENDED='extended';
protectedfunctionconfigure()
{
$options=[
newInputOption(
self::INPUT_KEY_EXTENDED,
null,
InputOption::VALUE_NONE,
'Getextendedinfo'
),
];
$this->setName('helloworld:info')
->setDescription('Getinfoaboutinstallation')
->setDefinition($options);
parent::configure();
}
protectedfunctionexecute(InputInterface$input,OutputInterface
$output)
{
$output->writeln('<error>'.'writelnsurroundedbyerrortag'.
'</error>');
$output->writeln('<comment>'.'writelnsurroundedbycommenttag'
.'</comment>');
$output->writeln('<info>'.'writelnsurroundedbyinfotag'.
'</info>');
$output->writeln('<question>'.'writelnsurroundedbyquestion
tag'.'</question>');
$output->writeln('writelnwithnormaloutput');
if($input->getOption(self::INPUT_KEY_EXTENDED)){
$output->writeln('');
$output->writeln('<info>'.'Extendedparameteris
given'.'</info>');
}
$output->writeln('');
}
4. Cleanthecache,removethevar/generationfolder,andrunthephpbin/magentocommand.Youwillseethatthehelloworld:infocommandwillbeinthelist.
5. Whenyourunthecommand,youwillseethefollowingoutput:
6. Whenyourunthecommandwiththeextendedparameter,youwillseesomeextraoutput.Todothis,wehavetorunthecommandasfollows:
phpbin/magentohelloworld:info--extended
Howitworks…Toregistertheconsoleclasstothecommandlist,wehadtocreateanextraargumentfortheMagento\Framework\Console\CommandListclassinthedi.xmlfile.Inthisfile,werefertothePackt\HelloWorld\Console\Command\HelloWorldCommandclassforourcustomcommand.
Intheconfigure()method,weregisteredthenameofthecommand,thedescription,andtheotheroptions.Inthiscase,weinitializedanoptionalinputoption.
Theexecute()methodismadetoexecutethecommand.The$inputparametercontainstheinputofthecommand,suchastheoptionsandarguments.Withthe$outputparameter,wecanmodifytheoutputofthecommand.Thisparameterisusedtowriteoutputtotheconsolewiththewrite()andwriteln()methods.
Inthisrecipe,weworkedwithsomecolorstostyletheconsoleoutput.Thetextbetweentheerror,comment,information,andquestiontagswillberenderedinadifferentcolor,aswehaveseeninthisrecipe.
Wealsohadanoptionalparametercalledextended.Togetthevalueofthisparameter,wecanusethegetOption()methodofthe$inputparameter.Whentheparameterissetwithoutvalue,itwillreturntrue.Iftheparameterisn’tset,itwillreturnfalse.Ifatextisgiventotheparameter,itwillreturnthetext.
Seealso…TheMagentoconsoleisbuiltusingtheSymfonyconsolecomponent.MoreinformationabouthowtousetheSymfonyconsolecanbefoundatthefollowingURL:
http://symfony.com/doc/current/components/console/introduction.html
Chapter5.DatabasesandModulesInthischapter,wewillcover:
RegisteringdatabaseconnectionsCreatinganinstallandupgradescriptCreatingaflattablewithmodelsWorkingwithMagentocollectionsProgrammaticallyaddingproductattributesRepairingthedatabase
IntroductionInthepreviouschapter,welearnedhowtocreateamoduleandhowwecandosomebasicstuff.
Inthisrecipe,wewilllearnhowwecanintegrateamodulewiththedatabaseofMagento.Forthis,weneedtheMagentomodulethatwehavecreatedinChapter4,CreatingaModule.
Wewilllearnhowwecanrunqueries,addourownentities,andautomatetheinstallationofconfigurationsinthedatabase.
CreatinganinstallandupgradescriptInsomecases,youneedtoupdatethedatabasetofinishyourwork.Commoncasesareanextracolumntoatable,anewproductattribute,orasetting.
Ifyouhaveadevelopment,staging,andproductionenvironment,youhavetomakesurethattherightupdatesaredonewhendeployingtotheseenvironments.
Magentohasawaytoautomaticallytriggertheexecutionofcertainscriptsforanupdate.That’sthethingwewilllearninthisrecipe.CommonthingstoautomatearesettingsintheStore|Configurationpageinthebackend.
GettingreadyWewillcreateanewmodulewherewewilladdaninstallandupgradescript.Wewillalsousethecommandlinetorunthescripts.
InMagento2,theprincipleofinstallingandupgradingscriptsisthesameasinMagento1butsomethingshavechangedinthewayofworking.Inthisrecipe,wewillseehowitworkstosavesomedatainthedatabasewithaninstallscript.
Howtodoit…Thefollowingstepsdescribehowwecancreateanautomatedscript:
1. CreateanewmodulecalledPackt_SEO.Wecandothisbycreatingthefileapp/code/Packt/SEO/etc/module.xmlwiththefollowingcontent:
<?xmlversion="1.0"?>
<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.
xsd">
<modulename="Packt_SEO"setup_version="2.0.0">
<sequence>
<modulename="Magento_Backend"/>
</sequence>
</module>
</config>
2. Next,createafilecalledregistration.phpinthefolderapp/code/Packt/SEO/withthefollowingcontent:
<?php
\Magento\Framework\Component\ComponentRegistrar::register(
\Magento\Framework\Component\ComponentRegistrar::MODULE,
'Packt_SEO',
__DIR__
);
3. Thefollowingstepistocreatetheinstallscript.Wecandothisbycreatingthefileapp/code/Packt/SEO/Setup/InstallData.phpwiththefollowingcontent:
<?php
namespacePackt\SEO\Setup;
useMagento\Framework\Setup\InstallDataInterface;
useMagento\Framework\Setup\ModuleContextInterface;
useMagento\Framework\Setup\ModuleDataSetupInterface;
classInstallDataimplementsInstallDataInterface{
protected$resourceConfig;
publicfunction
__construct(\Magento\Config\Model\ResourceModel\Config$resourceConfig)
{
$this->resourceConfig=$resourceConfig;
}
publicfunctioninstall(ModuleDataSetupInterface$setup,
ModuleContextInterface$context){
$setup->startSetup();
$this->resourceConfig->saveConfig(
'catalog/seo/category_canonical_tag',
true,
\Magento\Config\Block\System\Config\Form::SCOPE_DEFAULT,
0
);
$this->resourceConfig->saveConfig(
'catalog/seo/product_canonical_tag',
true,
\Magento\Config\Block\System\Config\Form::SCOPE_DEFAULT,
0
);
$setup->endSetup();
}
}
4. Whenwewanttorunthescript,wehavetoinstallthemodule.Wecandothisbyrunningthefollowingcommand:
phpbin/magentosetup:upgrade
NoteThepreviousscriptwillenablethecanonicaltagsontheproductandcategorypages.
5. Ifwewanttoaddanewscripttotheexistingmodule,wehavetocreateanupgradescript.Wecandothisbycreatingthefileapp/code/Packt/SEO/Setup/UpgradeData.phpwiththefollowingcontent:
<?php
namespacePackt\SEO\Setup;
useMagento\Framework\Setup\UpgradeDataInterface;
useMagento\Framework\Setup\ModuleContextInterface;
useMagento\Framework\Setup\ModuleDataSetupInterface;
classUpgradeDataimplementsUpgradeDataInterface{
protected$resourceConfig;
publicfunction
__construct(\Magento\Config\Model\ResourceModel\Config$resourceConfig)
{
$this->resourceConfig=$resourceConfig;
}
publicfunctionupgrade(ModuleDataSetupInterface$setup,
ModuleContextInterface$context){
if(version_compare($context->getVersion(),'2.0.1')<0){
$this->resourceConfig->saveConfig(
'web/cookie/cookie_lifetime',
'7200',
\Magento\Config\Block\System\Config\Form::SCOPE_DEFAULT,
0
);
}
}
}
6. Thisscriptwillrunforversion2.0.1sowehavetoconfigureourmoduletothatversion.Wecandothisbyraisingtheversionnumberinthefileapp/code/Packt/SEO/etc/module.xmlinthesetup_versionattribute.Thefilewillhavethefollowingcontent:
<?xmlversion="1.0"?>
<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.
xsd">
<modulename="Packt_SEO"setup_version="2.0.1">
<sequence>
<modulename="Magento_Backend"/>
</sequence>
</module>
</config>
7. Torunthescript,wehavetorunthesamecommandphpbin/magentosetup:upgrade.
8. Whentheupgradescripthasfinished,lookatthepageStores|Configuration|Web|DefaultCookieSettingsandyouwillseethatthelifetimehasthevalue7200.
Howitworks…EveryMagentomodulehasaversionnumberthatisstoredinthemodule.xmlfile.Whenwelookinthedatabasetablesetup_module,weseetheversionnumbersofeverymodule.
Whenrunningthecommandphpbin/magentosetup:upgrade,Magentowillcheck,foreverymodule,theversionnumberinthemodule.xmlfileandthedatabasetablesetup_module.
NoteIfyouwanttore-runaninstallorupgradescript,youcandeleteorchangetheversionnumberofthemoduleinthetablesetup_module.
Ifamoduleisnotinthelist,theInstallData.phpscriptwillbeexecuted.Afterthat,theUpgradeData.phpscriptisalwaysexecuted.
Withanifstatement,wecanmanagewhatthingsneedtobeexecutedinaspecificupgrade.
Inthedatabasetablesetup_module,weseethefollowingthreecolumns:
module(thenameofthemodule)schema_version(theinstalledschemascript)data_version(theinstalleddatascript)
Therearetwodifferenttypesofinstallscripts.Aschemainstallandadatainstall.Aschemainstallisusedtoinstalldatabasestructuressuchasnewtables,columns,andrelations.
Adatainstallorupgradeisusedtoadddatatothedatabasesuchasasetting,page,category,stores,andmanymore.
Alltheschemascriptsareexecutedbeforethedataupgradescriptswillstart.ThismeansthatwecanusetheMagentomodelsinthedatascripts.Thisisnotalwayspossibleinaschemascriptbecauseitcouldbethatthedatabasestructureisnotcompletelyinstalledwhenrunningyourscript.
Inthescripts,weusedtheresourceConfigobjecttosaveconfigurationparameters.Withthiscode,dataisupdatedintheconfigurationpagesthatyoucanfindinthebackendatStores|Configuration.
Thevaluesofthesepagesaresavedinthetablecore_config_data.
CreatingaflattablewithmodelsWhenyouwanttosavedatainamodule,youmaywanttostorethatinacustomentity.Thatentityneedsadatabasetableandamodelthattalkswiththatdatabasetable.
Wewillcreateasubscriptionsentitywherewecanstoresubscriptions.
GettingreadyInthisrecipe,wewillextendthemoduleofChapter4,CreatingaModule,withanentitywithadatabasetable.Makesureyouhavethestarterfilesforthisrecipeinstalled.
Howtodoit…Inthenextsteps,wewilllearnhowwecanaddentitiestoanexistingmodule:
1. Wheninstallinganewentity,wehavetocreatearesourcemodel.Wecandothisbycreatingthefileapp/code/Packt/HelloWorld/Model/ResourceModel/Subscription.phpwiththefollowingcontent:
<?php
namespacePackt\HelloWorld\Model\ResourceModel;
classSubscriptionextends
\Magento\Framework\Model\ResourceModel\Db\AbstractDb{
publicfunction_construct(){
$this->_init('packt_helloworld_subscription','subscription_id');
}
}
2. Thesecondstepistocreatethetablewithaschemaupgradescript.Wehavetocreatethescriptapp/code/Packt/HelloWorld/Setup/UpgradeSchema.phpwiththefollowingcontent:
<?php
namespacePackt\HelloWorld\Setup;
useMagento\Framework\Setup\UpgradeSchemaInterface;
useMagento\Framework\Setup\ModuleContextInterface;
useMagento\Framework\Setup\SchemaSetupInterface;
classUpgradeSchemaimplementsUpgradeSchemaInterface{
publicfunctionupgrade(SchemaSetupInterface$setup,
ModuleContextInterface$context){
if(version_compare($context->getVersion(),'2.0.1')<0){
$installer=$setup;
$installer->startSetup();
$connection=$installer->getConnection();
//Installnewdatabasetable
$table=$installer->getConnection()->newTable(
$installer->getTable('packt_helloworld_subscription')
)->addColumn(
'subscription_id',
\Magento\Framework\DB\Ddl\Table::TYPE_INTEGER,
null,[
'identity'=>true,
'unsigned'=>true,
'nullable'=>false,
'primary'=>true
],
'SubscriptionId'
)->addColumn(
'created_at',
\Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP,
null,[
'nullable'=>false,
'default'=>\Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT
],
'Createdat'
)->addColumn(
'updated_at',
\Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP,
null,
[],
'Updatedat'
)->addColumn(
'firstname',
\Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
64,
['nullable'=>false],
'Firstname'
)->addColumn(
'lastname',
\Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
64,
['nullable'=>false],
'Lastname'
)->addColumn(
'email',
\Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
255,
['nullable'=>false],
'Emailaddress'
)->addColumn(
'status',
\Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
255,[
'nullable'=>false,
'default'=>'pending',
],
'Status'
)->addColumn(
'message',
\Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
'64k',[
'unsigned'=>true,
'nullable'=>false
],
'Subscriptionnotes'
)->addIndex(
$installer->getIdxName('packt_helloworld_subscription',
['email']),
['email']
)->setComment(
'CronSchedule'
);
$installer->getConnection()->createTable($table);
$installer->endSetup();
}
}
3. Raisethemoduleversionnumberinthefileapp/code/Packt/HelloWorld/etc/module.xmlto2.0.1.
4. Runthefollowingcommandtoexecutetheupgradescript:
phpbin/magentosetup:upgrade
5. Checkthatthetableisinstalledinthedatabase.Wecandothisbyrunningthequerydescribepackt_helloworld_subscription;onaMySQLcommandline.Thiswillgivethefollowingoutput:
6. Thenextpartistocreateamodelthatinteractswiththedatabasetable.Tocreateamodel,wehavetocreatethefileapp/code/Packt/HelloWorld/Model/Subscription.phpwiththefollowingcontent:
<?php
namespacePackt\HelloWorld\Model;
classSubscriptionextends\Magento\Framework\Model\AbstractModel{
constSTATUS_PENDING='pending';
constSTATUS_APPROVED='approved';
constSTATUS_DECLINED='declined';
publicfunction__construct(
\Magento\Framework\Model\Context$context,
\Magento\Framework\Registry$registry,
\Magento\Framework\Model\ResourceModel\AbstractResource$resource=
null,
\Magento\Framework\Data\Collection\AbstractDb$resourceCollection=
null,
array$data=[]
){
parent::__construct($context,$registry,$resource,
$resourceCollection,$data);
}
publicfunction_construct(){
$this->_init('Packt\HelloWorld\Model\ResourceModel\Subscription');
}
}
7. Thelastclassthatwehavetocreateisthecollectionclass.Createthefileapp/code/Packt/HelloWorld/Model/ResourceModel/Subscription/Collection.php
withthefollowingcontent:
<?php
namespacePackt\HelloWorld\Model\ResourceModel\Subscription;
/**
*SubscriptionCollection
*/
classCollectionextends
\Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection
{
/**
*Initializeresourcecollection
*
*@returnvoid
*/
publicfunction_construct(){
$this->_init('Packt\HelloWorld\Model\Subscription',
'Packt\HelloWorld\Model\ResourceModel\Subscription');
}
}
8. Allthefilesareintherightplacetostartatest.Toperformasimpletest,wecancreateacontrolleractionintheIndexControlleroftheHelloWorldmodulethatwehavecreatedinthepreviousrecipe.Addthefileapp/code/Packt/HelloWorld/Controller/Index/Subscription.phpwiththefollowingcontent:
<?php
namespacePackt\HelloWorld\Controller\Index;
classSubscriptionextends\Magento\Framework\App\Action\Action{
publicfunctionexecute(){
$subscription=$this->_objectManager-
>create('Packt\HelloWorld\Model\Subscription');
$subscription->setFirstname('John');
$subscription->setLastname('Doe');
$subscription->setEmail('[email protected]');
$subscription->setMessage('Ashortmessagetotest');
$subscription->save();
$this->getResponse()->setBody('success');
}
}
9. OpenthebrowserandgototheURL/helloworld/index/subscriptionofyourMagento.Whenyouseethewordsuccess,thiswillmeanthatanewsubscriptionisaddedtothedatabasetable.
10. Whenyoulookinyourdatabaseandrunthequeryselect*frompackt_helloworld_subscription;,wewillseethefollowingoutput:
Howitworks…Whenweworkwiththeprevioussetupofentities,theMagentoORMmakesthelinkbetweentheentityandthedatabase.AlltheSQLqueries,security,andmorearemanagedbytheORM.
Thefirststepwedidwastocreatearesourcemodel.Aresourcemodelisusedforthelinkbetweenthemodelandthedatabase.Thisclassisinitializedbyspecifyingthedatabasetableandtheprimarykeyofthattable.
Thesecondpartwastocreateascriptthatautomaticallycreatesadatabasetable.Adatabasetableiscreatedwiththeschemainstaller.Becausethismodulewasalreadyinstalled,wehadtocreateanupgradescriptandwehadtoraisetheversionnumber.
Thethirdstepwasthecreationofthemodel.Inamodel,alotofbusinesslogicisavailableinfunctionsandvariables.Animportantthingtomentionistheinitializationoftheresourcemodelthatisdoneinthe_construct()methodofthemodel.
Thelastparttofinishthemodelsetupwastocreatethecollectionclass.ThisclassisresponsibleforallowingustoworkwithMagentocollectionsinourmodel.TheprincipleofcollectionsisexplainedinthenextrecipeWorkingwithMagentocollections.
Totestthemodel,wecreatedatest()methodinacontrolleraction.ToloadaMagentoentity,wehavetousetheObjectManagerinterface.Inacontrolleraction,thisinterfaceisavailableinthevariable_objectManager.
NoteInthecontrolleraction,weusedtheobjectmanagertogetthemodelandsavearecordtothetable.Thiswasjustfordebuggingpurposes.Intherealworld,thecontrollerisusedforcontrolleractionssuchasredirects,addingmessages,renderingthepage,andmore.Thesaveofanentityismostlydoneinaseparatemodelbut,forsimplicity,wediditinthecontroller.
TocreateaMagentoentity,wehavetousethecreate()methodoftheobjectmanager.Theparameterisastringwiththenamespaceandclassnameoftheentitysuchasthefollowing:
$this->_objectManager->create('Packt\HelloWorld\Model\Subscription');
WithourMagentoentity,wecanusethefollowingmethodstointeractwiththedatabase:
load($entityId)
save()
delete()
Allthesemethodsareimplementedinthe\Magento\Framework\Model\AbstractModelclass.AlltheentitiesthatusetheMagentoORMframeworkwillextendthisclass.
WorkingwithMagentocollectionsWhenyouwanttoretrieveasetofentitiesofthesametype,weusuallyuseaquerytogetthedataofatable.
ButinMagentonoteveryentityisstoredinasingletableandthatmeansthataverycomplexqueryisrequiredtogetthedata.Agenericsystem/languagetocreatequeriesforMagentoentitieswasthesolution.
Forthissolution,Magentohascreatedasystemcalledcollections.Acollectionisasetofentitiesofthesametypewhereyoucanaddfilterstoittocustomizeyourresult.
Inthisrecipe,wewillseewhatwecandowithMagentocollections.
GettingreadyForthisrecipe,itisrequiredtohavethePackt_HelloWorldmoduleinstalledwiththecodeofthepreviousrecipeCreatingaflattablewithmodels.
Howtodoit…ThenextexamplesshowthepossibilitiesofworkingwithMagentocollections:
1. CreateaCollectioncontrolleractionbycreatingthefileapp/code/Packt/HelloWorld/Controller/Index/Collection.phpwiththefollowingcontent:
<?php
namespacePackt\HelloWorld\Controller\Index;
classCollectionextends\Magento\Framework\App\Action\Action{
publicfunctionexecute(){
}
}
2. Pastethefollowingcodeintheexecute()methodofthatclass:
publicfunctionexecute(){
$productCollection=$this->_objectManager
->create('Magento\Catalog\Model\ResourceModel\Product\Collection')
->setPageSize(10,1);
$output='';
foreach($productCollectionas$product){
$output.=\Zend_Debug::dump($product->debug(),null,false);
}
$this->getResponse()->setBody($output);
}
3. Whenloadingthepage/helloworld/index/collection,weseeadumpofthefirst10productslikethefollowingarray:
array(9){
["entity_id"]=>
string(1)"1"
["attribute_set_id"]=>
string(2)"15"
["type_id"]=>
string(6)"simple"
["sku"]=>
string(7)"24-MB01"
["has_options"]=>
string(1)"0"
["required_options"]=>
string(1)"0"
["created_at"]=>
string(19)"2015-07-1612:36:24"
["updated_at"]=>
string(19)"2015-07-1612:36:24"
["is_salable"]=>
string(1)"1"
}
4. Whenwelookatthearray,weseenoproductattributesinthedump.Toaddanattributetoacollection,wehavetousethemethodaddAttributeToSelect('<attribute_code>').Addthefollowingcodeintheexecute()methodandwewillseethename,price,andimageoftheproducts:
publicfunctionexecute(){
$productCollection=$this->_objectManager
->create('Magento\Catalog\Model\ResourceModel\Product\Collection')
->addAttributeToSelect([
'name',
'price',
'image',
])
->setPageSize(10,1);
$output='';
foreach($productCollectionas$product){
$output.=\Zend_Debug::dump($product->debug(),null,false);
}
$this->getResponse()->setBody($output);
}
5. Whenreloadingthepage,wewillseeanarrayofeachproductwiththethreeattributesinitlikethefollowingcode:
array(12){
["entity_id"]=>string(1)"2"
["attribute_set_id"]=>string(2)"15"
["type_id"]=>string(6)"simple"
["sku"]=>string(7)"24-MB04"
["has_options"]=>string(1)"0"
["required_options"]=>string(1)"0"
["created_at"]=>string(19)"2015-07-1612:36:25"
["updated_at"]=>string(19)"2015-07-1612:36:25"
["name"]=>string(20)"StriveShoulderPack"
["image"]=>string(33)"/sample_data/m/b/mb04-black-0.jpg"
["price"]=>string(7)"32.0000"
["is_salable"]=>string(1)"1"
}
6. WewillnowcreateafilterontheproductcollectionwiththeaddAttributeToFilter()method.ThenextcodeshowshowyoucanfiltertheproductswiththenameOvernightDuffle:
publicfunctionexecute(){
$productCollection=$this->_objectManager
->create('Magento\Catalog\Model\ResourceModel\Product\Collection')
->addAttributeToSelect([
'name',
'price',
'image',
])
->addAttributeToFilter('name','OvernightDuffle');
$output='';
foreach($productCollectionas$product){
$output.=\Zend_Debug::dump($product->debug(),null,false);
}
$this->getResponse()->setBody($output);
}
NoteThecodeinthisstatementwillcreateaWHEREname='OvernightDuffle'statementtothequery,soalltheitemswherethenameisOvernightDufflewillbereturned.
7. WiththeaddAttributeToFilter()method,wecandomore.ThefollowingcodeshowsyouhowyoucancreateaWHEREentity_idIN(159,160,161)statement:
publicfunctionexecute(){
$productCollection=$this->_objectManager
->create('Magento\Catalog\Model\ResourceModel\Product\Collection')
->addAttributeToSelect([
'name',
'price',
'image',
])
->addAttributeToFilter('entity_id',array(
'in'=>array(159,160,161)
));
$output='';
foreach($productCollectionas$product){
$output.=\Zend_Debug::dump($product->debug(),null,false);
}
$this->getResponse()->setBody($output);
}
8. Thenextfilterthatwewilluseisthelikefilter.AddthefollowingcodetomakeaquerywiththeWHEREnameLIKE'%Sport%'statement:
publicfunctionexecute(){
$productCollection=$this->_objectManager
->create('Magento\Catalog\Model\ResourceModel\Product\Collection')
->addAttributeToSelect([
'name',
'price',
'image',
])
->addAttributeToFilter('name',array(
'like'=>'%Sport%'
));
$output='';
foreach($productCollectionas$product){
$output.=\Zend_Debug::dump($product->debug(),null,false);
}
$this->getResponse()->setBody($output);
}
9. Whenthequeriesbecomemorecomplex,sometimesitisnicetoknowwhatSQLquerywillbegeneratedtogetthecollection.ToprinttheSQLquery,whichisusedforacollection,wecanusethefollowinglineofcode:
$productCollection->getSelect()->__toString();
10. Whenweaddthefollowingcode,wecanseethequeryforthepreviouscollection:
publicfunctionexecute(){
$productCollection=$this->_objectManager
->create('Magento\Catalog\Model\ResourceModel\Product\Collection')
->addAttributeToSelect([
'name',
'price',
'image',
])
->addAttributeToFilter('name',array(
'like'=>'%Sport%'
));
$output=$productCollection->getSelect()->__toString();
$this->getResponse()->setBody($output);
}
11. ThiscodewilloutputthefollowingSQLquery:
SELECT
e.*,
IF(at_name.value_id>0,at_name.value,at_name_default.value)AS`name`
FROMcatalog_product_entity`ASe
INNERJOINcatalog_product_entity_varcharAS`at_name_default
ON(at_name_default.entity_id=e.entity_id)
AND(at_name_default.attribute_id='69')
ANDat_name_default.store_id=0
LEFTJOINcatalog_product_entity_varcharASat_name
ON(at_name.entity_id=e.entity_id)
AND(at_name.attribute_id='69')
AND(at_name.store_id=1)
WHERE(IF(at_name.value_id>0,at_name.value,at_name_default.value)
LIKE'%Sport%')
12. WhenyourunthepreviousqueryinanSQLpromptsuchasphpMyAdmin,wecanseetheresponsewithalltheattributesthatareusedfortheproductcollection.
13. Withthepreviouscodeexamples,wecanonlyreaddatafromthedatabase.ByusingthesetDataToAll()method,wecanupdatesomeattributesforalltheentitiesinthe
collection.Usethenextcodetoupdateallthepricesinthecollection:
publicfunctionexecute(){
$productCollection=$this->_objectManager
->create('Magento\Catalog\Model\ResourceModel\Product\Collection')
->addAttributeToSelect([
'name',
'price',
'image',
])
->addAttributeToFilter('entity_id',array(
'in'=>array(159,160,161)
));
$output='';
$productCollection->setDataToAll('price',20);
foreach($productCollectionas$product){
$output.=\Zend_Debug::dump($product->debug(),null,false);
}
$this->getResponse()->setBody($output);
}
14. WhenyouusethesetDataToAll()method,nothingwillbechangedinthedatabaseuntilyouhavecalledthesave()method.AddthefollowingcodeafterthesetDataToAll()methodtosavethecollection:
$productCollection->save();
Howitworks…WhenwewantacollectionofMagentoentities,wecandothisbycallingthecollectionclassofthemodel.Thisclassislocatedintheresourcemodelfolderofamodule(Model/ResourceModel/<Modelname>/Collection.php).
Togetacollection,wecanusethegetCollection()methodofamodel.Whenweusethatmethod,aninstanceofthecollectionclassisreturned.Abetterwayistodirectlycallthecollectionclasslikewedidinthisrecipe.
Acollectionclassalwaysextendsfromthe\Magento\Framework\Data\Collectionclass,whichgivesaccesstoallthemethodstoworkwithcollections.Theresultofacollectionisalwaysreturnedasaniterableobjectsowecanloopthroughtheobjectsinthecollection.
Whenworkingwithcollections,wehavetwotypesofcollections.Thecollectionsworkingwithflatentities,andthecollectionsworkingwithEAVentities(suchasproducts).
ThedifferencebetweenaflatandanEAVcollectionisthat,withaflatcollection,allthefieldsofthetableareincludedinthecollection.WithanEAVcollection,wehavetousethemethodaddAttributeToSelect()toincludetheEAVattributesthatwewantinourcollection.
ThisisbecauseEAVdataisstoredinmultipletablesforasingleentity.Foraproduct,thevaluesofeverytypeofattributearestoredinadifferenttable(thecatalog_product_entity_*tables).
WiththeaddAttributeToFilter()method,wecancreateconditionsontheSQLstatement.Withflatcollections,wecanalsousetheaddFieldToFilter()method.
Forthisrecipe,weusedthe\Zend_Debug::dump()methodtocreatedumpsofourobjects.Weusethismethodbecausethisadds<pre>tagsaroundthedumpsowecaneasilyreaditinabrowser.
Inthatdumpstatement,wesetthethirdparametertofalse.Thismeansthatthismethodwillreturnthedatainsteadofprintingit.Tocorrectlyaddaresponsetoacontroller,weneedtousethe$this->getResponse()->setData()methodattheendofthecontrolleraction.
ProgrammaticallyaddingproductattributesIntherecipeCreatinganinstallandupgradescript,welearnedhowwecanautomatetheexecutionofdatabasechanges.
Intheseinstallscripts,wecanalsoaddattributestoproducts,aswewilllearninthisrecipe.
GettingreadyWewillworkfurtheronthePackt_HelloWorldmodulethatwehavecreatedinthepreviousrecipes.Makesureyouhavethemoduleinstalled.
Howtodoit…Thefollowingstepsdescribetheproceduretocreateanupgradescriptthataddsaproductattributetoallproducts:
1. ThemodulePackt_HelloWorldisalreadyinstalledinoursystemsowehavetocreateanupgradescript.CreatethefileUpgradeData.phpinthefolderapp/code/Packt/HelloWorld/Setup.Addthefollowingcontentinthatfile:
<?php
namespacePackt\HelloWorld\Setup;
useMagento\Framework\Setup\UpgradeDataInterface;
useMagento\Framework\Setup\ModuleContextInterface;
useMagento\Framework\Setup\ModuleDataSetupInterface;
classUpgradeDataimplementsUpgradeDataInterface{
protected$categorySetupFactory;
publicfunction
__construct(\Magento\Catalog\Setup\CategorySetupFactory
$categorySetupFactory){
$this->categorySetupFactory=$categorySetupFactory;
}
publicfunctionupgrade(ModuleDataSetupInterface$setup,
ModuleContextInterface$context){
if(version_compare($context->getVersion(),'2.0.2')<0){
$categorySetup=$this->categorySetupFactory->create(['setup'=>
$setup]);
$entityTypeId=$categorySetup-
>getEntityTypeId(\Magento\Catalog\Model\Product::ENTITY);
$categorySetup->addAttribute($entityTypeId,'helloworld_label',
array(
'type'=>'varchar',
'label'=>'HeloWorldlabel',
'input'=>'text',
'required'=>false,
'visible_on_front'=>true,
'apply_to'=>
'simple,configurable,virtual,bundle,downloadable',
'unique'=>false,
'group'=>'HelloWorld'
));
}
}
}
2. Updatetheversionnumberinthemodule.xmlfile.Openthefileapp/code/Packt/HelloWorld/etc/module.xmlandchangethesetup_versionattributeofthe<module>tagto2.0.2.Thisfilewilllookasfollows:
<?xmlversion="1.0"?>
<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.
xsd">
<modulename="Packt_HelloWorld"setup_version="2.0.2">
<sequence>
<modulename="Magento_Catalog"/>
</sequence>
</module>
</config>
3. Runthecommandphpbin/magentosetup:upgradeinyourcommandlinetoruntheupgradescripts.
4. Logintothebackendandlookforaproduct.YouwillseethattheattributeisaddedtotheHelloWorldtablikeinthefollowingscreenshot:
NoteThefirstversionofMagento2hasanissuethattheproducteditpagewillnotloadcorrectlyafteraddinganewattribute.Iftheproducteditpagedoesn’tloadcorrectly,youhavetosavetheproducttemplate(attributeset)oftheproductmanually.Aftersavingthis,thepagewillloadcorrectly.
Howitworks…Installingaproductattributeisdoneinthedatainstallorupgradescripts.Inthatscript,weusethecategorySetupclasstoaddtheattributes.Inthisclass,theaddAttribute()methodisavailableforaddingattributes.
TheaddAttribute()methodwillcreateanEAVattributethatwillbestoredinthetableeav_attribute.EveryEAVattributeisfromaspecificentitytype.That’sthereasonwhywehaveretrievedtheentitytypewiththemethod$categorySetup->getEntityTypeId(\Magento\Catalog\Model\Product::ENTITY);.
Foraproduct,someinformationoftheattribute(suchasthevisible_on_front,used_in_product_listing,andmore)isstoredinaseparatetable.Thistableisthecatalog_eav_attributetablewherealltheextendedinformationofthecatalogattributesissaved.Withtheattribute_idparameter,everyrowisrelatedtoanEAVattributefromthetableeav_attribute.
RepairingthedatabaseSometimes,itcanhappenthatyourMagentodatabaseisbrokenorcorrupt.Thiscanbecausedbyvariousreasonssuchasahackoraservercrash.Whenthedatabaseisbroken,everyonewantstorepairitasquicklyaspossible.
Commonexamplesofacorruptdatabasearemissingtablesorcolumnsafterrunninganunsavedquery,ormissingrelationsafteranimportofadumpwithouttherelations.
MagentohasadatabaserepairtoolthatcomparesdatabaseAwithdatabaseBandthistoolcanfixthemissingstructuresbetweenthem.
Inthisrecipe,wewillmakeourdatabasecorruptandwewillrepairitwiththerepairtool.
GettingreadyToprepareyourselves,downloadthedatabaserepairtoolfromtheMagentositeathttp://www.magentocommerce.comandplacethePHPfileinyourwebroot.
Howtodoit…Thenextstepsshowyouhowyoucanbreakyourdatabaseandhowtofixitusingthedatabaserepairtool:
1. CreateabackupofyourexistingMagento2database.2. Createanewemptydatabasethatwewilluseasthereferencedatabase.Let’ssaywe
callitmagento2_dev_repair.3. ConfigureMagentotousethisemptydatabaseintheapp/etc/env.phpfile.4. Runthecommandphpbin/magentosetupsetupsetup:upgradeintheMagento
root.ThiswillinstallanemptyMagentointhedatabase.5. Makeyouroriginaldatabasecorrupt.Youcandothisbyrunningthefollowing
queriesthatremoveaforeignkeyandatable:
ALTERTABLEstoreDROPFOREIGNKEY
STORE_WEBSITE_ID_STORE_WEBSITE_WEBSITE_ID;
DROPTABLEcatalog_product_index_price;
6. Browsetotherepairtoolinyourbrowser,andconfigureyouroriginalandreferencedatabaseasshowninthefollowingscreenshot:
7. Submittheformandthescriptwillrepairyourdatabase.Onthenextpage,youwillseewhatchangesaremadetoyouroriginaldatabase.
8. Switchthedatabasebacktotheoriginaloneinapp/etc/env.php,flushthecache,andyourstoreisbackupandrunning.
Howitworks…ThedatabaserepairtoolofMagentocomparesthestructureoftwodatabaseswitheachotherandwillfixthedifferences.
WecreatedanemptydatabasewhereweinstalledanewemptyMagentowiththeinstallscripts.Thisdatabasewasusedasthereferencedatabase.
Inthedatabaserepairtool,weenteredtheoriginaldatabaseasthecorrupteddatabase.
Thetoolwillcomparethestructureofthereferencedatabasewiththecorrupteddatabase.Whenthecomparisonsaredone,thetoolwillmakethestructureofthecorrupteddatabasethesameasthestructureofthereferencedatabase.
Whenthisisdone,allbrokenstructures(suchasmissingrelations,tables,columns,andmore)arefixedinthecorrupteddatabase.
Thecomparetoolonlyfixesstructuralissueswithyourdatabase.Ifyoumisssomeofyourdata,thisisnottherighttooltogetitback.
Chapter6.MagentoBackendInthischapter,wewillcover:
RegisteringabackendcontrollerExtendingthemenuAddinganACLAddingconfigurationparametersCreatingagridofadatabasetableWorkingwithbackendcomponentsAddingcustomerattributesWorkingwithsourcemodels
IntroductionForastoreowner,thebackendistheinterfacetomanageeverythingintheirstore.Itisveryimportantforeverythingtobesecuredagainstvisitorswithbadintentions.ThebackendofastandardMagentoinstallationisextendibleinmanyways(likethefrontend),soeveryonecanextenditwithcustompages,configuration,roles,andsoon.
ByfollowingthebestpracticesofMagento,allsecurityrisksaremanagedbyMagentoliketheaccessforanonymoususers.
TherecipesinthischapterdescribethemostcommonwaysinwhichyoucanextendyourbackendusingthebestpracticesofMagento2.
RegisteringabackendcontrollerThefirstthingthatwewilllearnishowtoextendthebackendwithacustomcontrolleraction.Weneedacontrollerthatissecuredsothatonlylogged-inbackenduserscanseethecontentsofthispage.
AbackendcontrollerisrequiredwhenyouwanttoaddanextrapagetothebackendofMagento.Thisismostlythecasewhenyouareworkingwithacustomformoroverviewthatyouneedforyourmodule.
GettingreadyFordevelopmentpurposes,itisrecommendedthatweremovethesecretkey(thehashintheURL)fromtheadminURLs.YoucanconfigurethisinStores|Configuration|Advanced|Admin|Security.Changetheconfigurationasshowninthefollowingscreenshot:
Howtodoit…Whenyouwanttoaddanextrapagetoyourbackend,youhavetofollowthesesteps:
1. Createthefileroutes.xmlinthefolderapp/code/Packt/HelloWorld/etc/adminhtmlwiththefollowingcontent:
<?xmlversion="1.0"?>
<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd
">
<routerid="admin">
<routeid="helloworld"frontName="helloworld">
<modulename="Packt_HelloWorld"before="Magento_Backend"/>
</route>
</router>
</config>
2. Inthefolderapp/code/Packt/HelloWorld/Controller/Adminhtml/Index,createafilecalledIndex.phpwiththefollowingcontent:
<?php
namespacePackt\HelloWorld\Controller\Adminhtml\Index;
useMagento\Backend\App\Action\Context;
useMagento\Framework\View\Result\PageFactory;
classIndexextends\Magento\Backend\App\Action
{
protected$resultPageFactory;
publicfunction__construct(
Context$context,
PageFactory$resultPageFactory
){
parent::__construct($context);
$this->resultPageFactory=$resultPageFactory;
}
publicfunctionexecute()
{
}
}
NoteMakesurethatyourcontrolleractionextendsfromthe\Magento\Backend\App\Actionclass.Thiswillcovertheaccessforauthorizedusers.
3. Cleanthecacheusingthecommandphpbin/magentocache:cleanandnavigatetotheURL/admin/helloworld/index.Youwillseeawhitepage,whichisnormalbecausetheactionisempty.
4. Whenyouaddthefollowingcodeintheexecute()functionofthatclass,youwillseethatthebackendinterfaceistherewithanemptypage:
publicfunctionexecute()
{
$resultPage=$this->resultPageFactory->create();
return$resultPage;
}
Howitworks…Backendcontrollersworksimilartofrontendcontrollers.Themaindifferencebetweenthemisthatabackendcontrolleractionwillextendfromtheclass\Magento\Backend\App\Actionwhereafrontendcontrollerwillextendfromtheclass\Magento\Framework\App\Action\Action.
Theclasswherethebackendactionextendsfrommanagesallthesecuritysothatthepageisonlyaccessibleforauthenticatedbackendusers.
Therouteforthebackendcontrollerisinitializedintheroutes.xmlfileforadminhtml.Inthisfile,theroutenameisconfiguredforthePackt_HelloWorldmodule.Wecalledthisroutehelloworld.
ThecontrolleractionfilesareplacedintheAdminhtmlsubfolderoftheControllerfolderofthemodule.MagentoknowsthatithastolookintheAdminhtmlfolderforbackendcontrollersandactions.
Intheconfiguration,wedisabledthesecretkeystotheadminURLssoitiseasytodebugnewURLs.ThestructureofabackendURLislikelythesameasinthefrontend.Theonlydifferenceisthatbackendcontrolleractionsalwaysstartwiththeadminhtmlprefix.Whenthisprefixisadmin,theURLswillhavethefollowingstructure:<path_to_backend>/<route_name>/<controller_name>/<action_name>
ExtendingthemenuInthepreviousrecipe,weaddedanewpagetothebackend,butitisimportantthatyouareabletoeasilynavigatetoyourcustompages.NoteveryoneknowstheURL,andifthesecretkeysareenabledfortheadministratorURLs,itislikelyimpossibletobuildacorrectURLbecauseyouhavetoknowthekey.
GettingreadyThisrecipebuildsfurtheronthepreviousone.Makesureyouhavethecodeofthepreviousrecipeinstalled.
Howtodoit…ThefollowingstepsdescribehowwecanaddextramenuitemstotheAdminmenu:
1. FirstwehavetothinkaboutwherewewillgetanextramenuitemintheAdminmenu.Forthistutorial,wewilladdanextraitemintheMarketingmenu.ForthisweneedtoknowtheIDofthemarketingmenu.
2. Createthefileapp/code/Packt/HelloWorld/etc/adminhtml/menu.xmlwiththefollowingcontent:
<?xmlversion="1.0"?>
<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Backend:etc/m
enu.xsd">
<menu>
<add
id="Packt_HelloWorld::helloworld"
title="HelloWorld"
module="Packt_HelloWorld"
sortOrder="50"
parent="Magento_Backend::marketing"
resource="Packt_HelloWorld::helloworld"
/>
<add
id="Packt_HelloWorld::index"
title="HelloworldIndex"
module="Packt_HelloWorld"
sortOrder="55"
parent="Packt_HelloWorld::helloworld"
action="helloworld/index/"
resource="Packt_HelloWorld::index"
/>
</menu>
</config>
3. Cleanthecacheandreloadthebackend.Whenopeningthemarketingmenu,youwillseethatthereisaHelloWorldgroupwithalink,likeinthefollowingscreenshot:
4. Tochangethepositionofthelinks,wecanplaywiththesortOrderattributeoftheconfiguration.
Howitworks…TheAdminmenuofMagentoisbasedonallthemenu.xmlfilesofallthemodules.Thestandardmenucontainsthefollowingrootitems:
DashboardSalesProductsCustomersMarketingContentReportsStoresSystem
Alltheseitemsaredefinedinthefileapp/code/Magento/Backend/etc/adminhtml/menu.xml,soifyouneedanidentifieroftherootitems,youhavetolookinthatfile.Ifalinkisnotconfiguredinthatfile,youhavetolookinthemenu.xmlfilesoftheothermodules,suchascatalog,customer,andreports.
Weaddedanitemtothemenuwithacustommenu.xmlfile.Inthatfile,weconfiguredtwostatementsthataddamenuitem.ThefirstoneisusedtoconfiguretheHelloWorldheadingofthemenu.Thisitemhasnoactionattribute,andtheparentisMagento_Backend::marketing(theIDoftheparentmenuitem).Thesecondoneisusedtoconfigurethelink.Intheactionattribute,theURLisconfigured.TheparentattributeistheIDoftheHelloWorldheading(Packt_HelloWorld::helloworld).
An<add/>statementcanhavethefollowingattributes:
id:Theidentifierofthemenuitemtitle:Thetitleofthemenuitemmodule:ThemodulethatthemenuitemwillrefertosortOrder:Thesequencebywhichallthemenuitemswillbeorderedparent:Theparentmenuitemaction:Iftheitemcontainsalink,thisistheURLleadingtothecontrolleractionresource:TheidentifieroftheACLresourceexplainedintherecipeAddinganACL
AddinganACLInthepreviousrecipesofthischapter,wecreatedabackendcontrolleractionthatisaccessibleusingtheadminmenu.However,whenyouwanttoconfigureacustomadminrole,youcan’trestricttheaccesstothispageforaspecificrole.Inthisrecipe,wewillcreateanAccessControlList(ACL)forthebackendpageandcreatearolewithrestrictedpermissionssoanadminusercanonlyviewarestrictedsetofpages.
GettingreadyEachadminuserisamemberofanadminrole.Theseroleshaveaccesspermissionsthatmanagetheaccesstocertainpagesinthebackend.
Inthisrecipe,wewilladdnewpermissionsforthepagewecreatedinthepreviousrecipesofthischapter.Makesureyouhaveinstalledthefilesofthepreviousrecipes.
Howtodoit…Thefollowingstepsdescribehowyoucanlimittheaccesstoapageforaroleofusers:
1. OpenthebackendandnavigatetoSystem|Permissions|UserRoles.ClickonthebuttonAddNewRoleandopentheRoleResourcestab.YouwillseeatreewithalltheavailableACLsinthisMagentoinstallation.
2. ThesecondstepistoaddanextraACLtothatpage.Forthisweneedtocreatethefileapp/code/Packt/HelloWorld/etc/acl.xmlwiththefollowingcontent:
<?xmlversion="1.0"?>
<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Acl/etc/acl.xsd">
<acl>
<resources>
<resourceid="Magento_Backend::admin">
<resourceid="Magento_Backend::marketing">
<resourceid="Packt_HelloWorld::helloworld"
title="HelloWorld"sortOrder="60">
<resourceid="Packt_HelloWorld::index"
title="HelloWorldindex"/>
</resource>
</resource>
</resource>
</resources>
</acl>
</config>
3. CleanthecacheandnavigatetoSystem|Permissions|UserRoles,clickontheAddNewRolebutton,andopentheRoleResourcestab.WhenyousearchforHelloWorld,youwillseethatthattheACLswehavejustcreatedareinthelist,asyoucanseeinthefollowingscreenshot:
4. SubmitthisformtoaddanewrolewiththeHelloWorldindexcheckboxchecked.Let’ssaywecallthisroleTestHelloWorld.
5. CreateanewbackenduserinSystem|Permissions|AllUsers.Fillintherequiredfieldsandaddtheusertotherolethatwehavejustcreated,asshowninthefollowingscreenshot:
6. WehavejustcreatedanACLforthepagethatisavailableat/admin/helloworld/index/index.WehavetoaddthefollowingfunctiontothatcontrollersothecontrolleractionknowswhichACLisactiveforthatpage.Openapp/code/Packt/HelloWorld/Controller/Adminhtml/Index/Index.phpandaddthefollowingfunctioninclassofthatfile:
protectedfunction_isAllowed()
{
return$this->_authorization->isAllowed('Packt_HelloWorld::index');
}
7. Logintothebackendwiththenewuserandyouwillseethatthisuserhasonlyaccesstothepagesthatwehaveconfiguredforthespecificroleofthatuser.
Howitworks…WiththeMagentoACLs,itispossibletorestricttheaccessofbackendpagestoaspecificuserrole.Forexample,theproductmanagerrolecanonlyaccesstheproducts,categories,andpromotionrulespages,andthelogisticpartneronlyhasaccesstotheorderpages.
Whenyoudon’tcreatetheACLsforcustompages,onlytherolesthathaveaccesstoalltheresourcescanaccessthepages.Inmostcases,thisistheadministrator.Forotherroles,itisnotpossibletoaccesspageswithnoACLs.
TipIntheMagentoCommunityEditions,itisnotpossibletorestricttheaccesstothedataofaspecificstore.Forexample,alogisticpartnercanonlyseetheordersofstore1.TheACLrestrictionsareonlybasedoncontrolleractions.
That’sthereasonthatwehaveconfiguredtheACLsintheacl.xmlfileofthemodule.Inthisfile,wedefinenewACLsinatreestructure.TheseACLshavesanidentifierthatisusedtomanagetheaccesstothemenuandcontrollerpage.
Inthecontrolleraction,wehavecreatedan_isAllowed()functionthatcheckstheaccesstothegivenACL.
Inthemenu.xmlfileofthemodule,theACLforeverymenuitemisconfiguredintheresourceattributeoftheconfigurations(likethehighlightedcodeshownhere):
<add
id="Packt_HelloWorld::helloworld"
title="HelloWorld"
module="Packt_HelloWorld"
sortOrder="50"
parent="Magento_Backend::marketing"
resource="Packt_HelloWorld::helloworld"
/>
AddingconfigurationparametersWhenyouwanttosavesomeconfigurationparametersforyourmodule,youcanusetheMagentoconfigurationtabletosaveyourconfigurationinit.YoucanfindalltheconfigurationformsunderStores|ConfigurationintheMagentobackend.Inthisrecipe,wewilladdanextrapagewithsomeconfigurationparameters.
GettingreadyLikethepreviousrecipesinthischapter,wewillbuildfurtheronthemodulethatwehavecreatedinthepreviousrecipes.Makesureyouhavethemodulefilesinstalled.
Howtodoit…Thefollowinginstructionsdescribewhatyouhavetodowhenyouwanttoaddsomecustomconfigurationparameters:
1. Thesystemconfigurationisalwaysconfiguredinthesystem.xmlfileofthemodule.Socreatethefileapp/code/Packt/HelloWorld/etc/adminhtml/system.xmlwiththefollowingcontent:
<?xmlversion="1.0"?>
<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/sy
stem_file.xsd">
<system>
</system>
</config>
2. Toaddanewtabtotheconfigurationmenu,wehavetoaddthefollowingconfigurationXMLinthesystem.xmlfile.Pastethefollowingcodeasachildofthe<system>tag:
<tabid="packt"translate="label"sortOrder="500">
<label>Packt</label>
</tab>
3. ThenextthingtodoistocreateanACLforthenewconfigurationpage.Addthefollowingconfigurationinthefileapp/code/Packt/HelloWorld/etc/acl.xml.Pastethefollowingcodeasachildofthe<resourceid="Magento_Backend::admin">tag:
<resourceid="Magento_Backend::stores">
<resourceid="Magento_Backend::stores_settings">
<resourceid="Magento_Config::config">
<resourceid="Packt_HelloWorld::config_helloworld"
title="HelloWorldSection"/>
</resource>
</resource>
</resource>
4. CleanthecacheandopenSystem|Permissions|UserRoles.ClickontheAddNewRolebuttonandopentheRoleResourcestab.IfeverythingisOK,youwillseeHelloWorldSectioninthelist,likeinthefollowingscreenshot:
5. WhenHelloWorldSectionisinthelist,itmeansthattheACLisadded.Don’tsavethenewrole;thisstepwasjusttoverifythattheACLsettingsareright.
6. WiththerightACLsettingsinstalled,wecanaddtherightcodeforanewMagentoconfigurationpage.Openthefileapp/code/Packt/HelloWorld/etc/adminhtml/system.xmlandaddthefollowingcodeasachildofthe<system>tag:
<sectionid="helloworld"translate="label"type="text"sortOrder="100"
showInDefault="1"showInWebsite="1"showInStore="1">
<label>HelloWorld</label>
<tab>packt</tab>
<resource>Packt_HelloWorld::config_helloworld</resource>
<groupid="hellopage"translate="label"type="text"sortOrder="1"
showInDefault="1"showInWebsite="1"showInStore="1">
<label>HelloWorldpagesettings</label>
<fieldid="is_enabled"translate="label"type="select"
sortOrder="10"showInDefault="1"showInWebsite="1"showInStore="1">
<label>IsEnabled</label>
<source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
</field>
<fieldid="header_title"translate="label"type="text"
sortOrder="20"showInDefault="1"showInWebsite="1"showInStore="1">
<label>Headertitle</label>
</field>
</group>
</section>
7. CleanthecacheandopenStores|Configuration.Ifeverythingwentwell,youwillseethePACKTgroupinthemenu.Whenyouclickonit,youwillseetheconfigurationfieldsIsEnabledandHeadertitle,asshowninthefollowingscreenshot:
8. Whenwewanttoconfigureadefaultvalueforthisfield,wehavetocreatethefileapp/code/Packt/HelloWorld/etc/config.xmlwiththefollowingcontent:
<?xmlversion="1.0"?>
<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/con
fig.xsd">
<default>
<helloworld>
<hellopage>
<is_enabled>1</is_enabled>
<header_title>HelloWorldtitle</header_title>
</hellopage>
</helloworld>
</default>
</config>
9. Cleanthecacheandreloadtheconfigurationpage.Youwillseethatthedefaultvaluesarefilledasspecifiedintheconfig.xmlfilethatwejustcreated.
Howitworks…AllthevaluesoftheMagentoconfigurationarespecifiedinthesystem.xmlfileofthemodule.Magentoreadseachfileandmergesthemtogetherwhenbuildingtheconfigurationpage.
Thefirstthingwedidwastocreateatabinthemenuoftheconfigurationpages.ThetabhasanIDattributethatisusedtolinkasectiontoatab.
Thesecondthingwastoaddanewpagewithconfigurationparameters.Becauseanewpagerequiresaccesspermissions,wehadtocreateanewACLforthatpage.Weextendedtheacl.xmlfilewithanACLforthenewconfigurationpage.
AftertheACL,wecreatedtheconfigurationtoshowthetwoconfigurationfields.TheXMLtreestartstodefinethesection.Asectionisusedtodefineanewconfigurationpage.Inthistutorial,wehavecreatedasectionwiththeidentifierhelloworld.
Eachsectionisdividedintogroups.Thesearetheaccordionsthatyoucanopenandcloseontheconfigurationpage.WecreatedagroupwiththeIDhellopage.
Inagroup,wecanconfiguretheconfigurationfields.Inthisrecipe,wecreatedtwofields.Thefirstfieldwasafieldwithadropdowntosaywhetheritisenabledornot.Thesecondonewasatextfieldtoconfigurethetitle.
Withthe<source_model>tag,weconfiguredthevaluesforthedropdownmenubyspecifyingtheclassofthesourcemodel.MoreinformationaboutsourcemodelscanbefoundintherecipeWorkingwithsourcemodelsinthischapter.
Thelastthingwedidinthisrecipewastoprovidesomedefaultvaluesfortheconfigurationparameters.Thedefaultvaluesaresetintheconfig.xmlfileofthemodule.Inthe<default>tag,youcanspecifyadefaultvalueforaconfigurationparameter.Let’stakealookatthefollowingcodesnippet:
<section_id>
<group_id>
<field_id>Value</field_id>
</group_id>
</section_id>
Makesureyoureplacesection_id,group_idandfield_idsoitmatchestheconfigurationforyourfield.
Eachsection,group,andfieldhastheattributesshowInDefault,showInWebsite,andshowInStore.Withtheseattributes,youcanspecifythattheconfigurationoptionisvisiblewhenconfiguringsomethingforthatscope.
InMagento,therearethreelevelsofconfigurationscopesavailable.ThesescopesareusedwhenyourunmultiplestoresonthesameMagentoinstallation.ThefollowingconfigurationscopesareavailableinMagento:
Globalconfiguration(showInDefault)Websiteconfiguration(showInWebsite)
Storeviewconfiguration(showInStore)
Whenyouwanttochangethescopeoftheconfigurationpage,youcanusetheStoreViewdropdownontheconfigurationpage,asshowninthefollowingscreenshot:
Whenyousavetheconfigurationform,thevaluesarestoredinthedatabasetablecore_config_data.Whenyouopenthattable,youseethefollowingcolumns:
config_id:TheconfigurationIDscope:Bydefault,thisiswebsiteorstorescope_id:TheIDofthewebsiteorstorepath:Theconfigurationpathvalue:Theconfigurationvalue
Thepathcolumnisthecombinationofthesectionid,groupid,andfieldidcolumns.Forthefieldsthatwecreatedinthisrecipe,thepathswillbeasshownhere:
helloworld/hellopage/is_enabled
helloworld/hellopage/header_title
CreatingagridofadatabasetableInthepreviouschapter,wecreatedaMagentoentitythatwaslinkedtoadatabasetable.Inthisrecipe,wewillcreateabackendinterfacesothatthebackenduserscanseethedatafromthistableinthebackend.
GettingreadyMakesureyouhavetherightfilesforthisrecipeinstalledinyourMagentoinstance.
WewillcreateanoverviewthatwillusethestandardMagentobackendgridwidget.Thiswidgetisusedforalotofgridsinthebackend,suchastheproducts,orders,CMSpages,andmore.
Howtodoit…Followthesestepstocreatebackendgrids:
1. Whenwewantanewpage,weneedacontrollerwithacontrolleraction.SoweneedtoaddanACLforthatpage.Openthefileapp/code/Packt/HelloWorld/etc/acl.xml,andaddthefollowinglineofcodeasachildofthe<resourceid="Packt_HelloWorld::helloworld"title="HelloWorld"sortOrder="60">tag:
<resourceid="Packt_HelloWorld::subscription"title="HelloWorld
subscription"/>
2. Weneedsomenavigationtothenewpage.Wecandothisbycreatingamenuitemforthepage.Openapp/code/Packt/HelloWorld/etc/adminhtml/menu.xmlandaddthefollowingcodeasachildofthe<menu>tag:
<add
id="Packt_HelloWorld::subscription"
title="Subscriptions"
module="Packt_HelloWorld"
sortOrder="70"
parent="Packt_HelloWorld::helloworld"
action="helloworld/subscription/"
resource="Packt_HelloWorld::subscription"
/>
3. Aspreviously,weneedtocreatethecontrolleraction.Createthefileapp/code/Packt/HelloWorld/Controller/Adminhtml/Subscription/Index.php
withthefollowingcontent:
<?php
namespacePackt\HelloWorld\Controller\Adminhtml\Subscription;
useMagento\Backend\App\Action\Context;
useMagento\Framework\View\Result\PageFactory;
classIndexextends\Magento\Backend\App\Action
{
protected$resultPageFactory;
publicfunction__construct(
Context$context,
PageFactory$resultPageFactory
){
parent::__construct($context);
$this->resultPageFactory=$resultPageFactory;
}
publicfunctionexecute()
{
$resultPage=$this->resultPageFactory->create();
$resultPage->setActiveMenu('Packt_HelloWorld::subscription');
$resultPage->addBreadcrumb(__('HelloWorld'),__('HelloWorld'));
$resultPage->addBreadcrumb(__('ManageSubscriptions'),
__('ManageSubscriptions'));
$resultPage->getConfig()->getTitle()-
>prepend(__('Subscriptions'));
return$resultPage;
}
protectedfunction_isAllowed()
{
return$this->_authorization-
>isAllowed('Packt_HelloWorld::subscription');
}
}
4. CleanthecacheandopentheMarketingmenuinthebackend.YouwillseethatthereisanewmenuitemaddedundertheHelloWorldsection.WhenyouclickonthelinkSubscriptions,youwillberedirectedtothepagethatwehavejustcreated.
5. Thebackendpageisnowupandrunning.Inthenextsteps,wewillcompletethisrecipebyaddingagridonthispage.
6. Toaddagrid,wehavetoaddthegridcontainerblock.Tocreatethisblock,wehavetocreatethefileapp/code/Packt/HelloWorld/Block/Adminhtml/Subscription.phpwiththefollowingcontent:
<?php
namespacePackt\HelloWorld\Block\Adminhtml;
classSubscriptionextends\Magento\Backend\Block\Widget\Grid\Container
{
protectedfunction_construct()
{
$this->_blockGroup='Packt_HelloWorld';
$this->_controller='adminhtml_subscription';
parent::_construct();
}
}
7. Inthegridcontainerblock,wehavetoaddthegridblock.Forthis,weneedtocreatethefileapp/code/Packt/HelloWorld/Block/Adminhtml/Subscription/Grid.phpwiththefollowingcontent:
<?php
namespacePackt\HelloWorld\Block\Adminhtml\Subscription;
useMagento\Backend\Block\Widget\GridasWidgetGrid;
classGridextends\Magento\Backend\Block\Widget\Grid\Extended
{
/**
*@var\Packt\HelloWorld\Model\Resource\Subscription\Collection
*/
protected$_subscriptionCollection;
/**
*@param\Magento\Backend\Block\Template\Context$context
*@param\Magento\Backend\Helper\Data$backendHelper
*@param
\Packt\HelloWorld\Model\ResourceModel\Subscription\Collection
$subscriptionCollection
*@paramarray$data
*/
publicfunction__construct(
\Magento\Backend\Block\Template\Context$context,
\Magento\Backend\Helper\Data$backendHelper,
\Packt\HelloWorld\Model\ResourceModel\Subscription\Collection
$subscriptionCollection,
array$data=[]
){
$this->_subscriptionCollection=$subscriptionCollection;
parent::__construct($context,$backendHelper,$data);
$this->setEmptyText(__('NoSubscriptionsFound'));
}
/**
*Initializethesubscriptioncollection
*
*@returnWidgetGrid
*/
protectedfunction_prepareCollection()
{
$this->setCollection($this->_subscriptionCollection);
returnparent::_prepareCollection();
}
}
8. Theblocksarecreated,butwehavetoaddthemtothecontrollerwithalayoutupdate.Createthefileapp/code/Packt/HelloWorld/view/adminhtml/layout/helloworld_subscription_index.xml
withthefollowingcontenttocreatethelayoutupdate:
<?xmlversion="1.0"?>
<pagexmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/pa
ge_configuration.xsd">
<body>
<referenceContainername="content">
<block
class="Packt\HelloWorld\Block\Adminhtml\Subscription"
name="adminhtml.block.helloworld.subscription.container">
<block
class="Packt\HelloWorld\Block\Adminhtml\Subscription\Grid"
name="adminhtml.block.helloworld.subscription.grid"as="grid"/>
</block>
</referenceContainer>
</body>
</page>
9. CleanthecacheandreloadtheSubscriptionspageinthebackend.Youwillseesomethinglikethefollowingscreenshot:
10. Inthescreenshot,weseethefilterbuttonsofthegrid,butifwewanttoshowthegrid,weneedtodefinethecolumns.Wecandothisbyaddingthefollowingfunctiontothefileapp/code/Packt/HelloWorld/Block/Adminhtml/Subscription/Grid.php.Pastethefunctionintheexistingclass.
/**
*Preparegridcolumns
*
*@return$this
*/
protectedfunction_prepareColumns()
{
$this->addColumn(
'subscription_id',
[
'header'=>__('ID'),
'index'=>'subscription_id',
]
);
$this->addColumn(
'firstname',
[
'header'=>__('Firstname'),
'index'=>'firstname',
]
);
$this->addColumn(
'lastname',
[
'header'=>__('Lastname'),
'index'=>'lastname',
]
);
$this->addColumn(
'email',
[
'header'=>__('Emailaddress'),
'index'=>'email',
]
);
$this->addColumn(
'status',
[
'header'=>__('Status'),
'index'=>'status',
]
);
return$this;
}
11. Whenyoureloadthepage,youwillseethatagridappearswiththecontentofthedatabasetable.
12. Whenwewanttodecoratethestatuscolumntoimprovethevisibilityofthestatuses,wehavetocreateaframe_callbackparameter.Addtheframe_callbackparametertotheaddColumn()functionofthestatussoitlookslikethis:
$this->addColumn(
'status',
[
'header'=>__('Status'),
'index'=>'status',
'frame_callback'=>[$this,'decorateStatus']
]
);
TipAddthehighlightedlineofcode.
13. frame_callbackreferstothefunctiondecorateStatus().Addthefollowingfunctiontotheclassthatwe’reediting:
publicfunctiondecorateStatus($value){
$class='';
switch($value){
case'pending':
$class='grid-severity-minor';
break;
case'approved':
$class='grid-severity-notice';
break;
case'declined':
default:
$class='grid-severity-critical';
break;
}
return'<spanclass="'.$class.'"><span>'.$value.'</span>
</span>';
}
14. Reloadthepage,andyoushouldhaveanoutputlikethatshowninthefollowingscreenshot:
Howitworks…ThebackendgridisoneofthebackendwidgetsthatisavailableinMagento.Otherwidgetsthatarewidelyusedaretheformstoeditdataorthetabbedmenuontheleft-handside.
Thegridwidgetismadetodisplaythecontentofacollectioninagrid.ByusingthegridwidgetofMagento,itispossibletosortandfilterthecolumnsthatarevisibleinthegrid.Apagerisautomaticallyincludedwhenyouhavealotofdata.Thispreventsout-of-memoryexceptionswhentherearealargenumberofrecordsinacollection.
Agridwidgetisalwaysplacedinsideagridcontainer.Westartedthisrecipebycreatingacontainerwidgetinwhichwewillplaceagrid.ThecontainerwidgetisusedtoshowtheheadingandbuttonsliketheAddNewbutton(whichhascurrentlynoaction).Agridcontainerblockalwaysextendsfromtheclass\Magento\Backend\Block\Widget\Grid\Container.
Inthatcontainer,wecreatedaGridblockthatisresponsibleforshowingthegrid.Agridblockalwaysextendsfromtheclass\Magento\Backend\Block\Widget\Grid.Inthisrecipe,ourclassextendsfrom\Magento\Backend\Block\Widget\Grid\Extended.Thisclassaddsthefilterstothegrid.Agridneedsacollection.Thiscollectionispassedasaparameterinthe__construct()methodoftheclass.Inthe_prepareCollection()method,thecollectionisassignedtotheclass.Agridneedsalsoaspecificationofthecolumnstobeshown.Thecolumnsarespecifiedinthe_prepareColumns()functionofthatclass.Inthatfunction,theaddColumn()functionisusedtospecifythecolumns.
TheaddColumn()functionhastwoparameters.Thefirstoneistheidentifier,andthesecondoneisakey-valuearraywiththespecificationsofthecolumn.Inthisrecipe,weusedthefollowingparameters:
header:Thenameofthecolumnindex:Thecolumninthedatabase
Thepreviousoptionsarerequired,whilethefollowingonesareoptional:
frame_callback(Afunctioncalltorenderthevalueofacell)type(Thisdefinesthefilterwidgetlikenumber,datetime,andoptions)options(Thisdefinesasourcemodelwhenthetypeisoptions)
WorkingwithbackendcomponentsTheMagentobackendexistswithalotofreusablecomponents,suchasabutton,menus,forms,andmore.Whencreatingbackendextensions,youcanusethesecomponentstobuildyoucustompages.Inthisrecipe,wewillcreateapagewherewecanplaywiththecomponentsavailable.
GettingreadyWewillextendtheexistingPackt_HelloWorldmodulewithanewpagewherewecanplaywiththebackendcomponents.
Howtodoit…Inthefollowingsteps,wewillcreateapagethatusessomebackendcomponents:
1. Wewillmakesometestsinanewcontrolleraction.Createafileapp/code/Packt/HelloWorld/Controller/Adminhtml/Component/Index.phpwiththefollowingcontent:
<?php
namespacePackt\HelloWorld\Controller\Adminhtml\Component;
useMagento\Backend\App\Action\Context;
useMagento\Framework\View\Result\PageFactory;
classIndexextends\Magento\Backend\App\Action
{
protected$resultPageFactory;
publicfunction__construct(
Context$context,
PageFactory$resultPageFactory
){
parent::__construct($context);
$this->resultPageFactory=$resultPageFactory;
}
publicfunctionexecute()
{
$resultPage=$this->resultPageFactory->create();
$resultPage->setActiveMenu('Packt_HelloWorld::component');
$resultPage->addBreadcrumb(__('HelloWorld'),__('HelloWorld'));
$resultPage->addBreadcrumb(__('Components'),__('Components'));
$resultPage->getConfig()->getTitle()-
>prepend(__('Components'));
return$resultPage;
}
protectedfunction_isAllowed()
{
return$this->_authorization-
>isAllowed('Packt_HelloWorld::helloworld');
}
}
2. CreateamenuitemforthepagebyaddingthefollowingXMLinthefileapp/code/Packt/HelloWorld/etc/adminhtml/menu.xml.Pastethecodeasachildofthe<menu>tag:
<add
id="Packt_HelloWorld::component"
title="Components"
module="Packt_HelloWorld"
sortOrder="80"
parent="Packt_HelloWorld::helloworld"
action="helloworld/component/"
resource="Packt_HelloWorld::helloworld"
/>
3. Cleanthecacheandreloadthebackend.Inthemenu,youwillseeanitemthatleadstothecomponentpagethatwehavejustcreated.ThismenuitemisvisibleintheMarketingmenu.Whenyouopenthatpage,weseeanemptypage.
4. Toaddsomebuttonstothepage,wehavetocreatethefileapp/code/Packt/HelloWorld/view/adminhtml/templates/component/toolbar/buttons.phtml
withthefollowingcontent:
<divclass="page-actions">
<divclass="page-actions-inner">
<divclass="page-actions-buttons">
<button
class="action-primary"
title="<?phpecho__('Primarybutton')?>">
<?phpecho__('Primarybutton')?>
</button>
<button
class="action-secondary"
title="<?phpecho__('Secondarybutton')?>">
<?phpecho__('Secondarybutton')?>
</button>
<button
class="action-secondaryback"
title="<?phpecho__('Back')?>">
<?phpecho__('Back')?>
</button>
</div>
</div>
</div>
5. Toaddthisfiletothecomponentspage,wehavetoaddthefileapp/code/Packt/HelloWorld/view/adminhtml/layout/helloworld_component_index.xml
withthefollowingcontent:
<?xmlversion="1.0"?>
<pagexmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/pa
ge_configuration.xsd">
<body>
<referenceContainername="page.main.actions">
<blockclass="Magento\Backend\Block\Template"
name="component_buttons"
template="Packt_HelloWorld::component/toolbar/buttons.phtml"/>
</referenceContainer>
</body>
</page>
6. Cleanthecacheandreloadthepage.Youwillnowseesomethinglikeinthefollowingscreenshot:
7. Wewillcontinuewithaddingthecontentofthepage.Thefollowingcodeisanexampleofthepossibilitiesforformcomponents.Additinthefileapp/code/Packt/HelloWorld/view/adminhtml/templates/component/index.phtml
<divclass="col-m-4">
<fieldsetclass="fieldsetadmin__fieldset"id="theme">
<divclass="admin__fieldfield"data-ui-id="theme-edit-tabs-
tab-general-section-fieldset-element-form-field-theme-path">
<labelclass="labeladmin__field-label"><span>Label</span>
</label>
<divclass="admin__field-controlcontrol">
<divclass="control-value">Avalue</div>
</div>
</div>
<divclass="admin__fieldfield">
<labelclass="labeladmin__field-label"for="test_input">
<span>Testinput</span></label>
<divclass="admin__field-controlcontrol">
<inputname="design[test_input]"id="test_input"
value=""title="Testinput"type="text"class="input-text
admin__control-text"/>
</div>
</div>
</fieldset>
</div>
8. Toaddthisfiletothepage,addthefollowinglineofcodetothefileapp/code/Packt/HelloWorld/view/adminhtml/layout/helloworld_component_index.xml
asachildofthe<body>tag:
<referenceContainername="content">
<blockclass="Magento\Backend\Block\Template"
name="adminhtml.block.helloworld.component"
template="Packt_HelloWorld::component/index.phtml"/>
</referenceContainer>
9. Cleanthecacheandreloadthepage.Youwillseethataformappearsonthescreen.
NoteMagentoalsohasaformAPIthatgeneratestheelementsforyou.ThisAPIiswidelyusedforbackendformssuchasfortheCMSpages(andmanyotherforms).
10. Thefollowingcodeshowshowtoaddasmalldatatabletoapage.Addittotheendofthefileapp/code/Packt/HelloWorld/view/adminhtml/templates/component/index.phtml
<divclass="col-m-4">
<tableclass="admin__table-secondary">
<tr>
<th><?phpecho__('Firstname')?></th>
<td>John</td>
</tr>
<tr>
<th><?phpecho__('Lastname')?></th>
<td>Doe</td>
</tr>
<tr>
<th><?phpecho__('Email')?></th>
<td>[email protected]</td>
</tr>
<tr>
<th><?phpecho__('Phone')?></th>
<td>000000000</td>
</tr>
</table>
</div>
11. Reloadthepage,andyouwillseeasmalltableneartheform.
Howitworks…Inthisrecipe,wecreatedsomeHTMLthatisusedtorenderbackendcomponents.TheMagentobackendisacombinationofmanyofthesecomponents,suchasthebuttonbar,forms,tables,grids,andmanymore.
Inthefirststeps,wecreatedacontrolleractionthatusesanexistingACL.Onthiscontrolleraction,weplacedablockthatcontainsthebuttons.Thebuttonbarisplacedinthepage.main.actionsblock.Thisblockisthegrayactionbarthatappearsonmanypagesinthebackend.
Later,wecreatedatemplatethatcontainssomebackendcomponents.Thefirstonewasaformwithafieldsetelement.Thisfieldsetelementcontainsatextbox,alabel,andanon-offbutton.
Thelastthingwedidwastocreateasmalltablewithdatainanewcolumn.Forthesecolumns,weusedtheclasscol-m-4.Byusingthisclass,thedivinstancewillhaveawidthoffourcolumns.ThestandardMagentobackendisdividedinto12columns,sofourcolumnsisathirdofthewidthofthebackendpage.
AddingcustomerattributesInsomecases,itcouldbethatweneedextraattributesforacustomerlikewedidforproducts.BecauseacustomerisanEAVobject,itispossibletoaddattributestoit,butthereisnointerfaceforthatintheCommunityEditionofMagento.Whenwewanttodothat,weneedtoinstalltheattributesbycode,andthat’sthethingthatwewilldointhisrecipe.Wewilladdanewfieldloyaltynumbertothecustomer.
GettingreadyToaddacustomerattribute,weneedtocreateaninstallationorupgradescript.Inthisrecipe,wewillcreateanewmodulethatwillinstalltheattributewiththeinstallationscriptsowedon’tneedtoinstallstarterfilesinMagento.
Howtodoit…Inthefollowingsteps,wewillcreateasmallmodulethataddsacustomerattribute:
1. CreateamodulePackt_CustomerAttributebycreatingafileapp/code/Packt/CustomerAttribute/etc/module.xmlwiththefollowingcontent:
<?xmlversion="1.0"?>
<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.
xsd">
<modulename="Packt_CustomerAttribute"setup_version="2.0.0">
<sequence>
<modulename="Magento_Customer"/>
</sequence>
</module>
</config>
2. Inapp/code/Packt/HelloWorld/,createafilecalledregistration.phpwiththefollowingcontent:
<?php
\Magento\Framework\Component\ComponentRegistrar::register(
\Magento\Framework\Component\ComponentRegistrar::MODULE,
'Packt_CustomerAttribute',
__DIR__
);
3. Createadatainstallationscriptbycreatingafileapp/code/Packt/CustomerAttribute/Setup/InstallData.phpwiththefollowingcontent:
<?php
namespacePackt\CustomerAttribute\Setup;
useMagento\Framework\Setup\InstallDataInterface;
useMagento\Framework\Setup\ModuleContextInterface;
useMagento\Framework\Setup\ModuleDataSetupInterface;
classInstallDataimplementsInstallDataInterface
{
private$customerSetupFactory;
publicfunction
__construct(\Magento\Customer\Setup\CustomerSetupFactory
$customerSetupFactory)
{
$this->customerSetupFactory=$customerSetupFactory;
}
publicfunctioninstall(ModuleDataSetupInterface$setup,
ModuleContextInterface$context)
{
/**@varCustomerSetup$customerSetup*/
$customerSetup=$this->customerSetupFactory->create(['setup'
=>$setup]);
$setup->startSetup();
$customerSetup->addAttribute('customer','loyaltynumber',[
'label'=>'Loyaltynumber',
'type'=>'static',
'frontend_input'=>'text',
'required'=>false,
'visible'=>true,
'position'=>105,
]);
$loyaltyAttribute=$customerSetup->getEavConfig()-
>getAttribute('customer','loyaltynumber');
$loyaltyAttribute->setData('used_in_forms',
['adminhtml_customer']);
$loyaltyAttribute->save();
$setup->endSetup();
}
}
4. Toaddtheattributetothebackend,wehavetocreateaui_componentXMLfile.Createafileapp/code/Packt/CustomerAttribute/view/base/ui_component/customer_form.xml
withthefollowingcontent:
<?xmlversion="1.0"encoding="UTF-8"?>
<formxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_con
figuration.xsd">
<fieldsetname="customer">
<fieldname="loyaltynumber">
<argumentname="data"xsi:type="array">
<itemname="config"xsi:type="array">
<itemname="dataType"xsi:type="string">text</item>
<itemname="formElement"
xsi:type="string">input</item>
<itemname="source"
xsi:type="string">customer</item>
</item>
</argument>
</field>
</fieldset>
</form>
5. Toinstalltheattributeinthedatabase,runthecommandphpbin/magentosetup:upgrade.
6. Inthebackend,createanewcustomer.YouwillseetheLoyaltynumberattributeintheformlikeinthefollowingscreenshot:
Howitworks…WestartedtocreateasimplemodulewithadatainstallationscriptlikewedidinChapter4,CreatingaModule,andChapter5,DatabasesandModules.
Intheinstallationscript,weplacedsomecodethatinstallsacustomerattribute.Thisworksinlikelythesamewayasaddingaproductattribute.ThemaindifferenceisthefirstparameteroftheaddAttribute()function.
Toaddacustomerattribute,wehavetoplacethevaluecustomerinthatparameter.Theoptionsofthethirdparameterarealsodifferentwhenyoucompareitwithaproduct.
Theconfigurationofacustomerattributeissavedinthefollowingtwotables:
eav_attribute
customer_eav_attribute
Inthetableeav_attribute,thegenericdataoftheattribute,suchastheentitytypeandtheattributecode,issaved.
Inthetablecustomer_eav_attribute,thespecificdataforacustomerattribute,suchasthepositionandvalidationrules,issaved.
TheattributeisaddedtothedatabasewiththeaddAttribute()function.Toaddtheattributetothecustomerform,wehavetosavethatconfigurationinthedatabasewiththefollowingcode:
$loyaltyAttribute->setData('used_in_forms',['adminhtml_customer']);
Withtheprecedingcode,theattributewillbelinkedtotheadminhtml_customerform.Thisisthecustomerformofthebackend.
Anotherrequiredstepistoaddtheattributeintheui_componentXMLconfiguration.ThisconfigurationworkslikethelayoutXMLfiles.ThislayoutXMLfileworksasalayouthandle.Whenyoulookinthefileapp/code/Magento/Customer/view/adminhtml/layout/customer_index_edit.xml,youwillseethefollowingcodethatinitializesthelayoutupdateoftheui_component:
<uiComponentname="customer_form"/>
Withourcustomcustomer_formUIcomponent,weextendedthefieldsofthecustomerentity.
WorkingwithsourcemodelsMagentoworkswithalotofdropdownfieldsthatyoucanselectintheformsoftheapplication.Wecanseedropdownsintheconfiguration,product,customer,andmanymorepages.
Magentohasasystemtosettheoptionsofthedropdownandmultiselectfields.Magentousesamodelthatreturnsthevaluesandlabelstorendertheoptionsofadropdownormultiselectfield.Thesemodelsarecalledsourcemodels.
Inthisrecipe,wewillseewhichsourcemodelsMagentousesandhowwecancreateacustomsourcemodelforacustomconfigurationfield.
GettingreadyInthisrecipe,wewillextendthePackt_HelloWorldmodulethatwecreatedinthepreviousrecipes.Makesureyouhavetherightversioninstalledforthisrecipe.
Howtodoit…Thefollowingstepsdescribehowyoucancreateyourcustomsourcemodelsforyourcustomformfields:
1. Firstwewillcreateanextrafieldinthesystemconfigurationtorunsometests.ThefollowingcodeaddsanewfieldtotheHelloWorldconfigurationpage.Addittothefileapp/code/Packt/HelloWorld/etc/adminhtml/system.xmlasachildofthe<groupid="hellopage"translate="label"type="text"sortOrder="1"
showInDefault="1"showInWebsite="1"showInStore="1">tag.
<fieldid="source_model_test"translate="label"type="text"
sortOrder="30"showInDefault="1"showInWebsite="1"showInStore="1">
<label>Sourcemodeltest</label>
</field>
2. Inthebackend,openStores|Configuration|Packt|HelloWorld.YouwillseeanewtextfieldcalledSourcemodeltest.
3. Ifwewanttochangethatfieldtoadropdown,wehavetochangethetypeattributetoselectlikeinthefollowingcodesnippet:
<fieldid="source_model_test"translate="label"type="select"
sortOrder="30"showInDefault="1"showInWebsite="1"showInStore="1">
<label>Sourcemodeltest</label>
</field>
4. Cleanthecacheandreloadthepage.Youwillnowseeafieldwithanemptydropdown.
5. Toaddoptionstothedropdown,wehavetospecifyasourcemodel.Wecandothisbyaddingthe<source_model>tagasinthefollowingcode:
<fieldid="source_model_test"translate="label"type="select"
sortOrder="30"showInDefault="1"showInWebsite="1"showInStore="1">
<label>Sourcemodeltest</label>
<source_model>Magento\Config\Model\Config\Source\Locale</source_model>
</field>
6. Ifwewanttocreateacustomsourcemodel,wehavetocreateacustommodelclass.Createafileapp/code/Packt/HelloWorld/Model/Config/Source/Relation.phpwiththefollowingcontent:
<?php
namespacePackt\HelloWorld\Model\Config\Source;
classRelationimplements\Magento\Framework\Option\ArrayInterface
{
publicfunctiontoOptionArray()
{
return[
[
'value'=>null,
'label'=>__('--PleaseSelect--')
],
[
'value'=>'bronze',
'label'=>__('Bronze')
],
[
'value'=>'silver',
'label'=>__('Silver')
],
[
'value'=>'gold',
'label'=>__('Gold')
],
];
}
}
7. Toassignthepreviouslycreatedsourcemodeltothetestconfigurationfield,wehavetochangetheline<source_model>inthesystem.xmlfile.Changeittothefollowing:
<source_model>Packt\HelloWorld\Model\Config\Source\Relation</source_mod
el>
8. Cleanyourcacheandyouwillseethattheoptionsofthefieldarechangedbasedontheoutputfromthesourcemodel.Youcanseethisinthefollowingscreenshot:
Howitworks…AsourcemodelisamodelinstancewithatoOptionArray()function.Thisfunctionreturnsanarraywithalltheitemsofthesourcearray.Thisarrayhasthefollowingformat:
[
'value'=>'0',
'label'=>'Labeloption0'
],
[
'value'=>'1',
'label'=>'Labeloption1'
]
Thevaluekeyisthevalueofthe<option>elementinthedropdownlist.Thelabelkeyisthetextthatappearsinthedropdownlist.
Inthisrecipe,weconfiguredasourcemodelforaconfigurationfield.Wecanalsousesourcemodelsinthefollowingcases:
AproductattributeinthebackendAcustomerattributeinthebackendDropdownfiltersinbackendgridsMagentoformsinthefrontendandbackend(likethecountrydropdownatcheckoutandsoon)
TheconfigurationofthesourcemodelismostlydoneintheXMLconfigurationofthefield.ForEAVfields,theinformationofthesourcemodelisstoredintheattributeconfiguration,whichisstoredinthedatabase.
Whenadropdownfieldormultiselectfieldissaved,itisalwayssavedinasinglefieldofthedatabase.Ifafieldisadropdown,thevaluewillbestoredforthatfield.Whenthefieldisamultiselectfield,acomma-separatedlistoftheselectedvalueswillbesavedinthatfield.
Chapter7.EventHandlersandCronjobsInthischapter,wewillcoverthefollowingtopics:
UnderstandingeventtypesCreatingyourowneventAddinganeventobserverIntroducingcronjobsCreatingandtestinganewcronjob
IntroductionIntheMagentoapplication,therearealotofeventsthathappenwhenvisitorsarebrowsingthroughyourwebsite.Thevisitorcanaddsomethingtothecart,login,createanorder,anddoalotmore.
Magentohasaneventsystemthatfireseventswhensomeactionshappensinyourshop.Withaconfiguration,itispossibletoexecutesomecodewhenaneventoccurs.It’slikehookingintoaclickeventinJavaScript.
Theobserverdesignpatternisusedtoimplementtheeventhandlingsystem.Whenaneventistriggered,Magentolooksforeventobserversthathookintothateventandexecutetherightmethodthatisconfiguredforthateventhandler.
AnothersimilarsysteminMagentoisacronjob.Intheconfiguration,youcancreateacronjobthatwillbeexecutedonaspecifictimestamp.Whenitistherighttime,Magentowillexecutethecodethatisconfiguredforthatcronjob.
Inthischapter,wewillexplorethepossibilitiesofusingthesetwosystems(Eventhandlersandcronjobs).
UnderstandingeventtypesWorkingwitheventtypesisbetterthanoverwritingafunctionwithdependencyinjection.Whenanalyzingaprocess,itisgoodtothinkabouthowyoucansolveyourproblem.ItisbettertoexecuteextracodeinsteadofrewritingthestandardfunctionsofMagento.
Withevents,itispossibletoexecutecodewhensomethinghappens,butbeforewecandothat,wehavetoknowwhicheventsareavailable,whentheyaredispatched,andwhichparametersareavailable.
GettingreadyInthisrecipe,wewilldebugtheeventsthatarefiredinMagento.EnsurethatyouhaveaccesstothecommandlinebecausewewilldebugusingtheMagentologfiles.
Howtodoit…ThefollowingstepsdescribehowwecancreatealistfromthedispatchedeventsinaMagentorequest:
1. Magentoeventsaredispatchedusingthedispatch()methodoftheeventManagerinterface.Whenwewanttodebugthisfunction,wehavetomodifytheMagento\Framework\Event\Managerclass.Thefirstthingtodoistoenabletheloggerinterface.Createthe$_loggervariableinthelib/internal/Magento/Framework/Event/Manager.phpfile,asshownbythehighlightedcode.
...
protected$_eventConfig;
/**
*Loggerinterface
*@var\Psr\Log\LoggerInterface$logger
*/
protected$_logger;
/**
*@paramInvokerInterface$invoker
*@paramConfigInterface$eventConfig
*/
publicfunction__construct(InvokerInterface$invoker,ConfigInterface
$eventConfig){
$this->_invoker=$invoker;
...
NoteIfyouhaveinstalledMagentowithcomposer,youwillhavetoeditthevendor/magento/framework/Event/Manager.phpfile.
2. Addtheloggerinterfacetotheconstructorandinitializetheparameterthatwehavejustcreated.Replacetheconstructorofthatclasswiththefollowingcode:
/**
*@paramInvokerInterface$invoker
*@paramConfigInterface$eventConfig
*@param\Psr\Log\LoggerInterface$logger
*/
publicfunction__construct(InvokerInterface$invoker,ConfigInterface
$eventConfig,\Psr\Log\LoggerInterface$logger){
$this->_invoker=$invoker;
$this->_eventConfig=$eventConfig;
$this->_logger=$logger;
}
3. Inthesameclass,gotothedispatch()function.Inthefirstline,wewilladdalogstatementtoprinttheeventnameinthelogfilewhenaneventisfired.Addthehighlightedlineofcodetothatfunction:
publicfunctiondispatch($eventName,array$data=[]){
$this->_logger->debug($eventName);
\Magento\Framework\Profiler::start('EVENT:'.$eventName,['group'=>
'EVENT','name'=>$eventName]);
foreach($this->_eventConfig->getObservers($eventName)as
$observerConfig){
$event=new\Magento\Framework\Event($data);
$event->setName($eventName);
$wrapper=newObserver();
$wrapper->setData(array_merge(['event'=>$event],$data));
\Magento\Framework\Profiler::start('OBSERVER:'.
$observerConfig['name']);
...
4. Takealookatthelogfilethatyoucanfindinthevar/log/debug.logfolder.Usingthetail-fcommand,youcanseewhattextwillbeaddedtothatfile.InyourMagentoinstallation,changethedirectorytoMagentorootandrunthefollowingcommand:
tail-fvar/log/debug.log
5. Whenyouloadapage,youwillseealotofoutputinthelogfileasshowninthefollowingscreenshot.Thesearealltheeventsthataredispatchedwhenloadingapage:
6. Enoughdebuggingfornow.Itistimetorevertthelib/internal/Magento/Framework/Event/Manager.phpfile.Undoyourchanges,orifyouuseGit,youcanusethegitcheckoutlib/internal/Magento/Framework/Event/Manager.phpcommandtoundothelocalchangesinthatfile.
Howitworks…Inthisrecipe,weusedtheMagentodebugloggertoprintthenamesofthefiredevents.Thisisanalternativeforprintingthenamesdirectlyinthebrowser.Themainadvantageofthismethodisthatthedebugmessagesdoesn’taffecttheoutputofyourbrowser.
IfyouareawareaboutthesystemofMagento1,youwillnoticethatlogginginMagento2ischanged.InMagento1,wehadtheMage::log()function,butthisfunctiondoesn’texistinMagento2.InMagento2,wehavetousetheloggerinterfacethatweinitializeintheconstructorofaclass.
Todebugtheeventnames,weusedthedebug()functionoftheloggerinterfacethatwritesamessagetothevar/log/debug.logfile.Intheloggerinterface,thefollowingfunctionsareavailable:
alert()
critical()
debug()
emergency()
error()
info()
log()
notice()
warning()
Thedebug()functionwritestothevar/log/debug.logfile,andtheexception()functionwritestothevar/log/exception.logfile.Theotherfunctionswritetovar/log/system.log.
Weplacedthelogstatementinthedispatch()functionofthelib/internal/Magento/Framework/Event/Manager.phpclass.Thisfunctioniscalledeverytimeaneventisdispatched.Byloggingthename,wecanseealltheeventsthatarecalledinaMagentorequest.
Onalltheseevents,wecanwritehooksthatexecutethecodewhenaneventisdispatched.Asyoucansee,thisfunctiondoesn’treturnsomethingtotheparent,soitisnotpossibletosenddatabacktotheinitiatoroftheevent.However,youcanmodifytheobjectsthataresentwiththeevent.
Fordebuggingpurposes,wemodifiedacoreclassofMagento.Attheend,werevertedtheclasstotheoriginalcode.ModifyingthecoreofMagentoisnotrecommended,butfordebugging,youcandoitifyourevertyourcodewhenthedebuggingisdone.
SeealsoMagento2iscurrentlynewanddoesnothavealotofdocumentation,butyoucanfindmoreinformationaboutsomeoftheavailableeventsinMagento2athttp://cyrillschumacher.com/magento2-list-of-all-dispatched-events/.
CreatingyourowneventWhenwecreateourownevent,wehavetodispatchitwithacustomname.Inthisrecipe,youwilllearnhoweventsaredispatchedandwhatwecandowithparametersthataresentwiththeevent.
GettingreadyWewillcreateaneventthatisfiredwhenavisitoropensthe/helloworld/index/eventpage.
ThecodeinthisrecipebuildsfurtheronthePackt_HelloWorldmodulethatwecreatedinChapter4,CreatingaModule,Chapter5,DatabasesandModulesandChapter6,MagentoBackend.Ensurethatyouhaveinstalledthestartfiles.
Howtodoit…Thefollowingstepsdescribehowwecandispatchourownevent:
1. First,wewillcreatetheeventpage.Forthis,weneedacontrolleraction.Createtheapp/code/Packt/HelloWorld/Controller/Index/Event.phpfilewiththefollowingcontent:
<?php
namespacePackt\HelloWorld\Controller\Index;
classEventextends\Magento\Framework\App\Action\Action{
/**@var\Magento\Framework\View\Result\PageFactory*/
protected$resultPageFactory;
publicfunction__construct(
\Magento\Framework\App\Action\Context$context,
\Magento\Framework\View\Result\PageFactory$resultPageFactory
){
$this->resultPageFactory=$resultPageFactory;
parent::__construct($context);
}
publicfunctionexecute(){
$resultPage=$this->resultPageFactory->create();
return$resultPage;
}
}
2. Cleanthecacheandnavigatetothe/helloworld/index/eventpage.YouwillseeaMagentopagewithoutanycontent.
3. Whenwewanttodispatchaneventonthepageview,wehavetoaddthehighlightedcodeintheexecute()action:
publicfunctionexecute(){
$resultPage=$this->resultPageFactory->create();
$this->_eventManager->dispatch('helloworld_register_visit');
return$resultPage;
}
4. Itisalsopossibletosendsomeparameterswiththeevent.Wewillsendaproductandcategorywiththeevent.Tosendtheevent,wehavetopassthefollowingkey-valuearraytotheevent:
publicfunctionexecute(){
$resultPage=$this->resultPageFactory->create();
$parameters=[
'product'=>$this->_objectManager-
>create('Magento\Catalog\Model\Product')->load(50),
'category'=>$this->_objectManager-
>create('Magento\Catalog\Model\Product')->load(10),
];
$this->_eventManager->dispatch('helloworld_register_visit',
$parameters);
return$resultPage;
}
NoteEnsurethattheproductIDandcategoryIDexistsinyourinstallation.Ifnot,youcanchooseanotherIDthatexistsinyourinstallation.
Howitworks…Thedispatch()functionoftheeventManagermethodfiresaneventinMagento.Theeventmanageriscreatedintheconstructofthecontrollerclass(inthisexample,itisthecontrolleraction)andstoredinthe$_eventManagervariable.Thisisaninstanceofthe\Magento\Framework\Event\ManagerInterfaceclass.
Whenthisfunctionisfired,Magentowilllookintotheconfigurationtoseewhicheventlistenersarewatchingforthatevent.Iftherearematchinglisteners,Magentowillexecutethecodeoftheobserver.
AddinganeventobserverIfyoureadthepreviousrecipe,youknowhowtofireaneventusingthedispatch()function.Inthisrecipe,youwilllearnhowtoexecutesomecodewhenaneventisdispatchedandwhatwecandowithit.
GettingreadyInthisrecipe,wewilladdtwoeventobservers.Thefirstonewillcatchtheeventthatwecreatedinthepreviousrecipe.Thesecondeventlistener(observer)willhookintotheaddtocartactionofaproduct.
Inthisrecipe,wewillworkfurtheronthePackt_HelloWorldmodulefromthepreviousrecipes.Ensurethatyouhavetherightcodefilesinstalledforthisrecipe.
Howtodoit…Inthistutorial,youwilldiscoverhowtolistenforaneventandexecuteyourcodebyperformingthesesteps:
1. Whenwewanttoaddaneventobserverforthehelloworld_register_visitevent,wehavetoaddthefollowingconfigurationtotheapp/code/Packt/HelloWorld/etc/events.xmlfilewiththefollowingcontent:
<?xmlversion="1.0"?>
<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.x
sd">
<eventname="helloworld_register_visit">
<observername="register_helloworld_visit"
instance="Packt\HelloWorld\Observer\RegisterVisitObserver"/>
</event>
</config>
2. TheconfigurationofthepreviousfileexecutestheregisterVisit()functioninthePackt\HelloWorld\Observer\RegisterVisitObserverclass.Tocreatethisclass,wehavetoaddtheapp/code/Packt/HelloWorld/Model/Observer.phpfilewiththefollowingcontent:
<?php
namespacePackt\HelloWorld\Observer;
useMagento\Framework\Event\ObserverInterface;
classRegisterVisitObserverimplementsObserverInterface
{
/**@var\Psr\Log\LoggerInterface$logger*/
protected$logger;
publicfunction__construct(\Psr\Log\LoggerInterface$logger)
{
$this->logger=$logger;
}
publicfunctionexecute(\Magento\Framework\Event\Observer
$observer)
{
$this->logger->debug('Registered');
}
}
3. Now,itistimetotestourevent.Cleanthecacheandopenyourterminalandexecutethetail-fvar/log/debug.logcommandinyourMagentoroot.Whenyouloadthe/helloworld/index/eventpage,youwillseethattheRegisteredmessageappearsinthelogfile.
4. Let’slookattheparametersthataresentwiththeevent.Theseparametersarestoredinthe$observervariablethatispassedtotheexecute()method.Addthefollowing
codetothismethodtodebugtheproductandcategorythatispassedtothatevent:
publicfunctionexecute(\Magento\Framework\Event\Observer$observer)
{
$product=$observer->getProduct();
$category=$observer->getCategory();
$this->logger->debug(print_r($product->debug(),true));
$this->logger->debug(print_r($category->debug(),true));
}
5. Whenyoureloadthe/helloworld/index/eventpage,youwillseethatadumpofproductsandcategoriesappearsinthelogfile.
TipWhennothingappearsinyourlogfile,itispossiblethatyourfullpagecacheisactive.Ifthisisso,youcandisableitandflushthecacheafterdisablingit.Youcanalsoflushthecacheeachtimeyouaretesting.
6. Forthenextpart,wewillhookintotheaddtocartevent.Whenauseraddsaproducttothecart,weneedtocheckthatthequantityisodd.Ifnot,wewillhavetoshowanerrormessagethattheproductcan’tbeaddedtothecart.
7. Wecandothisbycreatinganeventobserverforthecheckout_cart_product_add_afterevent.Tocreateaneventlistenerforthisevent,addthefollowingcodeintheapp/code/Packt/HelloWorld/etc/frontend/events.xmlfile:
<?xmlversion="1.0"?>
<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.x
sd">
<eventname="checkout_cart_product_add_after">
<observername="check_cart_qty"
instance="Packt\HelloWorld\Observer\CheckCartQtyObserver"/>
</event>
</config>
8. Createtheapp/code/Packt/HelloWorld/Observer/CheckCartQtyObserver.phpfilewiththefollowingcontent:
<?php
namespacePackt\HelloWorld\Observer;
useMagento\Framework\Event\ObserverInterface;
classCheckCartQtyObserverimplementsObserverInterface
{
publicfunctionexecute(\Magento\Framework\Event\Observer
$observer)
{
if($observer->getProduct()->getQty()%2!=0){
//Oddqty
thrownew\Exception('Qtymustbeeven');
}
}
}
9. Cleanthecacheandtrytoaddsomethingtothecartwithanevenandoddquantity.Youwillseethatanerroroccurswhenyouaddanoddquantity.Whenyouaddanevenquantity.
Howitworks…Eventlistenersareconfiguredintheevents.xmlfile.WhenwelookatthestructureoftheXMLfile,wecansaythefollowingthingsabouttagsandattributeswhenwehavethefollowingcode:
<?xmlversion="1.0"?>
<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
<eventname="checkout_cart_product_add_after">
<observername="check_cart_qty"
instance="Packt\HelloWorld\Observer\CheckCartQtyObserver"/>
</event>
</config>
Theroottagisthe<config>tag.Thistagcanhaveoneormore<event>subtags.
The<event>subtagdefinesaneweventobserver.Withthenameattribute,weconfigurethenameoftheeventthatwillbeobserved.Inthisrecipe,weusedthecheckout_cart_product_add_aftereventtocheckthequantity.Thiseventisdispatchedintheapp/code/Magento/Checkout/Model/Cart.phpfile.
The<event>tagcanhaveoneormore<observer>subtags.Withthistag,weconfiguretheeventobserver.Thenameattributeisthenameoftheobserver.Thisnamemustbeunique.Theinstanceattributeistheclassthatwillbecalled.
Forthepreviousexample,thismeans:
Thecheckout_cart_product_add_aftereventwillbeobservedThenameoftheobserverischeck_cart_qtyTheexecute()methodwillbeexecutedfromtheclassPackt\HelloWorld\Observer\CheckCartQtyObserver
Withaneventobserver,thereisoneobjectpassedthatcontainsanarrayofdatathatissentwiththeeventinthedispatch()function.
Inthepreviousrecipe,wedispatchedaneventcalledhelloworld_register_visit.Withthatdispatch,weaddedaproductandcategorytothatevent.
Inthisrecipe,weextractedaproductandcategoryfromthe$observerparameter,whichisthefirstargumentoftheobservedeventfunction.
IntroducingcronjobsCronjobs,orscheduledtasks,arebackgroundprocessesthatkeepyourMagentowebshoprunningbyautomatingsometasks.Someexamplesofcronjobsareasfollows:
SendingnewslettersRecalculatingcatalogpromotionrulesCleaningvisitorlogsSendingpriceandstockalerte-mailsUpdatingcurrencyrates
WhentheMagentocronisnotconfiguredcorrectly,youwillseethatsomefeaturesofyourMagentoshopwillnotworkasexpected.
GettingreadyInthisrecipe,youwilllearnhowyoucanconfigurecronjobsontheserverandhowyoucanverifythattheyareworking.OpenyourSSHclientandchangetotheMagentofolder.
Howtodoit…Usingthefollowingsteps,wewillconfigurecronjobsontheserver:
1. TheMagentocronneedstobeexecutedonperiodictimestamps,forexample,everyfiveminutes.Torunthecrons,thefollowingcommandisused:
phpbin/magentocron:run
TipToavoidpermissionproblems,youhavetorunthiscommandastheuserthatApacheusestoserveHTTPrequests.Onabasicapachesetup,thisuseriswww-data,butthiscanbedifferentonothersetups.
2. Whenyouexecutethepreviouscommand,thecronjobtable,cron_schedule,isupdatedwiththerecentcronjobs.Whenyourunthefollowingcommandinyourdatabaseclient,youcanseethecontentofthattable:
SELECT*FROMcron_schedule;
Thisquerygivesthefollowingoutput:
3. Inthescheduled_atcolumn,wecanseewhenthecronjobisplannedtorun.Runthelastcommandagainafteraminute.Magentowillrunthecronjobswherethescheduled_attimeisinthepastandthestatusispending.Thecontentofthecron_scheduletablewilllookasfollows:
4. Whenwewanttoconfigurethatthecroncommandwillbeexecutedeveryminute,wehavetousethecrontabfileoftheLinuxserver.First,switchtothewwwuserofyourproject.ForastandardApache2webserver,itiswww-data.Wecanchangetheuserwiththefollowingcommand:
sudosuwww-data
5. Thenextthingtodoistoopenthecrontabfile.Wecandothisbyrunningthecrontab-ecommand.Thiswillopenafilewherewehavetoputthecontent,asshowninthefollowingscreenshot:
6. Savethefileandyourcronjobwillrunaftereveryminute.
Howitworks…TheMagentocronwillbeexecutedwiththeMagento2command-linetool.Thecommandtorunthecron(thecron.shscriptinMagento1)isasfollows:
phpbin/magentocron:run
ThiswillstarttheMagentocronprocess.ThecroncommandwillexecutethePHPcodeovertheCommandLineInterface(CLI).Thismeansthatthephp-clisettingsareusedtoexecutethecronjobsinsteadoftheapachePHPsettingsthatareusedbyapache.
Whenthecronprocessisinitialized,Magentolooksatthecron_scheduletable.Everyscheduledcronjobwiththescheduled_atfieldinthepastandwiththestatuspendingwillbeexecuted.Whenajobstarts,theexecuted_atfieldwillbeupdatedwiththecurrenttimestampandthestatuswillbechanged.
Whenajobisfinished,thefinished_atfieldisupdatedwiththecurrenttimestamp.Also,thestatuswillbeupdated.Whenthestatusisanerror,themessagefieldwillbeupdatedwiththeerrormessage.
Whentheprocessisfinished,Magentowillcreateaqueueforthenextperiod.Basedontheconfigurationfiles,Magentoknowswhenithastoscheduleeachcronjob.
CreatingandtestinganewcronjobInMagento2,cronjobsaredefinedinthecrontab.xmlfileofeachmodule.LikeeveryconfigurationintheMagentomodules,theconfigurationofthecronjobsiseasytoextendincustommodules.Andthat’swhatwewilldointhisrecipe.Wewillcreateanewcronjobforourmodule.
Testingacronjobisabittricky.Youcanwaitwhilethecronwillisexecuted,butinthisrecipe,wewillseehowwecantriggeritfordevelopmentpurposes.
GettingreadyTheworkflowtocreateanewcronjobismostlythesameasworkingwitheventobservers.Wehavetoconfigureanewcronjobthatwillstartafunctioninaconfiguredclass.
Forthecreationofanewcronjob,wewillusetheexistingPackt_HelloWorldmodule.Makesureyouhavethelatestversioninstalled.
Howtodoit…Inthenextsteps,wewillcreatetheconfigurationforanewcronjob:
1. Thefirstthingistocreatetheconfigurationfile.Createtheapp/code/Packt/HelloWorld/etc/crontab.xmlfilewiththefollowingcontent:
<?xmlversion="1.0"?>
<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Cron:etc/cron
tab.xsd">
<groupid="default">
<jobname="helloworld_check_subscriptions"
instance="Packt\HelloWorld\Model\Cron"method="checkSubscriptions">
<schedule>*****</schedule>
</job>
</group>
</config>
2. Thenextthingistocreatethecronclasswiththefunctionthatwillbeexecuted.Createtheapp/code/Packt/HelloWorld/Model/Cron.phpfilewiththefollowingcontent:
<?php
namespacePackt\HelloWorld\Model;
classCron{
/**@var\Psr\Log\LoggerInterface$logger*/
protected$logger;
/**@var\Magento\Framework\ObjectManagerInterface*/
protected$objectManager;
publicfunction__construct(
\Psr\Log\LoggerInterface
$logger,\Magento\Framework\ObjectManagerInterface$objectManager
){
$this->logger=$logger;
$this->objectManager=$objectManager;
}
publicfunctioncheckSubscriptions(){
$subscription=$this->objectManager-
>create(''Packt\HelloWorld\Model\Subscription'');
$subscription->setFirstname(''Cron'');
$subscription->setLastname(''Job'');
$subscription->setEmail(''[email protected]'');
$subscription->setMessage(''Createdfromcron'');
$subscription->save();
$this->logger->debug(''Testsubscriptionadded'');
}
}
3. Whenwewanttotestourcronjob,wecancreateacrongrouptorunthetestwith.Tocreateacrongroup,wehavetocreatetheapp/code/Packt/HelloWorld/etc/cron_groups.xmlfilewiththefollowingcontent:
<?xmlversion="1.0"?>
<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Cron:etc/cron
_groups.xsd">
<groupid="packt">
<schedule_generate_every>1</schedule_generate_every>
<schedule_ahead_for>4</schedule_ahead_for>
<schedule_lifetime>2</schedule_lifetime>
<history_cleanup_every>10</history_cleanup_every>
<history_success_lifetime>60</history_success_lifetime>
<history_failure_lifetime>600</history_failure_lifetime>
<use_separate_process>1</use_separate_process>
</group>
</config>
4. Thenextthingtodoistolinkthecronjobtothegroup.Intheapp/code/Packt/HelloWorld/etc/crontab.xmlfile,changetheidattributeofthe<group>tagtothefollowing:
<groupid="packt">
5. Cleanthecacheandrunthefollowingcommand:
phpbin/magentocron:run--group="packt"
6. Whenyoulookatthecontentsofthecron_scheduletable,youcanseethatanewpendingjobisadded.Thejob_codeinstanceofthisishelloworld_check_subscriptionsaswehaveconfiguredinthecrontab.xmlfile.
7. Runthesamecommandagainandthejobwillbeexecuted.Whenyoulookintothevar/log/debug.logfile,youwillseethattheTestsubscriptionaddedmessageisloggedattheendofthefile.
8. WhenwelookatthesubscriptionstableinthebackendofMagento(Marketing|HelloWorld|Subscriptions),weseethatanewsubscriptionwithnameCronJobisadded.
9. Now,weknowthatthecronjobisworking.Tofinishit,wecanaddthejobtothedefaultgroupandscheduleitinsuchawaythatitrunseverynightat2:30a.m.Forthis,wehavetochangethehighlightedlinesofcodefromtheapp/code/Packt/HelloWorld/etc/crontab.xml:
<?xmlversion="1.0"?>
<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Cron:etc/cron
tab.xsd">
<groupid="default">
<jobname="helloworld_check_subscriptions"
instance="Packt\HelloWorld\Model\Cron"method="checkSubscriptions">
<schedule>302***</schedule>
</job>
</group>
</config>
10. Cleanthecacheandyourcronwillruninthedefaultgroup.Thejobwillbescheduledfor2:30a.m.
Howitworks…Cronjobsarealwaysconfiguredinthecrontab.xmlfile.LikeinMagento1,acronjobhasanamewithaclassandmethodthatwillbeexecuted.Foreverycronjob,youhavetogiveascheduleinthecrontimeformattospecifytheinterval.
TipThenameofacronjobisalwayswrittenusinglowercaseandunderscores.
AnothernewthinginMagento2isthatyouneedtoassociateacronjobtoagroup.Agroupisalwaysspecifiedinthecron_groups.xmlfile.Bydefault,Magento2hasthefollowingcrongroups:
default
index
Inacrongroup,youcanspecifysettingsforhowcronswillbehandledinthecron_scheduletable.Youcanmakesettings,suchasthelifetimeoferrors,cleanup,andschedule.Forthedevelopmentgroup,weusedasettingthatalwaysschedulesanewcronjobwhenthepreviouscronjobisexecuted.
Youhavetwoadvantageswhenyouuseacrongroupfordevelopmentstuff.
Thefirstoneisthatyoucanusespecificsettingsabouttheschedulingofthecronjob.Thesecondoneisthatyoucanusethecroncommandtorunonlythecronjobsofaspecificgroup.
Withthe--groupparameter,youcanspecifythegroupforwhichthejobsneedtobeexecutedandscheduled.Whenthisparameterisempty,allthegroupswillbeexecutedandscheduled.
Ineverycronjobtag(the<job>tag),thereisa<schedule>subtag.Inthissubtag,youcanconfiguretheintervalofthecronjob.Thisconfigurationcontainsfiveparametersthatrepresentthefollowingconfigurations:
MinuteHourDayMonthYear
Whenyouhaveaconfigurationlike010***,thismeansthatthiscronrunsatminute0,hour10,alldays,allmonths,allyears.Sothetimeatwhichthisrunsisat10a.m.
Chapter8.CreatingaShippingModuleInthischapter,wewillcover:
InitializingmoduleconfigurationsWritinganadaptermodelExtendingtheshippingmethodfeaturesAddingthemoduleinthefrontend
IntroductionShippingtheorderedproductstothecustomersisoneofthekeypartsofthee-commerceflow.Inmostcases,astoreownerhasacontractwithashippinghandler,andeveryshippinghandlerhastheirownbusinessrules.
InMagento2,thefollowingshippinghandlershaveanextension:
DHLFedExUPSUSPS
Ifyourhandlerisnotonthislist,checkwhetheryourshippinghandlerhasaMagentomodule.Ifnot,youcanconfigureastandardshippingmethodoryoucancreateyourownshippingmethod,asyouwilllearntodointhischapter.
InitializingmoduleconfigurationsInChapter4,CreatingaModule,youlearnedhowtocreateacustommodule.Inthisrecipe,wewillcreateanewmodulewherewewilladdtherequiredsettingsforashippingmodule.
Inthelaterrecipesofthischapter,wewillextendthismodulewithmoreshippingfeatures.
GettingreadyOpenyourIDEwiththeMagento2project.Wewillalsoneedthebackend,wherewewillchecksomeconfigurations.
Howtodoit…Thefollowingstepsdescribehowwecancreatetheconfigurationforashippingmodule:
1. First,wehavetocreatethefollowingfolders:
app/code/Packt/
app/code/Packt/Shipme/
app/code/Packt/Shipme/etc/
app/code/Packt/Shipme/Model/
app/code/Packt/Shipme/Model/Carrier/
2. Createamodule.xmlfileintheapp/code/Packt/Shipme/etc/folderwiththefollowingcontent:
<?xmlversion="1.0"?>
<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.
xsd">
<modulename="Packt_Shipme"setup_version="2.0.0">
<sequence>
<modulename="Magento_Shipping"/>
</sequence>
</module>
</config>
3. Createaregistration.phpfileintheapp/code/Packt/Shipme/folderwiththefollowingcontent:
<?php
\Magento\Framework\Component\ComponentRegistrar::register(
\Magento\Framework\Component\ComponentRegistrar::MODULE,
'Packt_Shipme',
__DIR__
);
4. Withthisfile,wecaninstallthemodulebyrunningthephpbin/magentosetup:upgradecommand.
5. Tocheckthatthemoduleisactive,openthebackendandnavigatetoStores|Configuration|Advanced|AdvancedandcheckwhetherPackt_Shipmeisinthelistandisenabled.
6. Atthispoint,themoduleisinitializedandactive.Wecannowcreateasystem.xmlfileintheapp/code/Packt/Shipme/etc/adminhtml/folder.
7. Whenthepreviousfileiscreated,addthefollowingcontenttoit.Thiswillcreatetheshippingconfigurationparametersnearalltheothershippingmethods:
<?xmlversion="1.0"?>
<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/sy
stem_file.xsd">
<system>
<sectionid="carriers">
<groupid="shipme"translate="label"type="text"sortOrder="50"
showInDefault="1"showInWebsite="1"showInStore="1">
<label>Shipme</label>
<fieldid="active"translate="label"type="select"
sortOrder="10"showInDefault="1"showInWebsite="1"showInStore="0">
<label>Enabled</label>
<source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
</field>
<fieldid="name"translate="label"type="text"sortOrder="20"
showInDefault="1"showInWebsite="1"showInStore="1">
<label>MethodName</label>
</field>
<fieldid="title"translate="label"type="text"sortOrder="20"
showInDefault="1"showInWebsite="1"showInStore="1">
<label>MethodTitle</label>
</field>
</group>
</section>
</system>
</config>
8. Next,wecanconfiguresomedefaultsettings.Createaconfig.xmlfileintheapp/code/Packt/Shipme/etc/folderwiththefollowingcontent:
<?xmlversion="1.0"?>
<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/con
fig.xsd">
<default>
<carriers>
<shipme>
<model>Packt\Shipme\Model\Carrier\Shipme</model>
<active>1</active>
<name>ShipmeShipping</name>
<title>ShipmeShipping</title>
</shipme>
</carriers>
</default>
</config>
9. CleanthecacheandnavigatetoStores|Configuration|Sales|ShippingMethods.Youwillseethatanextragroupisadded,asshowninthefollowingscreenshot:
10. Now,whentheconfigurationisworking,wecanextendtheconfigurationwithsomeextravalues.Modifythesystem.xmlfilesothatitlooksasfollows(thehighlightedcodeisaddedinthisstep):
<?xmlversion="1.0"?>
<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/sy
stem_file.xsd">
<system>
<sectionid="carriers">
<groupid="shipme"translate="label"type="text"sortOrder="50"
showInDefault="1"showInWebsite="1"showInStore="1">
<label>Shipme</label>
<fieldid="active"translate="label"type="select"
sortOrder="10"showInDefault="1"showInWebsite="1"showInStore="0">
<label>Enabled</label>
<source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
</field>
<fieldid="name"translate="label"type="text"sortOrder="20"
showInDefault="1"showInWebsite="1"showInStore="1">
<label>MethodName</label>
</field>
<fieldid="title"translate="label"type="text"sortOrder="20"
showInDefault="1"showInWebsite="1"showInStore="1">
<label>MethodTitle</label>
</field>
<fieldid="express_enabled"translate="label"type="select"
sortOrder="30"showInDefault="1"showInWebsite="1"showInStore="0">
<label>Enableexpress</label>
<source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
</field>
<fieldid="express_title"translate="label"type="text"
sortOrder="40"showInDefault="1"showInWebsite="1"showInStore="1">
<label>Titleexpress</label>
</field>
<fieldid="express_price"translate="label"type="text"
sortOrder="50"showInDefault="1"showInWebsite="1"showInStore="1">
<label>Priceexpress</label>
</field>
<fieldid="business_enabled"translate="label"type="select"
sortOrder="60"showInDefault="1"showInWebsite="1"showInStore="0">
<label>Enablebusiness</label>
<source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
</field>
<fieldid="business_title"translate="label"type="text"
sortOrder="70"showInDefault="1"showInWebsite="1"showInStore="1">
<label>Titlebusiness</label>
</field>
<fieldid="business_price"translate="label"type="text"
sortOrder="80"showInDefault="1"showInWebsite="1"showInStore="1">
<label>Pricebusiness</label>
</field>
<fieldid="specificerrmsg"translate="label"type="textarea"
sortOrder="90"showInDefault="1"showInWebsite="1"showInStore="1">
<label>DisplayedErrorMessage</label>
</field>
</group>
</section>
</system>
</config>
11. Toconfigurethedefaultvalues,addthehighlightedcodeintotheconfig.xmlfileofthemodule:
<?xmlversion="1.0"?>
<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/con
fig.xsd">
<default>
<carriers>
<shipme>
<model>Packt\Shipme\Model\Carrier\Shipme</model>
<active>1</active>
<name>ShipmeShipping</name>
<title>ShipmeShipping</title>
<express_enabled>1</express_enabled>
<express_title>Expressdelivery</express_title>
<express_price>4</express_price>
<business_enabled>1</business_enabled>
<business_title>Businessdelivery</business_title>
<business_price>5</business_price>
<specificerrmsg>Thisshippingmethodiscurrentlyunavailable.
Ifyouwouldliketoshipusingthisshippingmethod,pleasecontact
us.</specificerrmsg>
</shipme>
</carriers>
</default>
</config>
12. Cleanthecacheandreloadtheshippingmethodconfigurationpageinthebackend.YouwillseethattheextrafieldsareavailableundertheShipmesection,asshowninthefollowingscreenshot:
Howitworks…Inthefirststepsofthisrecipe,wecreatedthenecessaryfilestoinitializethemodule.Toinitializeamodule,weneedamodule.xmlfileintheetcfolderofthemodule.
Inthemodule.xmlfile,weconfiguredthenameandversioninthe<module>tag.Inthe<sequence>subtag,weconfiguredthedependenciesofthemodule.
The<sequence>configurationcheckswhethertheMagento_ShippingandMagento_OfflineShippingmodulesareactive.Otherwise,themodulecannotbeinstalled.
Whenthemodulewasinstalled,weextendedthemodulewiththeconfigurationsforashippingmethod.AlltheMagentoshippingmethodsareconfigurableontheShippingMethodspageintheconfiguration.So,tocreateanextrashippingmethod,wehavetocreateanextrasectiononthatpage.
WecreatedashippinghandlerthathasaMethodNameoption.Inthathandler,wecreatedtwoshippingoptions:expressandbusiness.Forthosetwooptions,wecreatedthename,enabled,andpricefields.
Theconfigurationparametersareconfiguredinthesystem.xmlfileofthemodule.Foreveryconfigurationparameter,thereisadefaultvalue.Thesevaluesareconfiguredintheconfig.xmlfile.
Inthisconfig.xmlfile,thereisalsoamodelconfigured.Thisistheadaptermodelthatwewillcreateinthenextrecipe,Writinganadaptermodel.
SeealsoInthisrecipe,weusedthesystem.xmlfileofthemoduletocreatetheconfigurationvalues.MoreinformationaboutconfigurationvaluesisexplainedintheAddingconfigurationparametersrecipeofChapter6,MagentoBackend.
WritinganadaptermodelInthepreviousrecipe,weenabledanewmodulewiththesettingsforanewshippingmethod.Thiswasapreparationforthebusinesspart,whichwewilldointhisrecipe.
Wewilladdamodelwiththebusinesslogicfortheshippingmethod.ThemodeliscalledanadapterclassbecauseMagentorequiresanadapterclassforeachshippingmethod.
Theadapterclasswillbeusedforthefollowingthings:
MakingtheshippingmethodavailableCalculatingtheshippingcostsSettingthetitleinthefrontendoftheshippingmethods
GettingreadyEnsurethatyouhaveinstalledthemodulethatwecreatedinthepreviousrecipebecauseweneedthisforcreatingtheadapterclass.
Howtodoit…Thefollowingstepsdescribehowyoucanwriteanadapterclassforashippingmethod:
1. Createtheapp/code/Packt/Shipme/Model/Carrier/folderifitdoesn’talreadyexist.
2. Inthisfolder,createafilecalledShipme.phpwiththefollowingcontent:
<?php
namespacePackt\Shipme\Model\Carrier;
useMagento\Shipping\Model\Rate\Result;
classShipmeextends\Magento\Shipping\Model\Carrier\AbstractCarrier
implements
\Magento\Shipping\Model\Carrier\CarrierInterface{
protected$_code='shipme';
/**
*@var\Magento\Shipping\Model\Rate\ResultFactory
*/
protected$_rateResultFactory;
/**
*@var\Magento\Quote\Model\Quote\Address\RateResult\MethodFactory
*/
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=[]
){
$this->_rateResultFactory=$rateResultFactory;
$this->_rateMethodFactory=$rateMethodFactory;
parent::__construct($scopeConfig,$rateErrorFactory,$logger,
$data);
}
publicfunction
collectRates(\Magento\Quote\Model\Quote\Address\RateRequest$request){
if(!$this->getConfigFlag('active')){
returnfalse;
}
$result=$this->_rateResultFactory->create();
//Checkifexpressmethodisenabled
if($this->getConfigData('express_enabled')){
$method=$this->_rateMethodFactory->create();
$method->setCarrier($this->_code);
$method->setCarrierTitle($this->getConfigData('name'));
$method->setMethod('express');
$method->setMethodTitle($this->getConfigData('express_title'));
$method->setPrice($this->getConfigData('express_price'));
$method->setCost($this->getConfigData('express_price'));
$result->append($method);
}
//Checkifbusinessmethodisenabled
if($this->getConfigData('business_enabled')){
$method=$this->_rateMethodFactory->create();
$method->setCarrier($this->_code);
$method->setCarrierTitle($this->getConfigData('name'));
$method->setMethod('business');
$method->setMethodTitle($this->getConfigData('business_title'));
$method->setPrice($this->getConfigData('business_price'));
$method->setCost($this->getConfigData('business_price'));
$result->append($method);
}
return$result;
}
publicfunctiongetAllowedMethods(){
return['shipme'=>$this->getConfigData('name')];
}
}
3. Savethefileandclearthecache.Youradaptermodelisnowcreated.
Howitworks…Theclassthatwecreatedinthisrecipehandlesallthebusinesslogicthatisneededfortheshippingmethod.Becausethisadapterclassextendsfromthe\Magento\Shipping\Model\Carrier\AbstractCarrierclass,wecanoverwritesomemethodstocustomizethebusinesslogicofthestandardclass.
Thisclassimplementsthe\Magento\Shipping\Model\Carrier\CarrierInterfaceinterface.Whenwelookinthisclass,weseethatthefollowingmethodsmustbeavailableintheadapterclass:
isTrackingAvailable()
getAllowedMethods()
ThesemethodsaresetintheAbstractCarrierclass—theclasswheretheadaptermodelextendsfrom.Thismeansthattheclassisvalid.Inthatclass,thereisalsotheisAvailable()method.Inthismethod,thereisdecidedthattheshippingmethodisactiveornot.Ifyouwant,youcanoverwritethismethodwithyourcustomcode.
ThesecondandmostimportantfunctionisthecollectRates()function.Thisfunctiondecideswhichmethodsareavailableandtheshippingcosts.
InMagento,ashippingmethodhasacarrier.ThecarrierfromthemethodsofthisrecipeisShipme.Everycarriercanhavemultiplemethods,suchasExpressandBusinessdelivery.
InthecollectRates()function,wecreatearateResultvariabletowhichyoucanassignshippingmethods.Inthisrecipe,weaddedtwomethodstothis:ExpressandBusinessdelivery.
AmethodiscreatedformtherateMethodFactoryinstanceandthiscanhavethefollowingoptions:
Carrier(shipme)TitleofthecarrierMethod(codeofthemethod)MethodtitlePriceCost
Whentheseoptionsareset,themethodisaddedtotherateResultinstanceusingtheappend()function.
ThenextfunctionthatweusedisthegetAllowedMethods()function.Inthisfunction,weaddthemethodsoftheShipmecarriertotheallowedshippingmethods.
ExtendingtheshippingmethodfeaturesNowthatallthefilesareinstalled,wecanaddmorefeaturestotheshippingmethod.Inthisrecipe,wewilladdacountryconfigurationandenabletrackingcodesfortheshippingmethod.
GettingreadySimilartoallrecipesinthischapter,wewillbuildfurtheronthemodulethatwecreatedinthepreviousrecipesofthischapter.Ensurethatyouhavetherightfilesinstalled.
Howtodoit…Inthefollowingsteps,youwilllearnhowwecanenabletrackingcodesandcountryconfigurationsfortheshippingmethod:
1. Opentheshippingadapterfile,app/code/Packt/Shipme/Model/Carrier/Shipme.php.
2. Addthefollowingfunctiontothatclasstoenabletrackingcodes:
publicfunctionisTrackingAvailable(){
returntrue;
}
3. Next,wewillenablethecountry-specificoptions.Intheapp/code/Packt/Shipme/etc/adminhtml/system.xmlfile,addthefollowingcontent:
<fieldid="sallowspecific"translate="label"type="select"
sortOrder="100"showInDefault="1"showInWebsite="1"showInStore="0">
<label>ShiptoApplicableCountries</label>
<frontend_class>shipping-applicable-country</frontend_class>
<source_model>Magento\Shipping\Model\Config\Source\Allspecificcountries
</source_model>
</field>
<fieldid="specificcountry"translate="label"type="multiselect"
sortOrder="110"showInDefault="1"showInWebsite="1"showInStore="0">
<label>ShiptoSpecificCountries</label>
<source_model>Magento\Directory\Model\Config\Source\Country</source_mod
el>
<can_be_empty>1</can_be_empty>
</field>
4. Cleanthecacheandopentheconfigurationpageoftheshippingmethod.Youwillseethattherearetwonewconfigurationoptions.WhenyouchangethevalueoftheShiptoApplicableCountriesfieldtoSpecificCountries,youcanselectfrommultiplecountries,asyoucanseeinthefollowingscreenshot:
Howitworks…Inthisrecipe,weextendedtheshippingmethodwithtwonewfeatures.ThefirstfeaturewastoaddthepossibilitytocreatetrackingcodesfortheShipmeshippingmethod.WeoverwrotetheisTrackingAvailable()function,whichreturnsfalsebydefault.Byoverwritingthisfunctionandreturningtrue,weenablethetrackingcodes.
Thesecondthingthatwedidwastoenablecountry-specificshipping.Weaddedtwofieldswithastandardnamingconvention.Usingthefollowingnamesforthetwofields,Magentorecognisestheconfigurationforcountries:
sallowspecific
specificcountry
Whenweenablethisconfigurationinthebackend,theshippingmethodisonlyavailablewhenthecountryoftheshippingaddressisoneoftheselectedcountriesintheShiptoSpecificCountriesconfigurationoftheshippingmethod.
AddingthemoduleinthefrontendInthepreviousrecipes,wecreatedconfigurationsforanewshippingmethod.Wehaveseenthatwecanconfigurethisinthebackend.Now,itistimetotesttheshippingmethodinthefrontend.Wewillcreateatestorderwiththeshippingmethodthatwehavecreatedinthischapter.
GettingreadyWeneedtheshippingmodulethatwecreatedinthepreviousrecipes.Ensureyouhavetherightfilesinstalled.
Howtodoit…ThefollowingstepsdescribehowtheorderflowworksinMagento:
1. Logintothebackend.2. Navigatetotheconfigurationoftheshippingmethod.Youcanfindthisbynavigating
toStores|Configuration|Sales|ShippingMethods|Shipme.3. CheckwhetherallthevaluesarecorrectfortheShipme-Expressmethod.Ensure
thateverythingisenabled.4. Savetheconfiguration.5. Inthefrontend,addaproducttotheshoppingcartandproceedtocheckout.
NoteInChapter7,EventHandlersandCronjobs,wecreatedaneventthatcheckswhetherthequantityisoddorevenwhenaddingsomethingtothecart.WhenyougettheWecan’taddthisitemtoyourshoppingcartrightnow.message,youhavetoaddanevenquantityoryouhavetodisablethatcode.
6. Whenyouareonthecheckoutpage,fillintherightdatafortheshippingaddress.7. IntheShippingMethodssection,thenewmethodswillappearasshowninthe
followingscreenshot:
8. SelectoneoftheShipmeShippingmethodsandclickontheNextbutton.9. Checkyourpaymentinformation.EnsurethatyouhavecheckedtheCheck/Money
ordermethodifthereismorethanonemethodavailable.
TipIfyoudon’tseetheCheck\Moneyorderpaymentmethod,youhavetoenableitin
thestoresconfiguration.
10. ClickonthePlaceOrderbuttonandyourorderwillbecreated.Youwillseetheordersuccesspagewhereyouoptionallycancreateanaccountforthewebsite.
11. WhenyoulookintothebackendbynavigatingtoSales|Orders,youcanseetheorderthatwehavecreated.ClickontheViewlinkofthatorderandyouwillseeallthedetailsofthatorder.
12. Toprocesstheorder,wecancreateaninvoiceforittoconfirmthattheorderispaid.WhenyouclickontheInvoicebutton,youwillbeforwardedtotheformwhereyoucansubmittheinvoiceforthatorder.
13. Whentheinvoiceissaved,youwillseethatthestatusoftheorderischangedtoProcessing.Tocreateashipmentforthisorder,wecanclickontheShipbutton.Youwillseethefollowingscreen:
14. WhenyouclickontheAddTrackingNumberbutton,youcancreateatrackingcodeforthatshipment.IntheCarrierdropdown,selecttheShipmeShippingoptionandaddasampletrackingcode,suchas1234567890.
15. WhenyouclickontheSubmitShipmentbutton,yourshipmentisprocessedandthestatusoftheorderwillchangetoComplete.
Howitworks…Inthisrecipe,wetestedtheshippingmethodthatwecreatedinthischapter.Weplacedanorderwiththenewshippingmethodtocheckthateverythingworksasexpected.
Whentheorderisplaced,itisthetaskofthestoreownertocompletetheorder.ThepaymentmethodoftheorderisCheck/Moneyorder.Thismeansthatthepaymentwillhappenlater.
Whentheorderispaid,youcansetthepaidamountbycreatinganinvoice.ThestatuswillchangefromPendingtoProcessing.Processingmeansthattheorderisreadytobeprocessed.Whenyoulookattheordertotalsontheorderpage,youseethatTotalPaidisthesameastheGrandTotal(ifyouhavemarkedtheinvoiceaspaid).
Whentheordercanbeshipped,wecancreateashipmentoftheorder.Alltheshippinginformationcanbestoredintheshipmentsuchasthetrackingcode(s),comments,statusupdates,andmuchmore.
Whenthereisaninvoiceandshipmentforalltheorderitems,theorderisCompleteinMagento.Whentheorderiscomplete,itisalwayspossibletocreateaCreditMemoreceiptforspecialcasesintheorderflow(damagedshipping,areturningorder,andsoon).
Chapter9.CreatingaProductSliderWidgetInthischapter,wewillcoverthefollowingrecipes:
CreatinganemptymoduleCreatingawidgetconfigurationfileCreatingtheblockandtemplatefilesCreatingacustomconfigurationparameterFinalizingthetheming
IntroductionTheMagentowidgetssystemisagraphicalinterfacewhereyoucanconfigureblocksinthefrontend.Foreverywidget,thereisaconfigurationpageavailablewhereyoucansettherequiredvaluesforthatwidget.
WithaMagentowidget,youcanconfigurethelayoutinstructionstoshowthewidgetatseveralplacesinthefrontend.
Inthischapter,wewillcreateanewmoduleinwhichwewillcreateourownwidget.Wewillcreateaproductsliderwiththeproductsofacategorythatwecanconfigureinthatwidget.
Whenwearedonewiththetechnicalpart(configurationpage,blockclass,templateinitialization),wecanfinishwithitsrepresentationinthefrontend.WewillcreateaproductlistthatwewillstylewithajQuerysliderscript.
CreatinganemptymoduleAswedidinthepreviouschapterandfullyexplainedinChapter4,CreatingaModule,wewillcreatetherequiredfilestocreateanemptymodulethatwewillextendwithwidgetconfigurationsinfurtherchapters.WewillstartwithanemptyMagentomodulethatwewillcreateinthisrecipe.Wewillcreatealltherequiredfilestoinitializeanewmodulethatcanbeusedforthecreationofawidget.
GettingreadyWewillcreatenewmodulecalledPackt_ProductSlider.OpenyourIDEtoaddsomecodetoit.
Howtodoit…Usingthefollowingsteps,wewillcreateanemptymodulecalledPackt_ProductSlider:
1. CreatethefollowingfoldersinyourMagentoroot:
app/code/Packt/
app/code/Packt/ProductSlider/
app/code/Packt/ProductSlider/etc/
2. Intheapp/code/Packt/ProductSlider/etc/folder,createafilecalledmodule.xml.3. Inthisfile,pastethefollowingcode:
<?xmlversion="1.0"?>
<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.
xsd">
<modulename="Packt_ProductSlider"setup_version="2.0.0">
<sequence>
<modulename="Magento_Catalog"/>
<modulename="Magento_Widget"/>
</sequence>
</module>
</config>
4. Intheapp/code/Packt/ProductSlider/folder,createaregistration.phpfilewiththefollowingcontent:
<?php
\Magento\Framework\Component\ComponentRegistrar::register(
\Magento\Framework\Component\ComponentRegistrar::MODULE,
'Packt_ProductSlider',
__DIR__
);
5. Toinstallthemodule,runthefollowingcommand:
phpbin/magentosetup:upgrade
6. Tocheckwhetherthemoduleisinstalled,openthebackendandnavigatetotheconfigurationpage(Stores|Configuration|Advanced|Advanced).CheckwhetherthePackt_ProductSlidermoduleisinthelist.
7. Youcanalternativelyrunthefollowingcommandtogetalistofallinstalledandenabledmodules:
phpbin/magentomodule:status
Howitworks…Wehavejustcreated,installed,andenabledanewmodulecalledPackt_ProductSlider.Toinitializeamodule,weneedamodule.xmlfileintheetcfolderofthemodule.
Inthemodule.xmlfile,weconfiguredthenameandversioninthe<module>tag.Inthe<sequence>subtag,weconfiguredthedependenciesofthemodule.
The<sequence>configurationcheckswhethertheMagento_WidgetandMagento_Catalogmodulesareactive.Otherwise,themodulecannotbeinstalled.
Practically,thismoduledoesnothing,butwewillextendthisinthenextrecipesofthischapter.
CreatingawidgetconfigurationfileInthisrecipe,wewillextendthefeaturesofthePackt_ProductSlidermodulewithawidgetconfigurationfile.Inthisconfigurationfile,wewilldeclareanewwidgetorfrontendapptype.
Foranewfrontendapp,weneedtoconfigurethefollowingthings:
Nameofthewidget(usedinthebackend)WidgetconfigurationparametersWidgetblocktypeWidgettemplates(the.phtmlfiles)
GettingreadyWewillextendthemodulethatwecreatedinthepreviousrecipewithawidgetconfiguration.Ensurethatyouhavetherightfilesinstalled.
Howtodoit…Usingthefollowingsteps,youcanexplorethepurposeofawidget.xmlconfigurationfile:
1. Createtheapp/code/Packt/ProductSlider/etc/widget.xmlfileusingthefollowingcode:
<?xmlversion="1.0"encoding="UTF-8"?>
<widgetsxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Widget:etc/wi
dget.xsd">
<widget
id="category_product_slider"
class="Magento\Catalog\Block\Product\List"
is_email_compatible="false"
placeholder_image="Magento_Widget::placeholder.gif">
<labeltranslate="true">Categoryproductslider</label>
<descriptiontranslate="true">ListofProductsforagivencategory
inasliderwidget</description>
</widget>
</widgets>
2. Cleanthecacheandcheckwhethertheconfigurationworks.Wecancheckthisintwoways:
1. ThefirstmethodistonavigatetoContent|Widgetsinthebackend.WhenyouclickontheAddWidgetbutton,youcanseethenewwidgettypeinthelist,asshowninthefollowingscreenshot:
2. ThesecondwaytotestthewidgettypesistoaddawidgettoaCMSpage.NavigatetoContent|Elements|PagesandclickontheAddNewPagebutton.
IntheContenttab,clickonthehighlightedbuttonintheWYSIWYGeditor,asshowninthefollowingscreenshot:
3. Whenclickingonthatbutton,anoverlayshowsupwhereyoucanchooseawidget.IntheWidgetTypedropdown,youcanselectCategoryproductsliderwhentheconfigurationisright.
4. Nowthatthewidgetworks,it’stimetoaddsomeconfigurationparameterstothewidget.Whenweaddthefollowinghighlightedcodetothewidget.xmlfile,wewillcreateaparameterforthetitle:
<widget
id="category_product_slider"
class="Magento\Catalog\Block\Product\List"
is_email_compatible="false"
placeholder_image="Magento_Widget::placeholder.gif">
<labeltranslate="true">Categoryproductslider</label>
<descriptiontranslate="true">ListofProductsforagivencategory
inasliderwidget</description>
<parameters>
<parametername="title"xsi:type="text"required="true"
visible="true">
<labeltranslate="true">Title(frontend)</label>
</parameter>
</parameters>
</widget>
5. Cleanthecacheandopenthewidgetconfigurationpage.WecandothisbynavigatingtoContent|Elements|Widgets.ClickontheAddWidgetbuttonandchoosethefollowingconfigurationintheform:
Type:CategoryproductsliderDesignTheme:MagentoLuma(thethemeofyourshop)
6. WhenyouclickontheContinuebutton,youwilllandonthewidgetconfigurationpage.WhenyouclickonWidgetOptions,youwillseethetitleparameter,asshowninthefollowingscreenshot:
7. Whenwewanttoshowproductsforacategory,wehavetocreateaconfigurationfieldwherewecansetthecategoryID.WhenwewanttoaddatextfieldwherewecanconfiguretherightcategoryID,weneedtoaddthehighlightedcodetothewidget.xmlfile.Thehighlightedcodeneedstobepastedasthechildofthe<parameters>tag:
<parameters>
<parametername="title"xsi:type="text"required="true"
visible="true">
<labeltranslate="true">Title(frontend)</label>
</parameter>
<parametername="category_id"xsi:type="text"required="true"
visible="true">
<labeltranslate="true">CategoryID</label>
</parameter>
</parameters>
8. Cleanthecacheandreloadthefrontend.Youwillseethatasecondtextboxisaddedtotheconfigurationpage.
Howitworks…Thewidget.xmlfileisusedtodefinewidgettypesinyourMagentoinstallation.Allwidgettypesaredefinedaschild<widget>tagsoftheglobal<widgets>tag.
Awidgettypeisdeclaredinthe<widget>tagandthiselementhasthefollowingthreerequiredattributes:
id(theuniqueidentifierofawidget)class(theBlockclassforthewidget)is_email_compatible(theBooleanthatthewidgetcanbeusedine-mailtemplates)placeholder_image(animagethatisusedwhenthewidgetisinsertedinaWYSIWYGeditor)
ThewidgettypethatwecreatedinthisrecipeusestheMagento\Catalog\Block\Product\Listblockclass.InMagento,thisclassisusedtorenderallproductlists,justlikeitisusedonthecategorypage.
Inthechildtagsofthe<widget>tag,wecanconfiguretheadditionalfieldsforthewidgettype.
Withthe<name>tag,weconfiguredthenameforthewidgettypethatisdisplayedinthedropdownoftheconfigurationpage.
Inthe<description>tag,wecanconfigureadescriptionforthewidgettype.ThisdescriptionisshownwhenyouinsertanewwidgetinaCMSpage.
Finally,weusedthe<parameters>tagtodefinetheadditionalconfigurationparameters.Inthisrecipe,weaddednameandcategory_idastextconfigurationfields.
Everyconfigurationparameterisdefinedasachild<parameter>tagfromthe<parameters>tag.The<parameter>taghasthefollowingattributes:
name(theIDofthefield)xsi:type(thetypeofthefield,suchastext,dropdown,andsoon)required(canbesettotrueorfalse)visible(canbesettotrueorfalse)
Every<parameter>taghasa<label>subtagwhereyoucanconfigurethelabelofthefieldpage.
CreatingtheblockandtemplatefilesInthepreviousrecipe,youlearnedhowtoconfigureanextrawidgettypetoMagento.Now,itistimetodisplaythewidget.
Wewillextendthewidgetconfigurationwiththeoptiontoselecttwodifferenttemplatestorenderthewidgetinthefrontend.
ThesecondthingthatwewilldoiscreateacustomBlockclasswherewecanwriteourownspecificmethodsforthewidget.
GettingreadyWewillworkfurtheronthewidgetmodulethatwecreatedinthepreviousrecipes.Ensurethatyouhavetherightcodeinstalled.
Howtodoit…Usingthefollowingsteps,youwilllearnhowwecanconfigureacustomBlockclasswithcustomtemplatesforawidgetinstance:
1. ThefirstthingthatwewilldoiscreatetheBlockclassforthewidget.TheBlockclasswillextendMagento\Catalog\Block\Product\Listclassbecauseweneedthefunctionalityofthatclassinourwidgettype.CreateafilecalledProductSlider.phpintheapp/code/Packt/ProductSlider/Block/Catalog/Product/folder.
2. Addthefollowingcontenttothatfile:
<?php
namespacePackt\ProductSlider\Block\Catalog\Product;
classProductSliderextends\Magento\Catalog\Block\Product\ListProduct
{
}
3. ConfigurethewidgetconfigurationtousetheBlockclassthatwejustcreated.Opentheapp/code/Packt/ProductSlider/etc/widget.xmlfileandchangetheclassattributeasshowninthefollowinghighlightedcode:
...
<widget
id="category_product_slider"
class="Packt\ProductSlider\Block\Catalog\Product\ProductSlider"
is_email_compatible="false"
placeholder_image="Magento_Widget::placeholder.gif">
<labeltranslate="true">Categoryproductslider</label>
<descriptiontranslate="true">ListofProductsforagivencategory
inasliderwidget</description>
<parameters>
...
4. NowwhentheBlockclassiscreatedandconfigured,itistimetocreatetemplatesfortheblock.Forthiswidget,wewillconfiguretwotemplates.Thefirstonewillcontaintheimage,price,andtitleoftheproducts,andthesecondoneisasimplifiedversionthatonlyshowstheimageandanAddToCartbutton.
5. Tostorethetemplates,createthefollowingfolders:
app/code/Packt/ProductSlider/view/
app/code/Packt/ProductSlider/view/frontend/
app/code/Packt/ProductSlider/view/frontend/templates/
app/code/Packt/ProductSlider/view/frontend/templates/product/
app/code/Packt/ProductSlider/view/frontend/templates/product/slider/
6. Inthelastfolder,createthefollowingfiles:
list.phtml
teaser.phtml
7. Inthelist.phtmlfile,addthefollowingcontent:
<divclass="blockblock-product-sliderslider-list">
<divclass="block-title">
<h2>List</h2>
</div>
<divclass="block-content">
Productslider
</div>
</div>
8. Intheteaser.phtmlfile,addthefollowingcontent:
<divclass="blockblock-product-sliderslider-teaser">
<divclass="block-title">
<h2>Teaser</h2>
</div>
<divclass="block-content">
Productslider
</div>
</div>
9. Nowwhenthefilesarecreated,wecancreatetheconfigurationinthewidget.xmlfileforthetwotemplates.Addthefollowinghighlightedcodetothewidget.xmlfile.Thecodeneedstobepastedaschildofthe<parameters>tag:
...
</parameter>
<parametername="template"xsi:type="select"required="true"
visible="true">
<labeltranslate="true">Template</label>
<options>
<optionname="default"value="product/slider/list.phtml"
selected="true">
<labeltranslate="true">Productlistslider</label>
</option>
<optionname="teaser"value="product/slider/teaser.phtml">
<labeltranslate="true">Productteaserslider</label>
</option>
</options>
</parameter>
</parameters>
...
10. Cleanthecacheandgotothewidgetconfigurationpage.WhenyouclickontheAddLayoutUpdatebutton,youcanseethetwoconfiguredtemplates,asshowninthefollowingscreenshot:
11. Whenyoucompletetheformwiththelayoutupdateasshown,thewidgettemplatewillappearonthehomepage.
TipEnsurethatyouhavecleanedthecacheandconfiguredthewidgetfortherightthemeandstoreview.
12. Thelastthingthatwewilldoiscreatealoopthatshowsthenameoftheproducts.Addthefollowingcodetothelist.phtmlfile:
<?php$productCollection=$block->getLoadedProductCollection()?>
<divclass="blockblock-product-sliderslider-list">
<divclass="block-title">
<h2>List</h2>
</div>
<divclass="block-content">
<?phpif(count($productCollection)):?>
<ul>
<?phpforeach($productCollectionas$product):?>
<li><?phpecho$product->getName()?></li>
<?phpendforeach;?>
</ul>
<?phpendif;?>
</div>
</div>
13. Onthewidgetconfiguration,configureavalidcategoryID.Whenyousavetheconfigurationandopenthehomepage,youwillseealistofproductnamesofthatcategory,asshowninthefollowingscreenshot:
TipYoucanfindthecategoryIDwhilenavigatingtoacategoryinthebackend.NavigatetoProducts|Categoriesandselectthecategorythatyouwantinthetree.Whenselectingacategory,theIDappearsnearthename.
Howitworks…ThefirstthingthatwedidwastocreateaBlockclassthatextendstheproductlistclass(Magento\Catalog\Block\Product\ListProduct)fromtheMagento_Catalogmodule.
Whentheclassiscreated,weupdatedtheconfigurationinthewidget.xmlfiletousethisclass.
WithonlyaBlockclass,wecan’tdisplaytheoutputtothefrontend.So,wecreatethetemplatefiles.Wecreatedtwotemplatefilesthatwecanusetogenerateadifferentoutputwiththesamedata.
Toconfigurethetemplatesinthewidget,wehavetoaddanextra<parameter>configuration.Templatesarealwaysconfiguredwitha<parametername="template">configuration.Inthe<options>childtag,thedifferenttemplatesaresetasoptionsfromthedropdown.
Thewidgetconfigurationisnowfinished,sobycompletingtheform,thewidgetwillbeplacedonthefrontend.
Toshowawidgetonthefrontend,youhavetocreatealayoutupdateinthewidgetconfigurationpage.Inalayoutupdate,youcanconfigurethepagetype,container,andtemplatewherethewidgetneedstobedisplayed.Inthisexample,thedisplayedisshowninthecontentareaofthehomepage.
Thelastthingwedidwasdisplayingtheproductnamesoftheconfiguredcategory.Wecreatedaloopthatshowstheproductnamesforacategory.UsingthegetLoadedProductCollection()method,alltheproductsthatareinaconfiguredcategoryarereturned.ThecategoryIDneedstobeconfiguredinthecategory_idfieldofthatblock(thisissomethingthatisdoneusingthecategory_idwidgetparameter).
CreatingacustomconfigurationparameterAtthispoint,wehaveaworkingwidget.ItshowsupinthefrontendandtherightproductsaredisplayedforthegivencategoryID.
ToconfigurethecategoryID,wehavetoknowtheIDofthecategory.Wehavetocopyitfromthecategorypageandpasteitinthetextbox.
Forbetterusability,wewillcreateacustomconfigurationfieldtoselectacategory.WewillcreateabuttonthatopensanoverlaywherewecanchoosetherightcategoryID.
GettingreadyWewillcreateasimilarconfigurationfieldthatisusedfortheCatalogCategoryLinkwidgettypeinthebackend.Youcanlookatthisconfigurationwidget’sconfigurationtoseehowitworks.
Also,ensurethatyouhavetherightstartfilesinstalledbecausewewillbuildfurtheronthemodulethatwecreatedinthepreviousrecipes.
Howtodoit…Usingthefollowingsteps,wewillcreateacategorychooserthatwillbeusedonthewidgetconfigurationpage.
1. WhenwelookattheCatalogCategoryLinkwidget,weseethattheyuseacustomwidgettoselectthecategoryID.Wewillusethiswidgetinourmodule.
2. Openthewidget.xmlfilethatisplacedinthe/etcfolderofthePackt_ProductSlidermodule.Replacetheparameterofthecategory_idparameterwiththefollowinghighlightedcode:
...
<labeltranslate="true">Title(frontend)</label>
</parameter>
<parametername="category_id"xsi:type="block"visible="true"
required="true">
<labeltranslate="true">Category</label>
<block
class="Magento\Catalog\Block\Adminhtml\Category\Widget\Chooser">
<data>
<itemname="button"xsi:type="array">
<itemname="open"xsi:type="string">SelectCategory.</item>
</item>
</data>
</block>
</parameter>
<parametername="template"xsi:type="select"required="true"
visible="true">
<labeltranslate="true">Template</label>
...
3. Cleanthecacheandopenthewidgetconfigurationpagefortheproductsliderwidgettype.WhenyouclickontheSelectCategorybutton,apopupopenswiththecategorytree,asshowninthefollowingscreenshot:
4. WhenyouinspecttheSelectCategorybuttonandnavigatetothehiddenformfieldintheHTMLcode,youseethatthevalueissimilartothefollowingpattern:category/<category_id>
5. ThiswidgetrequiresacategoryIDthatisthenumberaftertheslash.Now,wehavethecategorypaththatisusedtogenerateURLs.Tofixthisproblem,wehavethechoicetoimplementoneofthefollowingthings:
ExtracttheIDfromthepathwithstringfunctionsEnsurethataproperIDissetinthewidgetconfigurationpageThemoststableoptionisthesecondone,sowewillimplementit
6. WewillcreateanewBlockclassthatextendsthestandardonesothatwecaninheritalotoffunctionality.Createtheapp/code/Packt/ProductSlider/Block/Adminhtml/Catalog/Category/Widget/Chooser.php
filewiththefollowingcontent:
<?php
namespacePackt\ProductSlider\Block\Adminhtml\Catalog\Category\Widget;
classChooserextends
\Magento\Catalog\Block\Adminhtml\Category\Widget\Chooser{
}
7. Tousethepreviouslycreatedblockintheconfigurationfield,wehavetoupdatethewidget.xmlfile.Thewidget.xmlfilewilllookasshowninthefollowingcode.Thehighlightedcodeisthelinethatyouhavetochange:
<?xmlversion="1.0"encoding="UTF-8"?>
<widgetsxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Widget:etc/wi
dget.xsd">
<widget
id="category_product_slider"
class="Packt\ProductSlider\Block\Catalog\Product\ProductSlider"
is_email_compatible="false"
placeholder_image="Magento_Widget::placeholder.gif">
<labeltranslate="true">Categoryproductslider</label>
<descriptiontranslate="true">ListofProductsforagivencategory
inasliderwidget</description>
<parameters>
<parametername="title"xsi:type="text"required="true"
visible="true">
<labeltranslate="true">Title(frontend)</label>
</parameter>
<parametername="category_id"xsi:type="block"visible="true"
required="true">
<labeltranslate="true">CategoryID</label>
<block
class="Packt\ProductSlider\Block\Adminhtml\Catalog\Category\Widget\Choo
ser">
<data>
<itemname="button"xsi:type="array">
<itemname="open"xsi:type="string">SelectCategory…</item>
</item>
</data>
</block>
</parameter>
<parametername="template"xsi:type="select"required="true"
visible="true">
<labeltranslate="true">Template</label>
<options>
<optionname="default"value="product/slider/list.phtml"
selected="true">
<labeltranslate="true">Productlistslider</label>
</option>
<optionname="teaser"value="product/slider/teaser.phtml">
<labeltranslate="true">Productteaserslider</label>
</option>
</options>
</parameter>
</parameters>
</widget>
</widgets>
8. Whenyoucleanthecacheandreloadtheconfigurationpageofthewidget,youwillseethatnothinghaschangedbecausetheclassthatwecreatedcontainsnofunctionality.Tochangethebehaviorthatwewant,wewilladdthreemethodstotheapp/code/Packt/ProductSlider/Block/Adminhtml/Catalog/Category/Widget/Chooser.php
file.Thehighlightedcodeshowsthedifferencesbetweenthemethodsfromtheextendedclass:
<?php
namespacePackt\ProductSlider\Block\Adminhtml\Catalog\Category\Widget;
classChooserextends
\Magento\Catalog\Block\Adminhtml\Category\Widget\Chooser{
protectedfunction_construct(){
$this->setModuleName('Magento_Catalog');
parent::_construct();
}
publicfunction
prepareElementHtml(\Magento\Framework\Data\Form\Element\AbstractElement
$element){
$uniqId=$this->mathRandom->getUniqueHash($element->getId());
$sourceUrl=$this->getUrl(
'productslider/catalog_category_widget/chooser',
['uniq_id'=>$uniqId,'use_massaction'=>false]
);
$chooser=$this->getLayout()->createBlock(
'Magento\Widget\Block\Adminhtml\Widget\Chooser'
)->setElement(
$element
)->setConfig(
$this->getConfig()
)->setFieldsetId(
$this->getFieldsetId()
)->setSourceUrl(
$sourceUrl
)->setUniqId(
$uniqId
);
if($element->getValue()){
$categoryId=$element->getValue();
$label=$this->_categoryFactory->create()->load($categoryId)-
>getName();
$chooser->setLabel($label);
}
$element->setData('after_element_html',$chooser->toHtml());
return$element;
}
publicfunctiongetNodeClickListener(){
if($this->getData('node_click_listener')){
return$this->getData('node_click_listener');
}
if($this->getUseMassaction()){
$js='
function(node,e){
if(node.ui.toggleCheck){
node.ui.toggleCheck(true);
}
}
';
}else{
$chooserJsObject=$this->getId();
$js='
function(node,e){
'.
$chooserJsObject.
'.setElementValue(node.attributes.id);
'.
$chooserJsObject.
'.setElementLabel(node.text);
'.
$chooserJsObject.
'.close();
}
';
}
return$js;
}
}
9. ThecodethatweaddedinthepreviousstepcontainsacalltoanAJAXcontrollerthatwewillcreate.Toregistertheadminrouterforthismodule,wewillcreatetheapp/code/Packt/ProductSlider/etc/adminhtml/routes.xmlfilewiththefollowingcontent:
<?xmlversion="1.0"?>
<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="
urn:magento:framework:App/etc/routes.xsd">
<routerid="admin">
<routeid="productslider"frontName="productslider">
<modulename="Packt_ProductSlider"before="Magento_Backend"/>
</route>
</router>
</config>
10. ThenextstepistocreatethecontrolleractionthatmatchestheURLthatwecallintheprepareElementHtml()method.Createtheapp/code/Packt/ProductSlider/Controller/Adminhtml/Catalog/Category/Widget/Chooser.php
filewiththefollowingcontent:
<?php
namespace
Packt\ProductSlider\Controller\Adminhtml\Catalog\Category\Widget;
classChooserextends
\Magento\Catalog\Controller\Adminhtml\Category\Widget\Chooser{
protectedfunction_getCategoryTreeBlock(){
return$this->layoutFactory->create()->createBlock(
'Packt\ProductSlider\Block\Adminhtml\Catalog\Category\Widget\Chooser',
'',
[
'data'=>[
'id'=>$this->getRequest()->getParam('uniq_id'),
'use_massaction'=>$this->getRequest()-
>getParam('use_massaction',false),
]
]
);
}
}
Thehighlightedcodeshowsthedifferencesbetweenwhatischangedinthecode.
11. Cleanthecacheandreloadtheconfigurationpageofthewidget.WhenyouclickontheSelectCategorybutton,apopupopenswiththecategorytree.Whenyouselectacategoryandyouinspecthiddeninputfield,youwillseethatanumber(thecategoryID)issetinsteadofthepath.
12. Savethewidgetandreloadthehomepage.Youwillseethattherightproductsareshownforthechosencategory.
Howitworks…Inthisrecipe,wecreatedacustomconfigurationparametertodevelopabetteruserexperiencefortheadminusers.
Webasedthisconfigurationfieldontheexistingcategorytreepop-upwindowthatisusedinotherwidgettypes,suchastheCatalogCategoryLinktype.Touseanexistingfield,wejusthavetomodifysomeconfigurationsinthewidget.xmlfileandthefieldisreadytouse.
However,thistypeofconfigurationfieldisnotexactlywhatwearelookingfor.ThefrontendrepresentationwasOK,butawronglyformattedcategoryIDwasreturnedinthebackground.
Tosolvethis,wecreatedacustomconfigurationfieldthatextendsthebehaviorfromthestandardcategorychooser.WeonlyhadtochangesomethingsthatareresponsibleforreturningacorrectlyformattedcategoryID.
ThefirstthingwedidwascreatinganewBlockclassthatextendsfromthestandardclassattheMagento\Catalog\Block\Adminhtml\Category\Widget\Chooserlocation.Inthisclass,weoverridethreemethods.First,weaddedthesetModuleName()methodinthe_construct()method.WecalledthismethodsothatwecanusethetemplatesfromtheMagento_CatalogmoduleinthePackt_ProductSlidermodule.
IntheprepareElementHtml()method,weconfiguredanAJAXURLtorendertherightblockwhenthepopupshowsup.Laterinthismethod,westrippedsomelogictoextractthecategoryIDfromthepath.
Inthelastmethod,getNodeClickListener(),wedidachangesothatonlythecategoryIDisreturnedtotheforminsteadofthecategorypath.
WhentheSelectCategorybuttonisclicked,anAJAXcallisdoneandablockwillberenderedtoshowthecategorytree.WealsoneededtochangetheblockthatisrenderedinthatAJAXcall,sowehadtooverwritethiscall.
Wedidthisbycreatinganewcontrolleractioninourmodulethatextendsfromthestandardcontrolleraction,Magento\Catalog\Controller\Adminhtml\Category\Widget\Chooser.Inthisaction,wechangedtheBlockclasstoourcustomclassusingthe_getCategoryTreeBlock()method.
There’smore…Likewedidinthisrecipe,itispossibletocreateconfigurationfieldsthatuseacustomHTMLoutput.
Alotispossibletoshowaconfigurationparameter,butyouhavetoreturnavaluethatwillbesavedinthewidgetconfiguration.Thisisalwaysdonewithainputformelement,whichhasthenamingconvention<inputname="parameters[<parameter_name>]">.
Replacethe<parameter_name>tagwiththenameofyourcustomconfigurationparameterandthevaluesofthiselementwillbehandledlikealltheotherconfigurationparametersofthatwidget.
FinalizingthethemingThefrontendrepresentationofthewidgetthatwejustcreatedisnotsomethingthatinvitespeopletobuysomeproducts.Itisjustalistoftheproductnamesforagivencategory.
Thelaststepofthischapteristofinalizethethemingofthewidget.WewillcreateanHTMLoutputthatshowsanimage,name,andpriceofthegivenproducts.
WithajQueryplugin,wewillconverttheHTMLoutputtoaslider,sowecanscrollthroughtheproducts.
GettingreadyOntheInternet,therearealotofgoodJavaScriptcarouselplugins.Inthisrecipe,wewillusethefollowing:
http://kenwheeler.github.io/slick/
Forthecode,wewillbuildfurtheronthethingsthatarecreatedinthepreviousrecipesofthischapter.Ensurethatyouhavetherightfilesinstalled.
Howtodoit…Inthefollowingsteps,thelastactionsaredescribedtocompletethewidgetwithagood-lookingcarousel:
1. ThefirstthingistogenerateagoodHTMLoutputthatisusableforthejQueryplugin.Addthefollowingcodetothelist.phtmlfileofthePackt_ProductSlidermodule:
<?php
$productCollection=$block->getLoadedProductCollection();
$_helper=$this->helper('Magento\Catalog\Helper\Output');
?>
<divclass="blockblock-product-sliderslider-list">
<divclass="block-title">
<h2><?phpecho$block->getTitle()?></h2>
</div>
<divclass="block-content">
<?phpif(count($productCollection)):?>
<divclass="product-slider">
<?phpforeach($productCollectionas$product):?>
<divclass="product">
<divclass="product-image">
<ahref="<?phpecho$product->getProductUrl()?>">
<?phpecho$block->getImage($product,
'category_page_grid')->toHtml()?>
</a>
</div>
<strongclass="product-name">
<a
href="<?phpecho$product->getProductUrl()?>">
<?phpecho$_helper->productAttribute($product,
$product->getName(),'name');?>
</a>
</strong>
<?phpecho$block->getProductPrice($product)?>
</div>
<?phpendforeach;?>
</div>
<?phpendif;?>
</div>
</div>
2. Whenwereloadthefrontend,weseeasimplelistofproductswiththeirname,price,andimage.
3. Thenextstepistoinitializethecarouselscript.GotothefollowingURLanddownloadthelatestversionoftheplugin:
http://kenwheeler.github.io/slick/
4. UnzipthearchiveonyourlocalPC.Forthisrecipe,weneedthefollowingtwofilesofthearchive:
slick/slick.js
slick/slick.css
5. WhenyouwanttoaddtheCSSfiletothemodule,createtheapp/code/Packt/ProductSlider/view/frontend/web/css/source/module/_slick.less
fileandcopythecontentoftheslick/slick.cssfiletothatfile.6. ToloadtheLESSfile,linkitinthe_module.lessfile.Createthe
app/code/Packt/ProductSlider/view/frontend/web/css/source/_module.less
filewiththefollowingcontent:
@import'module/_slick.less';
7. ThenextstepistoaddtheJavaScriptfile.InMagento2,weusetheRequireJSlibrarytoincludethefile.First,wecopytheslick/slick.jsfiletothefollowinglocation:app/code/Packt/ProductSlider/view/frontend/web/js/slick.js.
8. ToregistertheJavaScriptfile,wehavetocreateaRequireJSconfigurationfile.Createthefileatapp/code/Packt/ProductSlider/view/frontend/requirejs-config.jswiththefollowingcontent:
varconfig={
map:{
'*':{
slick:'Packt_ProductSlider/js/slick'
}
}
};
9. WeaddedJavaScriptandLESSfiles,sothismeansthatwehavetoclearthepreviouslygeneratedstaticcontent.Wecandothisbyremovingthefollowingfolders:
pub/static/frontend/
pub/static/_requirejs/
var/view_preprocessed/
10. Cleanthecacheandreloadthefrontend.Becausethestaticcontentneedstobegenerated,itcantakesometimetoloadthefrontend.
11. Thelastthingthatwehavetodoistoinitializetheslickcarousel.Addthefollowingcodeattheendoftheapp/code/Packt/ProductSlider/view/frontend/templates/product/slider/list.phtml
file:
<scripttype="text/javascript">
require(['jquery','slick'],function($){
$(function(){
$('.product-slider').slick({
dots:false,
infinite:false,
speed:300,slidesToShow:5,
slidesToScroll:5,
responsive:[
{
breakpoint:1024,
settings:{
slidesToShow:4,
slidesToScroll:4,
infinite:true
}
},
{
breakpoint:770,
settings:{
slidesToShow:3,
slidesToScroll:2
}
},
{
breakpoint:600,
settings:{
slidesToShow:2,
slidesToScroll:2
}
},
{
breakpoint:400,
settings:{
slidesToShow:1,
slidesToScroll:1
}
}
]
});
});
});
</script>
12. Reloadthehomepageandyouwillseethatthecarouselisinitialized,asshowninthefollowingscreenshot:
13. TheJavaScriptconfigurationcontainssomebreakpointsforresponsivedevices.Whenyouhaveasmallerscreen,youwillseesomethingasshowninthefollowingscreenshot:
14. Tofinishthischapter,wecanmakethetitleofthiswidgetconfigurable.Inthewidgetconfiguration,thereisatitlefieldthatwecanuseforthis.Whenwechangethefollowinghighlightedcodeofthelist.phtmlfiletothefollowing,thetitleofthatfieldisusedinthefrontend.
...
<divclass="block-title">
<h2><?phpecho$block->getTitle()?></h2>
</div>
...
Howitworks…Inthefirststep,wecreatedagoodHTMLoutputthatiscompatiblewiththeslickJavaScriptplugin.Foreveryproduct,weshowanimage,name,andprice.Behindeveryimageandnameisalinkthatredirectstotherespectiveproductdetailpage.
TheproductsliderscriptcontainsaJavaScriptandCSSfilethatwehavetoincludeintheshop.FortheCSSfile,weconvertedthecontenttoaLESSfilesothatitisrenderedwiththeCSSofthewholeshop.
Foreverymodule,Magentolooksfora_module.lessfileintheview/frontend/web/css/source/directory.Inthatfile,weincludeda_slick.lessfilethatcontainstheCSScodeoftheplugin.
FortheJavaScriptfile,weusedtheRequireJSsystemofMagentotoincludethefile.Weplacedtheslick.jsfileintheview/frontend/web/js/folder.
Thescriptisinitializedintheview/frontend/requirejs-config.jsfile.Withthecodeinthatfile,theslick.jsfileisinitializedbutnotloaded.Toloadthefile,wehadtousetherequirefunctionasshowninthefollowingcode:
require(['jquery','slick'],function($){
});
Withthepreviouscode,thejQuerylibraryandtheSlicklibraryareloadedbeforethecodebetweenthebracketsisexecuted.
Toinitializetheproductslider,weusedtheJavaScriptcodethatisusedinthedocumentationoftheslickplugin.Withthatconfiguration,wecreatedsomebreakpointstomakethepluginresponsive.
Chapter10.PerformanceOptimizationInthischapter,wewillcoverthefollowingrecipes:
BenchmarkingawebsiteOptimizingthefrontendofthewebsiteOptimizingthedatabaseandMySQLconfigurationsOptimizingtheApachewebserverFindingperformanceleaksinMagentoConfiguringOPcache,Redis,andMemcachedOptimizingthePHPconfigurations
IntroductionInasportcompetition,everysecond,millisecond,decidewhetheraplayerwinsacompetitionornot.Everysmallaspectthatimprovestheperformanceisastepintherightdirectiontowinacompetition.Forwebsites,thisisthesame.Thefasterawebsiteis,thebetteritis.AfastwebsitegivesabetteruserexperienceanditisbetterforSEO.So,thefaster,thebetter.
Magentoisaframeworkthatcallsalotofoperationswhenloadingapage.Alltheseoperationstakesometime.ThismeansthatMagentoisnotoneofthebestperformingsystemsintheworld,especiallywhenyouareworkingwithalotofproducts,attributes,multiplestoreviews,andmore.
However,withagoodsetupandtherighttools,youcanimprovetheperformanceofyourshopsothatitwillperformveryfast.
Theperformanceofawebsitehasalotofimpactonyourvisitors.Herearesomefactsabouttheperformanceofawebsite:
Whenyoursiteis100millisecondsslower,youlose1%ofthetotalsales.Whenasiteisslowerthan2-3seconds,userswillleavebecauseyoursiteisslow.AquicklyloadingpagehasapositiveinfluenceonyourSEOresults.MoreandmorepeoplehavemobiledeviceswithoutthefastestInternetconnection.
Asyoucansee,theperformanceisanimportantthingwhenyouwanttoimprovetheconversionofyourwebsite.Customerswillleaveyoursitewhenitisslow,andsearchengineswillgiveyoualowerrankwhenyoursiteisslow.
Theimprovementoftheperformanceismostlyoneofthelaststepsinthedevelopmentworkflowofasite.Peoplebuildsomethingandwhenitisready,theybegintolookathowthattheycanoptimizesomepartstomakeitfaster.
Inthischapter,wewillexplore,detect,andfixperformanceleaksinaMagentowebshopusingsomeperformancetools.
Magentodeliverssometoolsbydefault,butwehavetolookatthewholepicture.TwoidenticalMagentoinstallationscanhaveadifferentperformancethatcanbecausedbythefollowingreasons:
HardwareNetworkLoadDeviceoftheclient
BenchmarkingawebsiteWhenyouhaveahigh-trafficsite,youwouldprobablywanttoknowthelimitsofthewebsite.WhatwillbethecapacityofmywebsitewhenIlaunchamarketingcampaign?Whatisslowingdownmysite?Whichoptimizationshavethemosteffect?
Toknowthelimitsofawebsite,wehavetousebenchmarkingtools.Withabenchmarkingtool,wewillcreatealoadonthewebsiteandlogtheresponsetimetoafile.Byincreasingordecreasingsomevalues,wecandeterminetheloadthatisthelimitofawebsite.
Inthisrecipe,wewillbenchmarktheMagentositebydoingsometestswithApacheBenchandSiege.Withthesetools,wecanmeasuretheperformanceofdifferentpages.
GettingreadyForthisrecipe,weneedsometoolsthatneedtobeinstalledonthewebserver.Ensurethatyouhavethefollowingtoolsinstalled:
ApacheBench(ab):Thistoolcanbeinstalledusingthesudoapt-getinstallapache2-utilscommand.Whenthisisinstalledontheserver,youcanusetheab-hcommandtodisplaytheusageinformation.Siege:WewilldosomebenchmarkingtestswithSiegethatisinstalledonthesameserverastheMagentoinstance.Toseeifitisinstalled,youcanrunthefollowingcommand:
siege-V
Ensurethatthe-Voptionisinuppercase.WhenSiegeisinstalled,youwillseeitsversionnumber,asshowninthefollowingscreenshot:
Ifitisnotinstalled,youcanrunthefollowingcommandwhenyouareusingaDebian-basedLinuxdistribution:
sudoapt-getinstallsiege
Anotheroptionistodownloadtheinstallationfileandinstallitusingthefollowingsteps:
1. Downloadthearchiveusingthefollowingwgetcommand:
wgethttp://download.joedog.org/siege/siege-3.1.0.tar.gz
2. Extractthefilebyrunningthefollowingcommand:
tarxfzsiege-3.1.0.tar.gz
3. Movethefoldertothepreferredlocationandgotothatdirectoryusingthecdcommand.
4. Whenyouareinthatfolder,youcaninstallSiegeusingthefollowingcommand:
sudo./configure
Howtodoit…1. Togetanideaoftheresponsetimewithaloadofanumberofconcurrentusers,we
canuseApacheBenchtoperformsomesimpletests.Withthefollowingcommand,wewillrunatestthatwritestheresulttoaCSVfile:
ab–c10–n50–eapachebench.csvhttp://magento2.local
2. Inthepreviouscommand,wedidaloadtestwiththefollowingparameters:
-c:Thisparameterrepresentsthenumberofconcurrentusers.Inthistest,weran10requestsatthesametimethroughout.-n:Thisparameterrepresentsthenumberofrequests,whichis50inthiscase.So,wewillhave50resultsinthefile.-e:Thisparameterrepresentstheoutputfile.TheoutputiswrittentothegivenCSVfile.
TipThe-goptionmeansthesameasthe-eoption,buta-goptionwillgenerateaTSV(TabSeparatedValue)file,alsoknownasagnuplotfile.
3. Whenyouruntheprecedingcommand,itwillreturnanoutputasshowninthefollowingscreenshot:
4. Thisreportshowsthegeneralstatisticsofthetest.ThespecificresultsofeachrequestaresavedintheCSVfile(apachebench.csv).
5. LoadtestingwithSiege.
SiegeisanotherloadtestingtoollikeApacheBench.ThedifferencebetweenSiegeandApacheBenchisthatSiegehasmorefunctionsthanApacheBench.Itisdesignedtoperformastresstestwithanumberofconcurrentusers.SiegealsoprovidestheabilitytoworkwithHTTPauthentication,cookies,sessions,andmore.Whenyouwriteagoodscript,youcansimulatearealstresssituation.
6. ForaloadtestwithSiege,wewilluseatextfilewherewewillconfiguresomeURLsthatwillbeusedduringtheSiegeloadtest.WhenwedoatestwithdifferentURLs,wewilltestmorepages,andwecanfindmorepitfallsonthewebsite.Createafilecalledsiege_url.txtwiththefollowingcontent:
http://magento2.local/
http://magento2.local/pub/static/frontend/Magento/luma/en_US/mage/calendar.css
http://magento2.local/pub/static/frontend/Magento/luma/en_US/css/styles-
m.css
http://magento2.local/pub/static/frontend/Magento/luma/en_US/images/logo.svg
http://magento2.local/pub/static/frontend/Magento/luma/en_US/requirejs/require.js
http://magento2.local/women/tops-women.html
http://magento2.local/women/tops-women.html?cat=28
http://magento2.local/checkout/cart/add/?product=1
http://magento2.local/checkout/
http://magento2.local/pub/static/frontend/Magento/luma/en_US/js-
translation.json
http://magento2.local/pub/static/frontend/Magento/luma/en_US/Magento_Ui/templates/block-
loader.html
http://magento2.local/pub/static/frontend/Magento/luma/en_US/Magento_Ui/templates/modal/modal-
popup.html
http://magento2.local/pub/static/frontend/Magento/luma/en_US/Magento_Checkout/template/onepage.html
http://magento2.local/pub/static/frontend/Magento/luma/en_US/Magento_Checkout/template/progress-
bar.html
http://magento2.local/pub/static/frontend/Magento/luma/en_US/images/loader-
1.gif
http://magento2.local/pub/static/frontend/Magento/luma/en_US/images/select-
bg.svg
http://magento2.local/customer/account/login/
http://magento2.local/contact/
http://magento2.local/catalog/product_compare/add/?product=1
http://magento2.local/pub/static/frontend/Magento/luma/en_US/js/theme.js
http://magento2.local/catalog/product_compare/index/
http://magento2.local/catalogsearch/result/?q=watch
http://magento2.local/catalogsearch/advanced/result/?
name=Watch&sku=&description=analog&short_description=&price%5Bfrom%5D=50&price%5Bto%5D=100&tax_class_id=
http://magento2.local/pub/media/catalog/product/cache/1/small_image/240x300/e9c3970ab036de70892d86c6d221abfe/sample_data/m/g/mg05-
br-0.jpg
http://magento2.local/customer/account/forgotpassword/
http://magento2.local/search/term/popular/
7. ChangetheURLssothattheymatchyourMagentoconfigurations.EnsurethatyouaretestingyourdevelopmentenvironmentwithvalidURLs.
8. Withthefollowingcommand,wecanstartaloadtestwith50concurrentusersbasedontheURLsthatweenteredinthesiege_url.txtfile:
siege-c50-i-t1M-d3-fsiege_url.txt
9. Thetimetakenbythiscommandtocomplete,dependsonthewebshop’sperformance.Theoutputofthecommandwillbesimilartothefollowing:
$siege-c50-i-t1M-d3-fsiege_url.txt
**SIEGE3.0.5
**Preparing50concurrentusersforbattle.
Theserverisnowundersiege…[alert]socket:1772328704selecttimed
out:Connectiontimedout
[alert]socket:1998931712selecttimedout:Connectiontimedout
[alert]socket:1671616256selecttimedout:Connectiontimedout
socket:2024109824selecttimedout:Connectiontimedout
[alert]socket:1713579776selecttimedout:Connectiontimedout
Liftingtheserversiege…done.
Transactions:171hits
Availability:86.80%
Elapsedtime:59.44secs
Datatransferred:1.02MB
Responsetime:6.08secs
Transactionrate:2.88trans/sec
Throughput:0.02MB/sec
Concurrency:17.49
Successfultransactions:190
Failedtransactions:26
Longesttransaction:27.51
Shortesttransaction:0.00
NoteIntheprecedingoutput,wecanseesometimeouts.Thismeansthattherearerequeststhatarenotsuccessful.
10. Withthesiege-hcommand,youcanseealltheavailableoptionsofthatcommand.
Howitworks…WestartedthisrecipewithaloadtestusingApacheBench.ThisisatoolthatwaspreviouslyapartoftheApacheWebServer.Currently,itispackagedintheapache2-utilsbundle.Thispackagealsocontainstoolssuchaslogrotation,generationofhtpasswdfiles,andmore.
WithApacheBench,weperformedaloadtestwithanumberofconcurrentusers.Aconcurrentuserisauserthatisgeneratingloadonthewebsite.Whenwetestwith10concurrentusers,wewillsimulateacontinuousloadof10processesduringthetimeofthetest.Whenoneoftherequestsisfinished,anewoneisfired,sotherearealways10requestsrunning.
Whenweyoualimitof30seconds,wecanseehowmanyrequestsaresuccessfullyfinishedduringthattime.Thiscangiveyouagoodideaofthecapacityofyourwebsite.
WithSiege,wecandothesameaswithApacheBench.ThemaindifferencebetweenApacheBenchandSiegeisthatSiegehasmorefeatureswhenyoucompareitwithApacheBench.
Inthisrecipe,weperformedaloadtestwithalistofgenericMagentoURLssuchassomepages,staticcontent,productimages,andmore.WealsoaddedsomeproductstothecartwiththequerystringURLsothatwecansimulateahumanworkflowonthewebsite.
AlotofpagesarecachedinMagento,butwealsohavetotestthesession-specificpages,suchasthecart,checkout,search,andmore.
NoteWithApacheBenchandSiege,youcancreateloadonawebsite.Whenyoudothisonaremotesite,itispossiblethatyouwillbeblockedbyafirewallbecausealotofrequestsfromthesameIPwillgivetheimpressionofanattack.
OptimizingthefrontendofthewebsiteWhenyoulookattheperformanceofawebsite,therearemanypointsthatyoucanoptimize.Betweenthestartofarequestandtherenderedpageinthebrowser,alotofoperationsarecarriedout.
Inthisrecipe,youwilllearnhowtospotbottlenecksandoptimizesomethingsthatincreasetheperformance.
GettingreadyForthisrecipe,wewillusetwobrowserpluginswherewecanmeasuresomemetrics:
app.telemetry:Withthissimplebrowserplugin,wecanmonitortheloadtimeofeachpage.Wecandownloadthepluginfromthefollowingwebsite:
http://www.apptelemetry.com/en/page-speed-monitor.html
Thiswilladdaniconinthebrowserbarwherewecanreadtheresponsetime.
YSlow:Withthisbrowserplugin,canwegenerateareportofthingsthatwecanoptimizeinthewebsite.YoucaninstalltheYSlowplugintoChromeorFirefoxfromthefollowingURL:
http://yslow.org/
Howitworks…ThefollowingstepsdescribehowwecanfindpitfallsinthefrontendofMagentotodecreasethepageloadtime:
1. Whenyouhavetheapp.telemetryplugininstalled,youwillseeanicononthebrowserbar,asshowninthefollowingscreenshot:
2. Toanalyzeapageload,youhavetoreloadapage,andwhenthepageisloaded,youwillseethetimetakenforloadinginthebrowserbaricon.Whenyouclickontheicon,youcanseemoredetails,asshowninthefollowingscreenshot:
3. Whenwedothesameonaremotewebsite,wewillseedifferentresults.ThetimetakenbytheTCPandDNSstepswillbelonger.
4. WewillcontinuebygeneratingaperformanceanalysiswithYSlow.YSlowisabrowserpluginthatneedstobeinstalledinyourbrowser.Whenyouhaveinstalledthebrowserplugin,youcanclickontheiconinthebrowserbar.
5. AwindowwillshowupandwhenyouclickontheRunTestbutton,thereportwillbegenerated.
6. WhenthereportisreadyandyouclickontheGradetab,youwillseethereport,asshowninthefollowingscreenshot:
7. WithadefaultMagentoinstance,youwillhaveagoodscore.Butwecanincreasethiswithsomesimpleoptimizations.
8. ThefirstthingthatwecanoptimizeistoaddExpiresheaders.Withthefollowingcode,wecanaddExpiresheaderstosomestaticfiles.Addthiscodeinthe.htaccessfilebyreplacingthe<IfModulemod_expires.c>sectionwiththefollowingcode:
<IfModulemod_expires.c>
############################################
##AdddefaultExpiresheader
##http://developer.yahoo.com/performance/rules.html#expires
ExpiresDefault"accessplus1year"
ExpiresActiveOn
ExpiresByTypeimage/gif"access1year"
ExpiresByTypeimage/jpg"access1year"
ExpiresByTypeimage/jpeg"access1year"
ExpiresByTypeimage/png"access1year"
ExpiresByTypeimage/x-icon"access1year"
ExpiresByTypetext/css"access1month"
ExpiresByTypeapplication/x-javascript"access1month"
</IfModule>
9. Whenweaddthiscode,wewillhavetoreloadthewebsiteinordertocheckthatthesiteisnotbrokenafterthechangeinthe.htaccessfile.Whenthisisdone,wecanrunthetestagain.WewillseethattheAddExpiresHeadersisnowchangedtoanA.
10. AnotherimprovementistominifyJavaScriptandCSS.TherearealotoftoolstominifyJavaScriptandCSSfilesbutthisalsoasettingintheconfigurationofMagento.Inthebackend,navigatetoStores|Configuration|Advanced|DeveloperandchangethefollowingsettingstoYes:
MergeJavaScriptfiles:YesMinifyJavaScriptfiles:YesMergeCSSfiles:YesMinifyCSSfiles:Yes
11. Aftersavingthesesettings,theconfigurationwilllookasshowninthefollowingscreenshot:
12. Whenyourunthetestagain,youwillseethattheoverallscoreisraisedagain.Therearetwopointsthatneedmoreattention:UseaContentDeliveryNetworkandUsecookie-freedomains.YoucanuseanexistingCDNprovidertohostyourstaticfiles,butyoucanalsocreateanotherdomainsuchasstatic.magento2.localthatalsopointstotheMagentoroot.Inthebackend,youcanconfigureMagentotousethisdomainforstaticcontent.YoucanconfigurethisbynavigatingtoStores|Configuration|General|Web.InthebaseURLssection,youcanconfigurethevaluesasshowninthefollowingscreenshot:
Howitworks…Westartedthisrecipeusingtheapp.telemetryplugintoshowthedifferentpartsofapageload.Whenweclickontheiconofthatplugin,weseethefollowingparameters:
Redirect:Thisisthetimethatistakentoredirectthispage(ifthereisaredirect).AppCache:Thisisthetimeofyourlocalcache.DNSLookup:ThisisthetimetakentoresolvetheIPaddressforthegivendomainname.TCPConnection:Thisisthetimetakentosendarequesttotheserver.ThisismostlylongerwhensendingalargePOSTrequest.TCPRequest:Thisisthetimethattheserverneedstoprocessyourresponse.Inthistimeframe,theserverwillbuildtheHTMLfileofthepage.TCPResponse:Thisisthetimetakentodownloadtheresponsetoyourlocaldevice.Withslownetworks,thistimewillbemore.Processing:ThisisthetimeittakestorenderyourHTML,CSS,JavaScript,andotherstuff.Onloadevent:Thisisthetimethattheonloadeventtakes.Afterthis,thepageisfullyloaded.
Inthefirstcolumn,Offset,youseethetimefromthestartoftherequest.Inthesecondcolumn,Duration,youcanseehowmuchtimeistakenforeachstep.
Inthenextstep,wedidananalysisofthepagewithYSlow.YSlowwilltestawebsiteondifferenttopics.Allthesetopicsarebasedonaruleset.WiththeChromeplugin,theYSlow(v2)rulesetisautomaticallyselected.YoucanalsochoosetheClassic(v1)ruleset.
TheYSlow(v2)rulesetwilltestonthefollowingtopics:
MinimizingHTTPrequestsUsingaContendDeliveryNetworkAvoidingemptysrcorhrefinstancesAddingExpiresHeadersoracache-controlheaderUsingcompressionforstaticfilesPlacingstylesheetsatthetopPlacingJavaScriptatthebottomAvoidingCSSexpressionMakingJavaSriptandCSSexternalReducingDNSlookupsMinifyingJavaSriptandCSSAvoidingredirectsRemovingduplicateJavaScriptandCSSConfiguringETagsMakingAJAXcacheableUsingGETfoAJAXrequestsReducingtheNumberofDOMelementsReducingthenumberof404errors
ReducingthecookiesizeUsingcookie-freedomainsforstaticfilesAvoidingfiltersNoscalingofimagesinHTMLMakingfavicon.icosmallandcacheable
WhenwedidthetestonastandardMagento2shop,theoverallscorewasquitegood.Whenyoudevelopyourownstuff,youwillhavetokeepthepreviousrulesetsinmindbecausethesepointsreducetheloadofthepage.
TheYSlowrulesetmostlyincreasestheperformanceofthefrontend(theloadingofJavaScript,CSS,images,andsoon).Itdoesn’tgiveadviceonhowtooptimizeyourPHPprocesses.
First,weaddedsomeExpiresheadersinstancetothestaticfiles.Withtheseheaders,weconfiguredhowlongstaticfileswillbecachedinthebrowsersofthevisitors.ForeveryMIMEtype,wecanspecifyadifferenttime.
ThesecondpointtooptimizeistomergetheJavaScriptandCSSfiles.Loadingonebigfileisfasterthanloading100smallfiles.Thiscanbedeclaredbythefollowingreasons:
Foreveryfile,anHTTPrequestwillbecreated.Also,headersandcookiesaresentforeveryrequest.SomewebserverscanonlyhandlealimitednumberofHTTPrequestsatatime.So,iftherearealotofrequests,theywillbequeued.Sometimes,itmayhappensthatoneoftherequestshangs,soyourpagekeepsloading.Withfewerrequests,youhavelesserchancesthatthishappens.
Finally,weconfiguredadifferentdomaintothestaticcontent.Whenyouhaveadifferentdomain(oracookie-freedomain),thecookiesarenotsenttotheserverforeveryrequest,sothisreducesthebandwidth.
There’smore…Ifyougotothesitegtmetrix.com,youcanenteraURLthatyouwanttoanalyze.ThistoolwilldoaPageSpeedandYSlowtestonthegivenURL.
Becausethisisanonlinetool,youhavetoensurethatyoursiteisaccessiblebytheGTmetrixserver.
OptimizingthedatabaseandMySQLconfigurationsTheMagentoapplicationsuseadatabasetostoreallthedata,includingproducts,customers,orders,andmore.ThedatabaseisthecentralstorageofallthedatathatisavailableintheMagentoinstance.ScalingMagentotomultiplefrontendserversisnotthathardbutscalingthedatabaseismuchharderbecausethedataissomethingthatneedstobeinsync.
Inthisrecipe,wewillseehowwecanoptimizethedatabaseandtheMySQLserver.
GettingreadyEnsurethatyouhaveaccesstoadatabaseclientwhereyoucandosomequeriestoyourdatabase.Inthisrecipe,wewillusephpMyAdmin.
Howtodoit…Inthefirstpartofthisrecipe,wewilloptimizethetablestructuresoftheMagentodatabase.Takealookatthefollowingsteps:
1. OpenphpMyAdminandclickontheMagentodatabase.Youwillseeanoverviewofallthetables.
2. Atthebottomofthispage,clickontheCheckAllbutton.3. Whenyouclickonthedropdownlist,youcanrepairthetableandoptimizeit,as
showninthefollowingscreenshot:
TipEnsurethatyourunthisactionforallthetablesintheMagentodatabase.Itcanhappenthatthelistisdividedinmultiplepages.AMagento2shophasover300tables.ThephpMyAdmininstallationshowsbydefault250tablesperpage.
4. WehavenowoptimizedthetablesofMagento.Theotherthingthatyoucandoisrunthedatabaserepairtooltocheckwhethersomerelationsaremissing.
Wearenowatthesecondpartofthisrecipe.Inthispart,wewillseesomeoptimizationsthatwecandoontheMySQLserver:
1. AgoodMySQLserverstartswithgoodhardwareandtherightoperatingsystem.TorunMagento,itisrecommendedthatyouuseadedicatedserveroraVPS.Youcanalsouseasharedhostingenvironment,butthisisnotthebestoptionbecausetheRAMandCPUloadissharedbetweenotherusersonthatserver.WithaVPSordedicatedserver,youhaveafixednumberofCPUsandRAMavailable.
2. WhenyourserverhasenoughRAMavailable,youcanturnofftheswappeddevices.Sometimes,theswapoptionwillbeautomaticallyusedevenwhenthereisenoughmemoryavailable.
3. Onyourserver,openthe/etc/mysql/my.cnffile.Lookfortheskip-external-lockingsettingunderthe[mysqld]section.Ifthesettingisn’tthere,youcanaddthis
onanewline.4. ToshowthesizeofthekeybufferforMyISAMtables,wecanrunthefollowing
queryontheMySQLprompt:
mysql>SHOWVARIABLESLIKE'%key_buffer%';
5. ForMagento,therecommendedvalueis512MB.Tosetthisvalue,wecanrunthefollowingcommandontheMySQLprompt:
mysql>SETGLOBALkey_buffer_size=536870912;
6. Next,wewillchangesomedefaultconfigurationparameters.WecansettheseinthemainconfigurationfileoftheMySQLserverthatislocatedat/etc/mysql/my.cnf.
7. Openthatfileandpastethefollowingconfigurationunderthe[mysqld]section:
key_buffer=512M
max_allowed_packet=64M
thread_stack=192K
thread_cache_size=32
table_cache=512
query_cache_type=1
query_cache_size=52428800
tmp_table_size=128M
expire_logs_days=10
max_binlog_size=100M
sort_buffer_size=4M
read_buffer_size=4M
read_rnd_buffer_size=2M
myisam_sort_buffer_size=64M
wait_timeout=300
max_connections=400
8. SavethefileandrestartyourMySQLserver.Youcandothisbyrunningthefollowingcommand:
sudoservicemysqlrestart
Howitworks…WhenoptimizingaMySQLserver,youhavetoknowthecapabilitiesofyourserverandthetrafficthatyouexcept.Withtheseparameters,youcancalculateagoodvalueforthekey_buffer,query_cacheandtable_cache.
Withthefollowingcommands,youcanviewtheMySQLserverstatus:
Command Description
mysql>SHOWSTATUS;ThiscommandshowsthecurrentstatusoftheMySQLserver.ItisavailableinMySQL5.0andlater,andisthestandardquerytoshowalltheglobalvariables.
mysql>SHOW
VARIABLES;ThiscommandshowsalltheMySQLvariables.
mysql>SHOWENGINE
INNODBSTATUS;ThiscommandshowsthecurrentstatusoftheINNODBengine.
mysql>SHOWGLOBAL
STATUS;Thisqueryshowsvaluesofthecurrentloadonthedatabaseserverforallconnections.
mysql>SHOWLOCAL
STATUS;Thiscommandshowsthesameasthelastonebutnowonlyforthecurrentconnection.
$mysqladmin
extended-i100-r
YouneedtorunthiscommandinaLinuxprompt.ThiscommandshowswhatishappeningwiththeMySQLserver.
DatabaseoptimizationisoneofthekeyaspectstotuneyourMagentowebshop.Databaseprocessesareresponsibleforabigpartofthepageload,soagoodperformingdatabaseisveryimportant.
OptimizingtheApachewebserverMagentocanberunonApacheorNginx.Configurationfilesforbothsystemsareavailableinthecodebase(.htaccessandnginx.conf.sampleintherootfolder).
Theperformanceofthewebserverdependsonwhathardwaretheserverisrunning.Networkcard,RAM,disk,OS,andCPUarethemostimportanthardwarecomponentsthatyouhavetothinkaboutwhenchoosingaserver.
Howtodoit…1. Thefirstthingtothinkaboutisagoodoperatingsystemtorunyourwebserver.Itis
highlyrecommendedthatyouuseaLinuxdistributionbecauseitisthestandardforPHPapplications.
Intherecipesofthisbook,weusedanUbuntuserver(aDebian-basedLinuxdistribution).
TipDon’tuseaWindowsservertorunMagento.Itwillwork,butitislessefficientandyoucanhaveissueswithfilepermissions,code,andmore.
2. Updatetheoperatingsystemtothelateststableversion.Anupdatedsoftwareissaferandfaster.UseatleasttheApache2.4.Atthetimeofwriting,thisisthelateststablereleaseandhassomeimprovementsforperformance.
3. Installonlytherequiredsoftwareonyourwebserver.Lessismore!Whenalotofsoftwareisinstalledthatyoudon’tuse,youwillhavebackgroundtasksthatarerunningfornothing.
4. Useafastfilesystem.UseanSSDinyourserverbecausethisismuchfasterthanadisk.Neveruseafilesystemthatissharedoveranetworkbecausethisisveryslow.
5. Youhavetoconfigureyourwebserversothatitdoesn’tswap.Whenyourwebserverbeginstoswap,allrequestswillbeservedslower.ThefirstthingtodoistocomparethevolumeofRAMontheserverwiththeaveragememoryloadofarequestandanumberofrequests.ThesecondthingthatyoucandoisconfiguretheMaxClientssetting.Thissettingcontrolsthenumberofchildprocesseswhentheserverisswapping.
6. LookattheHostnameLookupssettingandcheckwhetherthisisconfiguredwiththeOffvalue.
7. EnabletheApachemodulesmod_deflateandmod_headers.Wecandothiswiththefollowingcommands:
sudoa2enmoddeflate
sudoa2enmodheaders
8. Openthe.htaccessfileintheMagentorootandgotothemod_deflateconfigurationtag.Uncommentsomelinessothattheblocklookslikethefollowingcode:
<IfModulemod_deflate.c>
############################################
##enableapacheservedfilescompression
##http://developer.yahoo.com/performance/rules.html#gzip
#Insertfilteronallcontent
SetOutputFilterDEFLATE
#Insertfilteronselectedcontenttypesonly
AddOutputFilterByTypeDEFLATEtext/htmltext/plaintext/xmltext/css
text/javascript
#Netscape4.xhassomeproblems…
BrowserMatch^Mozilla/4gzip-only-text/html
#Netscape4.06-4.08havesomemoreproblems
BrowserMatch^Mozilla/4\.0[678]no-gzip
#MSIEmasqueradesasNetscape,butitisfine
BrowserMatch\bMSIE!no-gzip!gzip-only-text/html
#Don'tcompressimages
SetEnvIfNoCaseRequest_URI\.(?:gif|jpe?g|png)$no-gzipdont-vary
#Makesureproxiesdon'tdeliverthewrongcontent
HeaderappendVaryUser-Agentenv=!dont-vary
</IfModule>
TipWhenyougetaninternalservererrorafterthechange,itispossiblethattheheadersmoduleisnogenabled.Runthesudoa2enmodheaderscommandandrestarttheservertofixthis.
9. TakealookattheKeepAlivesettingofyourApacheserver.Whenthissettingisactive,theApacheservercanhandlemultiplerequeststroughthesameTCPconnection.
10. ConfiguretheMulti-ProcessingModules(MPM)foryourcase.Thevaluesoftheseconfigurationsdependontheresourcesandloadthatyouexpectonyourserver:
StartServers50
MinSpareServers15
MaxSpareServers30
MaxClients225
MaxRequestsPerChild4000
11. Whenyourunsomeloadtestsagain,youcancomparethecurrentresultwiththeresultsbeforetheoptimization.Usually,youwillseesomedifferences.
Howitworks…Theperformanceofawebserverdependsonmanyfactors.Thekeypartsaretheapplication,hardware,operatingsystem,andthenetwork.
Application:Ensurethattheapplicationthatyouarerunningisworkingefficientlywiththeresourcesonyourserver.Ifyourapplicationexpectsalotofresourcesforasimpletask,maybeyoucanoptimizethis.Hardware:Ensurethatthehardwareresourcesarehighenoughtoservetheexpectedloadandpeaks.Operatingsystem:Theoperatingsystemandthewebserverversionareamongtheimportantfactors.UseaLinuxservertorunMagentowithanApacheorNginxwebserveronit.Alwaysusethelateststableversionsofthesoftwarebecausetheyarefasterandmoresecure.Network:Thewebserversendstheresponsetroughthenetworktotheclient.Whenthatnetworkisslow,thedownloadtimeofarequestwillbelong.Hostyourwebserverwithagoodnetworkconnectionandhostitgeographicallyintheregionofyourtargetaudience.Forexample,hostyourwebsiteinItalyforanItalianwebsite.
Asyoucansee,youcanoptimizealotofthingsonyourwebserver.Buteverycaseisdifferent.Normally,peoplehaveastandardserversetup.Theydeploytheapplicationonitandthentheystartoptimizing.Everyapplicationisdifferent(intermsofcode,load,andsoon).
FindingperformanceleaksinMagentoWhenoptimizingawebsite,wecandolotsofthingstotheserverenvironmentbuttheapplicationthatrunsonitcanalwaysbefaster.
WhenyouhaveaMagentostoreandonekindofpageissignificantlyslowerthanotherpages,itcouldpossiblebethatthereisaperformanceissueonthatpage.
Tofindperformanceissues,wecanusetheMagentoProfiler.WiththeMagentoProfiler,wecanseehowmuchtimeeveryprocesstakestorenderapage.
GettingreadyToenabletheprofiler,wehavetomodifytheapacheenvironmentvariables.WecandothisintheVirtualHostconfigurationorinthe.htaccessfile.Ensurethatyouhaveaccesstooneofthesefiles.
Howtodoit…Inthefollowingsteps,wewillspecifyhowwecanusetheMagentoprofiler:
1. Toenabletheprofiler,addthefollowingcodeintheVirtualHostorthe.htaccessfile:
SetEnvMAGE_PROFILERhtml
2. IfyouchangedthissettingintheVirtualHostfile,reloadtheApacheserver.3. Reloadapageinthefrontendandyouwillseethattheprofileroutputisshownatthe
endofthepage,asshowninthefollowingscreenshot:
4. ItispossiblethattheprofileroutputwillbreaksomefunctionalitywhenyouhaveJSONresponseswithAJAX.Forthisreason,itisalsopossibletowritetheprofileroutputtoa.csvfile.Toenablethis,wehavetochangetheSetEnvrulein.htaccessorVirtualHosttothefollowing:
SetEnvMAGE_PROFILERcsvfile
5. Theprofileroutputwillbewrittentothevar/log/profiler.csvfile.6. WhenweswitchtheprofilerbacktotheHTMLoutput,wecananalyzethepage.7. OpenaproductdetailpageandtakealookattheTimeandCountcolumn.Wesee
thestepsofapageloadinatree.Whenwelookforthemosttimeconsumingparts,
weseethattheLAYOUTpartistheslowestofthewholepage.
Howitworks…WhenyouenabletheMagentoprofiler,theprofilingresultswillbeshowedintheHTMLfile,CSVfile,orFirebug.
Intheprofilingresults,youhavethefollowingcolumns:
TimerId:Thisisthecodeofaprofiledstatement.Inthecode,youcanprofileablockofcodebysurroundingitwiththefollowingcode:
\Magento\Framework\Profiler::start('profiler_name');
//Yourcodetoprofile
\Magento\Framework\Profiler::stop('profiler_name');
Time:Thisisthetimetakentocompletethecodebetweenaprofilerstartandstop.Avg:Whenaprofilerstatementisexecutedmorethanonetime,thiswillshowtheaveragetimetocompleteonestatement.Cnt:Thisshowsthenumberoftimesaprofilerstatementiscalled.
NoteTheAvgcolumnmultipliedbytheCntcolumngivesthesamevalueastheTimecolumn.
Emalloc:Thisistheamountofmemorythatisallocatedfortheprofilerstatement.RealMem:ThisisthesameastheEmallocoptionbutthisistheamountofmemorythatisallocatedtothesystem.
Itispossibletorunprofilerstatementsineachother.Whenthisisdone,theHTMLoutputwillshowdotsfortheitemsthatareexecutedinaparentstatement.Thiswillgiveyouatreeoverviewsoyoucanseehowapageisexecuted.
ConfiguringOPcache,Redis,andMemcachedThedefaultcachingmechanismofMagentoisbasedonfilecaching.Allthecachefilesarewrittentothevar/cachefolder.Forasimplewebshop,thisisenough,butifyouarescalingyourshopwithmanyproductsandhightraffic,youwillneedtousesomeextracachingsystems.
RedisisareplacementofthestandardfilecachingofMagento.Thissystemworksfasterwhenyouhavealotofcachefiles.
MemcachedissimilartoRedis.Itisasysteminwhichyoucancacheobjects.
ZendOPcacheisanopcodecachingsystem.Whenthesecachingtechniquesareenabled,thePHPcodewillbecachedtothesesystems.
GettingreadyWhenwewanttoconfigurethecachingtoolsZendOPcache,Memcached,andRedis,wehavetoinstallthemonourserver.Toinstallthese,runthecommandsexplainedinthefollowingtopics:
ZendOPcacheThispackageisnormallystandardinstalledwithPHP5.5.Whenyouexecuteaphpinfo()methodorrunthephp-i|grepopcachecommand,youcansearchfortheopcachesettings.
MemcachedInstallMemcachedwiththesudoapt-getinstallphp5-memcached.command.
RedisInstallRediswiththesudoapt-getinstallredis-servercommand.
Howtodoit…Inthefollowingsteps,wewilluseZendOPcacheandMemcachedforMagento:
1. Whenyourunaphpinfo()methodinthebrowserorexecutethephp-i|grepopcachecommand,youwillseetheOPcachesettings.ThefollowingsettingsmustbesettoOntoensurethatOPcacheisenabled:
opcache.enable
opcache.enable_cli
2. WhenthesesettingsarenotsettoOn,wehavetosetitinthephp.inifile.Normally,thisislocatedinthe/etc/php5/apache2and/etc/php5/clifolders.ThisconfigurationisforaUbuntuserver.
3. ToenableRedis,wehavetomodifytheapp/etc/env.phpfile.Inthisfile,wehavetoaddthecacheconfigurationsothatMagentoknowswhichcachetypeithastouse.Addthefollowingcodetothisfile.Youhavetopastethiscodeintheexistingarray:
'cache'=>array(
'frontend'=>array(
'default'=>array(
'backend'=>'Cm_Cache_Backend_Redis',
'backend_options'=>array(
'server'=>'127.0.0.1',
'port'=>'6379',
'persistent'=>'',
'database'=>'0',
'force_standalone'=>'0',
'connect_retries'=>'1',
'read_timeout'=>'10',
'automatic_cleaning_factor'=>'0',
'compress_data'=>'1',
'compress_tags'=>'1',
'compress_threshold'=>'20480',
'compression_lib'=>'gzip',
),
),
'page_cache'=>array(
'backend'=>'Cm_Cache_Backend_Redis',
'backend_options'=>array(
'server'=>'127.0.0.1',
'port'=>'6379',
'persistent'=>'',
'database'=>'1',
'force_standalone'=>'0',
'connect_retries'=>'1',
'read_timeout'=>'10',
'automatic_cleaning_factor'=>'0',
'compress_data'=>'0',
'compress_tags'=>'1',
'compress_threshold'=>'20480',
'compression_lib'=>'gzip',
),
),
),
),
4. CleanalltheMagentocachesandrestartyourApacheserver.WhenyoudoaloadtestwithApacheBench,youwillseethatthereisanimprovementwiththeperformance.
NoteMagentousesFullPageCachingtocacheeverycontentpage.Ifyouwanttotesttheperformanceofuncachedpages,youcandisableit.
5. Toendthisrecipe,wewillconfigureMemcachedtostoretheMagentosessions.Inyourphpinfo()method,ensurethatMemcachedisenabled.
6. Opentheapp/etc/env.phpfileandaddthefollowingcodeinthatfile.Youhavetopasteitintheexistingarray:
'session'=>array(
'save'=>'memcached',
'save_path'=>'127.0.0.1:11211?
persistent=1&weight=2&timeout=10&retry_interval=10',
),
7. Thelastthingthatwecandoisstorethevar/cachefolderinmemory.WecandothisbymountingthefolderusingTMPFS:
mounttmpfs/var/www/magento2/var/cache-ttmpfs-osize-64m
Howitworks…ZendOPcacheisanopcodecachingmechanism.ThiswillcachePHPfilessothattheydon’thavetoloadfromthediskeverytimetheyarecalled.ZendOPcacheisthereplacementofAPC,whichisavailableinolderversionsofPHP.
APCisdeprecatedinPHP5.4andisnotavailablefromPHP5.5.Toreplacethis,ZendOPcacheisthetoolthatwehavetouse.That’salsothereasonwhyitisstandardenabledwhenyouinstallPHP5.5orhigher.
ThesecondthingthatweconfiguredisRedis.Redisisacachingtoolinwhichwecancacheobjects.ItisareplacementofthedefaultfilecachingofMagento.TheadvantageofRedisisthatitusescachetags.Whenyouhavealotofcachefiles,afilecachingmechanismwillopeneveryfiletoseethatitisrelevant.WithRedis,everycacheobjectistagged,soitcanbeopenedquicklywhenthereisalotofcache.
Thisisalsothereasonthatafilecachingsystembecomesslowwhenthereisalotofcache.Thecachingmechanismwillbecomeslowbecauseithastoopenlotsoffiles.
Atlast,weusedMemcachedtostorethesessionsin.MemcachedisasystemsimilartoRedis.Memcacheddoesn’tusecachetags,sointheory,itisslowerthanRedis,butpractically,youhavetoseewhichofthesetwosystemsisthebestforyou.
OptimizingthePHPconfigurationsInthepreviousrecipe,weexplainedhowsomecachingsystemsofPHPwork.SincePHP5.5,wehaveZendOPcachethatisusedastheopcodecache.
Inthisrecipe,wewilltunesomePHPsettingstooptimizethisforMagento.
GettingreadyWewillmakesomechangesinthephp.inifile,soensurethatyouhaveaccesstoit.
Howtodoit…Inthefollowingsteps,wegivesometipstooptimizePHP:
1. Atthismoment,PHP7isinBetaversion.Butwhenitisstable,itisrecommendedthatyouusethisversionbecausethisismuchfasterthanPHP5.x.
2. AlwaysusethelateststablePHPversionbecausethisismoresecureandfast.3. TryrunningPHPwithanefficientprocessmanagersuchasphp-fpmthatrunsonan
impressivespeedwithFastCGI.4. Usetherealpath_cache_sizeconfigurationsettingtoconfigurethesizeofthereal
pathcacheinPHP.OnsystemswherePHPopensandclosesalotoffiles,thisvalueneedstobeincreased.YoucanusethefollowingsettingforMagento:
realpath_cache_size=1M
realpath_cache_ttl=86400
5. ThefollowingsettingscanimprovetheperformanceofPHP:
Setting Description Recommendedvalue
max_execution_timeThissettingsetsthemaximumtime(inseconds)thataprocesscanexecute. 120
max_input_timeWiththisproperty,wesetthetime(inseconds).Ascriptwillwaitforinputdata. 240
memory_limitThissettingsetstheamountofmemorythataprocesscanuse.
ForMagento,itisrecommendedtouse768MB
output_bufferingWiththissetting,youcansettheamountofbytestobufferbeforesendingtheresponsetotheclient. 4096
6. EnsurethatXdebugisnotenabledonaproductionenvironment.7. Finally,wecandisablesomeerrorreportinglevelswhenyoursiteislive.Thiscanbe
configuredwiththefollowingsetting:
error_reporting=E_COMPILE_ERROR|E_ERROR|E_CORE_ERROR
Howitworks…Thevaluesinthephp.iniconfigurationdependmostlyontheapplicationthatyouarerunningandtheloadthatyouexpectonyoursystem.Ifyourapplicationhassomeprocessesthatwillrunforalongtime(thisispossiblewithsomere-indexingprocesseswithlargeamountsofproducts),itisrequiredtoincreasethevaluesofmax_execution_timeandmax_input_time.Thesamegoesforthememory_limitparameterwheretherecommendedvalueis768MB.
DisablingXdebugwillgiveabetterperformancebecausenodebugdatawillbeprocessed.
Disablingtheerrorreportingonaproductionsystemisrecommendedforwarningsandnoticesbutcriticalerrorsneedstobereportedbecauseyouneedthisinformationwhenyouwanttosolveapossiblebug.
Chapter11.DebuggingandUnitTestingInthischapter,wewillcoverthefollowingrecipes:
LoggingintoMagento2GettingstartedwithXdebugRunningautomatedtestsfromMagentoCreatingaMagentotestcase
IntroductionDebuggingawebsiteinanefficientwayisoneofthemostimportantjobsofPHPdevelopers.Thesedays,awebsiteisalotmorethanafewsimpleHTMLpages.InaMagentostore,youhavealotofcomplexbusinesslogicthatisusedintheflowofane-commercetransaction.
DebugginginPHPisnotoutoftheboxlikeinotherprogramminglanguages,suchas.NETandJava.TherearemanywaystoconfigureaPHPdebugger(suchasXdebug).Withagoodcodeeditoranddebugger,debugginginMagentoismucheasier.
Anotherpartofdebuggingandcodetestingareautomatedtests.Automatedtests,orUnittests,aredevelopedtotesttheoutputoffunctionsforagiveninput.Whensomecodeischanged,youcanrunthetestsandareportwillbegeneratedaboutthefailedandpassedtests.
LoggingintoMagento2ThestandarddebuggingtechniquesinPHPareecho$variable,die($variable)andvar_dump($variable).Thesesimpledebuggingtricksdon’talwayswork(whenyouareworkingwithAJAXandJSON)whenitisprintedinahiddenHTMLsection.
IfyouwanttodoasimpledebuggingtrickwithoutchangingtheHTMLoutputofapage,youcanusetheMagentologging.Thiswillwritethedebuggedresultstoalogfile.
GettingreadyWewillprintsomedatatotheMagentologfiles.Toeasilyviewthecontentofthesefiles,weneedcommandlineaccess.Also,openyourIDEbecausewewilladdsomeloggingstatementsintheMagentocode.
Howtodoit…PerformthefollowingstepstodescribehowwecanuselogginginMagento2:
1. Ifwewanttodebugsomedataonacategorypage,wecanusetheloggerinterfacetowritesomethingtoafile.Openthecategorypagecontroller,whichisinthefollowingfile:app/code/Magento/Catalog/Controller/Category/View.php.
NoteIfyouinstalledMagentowiththecomposer,youwillhavetoeditthevendor/magento/module-catalog/Controller/Category/View.phpfile.
2. Inthatfile,wehavetoaddthefollowinghighlightedcodetothe_initCategory()function:
try{
$this->_eventManager->dispatch(
'catalog_controller_category_init_after',
['category'=>$category,'controller_action'=>$this]
);
}
catch(\Magento\Framework\Exception\LocalizedException$e){
$this->_objectManager->get('Psr\Log\LoggerInterface')->critical($e);
returnfalse;
}
$this->_objectManager->get('Psr\Log\LoggerInterface')->debug(
print_r($category->debug(),true)
);
return$category;
3. Weneedtoaddthehighlightedcodebeforethereturnstatementofthatcode.4. Openacategorypageandthedataofthecategorypagewillbewrittentothe
var/log/debug.loglogfile.
Withthefollowingcommand,wecanfollowthenewlinesthatareaddedtothelogfile:
tail-fvar/log/debug.log
NoteMakesurethatthefullpagecachingisnotenabled.Ifitisenabled,thecachewillskiptheexecutionoftheloggingstatement.
5. Toexitthismode,wecanusetheshortcutCrtl+Cintheterminal.
Howitworks…InMagento1,wecouldusetheMage::log()functionthatwrotemessagestothelogfiles.InMagento2,theMageclassisgoneandalogginginterfaceiscreatedtodotheMagentologging.
TheloggerinterfaceisthePsr\Log\LoggerInterfaceinstance.Withthisinterface,wecancallthefollowingfunctionstowritedatatologfiles:
alert()
critical()
debug()
emergency()
error()
info()
log()
notice()
warning()
Whenyoulogdatatofiles,youcanusethefunctionthatfitstoyourlogmessage.Inthisrecipe,weusedthedebug()functiontologsomedebugdata.Ifyouwanttologthemessageofanerror,youcanusetheerror()function.
Theloggingmethodonlyacceptsstringvariables,soitisnotpossibletopassarraysorobjectstothismethod.Tofixthisissue,weusedtheprint_r()functiontoconvertthecontentofthearraytoastring.
Weloggedthedataofacategory,butthecategoryisanobject.Ifyouapplyaprint_r()methodtoanobject,youwillgetahugeoutput,whichisnoteasytoread.Tosolvethis,wecanusethedebug()methodonMagentoentities.Thismethodwillconvertthedataoftheobjecttoareadablearray.
GettingstartedwithXdebugWitharealdebugger,youcanpausetheexecutionofthescript.Itallowsyoutohavealookatthevariablesandvaluesthattheyhaveatthatpoint.Inadebugger,youcanalsochangevalues,skipstatements,andadomuchmore.
InPHP,thereistheXdebugextensionthatenablesyoutouseadebuggerincombinationwithanIDE.Inthisrecipe,wewillseehowtoinstallXdebugandintegrateitwiththeIDENetBeans.
GettingreadyInthisrecipe,wewillstartanXdebugsessionwiththeNetBeansIDE.OpenNetBeansandsettheMagentoProjectasMainProject.EnsurethatalltheURLsareconfiguredcorrectlyinthePropertysettingsoftheproject.
ToinstallXdebug,youhavetoensurethatthephp5-devandphp-pearpackagesareinstalledonyourserver.Ifnot,youcaninstallthemusingthefollowingcommands:
sudoapt-getinstallphp5-dev
sudoapt-getinstallphp-pear
Howtodoit…ThefollowingstepsdescribehowyoucaninstallXdebugonyourdevelopmentserver:
1. First,wewillinstallthexdebuglibrary.Youcandothisusingthefollowingcommand:
sudopeclinstallxdebug
Thiscommandwillgivethefollowingoutput:
2. Asyoucanreadinthescreenshot,wehavetolocatethexdebug.sofileinthephp.inifile.Tofindthepathofthexdebug.sofile,wecanusethefollowingcommand:
find/-name"xdebug.so"
3. Whenweknowthepath,wehavetoaddthefollowinglineinthephp.inifiles.OnanUbuntuserver,wehavetoaddthesameconfigurationtothefollowingfiles:
/etc/php5/apache2/php.ini
/etc/php5/cli/php.ini
4. Inthesefiles,addthefollowinglineattheendofthefile.Ensurethatthepathtothexdebug.sofilematchestheoneonyourserver:
zend_extension="/usr/lib/php5/20121212/xdebug.so"
5. Restarttheapacheserverusingthefollowingcommand:
sudoserviceapache2restart
6. WhenwewanttotestthatXdebugiscorrectlyinstalled,wecancheckthisintwoways:
ThefirstmethodistocreateaPHPscriptandcallthephpinfo()function.Whenyouopenthisscriptinthebrowser,youwillseeallthePHPsettingsthatareactiveinthatsession.Thesecondmethodistousethecommandphp-i.Thisgivesthesameinformationasphpinfo()butnowforphpovercli.
7. Whenwerunthephp-i|grepxdebugcommand,wewillseethefollowingoutput.ThesearetheXdebugsettings:
xdebug
xdebugsupport=>enabled
xdebug.auto_trace=>Off=>Off
xdebug.cli_color=>0=>0
xdebug.collect_assignments=>Off=>Off
xdebug.collect_includes=>On=>On
xdebug.collect_params=>0=>0
xdebug.collect_return=>Off=>Off
xdebug.collect_vars=>Off=>Off
xdebug.coverage_enable=>On=>On
xdebug.default_enable=>On=>On
xdebug.dump.COOKIE=>novalue=>novalue
xdebug.dump.ENV=>novalue=>novalue
xdebug.dump.FILES=>novalue=>novalue
xdebug.dump.GET=>novalue=>novalue
xdebug.dump.POST=>novalue=>novalue
xdebug.dump.REQUEST=>novalue=>novalue
xdebug.dump.SERVER=>novalue=>novalue
xdebug.dump.SESSION=>novalue=>novalue
xdebug.dump_globals=>On=>On
xdebug.dump_once=>On=>On
xdebug.dump_undefined=>Off=>Off
xdebug.extended_info=>On=>On
xdebug.file_link_format=>novalue=>novalue
xdebug.force_display_errors=>Off=>Off
xdebug.force_error_reporting=>0=>0
xdebug.halt_level=>0=>0
xdebug.idekey=>novalue=>novalue
xdebug.max_nesting_level=>256=>256
xdebug.max_stack_frames=>-1=>-1
xdebug.overload_var_dump=>On=>On
xdebug.profiler_aggregate=>Off=>Off
xdebug.profiler_append=>Off=>Off
xdebug.profiler_enable=>Off=>Off
xdebug.profiler_enable_trigger=>Off=>Off
xdebug.profiler_enable_trigger_value=>novalue=>novalue
xdebug.profiler_output_dir=>/tmp=>/tmp
xdebug.profiler_output_name=>cachegrind.out.%p=>cachegrind.out.%p
xdebug.remote_autostart=>Off=>Off
xdebug.remote_connect_back=>Off=>Off
xdebug.remote_cookie_expire_time=>3600=>3600
xdebug.remote_enable=>Off=>Off
xdebug.remote_handler=>dbgp=>dbgp
xdebug.remote_host=>localhost=>localhost
xdebug.remote_log=>novalue=>novalue
xdebug.remote_mode=>req=>req
xdebug.remote_port=>9000=>9000
xdebug.scream=>Off=>Off
xdebug.show_exception_trace=>Off=>Off
xdebug.show_local_vars=>Off=>Off
xdebug.show_mem_delta=>Off=>Off
xdebug.trace_enable_trigger=>Off=>Off
xdebug.trace_enable_trigger_value=>novalue=>novalue
xdebug.trace_format=>0=>0
xdebug.trace_options=>0=>0
xdebug.trace_output_dir=>/tmp=>/tmp
xdebug.trace_output_name=>trace.%c=>trace.%c
xdebug.var_display_max_children=>128=>128
xdebug.var_display_max_data=>512=>512
xdebug.var_display_max_depth=>3=>3
8. ThenextstepistoconfiguretheintegrationbetweenNetBeansandXdebug.Toconfiguretheintegration,wehavetoaddthefollowingconfigurationattheendofthephp.inifiles:
xdebug.remote_enable=1
xdebug.remote_handler=dbgp
xdebug.remote_mode=req
xdebug.remote_host=localhost
xdebug.remote_port=9000
xdebug.idekey="netbeans-xdebug"
9. RestartyourApacheserverandlookatthephpinfo()pagetocheckwhethertheXdebugsettingshavebeenapplied.
10. Next,wehavetoconfigureNetBeansforXdebug.InNetBeans,navigatetoTools|Optionsandconfigureitasshowninthefollowingscreenshot:
11. Inthepreviousstep,weconfiguredtheglobalNetBeanssettingsforXdebug.Inthenextstep,wewillconfiguretheproject-specificsettings.OpentheProjectPropertiesoption(byright-clickingonproject)andopentheRunConfigurationtab.EnsurethattheProjectURLfieldhasthecorrectvalue,asshowninthefollowingscreenshot:
12. Wearenowreadytostartthefirstdebugsession.Tostartit,wehavetoclickonthedebugbutton,whichisneartheRunbutton.WecanalsousetheshortcutCrtl+F5.
13. Whenstartingthedebugsession,yourbrowserwillbeopenedwithapagethathasthefollowingURL:
http://magento2.local/index.php?XDEBUG_SESSION_START=netbeans-xdebug.
14. Thewebpagedoesn’tloadbecausethedebuggerisinterruptingtheprocess.Tocontinue,wehavetousethedebuggercontrolsinNetBeans.
15. Addabreakpointintheindex.phpfileonthefollowingline:
$bootstrap=\Magento\Framework\App\Bootstrap::create(BP,$_SERVER);
16. Oncontinuingwiththedebugger,youwillseetheactualvaluesofthevariablesasshowninthefollowingscreenshot:
17. Whenyoucontinuethedebuggeratthebreakpoint,youwillseethatthepagewillbeloaded.
18. ThedebugsessionsstaysaliveuntilyouhitthestopbuttoninNetBeans.Whenyoubrowsetootherpagesonyourwebsite,thedebuggerwillcontinueaslongasthesessionisalive.
19. Tostopthedebugsession,clickontheStopbuttoninNetBeans.
Howitworks…WeneedtoinstallXdebugontheserverwherewewanttodebugasiteon.Inthisrecipe,itwillbetheserveronwhichourMagentoinstancewillrun.
TheinstallationoftheXdebugextensionisdonebyPEAR.PEARisanapplicationrepositoryforthePHPplugins.WithPEAR,wedownloadedandinstalledthexdebuglibrary.
WhenXdebugwasinstalledontheserver,weconfiguredthephp.inifiletousethexdebuglibrary.WeaddedsomesettingstomaketheXdebugconfigurationcompatiblewithNetBeans.
TipWhenusingXdebugonaremoteserver,ensurethatyoucanconnecttotheservertroughport9000.ThisismostlydisabledonthefirewalloftheserverandyourlocalPC.
Whentheserverwascorrectlyconfigured,wecheckedtheconfigurationsinNetBeansandwestartedthedebugsession.Whenthissessionwasstarted,wewereabletodebugtheMagentoapplicationlikeadebuggerdoesit.
Thedebuggerenablesustousethefollowingadvanceddebuggerfeatures,amongothers:
SettingbreakpointsExecutingthecodestatementbystatementBrowsingandchangingvaluesofvariables
RunningautomatedtestsfromMagentoWhenyoubuildsomefunctionality,youhavetotestthatthefunctionalityworkslikeyouwouldexpectit.Testingisusuallydoneattheendofyourprojectandthiscanbeautomated.
Withaunittest,wecanspecifywhataspecificpartofcodeneedstodo.Whatwillbetheinput,howwillitbeprocessed,whatistheoutput—theseareallthethingsthatyoucanspecifyinaunittest.
AnewadditiontoMagento2isthatunittestsareautomaticallyincludedinthecore.WhenyouwanttocontributetotheMagentocorewithGitHub,itisrequiredthatyourchangespassthroughtheunitandintegrationtests.
GettingreadyForrunningtheunittests,weneedthecommand-linetoolofMagento.Ensurethatyouhaveaccesstoit.
Howtodoit…Inthefollowingsteps,wedescribehowwecanrunautomatedtestsfromMagento:
1. First,weensurethattheMagento_Developermoduleisenabled.Wecancheckthiswiththephpbin/magentomodule:statuscommand.Thiswilloutputalist,andtheMagento_Developermoduleneedstobeinthislistofenabledmodules.
2. Whenthemoduleisdisabled,wecanenableitusingthefollowingcommand:
phpbin/magentomodule:enableMagento_Developer
3. WiththeMagentocommand-linetool,wecanstarttheexecutionofalltestsintheinstallation.Withthefollowingcommand,wecanseetheavailableoptions:
phpbin/magentodev:tests:run--help
4. Ifwelookattheoutputofthatcommand,weknowthatthecommandwillexecutealltestswithoutaparameter.Ifwewanttorunonlytheunittests,wecanusethefollowingcommand:
phpbin/magentodev:tests:rununit
Thiscommandwillgivethefollowingoutputwhenitisrunning:
ThisistheresultoftheexecutionofalltheunitteststhatareavailableinMagentoandthistakesquitealotoftime.Whenwe’redevelopingafunction,weonlywanttorunoneparticulartest.Forthis,weneedsomeextraconfiguration.
5. Torunspecifictests,wehavetocreatethefollowingfile:dev/tests/unit/phpunit.xml.
6. Inthatfile,addthefollowingcontent:
<?xmlversion="1.0"encoding="UTF-8"?>
<phpunitxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd
"
colors="true"
bootstrap="./framework/bootstrap.php"
>
<testsuitename="SpecificMagento2unittests">
<directory
suffix="Test.php">../../../app/code/Magento/Catalog/Test/Unit</director
y>
<directory
suffix="Test.php">../../../app/code/Magento/Cms/Test/Unit</directory>
</testsuite>
<php>
<ininame="date.timezone"value="America/Los_Angeles"/>
<ininame="xdebug.max_nesting_level"value="200"/>
</php>
<logging>
<logtype="testdox-html"target="./test-reports/testdox.html"/>
<logtype="testdox-text"target="./test-reports/testdox.txt"/>
</logging>
</phpunit>
NoteIfyouinstalledMagentowiththecomposer,youhavetoensurethatthepathsinthedirectoryaregoingtotherightpath.Withcomposer,youhavetolookfortheMagentomodulesinthevendor/magento/folder.
7. Withthepreviousconfiguration,wewillruntheteststhatareavailableinthesefolders:
app/code/Magento/Catalog/Test/Unit
app/code/Magento/Cms/Test/Unit
8. Torunthetests,wehavetochangethecommand-lineprompttothedev/tests/unitfolder.WecandothisbyrunningthefollowingcommandinourMagentoroot:
cddev/tests/unit/
9. Whenweareinthatfolder,wecanrunthefollowingcommandtostarttheunittests:
../../../vendor/bin/phpunit
10. Whenthiscommandhasfinishedexecuting,weseethatsometestsareskippedbutthefullnamesofthosetestsarenotdisplayed.Togetsomemoreinformationaboutunsuccessfultests,wecanusethefollowingcommand,whichshowsusmoreoutput:
../../../vendor/bin/phpunit--verbose
11. Whenrunningthiscommand,wegetthefollowingoutput:
12. Whenwelookinthetest-reportsfolder,weseethatthefollowinglogfilesaregenerated:
testdox.html
testdox.txt
Howitworks…InMagento2,wecanfindtheunittestsinthecodefilesofMagento.WhenwelookintheTestdirectoryofeachmodule,wewillfindthefilesthatwillbeusedwhenrunningautomatedtests.
Thedev:tests:runcommandintheMagentoconsoleisusedtorunallthetestsintheMagentoapplication.Normally,ifyouhavechangedsomething,youhavetotestthewholeapplicationbecauseasmallchangecanbreaktestsinplacesthatyoudonotexpect.
TheconsoletoolusesPHPUnittoruntheunittests.ThistoolisaPHPexecutableandisdeliveredwithMagento.Thephpunitexecutableisavailableinthevendor/binfolder.
Whenrunningthephpunitexecutable,thiswillrununitteststhatareconfiguredinaphpunit.xmlfile.Wecreatedthisfileinthedev/tests/unitfolder.Aphpunit.xml.distfileisavailableinthisfolder,whichcontainsanexampleconfiguration.
Inthisconfigurationfile,wespecifiedthefollowingthings:
ThepathofthebootstrapparameterThefoldersoftheteststhatneedstobeexecutedSomephp.inisettingsThepathoflogfiles
Thebootstrapparameterreferstotheframework/bootstrap.phpfile.ThisfileinitializestheMagentoapplicationbycallingthenecessaryfilesandfunctions.
Whenrunningthephpunittests,wegetanoutputwithdots.AdotmeansthatthetestresultisOK.Sometimes,adotisreplacedwithanSorI.AnSmeansthatatestisskippedandanImeansthatatestisinvalid.Withthe--verboseoption,wecangetmoreinformationonwhyitisinvalidorskipped.
CreatingaMagentotestcaseAsthisisthelastchapterofthisbook,wewillwriteatestthatwecanexecutewiththeMagentoTestingFramework.ThisframeworkusesPHPUnittoexecutethetests.
ByfollowingthepatternoftheMagentotests(Unittests,Integrationtests,andmore),thetestswillautomaticallyexecutewhenthedev:tests:runconsolecommandwillbeexecuted.
GettingreadyInthisrecipe,wewillcreateaunittestforthePackt_HelloWorldmodulethatwecreatedinChapters4,CreatingaModule,Chapter5,DatabasesandModules,Chapter6,MagentoBackend,andChapter7,EventHandlersandCronjobs.
Ifyoudon’thavethecompletecode,youcaninstallthestarterfilesforthisrecipe.
Howtodoit…Usingthefollowingsteps,wewillcreateasimpleunittestforMagento:
1. Foraunittest,wehavetocreatethefollowingfolders:
app/code/Packt/HelloWorld/Test/
app/code/Packt/HelloWorld/Test/Unit/
app/code/Packt/HelloWorld/Test/Unit/Block/
app/code/Packt/HelloWorld/Test/Unit/Block/Adminhtml/
app/code/Packt/HelloWorld/Test/Unit/Block/Adminhtml/Subscription/
2. Inthelastfolder,createafilecalledGridTest.php.3. Inthisfile,addthefollowingcontent:
<?php
namespacePackt\HelloWorld\Test\Unit\Block\Adminhtml\Subscription;
classGridTestextends\PHPUnit_Framework_TestCase{
/**
*@var\Packt\HelloWorld\Block\Adminhtml\Subscription\Grid
*/
protected$block;
protectedfunctionsetUp(){
}
protectedfunctiontearDown(){
}
publicfunctiontestDecorateStatus(){
}
}
4. WehavenowcreatedatestclassthatisresponsibletotestthePackt\HelloWorld\Block\Adminhtml\Subscription\Gridclass.Torunthistests,wehavetocreateaphpunit.xmlfileinthedev/tests/unitfolderwiththefollowingcontent:
<?xmlversion="1.0"encoding="UTF-8"?>
<phpunitxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd
"
colors="true"
bootstrap="./framework/bootstrap.php"
>
<testsuitename="PacktHelloWorldmoduletest">
<directory
suffix="Test.php">../../../app/code/Packt/HelloWorld/Test/Unit</directo
ry>
</testsuite>
<php>
<ininame="date.timezone"value="America/Los_Angeles"/>
<ininame="xdebug.max_nesting_level"value="200"/>
</php>
<logging>
<logtype="testdox-html"target="./test-reports/testdox.html"/>
<logtype="testdox-text"target="./test-reports/testdox.txt"/>
</logging>
</phpunit>
5. Torunthetests,wehavetoopentheterminalandopenthedev/tests/unitfolder.WhenyouareintheMagentoroot,youcandothisbyrunningthefollowingcommand:
cddev/tests/unit
6. Inthisfolder,runthefollowingcommandtorunthetests:
../../../vendor/bin/phpunit--verbose
Thetestswillpassasyoucanseeinthefollowingscreenshot:
7. Thetestswillpassbecausethetestclassisempty.IfwewanttotestthedecorateStatus()method,wehavetoinitializetheblockinthesetUp()methodoftheGridTestclass.AddthehighlightedcodetothesetUp()method:
protectedfunctionsetUp(){
$objectManager=new
\Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
$this->block=$objectManager->getObject(
'Packt\HelloWorld\Block\Adminhtml\Subscription\Grid'
);
}
8. Wealsohavetodestructtheblockwhenthetestsarefinished.WecandothisbyaddingthehighlightedcodetothetearDown()method:
protectedfunctiontearDown(){
$this->block=null;
}
9. Wecannowstartwritingourtest.Forthis,wehavetoknowwhattotest.IfwelookatthedecorateStatus()methodofthePackt\HelloWorld\Block\Adminhtml\Subscription\Gridclass,weseethataspecificHTMLoutputisreturnedbasedontheinputvariable.Totesttheoutputoftheinputvalues,wehavetoaddthehighlightedcodeinthetestDecorateStatus()method:
publicfunctiontestDecorateStatus(){
$this->assertContains('grid-severity-minor',$this->block-
>decorateStatus('pending'));
$this->assertContains('grid-severity-notice',$this->block-
>decorateStatus('approved'));
$this->assertContains('grid-severity-critical',$this->block-
>decorateStatus('declined'));
$this->assertContains('grid-severity-critical',$this->block-
>decorateStatus(6));
$this->assertContains('grid-severity-critical',$this->block-
>decorateStatus(null));
}
10. Runthetestsagainusingthephpunit--verbosecommand.Youwillseethatthetestswillpass(1test,5assertions).
11. WhenyouaddthefollowingassertsinthetestDecorateStatus()methodandrunthetestagain,youwillseethatitfails:
$this->assertContains('grid-severity-minor',$this->block-
>decorateStatus('approved'));
$this->assertNull($this->block->decorateStatus(null));
Howitworks…Tocreateunittests,wehavetousePHPUnit.Wecreatedaphpunit.xmlfilewhereweconfiguredthattheapp/code/Packt/HelloWorld/Test/Unitfolderisthefolderthatcontainstheunittests.
InMagento2,everyclasshasitsowntestclass.WecreatedatestclassforthePackt\HelloWorld\Block\Adminhtml\Subscription\Gridclass.Inthisclass,thereisadecorateStatus()methodthatwewilltest.
WecreatedaGridTestclassinthesamefolderstructureasitisintheoriginalmodule.ThefolderstructurewillcomebackintheTests/Unitfolder.EveryclassissuffixedwiththewordTest.
Everytestmethodinaclassstartswithtestfollowedbythenameofthemethodthatwillbetested.ForthedecorateStatus()function,thiswillbetestDecorateStatus().
AtestclassextendsfromthePHPUnit_Framework_TestCaseclass.ThisclasscontainstheframeworkthatwillexecutethetestsusingPHPUnit.
ThesetUp()methodiscalledbeforethetestsareexecuted.Itislikeaconstructorinanormalclass.Inthisclass,weinitializetheoriginalBlockclassthatwewilluseinthetestmethod.
ThetearDown()methodiscalledaftertheexecutionofthetests.Itworkslikeadestructor,andnormally,wesetalltheclassvariablestonullinthismethod.
Aunittestwillalwayspasswhenthecodeinatestfunctionisempty.WecreatedthetestDecorateStatus()methodwhereweaddedsomeassertionmethods.Inthisrecipe,weusedtheassertContains()method.Whenthismethodisused,thetestwillpassifthevalueofthesecondparameterwillcontainthevariableofthefirstparameter.
WeusedthismethodtotestdifferentinputvariablesofthedecorateStatus()method.Inthelaststep,weusedaassertNull()method.ThetestsfailedbecausetheoutputofthedecorateStatus()methodwasnotnullforthegiveninput.
Inlargeprojectswhereunittestsareused,mostlythetestsarewritteninthearchitecturalphaseofaproject.Aunittestcanbeusedasaspecificationofwhatamethodneedtodowhenitiscalled.Howwehavetohandleinvalidinputvariablesandothersuchthingsarespecifiedinaunittest.
There’smore…Inthisrecipe,weusedtheassertContains()andassertNull()methodstotesttheoutputofthedecorateStatus()method.However,therearemanymoreassertionmethodsthatyoucanusewithPHPUnit.Forexample,amethodthatcomparesanumber,amethodthatcomparesthetypeofobject,andmanymore.
AfulllistofassertionmethodscanbefoundatthefollowingURL,whichreferstotheoriginaldocumentationofPHPUnit:
https://phpunit.de/manual/current/en/appendixes.assertions.html
IndexA
AccessControlList(ACL)adding/AddinganACL,Howtodoit…,Howitworks…
adaptermodelwriting/Writinganadaptermodel,Howtodoit…,Howitworks…
Apacheabout/OptimizingtheApachewebserver
ApacheBenchabout/Benchmarkingawebsite,Gettingready
Apachewebserveroptimizing/OptimizingtheApachewebserver,Howtodoit…,Howitworks…
app.telemetryabout/GettingreadyURL/Gettingready
app.telemetrypluginRedirectparameter/Howitworks…AppCacheparameter/Howitworks…DNSLookupparameter/Howitworks…TCPConnectionparameter/Howitworks…TCPRequestparameter/Howitworks…TCPResponseparameter/Howitworks…Processingparameter/Howitworks…Onloadeventparameter/Howitworks…
attributesetsworkingwith/Workingwithattributesets,Howtodoit,Howitworksabout/Howitworks
automatedtestsrunning,fromMagento/RunningautomatedtestsfromMagento,Howtodoit…,Howitworks…
Bbackendcomponents
workingwith/Workingwithbackendcomponents,Howtodoit…,Howitworks…
backendcontrollerregistering/Registeringabackendcontroller,Gettingready,Howtodoit…,Howitworks…
Blankthemeabout/ExploringthedefaultMagento2themes
blockfilecreating/Creatingtheblockandtemplatefiles,Howtodoit…,Howitworks…
bundleproductabout/Abundleproduct
Ccatalogdefaults
configuring/Configuringthecatalogdefaults,Howtodoit,Howitworkscollections
about/WorkingwithMagentocollectionsworkingwith/WorkingwithMagentocollections,Howtodoit…,Howitworks…
CommandLineInterface(CLI)about/Howitworks…
configurableproductabout/Aconfigurableproduct
configurationparametersadding/Addingconfigurationparameters,Howtodoit…,Howitworks…
consolecommandadding/Addingaconsolecommand,Howtodoit…
controllercreating/Creatingacontroller,Gettingready,Howtodoit…,Howitworks…,There’smore…
cronjobabout/Introduction,Introducingcronjobsconfiguring/Howtodoit…,Howitworks…creating/Creatingandtestinganewcronjob,Howtodoit…,Howitworks…testing/Creatingandtestinganewcronjob,Howtodoit…,Howitworks…
customconfigurationparametercreating/Creatingacustomconfigurationparameter,Howtodoit…,Howitworks…
customerattributesadding/Addingcustomerattributes,Howtodoit…,Howitworks…
customeventcreating/Creatingyourownevent,Howtodoit…,Howitworks…
customthemecreating/CreatingaMagento2theme,Howtodoit…,There’smore…
Ddatabase
upgrading/Upgradingthedatabase,Howtodoit…,Howitworks…,There’smore…repairing/Repairingthedatabase,Howtodoit…,Howitworks…optimizing/OptimizingthedatabaseandMySQLconfigurations,Howtodoit…,Howitworks…
databasetablecreating,withmodels/Creatingaflattablewithmodels,Howtodoit…,Howitworks…grid,creating/Creatingagridofadatabasetable,Howtodoit…,Howitworks…
dependencyinjectionabout/AddinganinterceptorURL/Seealso
downloadableproductabout/Adownloadableproduct
Eemailtemplates
customizing/Customizingemailtemplates,Howtodoit…emptymodule
creating/Creatinganemptymodule,Howtodoit…,Howitworks…EntityAttributeValuesystem(EAV)
about/Howitworks…eventobserver
adding/Addinganeventobserver,Howtodoit…,Howitworks…events
URL/Seealsoeventtypes
about/Understandingeventtypes,Howtodoit…,Howitworks…
FFacebook
URL/Gettingreadyfallbackmechanism
about/Howitworks…filepermissions
URL/Howtodoit…FullPageCaching
about/Howtodoit…
GGitHub
about/RunningautomatedtestsfromMagentoGooglePlus
URL/Gettingreadygrid
creating,ofdatabasetable/Creatingagridofadatabasetable,Howtodoit…,Howitworks…
groupedproductabout/Agroupedproduct
Gruntconfiguring/There’smore…URL/There’smore…
GTmetrixserverabout/There’smore…
HHTMLobject
embedding/EmbeddinganHTMLobject,Howtodoit,HowitworksHTMLoutput
customizing/CustomizingtheHTMLoutput,Howtodoit…,Howitworks…
IIDENetBeans
about/GettingstartedwithXdebuginstallscript
creating/Creatinganinstallandupgradescript,Howtodoit…,Howitworks…
IntegratedDevelopmentEnvironment(IDE)about/UsinganIDEusing/UsinganIDE,Howtodoit…
interceptoradding/Addinganinterceptor,Howtodoit…,Howitworks…
JjQuerysliderscript
about/Introduction
Llayoutupdates
adding/Addinglayoutupdates,Howtodoit…,Howitworks…LESS
workingwith/WorkingwithLESS,Howtodoit…,Howitworks…,There’smore…
Lumathemeabout/ExploringthedefaultMagento2themes
MMagento
about/IntroductionURL/GettingreadyURL,forrulesets/Howitworks…performanceleaks,discovering/FindingperformanceleaksinMagento,Howtodoit…,Howitworks…automatedtests,running/RunningautomatedtestsfromMagento,Howtodoit…,Howitworks…
Magento1upgrade,preparing/PreparinganupgradefromMagento1,Howtodoit…,Howitworks…
Magento1websitecreating,withsampledata/CreatingaMagento1websitewithsampledata,Howtodoit…,Howitworks…
Magento2about/Introductiondefaultthemes,exploring/ExploringthedefaultMagento2themes,Howtodoit…,Howitworks…theme,creating/CreatingaMagento2theme,Howtodoit…,There’smore…loggingin/LoggingintoMagento2,Howtodoit…,Howitworks…
Magento2websitecreating/CreatingaMagento2website,Howtodoit…,Howitworks…,There’smore…
MagentoMigrationWhitepaperURL/Seealso
Memcachedconfiguring/ConfiguringOPcache,Redis,andMemcached,Howtodoit…,Howitworks…about/ConfiguringOPcache,Redis,andMemcached
menuextending/Extendingthemenu,Howtodoit…,Howitworks…
MigrationtoolURL/Gettingready
modelsdatabasetable,creatingwith/Creatingaflattablewithmodels,Howtodoit…,Howitworks…
moduleadding,infrontend/Addingthemoduleinthefrontend,Howtodoit…,Howitworks…
moduleconfigurationsinitializing/Initializingmoduleconfigurations,Howtodoit…,Howitworks…
modulefiles
creating/Creatingthemodulefiles,Howtodoit…,Howitworks…Multi-ProcessingModules(MPM)
about/Howtodoit…MyISAMtables
about/Howtodoit…MySQL
configurations,optimizing/OptimizingthedatabaseandMySQLconfigurations,Howtodoit…,Howitworks…
NNetBeans
about/UsinganIDEURL/Gettingready
Nginxabout/OptimizingtheApachewebserver
OOPcache
configuring/ConfiguringOPcache,Redis,andMemcached,Howtodoit…,Howitworks…
PPageSpeed
about/There’smore…pagetitle
modifying/Changingapagetitle,Howtodoit…PHP,settings
max_execution_time/Howtodoit…max_input_time/Howtodoit…memory_limit/Howtodoit…output_buffering/Howtodoit…
PHPconfigurationsoptimizing/OptimizingthePHPconfigurations,Howitworks…
phpcsmdpluginabout/There’smore…URL/There’smore…
PHPMessDetector(PHPMD)code,writingwith/WritingcleancodewithPHPMDandPHPCS,Howtodoit…,Howitworks…,There’smore…
phpMyAdminabout/Gettingready
PHPStormabout/There’smore…URL/There’smore…
PHPUnitabout/Howitworks…assertionmethods,URL/There’smore…
PHP_CodeSniffer(PHPCS)code,writingwith/Gettingready,Howtodoit…,Howitworks…,There’smore…URL/Howtodoit…
productattributesabout/Howitworksadding,programmatically/Programmaticallyaddingproductattributes,Howtodoit…,Howitworks…
productpageURL,modifying/ChangingtheURLofaproductpage,Howitworks,There’smore
productsadding/Addingablockofnewproducts,Howtodoit…,Howitworks…
producttemplatesabout/Workingwithattributesets
producttypesworkingwith/Workingwithproducttypes,Howtodoit,Howitworks…
simpleproduct/Asimpleproductconfigurableproduct/Aconfigurableproductbundleproduct/Abundleproductgroupedproduct/Agroupedproductvirtualproduct/Avirtualproductdownloadableproduct/Adownloadableproduct
RRedis
configuring/ConfiguringOPcache,Redis,andMemcached,Howtodoit…,Howitworks…about/ConfiguringOPcache,Redis,andMemcached
RequireJSlibraryabout/Howtodoit…
routingabout/Howitworks
Sshippingmethod
additionalfeatures,adding/Extendingtheshippingmethodfeatures,Howtodoit…,Howitworks…
Siegeabout/Benchmarkingawebsite,Gettingready
simpleproductabout/Asimpleproduct
slickURL/Gettingready,Howtodoit…
socialmediabuttonsadding/Addingsocialmediabuttons,Howtodoit,Howitworks
sourcemodelsworkingwith/Workingwithsourcemodels,Howtodoit…,Howitworks…
Symfonyconsoleabout/Seealso…URL/Seealso…
TTabSeparatedValue(TSV)file
about/Howtodoit…templatefile
creating/Creatingtheblockandtemplatefiles,Howtodoit…,Howitworks…testcase
creating/CreatingaMagentotestcase,Howtodoit…,Howitworks…,There’smore…
themesexploring,inMagento2/ExploringthedefaultMagento2themes,Howtodoit…,Howitworks…extrafiles,adding/Addingextrafilestothetheme,Howtodoit…,Howitworks…,There’smore…
themingfinalizing/Finalizingthetheming,Howtodoit…,Howitworks…
translationfileadding/Addingatranslationfile,Gettingready,Howitworks…
translationsworkingwith/Workingwithtranslations,Howtodoit…,Howitworks…
TwitterURL/Gettingready
Uupgradescript
creating/Creatinganinstallandupgradescript,Howtodoit…,Howitworks…
URLmodifying,ofproductpage/ChangingtheURLofaproductpage,Howitworks,There’smore
Vvirtualproduct
about/Avirtualproduct
Wwebserver,performancefactors
application/Howitworks…hardware/Howitworks…operatingsystem/Howitworks…network/Howitworks…
websitebenchmarking/Benchmarkingawebsite,Gettingready,Howtodoit…,Howitworks…frontend,optimizing/Optimizingthefrontendofthewebsite,Howitworks…,Howitworks…,There’smore…
widgetconfigurationfilecreating/Creatingawidgetconfigurationfile,Howtodoit…,Howitworks…
widgetsadding,tolayout/Addingwidgetstothelayout,Howtodoit…
XXdebug
disabling/Howitworks…about/GettingstartedwithXdebuginstalling/Howtodoit…,Howitworks…
XMLStyleDefinition(XSD)filesabout/Howtodoit…
YYSlow
about/Gettingready,There’smore…URL/Gettingready
ZZendOPcache
about/ConfiguringOPcache,Redis,andMemcached