571

Magento 2 Development Cookbook

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

Page 1: Magento 2 Development Cookbook
Page 2: Magento 2 Development Cookbook
Page 3: Magento 2 Development Cookbook

Magento2DevelopmentCookbook

Page 4: Magento 2 Development Cookbook

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

Page 5: Magento 2 Development Cookbook

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

Page 6: Magento 2 Development Cookbook

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

Page 7: Magento 2 Development Cookbook

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…

Page 8: Magento 2 Development Cookbook

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…

Page 9: Magento 2 Development Cookbook

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…

Page 10: Magento 2 Development Cookbook

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…

Page 11: Magento 2 Development Cookbook

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

Page 12: Magento 2 Development Cookbook

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

Page 13: Magento 2 Development Cookbook

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

Page 14: Magento 2 Development Cookbook

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

Page 15: Magento 2 Development Cookbook
Page 16: Magento 2 Development Cookbook

Magento2DevelopmentCookbook

Page 17: Magento 2 Development Cookbook
Page 18: Magento 2 Development Cookbook

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

Page 19: Magento 2 Development Cookbook
Page 20: Magento 2 Development Cookbook

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

Page 21: Magento 2 Development Cookbook
Page 22: Magento 2 Development Cookbook

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!

Page 23: Magento 2 Development Cookbook
Page 24: Magento 2 Development Cookbook

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

Page 25: Magento 2 Development Cookbook

cameoutin2008.HestartedlookingatMagento2assoonasheheardthatthesourcecodeisavailableinadevversion.

Page 26: Magento 2 Development Cookbook
Page 27: Magento 2 Development Cookbook

www.PacktPub.com

Page 28: Magento 2 Development Cookbook

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.

Page 29: Magento 2 Development Cookbook

Whysubscribe?FullysearchableacrosseverybookpublishedbyPacktCopyandpaste,print,andbookmarkcontentOndemandandaccessibleviaawebbrowser

Page 30: Magento 2 Development Cookbook

FreeaccessforPacktaccountholdersIfyouhaveanaccountwithPacktatwww.PacktPub.com,youcanusethistoaccessPacktLibtodayandview9entirelyfreebooks.Simplyuseyourlogincredentialsforimmediateaccess.

Page 31: Magento 2 Development Cookbook
Page 32: Magento 2 Development Cookbook

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.

Page 33: Magento 2 Development Cookbook

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.

Page 34: Magento 2 Development Cookbook
Page 35: Magento 2 Development Cookbook

WhatyouneedforthisbookMagento2sourcecodeAvirtualLinuxserver(Ubuntu15.10orhigher)Onthatvirtualserver,youneedthefollowing:

Apache2.4PHP5.5orhigherMySQLServer5.6orhigherSSHaccess

NetBeansIDE(oranyothergoodPHPeditorlikePhpStorm)Adatabaseclient(suchaphpMyAdmin)AstandardwebbrowserXdebugGitSCM

Page 36: Magento 2 Development Cookbook
Page 37: Magento 2 Development Cookbook

WhothisbookisforThisbookisforwebprogrammerswhoarefamiliarwithPHPandwanttostartwithMagento2.ThisbookisalsoforMagento1developerswhowanttoknowhoweverythingworksinMagento2.

ThisbookwillstartwiththebasicsofMagento2developmentandwillendwiththemoreadvancedtopics.EvenifyouknowledgeaboutMagentodevelopment,thisbookisagoodreferenceifyouwanttomoreaboutaparticulartopicinMagento.

Page 38: Magento 2 Development Cookbook
Page 39: Magento 2 Development Cookbook

SectionsInthisbook,youwillfindseveralheadingsthatappearfrequently(Gettingready,Howtodoit,Howitworks,There’smore,andSeealso).

Togiveclearinstructionsonhowtocompletearecipe,weusethesesectionsasfollows:

Page 40: Magento 2 Development Cookbook

GettingreadyThissectiontellsyouwhattoexpectintherecipe,anddescribeshowtosetupanysoftwareoranypreliminarysettingsrequiredfortherecipe.

Page 41: Magento 2 Development Cookbook

Howtodoit…Thissectioncontainsthestepsrequiredtofollowtherecipe.

Page 42: Magento 2 Development Cookbook

Howitworks…Thissectionusuallyconsistsofadetailedexplanationofwhathappenedintheprevioussection.

Page 43: Magento 2 Development Cookbook

There’smore…Thissectionconsistsofadditionalinformationabouttherecipeinordertomakethereadermoreknowledgeableabouttherecipe.

Page 44: Magento 2 Development Cookbook

SeealsoThissectionprovideshelpfullinkstootherusefulinformationfortherecipe.

Page 45: Magento 2 Development Cookbook
Page 46: Magento 2 Development Cookbook

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.

Page 47: Magento 2 Development Cookbook
Page 48: Magento 2 Development Cookbook

ReaderfeedbackFeedbackfromourreadersisalwayswelcome.Letusknowwhatyouthinkaboutthisbook—whatyoulikedordisliked.Readerfeedbackisimportantforusasithelpsusdeveloptitlesthatyouwillreallygetthemostoutof.

Tosendusgeneralfeedback,simplye-mail<[email protected]>,andmentionthebook’stitleinthesubjectofyourmessage.

Ifthereisatopicthatyouhaveexpertiseinandyouareinterestedineitherwritingorcontributingtoabook,seeourauthorguideatwww.packtpub.com/authors.

Page 49: Magento 2 Development Cookbook
Page 50: Magento 2 Development Cookbook

CustomersupportNowthatyouaretheproudownerofaPacktbook,wehaveanumberofthingstohelpyoutogetthemostfromyourpurchase.

Page 51: Magento 2 Development Cookbook

DownloadingtheexamplecodeYoucandownloadtheexamplecodefilesfromyouraccountathttp://www.packtpub.comforallthePacktPublishingbooksyouhavepurchased.Ifyoupurchasedthisbookelsewhere,youcanvisithttp://www.packtpub.com/supportandregistertohavethefilese-maileddirectlytoyou.

Page 52: Magento 2 Development Cookbook

DownloadingthecolorimagesofthisbookWealsoprovideyouwithaPDFfilethathascolorimagesofthescreenshots/diagramsusedinthisbook.Thecolorimageswillhelpyoubetterunderstandthechangesintheoutput.Youcandownloadthisfilefromhttp://www.packtpub.com/sites/default/files/downloads/1234OT_ColorImages.pdf.

Page 53: Magento 2 Development Cookbook

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.

Page 54: Magento 2 Development Cookbook

PiracyPiracyofcopyrightedmaterialontheInternetisanongoingproblemacrossallmedia.AtPackt,wetaketheprotectionofourcopyrightandlicensesveryseriously.IfyoucomeacrossanyillegalcopiesofourworksinanyformontheInternet,pleaseprovideuswiththelocationaddressorwebsitenameimmediatelysothatwecanpursuearemedy.

Pleasecontactusat<[email protected]>withalinktothesuspectedpiratedmaterial.

Weappreciateyourhelpinprotectingourauthorsandourabilitytobringyouvaluablecontent.

Page 55: Magento 2 Development Cookbook

QuestionsIfyouhaveaproblemwithanyaspectofthisbook,youcancontactusat<[email protected]>,andwewilldoourbesttoaddresstheproblem.

Page 56: Magento 2 Development Cookbook
Page 57: Magento 2 Development Cookbook

Chapter1.UpgradingfromMagento1Inthischapter,wewillcover:

CreatingaMagento1websitewithsampledataCreatingaMagento2websitePreparinganupgradefromMagento1UpgradingthedatabaseUsinganIDEWritingcleancodewithPHPMDandPHPCS

Page 58: Magento 2 Development Cookbook

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.

Page 59: Magento 2 Development Cookbook
Page 60: Magento 2 Development Cookbook

CreatingaMagento1websitewithsampledataTostartaMagento2upgrade,weneedaMagento1webshopwithsomedata.Inthisrecipe,wewillinstallthelatestMagentoversion,1.9,withthesampledataforthenewresponsivetheme.

Page 61: Magento 2 Development Cookbook

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.

Page 62: Magento 2 Development Cookbook

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

Page 63: Magento 2 Development Cookbook

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

Page 64: Magento 2 Development Cookbook

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:

Page 65: Magento 2 Development Cookbook

11. Thelayoutisresponsive.Whenscalingyourbrowsertoasmallerwidth,thewebsitewillswitchtothemobilelayoutlikeinthefollowingscreenshot:

Page 66: Magento 2 Development Cookbook

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.

Page 67: Magento 2 Development Cookbook
Page 68: Magento 2 Development Cookbook

CreatingaMagento2websiteInthepreviousrecipe,wecreatedaMagento1websitewithsampledatathatwewilluseforanupgrade.Inthisrecipe,wewilldothesame,butwewillcreateaMagento2websitewiththesampledataforMagento2.

Page 69: Magento 2 Development Cookbook

GettingreadyToinstallMagento2,weneedthenewesttoolstorunthatapplication.Makesureyourwebserverhasthefollowingstuffinstalled:

PHP5.5orhigherMySQL5.6orhigherApache2.2orhigherCommandlineaccessComposer

WecaninstallMagento2indifferentways.Inthisrecipe,wewillinstallMagento2usingComposer.TheadvantageofthisisthatwecanuseGITtoaddversioncontroltoourcustomdevelopment.

Page 70: Magento 2 Development Cookbook

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:

Page 71: Magento 2 Development Cookbook

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:

Page 72: Magento 2 Development Cookbook

11. RunthefollowingcommandsinyourMagentoinstallationdirectorytoconfigurethesampledata:

phpbin/magentosampledata:deploy

composerupdate

phpbin/magentosetup:upgrade

12. Theprecedingcommandswilldownloadandinstallthesampledatapackages.Becausetheycontainalotofimages,thiscouldtakesometime.Thesetup:upgradecommandwillinstallthesampledata,andthisalsotakessometime.

13. Theinstallationofthewebshopisnowcomplete.Younowhaveanup-and-runningMagento2webshop.WhenyounavigatetothecategoryGear|Bags,youshouldseesomethinglikeinthefollowingscreenshot:

Page 73: Magento 2 Development Cookbook
Page 74: Magento 2 Development Cookbook

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.

Page 75: Magento 2 Development Cookbook

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

Page 76: Magento 2 Development Cookbook
Page 77: Magento 2 Development Cookbook

PreparinganupgradefromMagento1ThedifferencesbetweenMagento1andMagento2arehuge.Thecodehasawholenewstructurewithalotofimprovementsbutthereisonebigdisadvantage.WhatdoIdoifIwanttoupgrademyMagento1shoptoaMagento2shop?

MagentocreatedanupgradetoolthatmigratesthedatafromaMagento1databasetotherightstructureforaMagento2database.

ThecustommodulesinyourMagento1shopwillnotworkinMagento2.ItispossiblethatsomeofyourmoduleswillhaveaMagento2version,anddependingonthemodule,themoduleauthorwillhaveamigrationtooltomigratethedatathatisinthemodule.

Page 78: Magento 2 Development Cookbook

GettingreadyBeforewegetstarted,makesureyouhaveanempty(withoutsampledata)Magento2installationwiththesameversionastheMigrationtoolthatisavailableat:

https://github.com/magento/data-migration-tool-ce.

Page 79: Magento 2 Development Cookbook

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

Page 80: Magento 2 Development Cookbook

Howitworks…Byaddingacomposerdependency,weinstalledthedatamigrationtoolforMagento2inthecodebase.ThismigrationtoolisaMagentoconsolecommandthatwillhandlethemigrationstepsfromaMagento1shop.

Intheetcfolderofthemigrationmodule,thereisasampleconfigurationofanemptyMagento1.9shop.

IfyouwanttomigrateanexistingMagento1shop,youhavetocustomizetheseconfigurationfilessoitmatchesyourpreferredstate.

Inthenextrecipe,wewilllearnhowwecanusethescripttostartthemigration.

Page 81: Magento 2 Development Cookbook
Page 82: Magento 2 Development Cookbook

UpgradingthedatabaseInthepreviousrecipe,weconfiguredthedatabasemigrationtool.Inthisrecipe,wewillrunthemigrationtoolsothatwecanmigratepartsfromaMagento1shoptoaMagento2shop.

Page 83: Magento 2 Development Cookbook

GettingreadyYouneedaMagento1websiteandaMagento2website.TheMagento2websiteneedstohavethedatabasemigrationtoolinstalledandconfiguredasdescribedinthepreviousrecipe.

Inthisrecipe,wewilldoamigrationfromacleanMagento1site,toaMagento2sitewithoutsampledata.

WedidamigrationfromacleanMagento1databasewithsometestproducts.MakesureyouhaveacleanlyinstalledMagento1shopwithsometestdata(products,orders,andsoon)init.

Page 84: Magento 2 Development Cookbook

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:

Page 85: Magento 2 Development Cookbook

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:

Page 86: Magento 2 Development Cookbook

Theorders(Sales|Orders)Theproducts(Products|Catalog)Thecustomers(Customers|AllCustomers)

9. Youcanalsocheckinthedatabaseifyoulookatthefollowingtables:

sales_order

customer_entity

catalog_product_entity

url_rewrite

Page 87: Magento 2 Development Cookbook

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.

Page 88: Magento 2 Development Cookbook

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.

Page 89: Magento 2 Development Cookbook

SeealsoMigratingconfigurationfilesisthemosttimeconsumingpartofadatamigration.Ifyouwantmoreinformationonthemigrationtool,youcanhavealookattheMagentoMigrationWhitepaper,availableathttp://magento.com/resources/magento-2-migration-whitepaper.

Page 90: Magento 2 Development Cookbook
Page 91: Magento 2 Development Cookbook

UsinganIDEWritinggoodcodestartswithagooddevelopmentenvironment.AnIntegratedDevelopmentEnvironment(IDE)isthemainpartofagooddevelopmentenvironment.NetBeansisafreeandopensourcePHPeditorthatcanbeusedforMagentodevelopment.Inthisrecipe,wewillsetupaMagento2projectinNetBeans.

Page 92: Magento 2 Development Cookbook

GettingreadyInstallthelatestversionofNetBeansIDEonyourcomputer.YoucandownloaditfromthefollowingURL:

https://netbeans.org/downloads/

ForPHPdevelopment,youneedtodownloadtheHTML5&PHPbundle.

Page 93: Magento 2 Development Cookbook

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:

Page 94: Magento 2 Development Cookbook

TipWhenyouareworkingwithaversioncontrolsystemlikeGIT,itisrecommendedthatyoucheckthecheckbox.PutNetBeansmetadataintoaseparatedirectory.Ifnotchecked,a.nbprojectfolderiscreatedinyourMagentoroot,andyoudon’twanttohavethatfolderinyourversioncontrolsystem.Anotherpossibilityistoaddthe.nbprojectfolderinthe.gitignorefile.

5. ClickonNextandconfigurethefinalsettings:

Runas:IfyouaredevelopingonalocalPC,chooseLocalWebServerProjectURL:TheURLofyourwebsiteIndexfile:Setthistoindex.php

Thesettingsareshowninthefollowingscreenshot:

Page 95: Magento 2 Development Cookbook

6. ClickontheFinishbuttonandyourNetBeansprojectisready.Youcannowstartdeveloping.

Page 96: Magento 2 Development Cookbook

There’smore…Inthisrecipe,weusedthefreecodeeditorNetBeans,buttherearealsosomeothergoodalternativesonthemarket,suchas:

PHPStormEclipsewithPDT(PHPDevelopmentTools)ZendStudio

Page 97: Magento 2 Development Cookbook
Page 98: Magento 2 Development Cookbook

WritingcleancodewithPHPMDandPHPCSMaintainingcleancodeismuchmoreefficientthanmaintainingspaghetticode,butwritingcleancodeisnotaseasyasitsounds.Thesedaystherearesometoolsthathelpyouwithwritingcleancode,suchasPHPMDandPHP_CodeSniffer.

PHPMDstandsforPHPMessDetector;thistoolwillcheckyourcodeoncomplexityandhowvariablesareusedandwilldetectsomepossiblebugs.ItgoesabitfurtherthanthesyntaxcheckinyourIDE.

PHP_CodeSnifferorPHPCSchecksyourcodeoncodingstandardssuchasPSR-1andPSR-2.

Page 99: Magento 2 Development Cookbook

GettingreadyWewillinstallPHPMDandPHP_CodeSnifferinourdevelopmentenvironment.Makesureyouhavecommand-lineaccesstoyourdevelopmentenvironment.

Page 100: Magento 2 Development Cookbook

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

----------------------------------------------------------------------

Page 101: Magento 2 Development Cookbook

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

Page 102: Magento 2 Development Cookbook

45|ERROR|Missingfunctiondoccomment

65|ERROR|Missingfunctiondoccomment

----------------------------------------------------------------------

Time:35ms;Memory:3.75Mb

Page 103: Magento 2 Development Cookbook

Howitworks…PHPMDandPHP_CodeSnifferaretoolsthatchecksPHPfilesoncodestyle.Thesetoolshavedefinedtheirdefaultrulesetsforcommonusage.

Magentohascreateditsownrulesets;theycanbefoundinthedirectorydev/tests/static/testsuite/Magento/Test/Php/_files/phpcs/ruleset.xml.

WhendevelopingcustomcodeinMagento2,itisrecommendedthatyouconfiguretheserulesetswhenworkingwithPHPMDandPHP_CodeSniffer.

Page 104: Magento 2 Development Cookbook

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.

Page 105: Magento 2 Development Cookbook
Page 106: Magento 2 Development Cookbook

Chapter2.WorkingwithProductsInthischapter,wewillcoverthefollowingrecipes:

ConfiguringthecatalogdefaultsWorkingwithattributesetsWorkingwithproducttypesAddingsocialmediabuttonsEmbeddinganHTMLobjectChangingtheURLofaproductpage

Page 107: Magento 2 Development Cookbook

IntroductionHowproductsaredisplayedinthefrontendisveryimportantformakingawebshopwithgoodusability.Convincingthevisitorstobuysomethingfromtheshopisthemaintargetofeveryshopowner.

Productsneedtobesetinsuchawaythatavisitorcanquicklyfindwhatheislookingfor.Withgoodproductcontent,ashoplooksreliable,duetowhichavisitorismorelikelytobuysomething.

Inthischapter,wewillexplainwhatyoucandotodisplayproductsinyourshop,andwewillseesomeextrathings,suchasavideoandaddtocartlinks,toraiseconversionfromaprospectivebuyertoanactualbuyer.

Thegoalofthischapteristomakeyourshopmoreuser-friendlywithjustalittledevelopment.

Page 108: Magento 2 Development Cookbook
Page 109: Magento 2 Development Cookbook

ConfiguringthecatalogdefaultsOneofthefirstthingsistoconfiguresomedefaultcatalogsettingstothepreferredvalues.WewillcoveralltheconfigurationvaluesthatarepossibleinaMagento2installation.

Wewillgothroughtheavailableconfigurationsandchangesomevaluestotherecommendedsettings.

Page 110: Magento 2 Development Cookbook

GettingreadyOpenyourfrontendandlogintothebackendinaseparatebrowsertab.Wewillmodifysomeconfigurationvaluestotherecommendedsettings.Whenchangingaconfigurationvalue,wecancheckwhathappensinthefrontend.

Page 111: Magento 2 Development Cookbook

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

Page 112: Magento 2 Development Cookbook

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.

Page 113: Magento 2 Development Cookbook

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.

Page 114: Magento 2 Development Cookbook

HowitworksAllthesesettingsaresavedintheconfigurationtableofMagento.Thefrontendfilesforthecatalogpageswillpickupthesesettingsandrendertheoutputbasedonthesesettings.

Whenyouaddextrafunctionalitytothecategorypage,youcaneasilyextendtheconfigurationwithextraparameters.MoreinformationaboutextendingtheconfigurationsisgivenintheExtendingthesystemconfigurationrecipeofChapter6,MagentoBackend.

Page 115: Magento 2 Development Cookbook
Page 116: Magento 2 Development Cookbook

WorkingwithattributesetsMagentohasaflexiblesystemtoworkwithproducts.Whenyousell,forexample,aboardgameoracomputer,thespecificationsofeachproductaredifferent.Foraboardgame,informationsuchasageanddurationisrelevant.Foracomputer,alotoftechnicalspecificationsarerelevant,suchastheCPUpower,discsize,andsoon.

Tocoverthis,Magento2comeswithasystemcalledproducttemplates,whichcanbecomparedwithattributesetsinMagento1.

Aproducttemplateisaspecificationofproductattributesthatyoucanassigntoproducts.

Page 117: Magento 2 Development Cookbook

GettingreadyInthebackend,wewillusethepagesStores|Attributes|ProductandStores|Attributes|AttributeSet.

Wewillcreateanewproductattributeandanewproducttemplate(suchasanattributeset)thatwecanuseinnewproducts.

Page 118: Magento 2 Development Cookbook

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:

Page 119: Magento 2 Development Cookbook

10. ClickingonSavewillopentheoverviewpage.11. CreateagroupnamedGamespecificdataanddragtheavailable_fromand

manufacturerattributestoit.Theoverviewwilllookasfollows:

12. SavetheAttributeSet.13. NavigatetoProduct|CatalogandcreateanewproductbyclickingontheNew

Productbutton.14. SelecttherightAttributeSetintheform,asshowninthefollowingscreenshot:

Page 120: Magento 2 Development Cookbook

15. Whenselectingtheproducttemplate,youwillseethattheGamespecificdatatabappearsintheproducttab,asshowninthefollowingscreenshot:

Page 121: Magento 2 Development Cookbook

HowitworksProductattributesandAttributeSetsareusedwhenyouworkwithmultiplefamiliesofproducts.Inthesampledataofourshop,therearemoreattributesetsavailableforbags,clothing,andmore.

WithAttributeSets,youcanmakegroupsofattributesforeveryproductfamily.Whencreatingaproductattribute,youhavetochoosethetypeoftheattribute,whichcanbeoneofthefollowing:

TextfieldTextareaDateYes/NoMultipleselectDropdownPriceMediaimageFixedProductTax

TipWhenyouwanttouseanattributeasafilterintheleftnavigationonthecategorypages,thisattributemusthavethetypeDropdown,MultipleSelectorPrice.

Page 122: Magento 2 Development Cookbook
Page 123: Magento 2 Development Cookbook

WorkingwithproducttypesInMagento2,itisstillpossibletoworkwithdifferenttypesofproducts.Thestandardproductisasimpleproduct,whichisusedtosellbasicproducts,buttherearemoretypesavailable,suchasproducts,whereyoucanchooseasizeandotheroptions,ordownloadproducts,virtualproducts(suchasalicense),andproductcombinations.

Page 124: Magento 2 Development Cookbook

GettingreadyInthisrecipe,wewillcreateaconfigurableproduct,forexample,youwanttobuyapairofshoeswhereyoucanchoosetheirsizeandcolor.OpentheMagentobackendandnavigatetoProducts|Catalog.

Page 125: Magento 2 Development Cookbook

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:

Page 126: Magento 2 Development Cookbook

7. ClickontheSavebuttonandapopupwillshowuptocreateaspecificattributesetforthisconfiguration.SelecttheAddconfigurableattributestothenewsetbasedoncurrentoptionandselectaSizeforthename.

8. ClickonConfirmtosavetheproductsinthedatabase.9. OntheProductEditpage,selectthecategorywhereyouwanttoaddtheproduct,

clickonSave,andsearchfortheproductintherightcategoryinthefrontend.Theproductdetailpagewilllookasfollows:

Page 127: Magento 2 Development Cookbook
Page 128: Magento 2 Development Cookbook

Howitworks…Aconfigurableproductisaproductwhereyoucanselectoneormoreoptionsontheproductdetailpage.Eachcombinationofselectionsleadstoasimpleproductinthedatabase.Thecustomerchoosestheoptions,andwhenaddingtheconfigurableproducttothecart,asimpleproductisalsoaddedinthebackground.

Thisisthereasonwhywehavegeneratedsimpleproductstobeshownasoptionsinourconfigurableproduct.Theconfigurableproductisaparentwrapperthatisusedtodisplayinthefrontend.ThesimpleproductsarehiddeninthefrontendbytheVisibilityattribute.

Whenaproductissold,theSKUoftheselectedchildproductwillbeusedtoprocesstheorder.That’sthereasonwhywehavetoconfigureastockonthechildproducts.

Page 129: Magento 2 Development Cookbook

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.

Page 130: Magento 2 Development Cookbook
Page 131: Magento 2 Development Cookbook

AddingsocialmediabuttonsThesedays,anincreasingnumberofpeoplearesharingtheirmindsthroughdifferentsocialmedia,suchasFacebook,Twitter,andmanymore.

Everysocialmediaplatformhastheoptiontosharepagesontheirplatform.Themostfamousexamplesofthisarethesharebuttons,suchastheLikebuttonofFacebook,theTweetbutton,theGooglePlusbutton,andmanymore.

Page 132: Magento 2 Development Cookbook

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.

Page 133: Magento 2 Development Cookbook

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

Page 134: Magento 2 Development Cookbook

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.

Page 135: Magento 2 Development Cookbook

HowitworksAsocialmediabuttonismostlyapieceofexternalHTMLthatwillrenderinyourwebsite.Withthisbutton,apagecanbeshared.

Torenderthesebuttons,externalstaticresourcesfromthesocialmediawebsitewillbeloaded.Whenyoureadthecodeofthebuttons,youcanseethatadditionalJavaScriptlibrariesareincludedinthecode.

Whensharingapage,thesocialmediacrawlsthepagetolookforatitle,image,anddescriptionforthepost.Inthefirstcase,thecrawlerwillsearchfortheOpenGraphmetatags.

ThesetagsaregeneratedbyMagentoonaproductdetailpage,soagoodtitle,image,anddescriptionwillbedisplayedforthepost.

Page 136: Magento 2 Development Cookbook
Page 137: Magento 2 Development Cookbook

EmbeddinganHTMLobjectInaproductdescription,wecanaddHTMLtagssothatwecanusethe<object>tag.Withan<object>tag,wecanembedwidgets,suchasaYouTubevideo,socialwidgets,andmore.

Inthisrecipe,wewillfocusonhowtoaddaYouTubevideoinaproductdescription.

Page 138: Magento 2 Development Cookbook

GettingreadyGotohttp://www.youtube.com,andchooseavideothatyouwanttoshowontheproductdetailpage.

Page 139: Magento 2 Development Cookbook

HowtodoitThenextstepsshowsyouhowtoembedaYouTubevideoonaproductdetailpage.

1. OntheYouTubevideopage,clickontheEmbedbutton.Whenyouclickthisbutton,thefollowingscreenshowsup:

2. CopytheHTMLcodeandpasteitinthedescriptionoftheproduct.3. Savetheproduct.4. Gototheproductinthefrontend.YouwillseethevideoontheProductpage.

Page 140: Magento 2 Development Cookbook

HowitworksTheabilitytouseHTMLtagsinproductdescriptionsgivesalotofflexibilityforthisfield.ItispossibletouseaWYSIWYGeditorforthecontentbecausethisallowsustousewidgets,suchasaYouTubevideoorotherthird-partywidgets.

OnaYouTubevideopage,weopenedtheembedoptionswherewecanconfigurethecodethatwewillincludeinoursite.Wecanspecifyoptionssuchaswidth,height,colorandmore.

Wheneverythingisconfigured,wecanpastetheEmbedcodeintheHTMLoftheproductdescription,andthevideowillbevisibleinthedescriptionoftheproduct.

Page 141: Magento 2 Development Cookbook
Page 142: Magento 2 Development Cookbook

ChangingtheURLofaproductpageWhenyouareonaproductpage,theURLofeveryproductalwayslooksclean.ThenameintheURLmakesitverySEOfriendly.

Inthisrecipe,wewillexplorethepossibilitiesofURLrewritesinMagento.

Page 143: Magento 2 Development Cookbook

GettingreadyInthebackend,navigatetoProducts|Inventory|CatalogandlookforasimpleproductwithvisibilityCatalog,Search.ThisrecipeisbasedontheEndeavorDaytripBackpackproductfromthesampledata.

Page 144: Magento 2 Development Cookbook

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.

Page 145: Magento 2 Development Cookbook

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.

Page 146: Magento 2 Development Cookbook

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.

Page 147: Magento 2 Development Cookbook
Page 148: Magento 2 Development Cookbook

Chapter3.ThemingInthischapter,wewillcover:

ExploringthedefaultMagento2themesCreatingaMagento2themeCustomizingtheHTMLoutputAddingextrafilestothethemeWorkingwithLESSChangingapagetitleWorkingwithtranslationsAddingwidgetstothelayoutCustomizingemailtemplates

Page 149: Magento 2 Development Cookbook

IntroductionThemostcommoncustomizationonaMagentoshopisthetheming.Makingagoodfirstimpressiontoyourcustomersisanimportantpointtoraisetheconversion.Almosteverystoreownerwantsalookandfeeloftheircompanyintheirshop.

Inthischapter,wewillcoverallthethingsyouneedtocustomizeaMagentotheme.Wewillseehowwecancustomizethetemplates,CSS,JavaScript,translations,andmore.

Page 150: Magento 2 Development Cookbook
Page 151: Magento 2 Development Cookbook

ExploringthedefaultMagento2themesWhenMagento2isinstalled,therearetwothemesavailable.YouhavetheLumathemethatyoucanseeinthesampledata,andyouhavetheBlankthemethatisdevelopedasastartingpointtocustomizeyourtheme.

Page 152: Magento 2 Development Cookbook

GettingreadyLogintothebackendandopenthedesignconfiguration.WecanfindthisinStores|Configuration|Design.

Page 153: Magento 2 Development Cookbook

Howtodoit…ThefollowinginstructionsdescribehowwecanconfigurethethemesettingsforaMagento2store:

1. Whenyoulookatthethemesettingsontheconfigurationpage,weseethattheMagentoLumathemeisselectedintheDesignThemesection.SelecttheMagentoBlankoptioninthedropdown,savetheconfiguration,andreloadthefrontend.Youshouldseesomethinglikethefollowingscreenshot:

2. WehavenowconfiguredtheMagentoBlankthemeforthestore.TheMagentoBlankthemeisthedefaultthemewheretheMagentoLumathemewillextendfrom.

3. OpenthepageContent|ThemesinthebackendandyouwillseeanoverviewoftheavailablethemesinMagento.WhenMagento2isinstalledwithoutmodules,thefollowingthemesareavailable:

MagentoBlank(usedasthedefaulttheme)

Page 154: Magento 2 Development Cookbook

MagentoLuma(usedinthesampledatastore)

4. WhenclickingonMagentoLuma,wewillseethedetailsofthethemelikethefollowingscreenshot:

5. WeseethattheParentThemeisMagentoBlank.ThismeansthatthisthemewillextendtheMagentoBlanktheme.

6. Inthebackend,openthepageContent|Schedule.WhenclickingtheAddDesignChangebutton,wecanconfigureadesignchangewithafromandtodate.

Page 155: Magento 2 Development Cookbook

Howitworks…InMagento2theconceptofthemesismucheasierthaninMagento1.Therearenopackagesanymore,andnoskins.

InMagento2,athemecanhaveaparentthemeandthat’sall.Whenathemehasaparenttheme,itwillinheriteverythingfromthatparentthemeexceptyoucanoverrideitinyourtheme.Thismeansthatifyourthemeisempty,allthesettingsoftheparentthemewillbeused.

LikeinMagento1,youcanoverridethethemesettingsindifferentwayssuchasthefollowing:

ByaUserAgentExceptionontheStores|Configuration|DesignpageByascheduledthemesettingontheContent|SchedulepageOnaspecificCMSpage(canbeconfiguredontheDesignsectionofaCMSeditpage)Onaspecificproductdetailpage(thiscanbeconfiguredontheDesignsectionofaproducteditpage)Onaspecificcategorypage(thiscanbeconfiguredontheCustomDesignstepofacategory)

Page 156: Magento 2 Development Cookbook
Page 157: Magento 2 Development Cookbook

CreatingaMagento2themeWewillstartcustomizingthelookandfeeloftheshopbycreatingacustomtheme.Thepurposeofacustomthemeisthatwedon’thavetomodifythecorefilesdeliveredbyMagento.

Page 158: Magento 2 Development Cookbook

GettingreadyOpenyourMagentowebrootinyourfavoriteIDE.Wewillcreateathemeand,forthis,wewillworkintheapp/design/frontendfolder.

Beforeyoustart,disablethefull-pagecachebecausethiswillsaveyoualotoftrouble.YoucandothisinthebackendonthepageSystem|CacheManagement.ClickontheFlushMagentoCachebuttonafteryouhavedisabledtheFullpagecaching.

Page 159: Magento 2 Development Cookbook

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

Page 160: Magento 2 Development Cookbook

Magentobackend.Youshouldseethethemeinthelist.Whenweclickonthetheme,wecanseethedetailsthatwehaveconfiguredinthetheme.xmlfile.

9. Thelaststepistoconfigurethetheme.GotothepageStores|Configuration|Designandconfigurethethemelikethefollowingscreenshot:

10. Flushthecacheandreloadthefrontend.YourshopwillnowhavethelookandfeeloftheBlanktheme.

NoteIfyouwanttouninstallatheme,youcanusethecommandphpbin/magentotheme:uninstall.

Page 161: Magento 2 Development Cookbook

Howitworks…WehavejustcreatedanemptythemethatinheritseverythingfromtheMagentobasetheme.Whenwewanttochangestuffonthetheme,wecandoitinthisthemewithoutoverridingthecore.

Whenwewanttocustomizesomeaspectsofthetheme,wecancopyafilefromtheMagento/Blankthemeandpasteitinourtheme.Whencopyingafile,thedirectorystructureneedstobethesameasthestructureoftheparenttheme.

NoteChangingcodeintheMagentocorewillalsowork,butthisisnotrecommended.Whenyouwanttoupgradeyourstore,allthecorefileswillbeoverwrittenandthenallyourchangeswillbelost.

Wecreatedatheme.xmlfileinourthemewherewehavesetthedefaultsettingsofthethemesuchasthenameandparenttheme.Magentowillstorethisdatainthethemetableofthedatabase.

NoteAlltheXMLconfigurationswillbecachedbyMagento.Sowhenthecachesareenabledandyouchangesomethingintheconfigfiles,makesureyoucleanandflushthecaches.Youcanalsodisablethecacheswhendeveloping.Youcancleanthecacheusingthecommandphpbin/magentocache:cleanoryoucandoitinthebackend.

Page 162: Magento 2 Development Cookbook

There’smore…InMagento,itispossibletoenabletemplatehintsinthefrontend.Whenthisisenabled,containerswillbeshownaroundblocksofHTMLwiththereferencetothefileandclassthatisloaded.

Toenablethis,navigatetoStores|Configuration|Advanced|DeveloperandchangetheconfigurationscopedropdowntoMainWebsitelikethefollowingscreenshot:

IntheDebugsection,youcanenablethehintsbysettingthedropdownvaluestoYesforthefollowingfields:TemplatePathHintsandAddBlockNamestoHints.

Whenthisisdone,reloadyourfrontendandyouwillseeredbordersaroundeachblockofHTML.

Page 163: Magento 2 Development Cookbook
Page 164: Magento 2 Development Cookbook

CustomizingtheHTMLoutputYoucancustomizeathemeintwoways.Wecanonlychangesomestylestomakeashoplookdifferent.ThesecondwayistocustomizetheHTMLoutput,whichiswhatwewillcoverinthisrecipe.

ItisverycommontowanttochangesomestuffontheHTMLstructuretomakeyourshoplookunique.

Page 165: Magento 2 Development Cookbook

GettingreadyMakesureyouhavethethemeinstalledandconfiguredlikewehavedoneinthepreviousrecipe.

Ifyoudon’thavethethemeinstalled,youcaninstallthestartfilesforthisrecipe.

Page 166: Magento 2 Development Cookbook

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.

Page 167: Magento 2 Development Cookbook

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

Page 168: Magento 2 Development Cookbook

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

Page 169: Magento 2 Development Cookbook

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.

Page 170: Magento 2 Development Cookbook
Page 171: Magento 2 Development Cookbook

AddingextrafilestothethemeInthepreviousrecipe,welearnedhowwecanmakestructuralchangestotheHTMLoutput.HoweveranHTMLoutputisnotacompletedwebsite.WithJavaScriptandCSS,wecanthemetheHTMLoutput.

Page 172: Magento 2 Development Cookbook

GettingreadyWewilllearnhowwecanaddCSSandJavaScriptfilestoourtheme.ThisisacommoncasewhenintegratinganexternalJavaScriptplugin.Todothis,wehavetoworkinthethemefolderthatwecreatedintherecipeCreatingaMagento2themeearlierinthischapter.

Page 173: Magento 2 Development Cookbook

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

Page 174: Magento 2 Development Cookbook

cookbook.jsfileisaddedinthe<head>.

Page 175: Magento 2 Development Cookbook

Howitworks…Likeinthepreviousrecipe,wedidsomelayoutXMLconfigurationstoaddtheCSSandJavaScriptfile.Intheconfigurationfiledefault_head_blocks.xml,weaddedXMLconfigurationstoaddaCSSfileandaJSfile.

WhenrenderingtheHTMLoutput,Magentowilllookforall<head>configurationentriesandwillgeneratealistofCSSandJSfiles.

WecreatedtheCSSandJavaScriptfilesinthethemefolder.ButifwelookattheHTMLsource,weseethatMagentoloadsthefilefromadifferentlocation.Thefilesareloadedfromthepub/staticfolder.

Magentobuildsasymboliclinkfromthepubfoldertothewebfolderinthetheme.WhenwehavestaticfilessuchasCSS,JavaScript,images,webfonts,andmore,wehavetoplacethesefilesinthewebfolderofthetheme.

Page 176: Magento 2 Development Cookbook

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.

Page 177: Magento 2 Development Cookbook
Page 178: Magento 2 Development Cookbook

WorkingwithLESSInMagento1,theCSSofaMagentothemewasstoredinonebigCSSfile(thestyles.css)butinMagento2,itiscompletelydifferent.ThebigCSSfilehasbeenreplacedbyacollectionofLESSfiles.

LESSisalanguagethatisusedtogenerateCSS.CSSisverystatic.Youcan’tusefunctions,variables,nesting,andsoonbutwithLESS,youcan.

MagentohasaLESSpre-processorthatgeneratesaCSSfilefromtheLESSfilesinthetheme.

Page 179: Magento 2 Development Cookbook

GettingreadyOpenyourfavoriteIDEandnavigatetothethemefolderofthePackt/cookbookthemethatwehavecreatedinthepreviousrecipes.

AccesstoacommandlineisalsousefultoworkwithGrunt.

Page 180: Magento 2 Development Cookbook

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:

Page 181: Magento 2 Development Cookbook

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;

}

}

Page 182: Magento 2 Development Cookbook

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.

Page 183: Magento 2 Development Cookbook

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.

Page 184: Magento 2 Development Cookbook

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.

Page 185: Magento 2 Development Cookbook
Page 186: Magento 2 Development Cookbook

ChangingapagetitleChangingapagetitlehelpsyoutoimprovetheSEOofyourwebsite.Forproductpages,wecanmanagethatwiththebackendbutonotherpages,suchasthecontactpage,wecan’tdothat.

Inthisrecipe,wewillchangethepagetitleofthecontactpagethatisavailableatthe/contactpath.

Page 187: Magento 2 Development Cookbook

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!.

Page 188: Magento 2 Development Cookbook

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.

Page 189: Magento 2 Development Cookbook
Page 190: Magento 2 Development Cookbook

WorkingwithtranslationsMagentohastheabilitytorunastoreinmultiplelanguages.Everystoreviewhasalanguage,andMagentohasasystemtotranslatetheinterfaceintothatparticularlanguage.

Page 191: Magento 2 Development Cookbook

GettingreadyOpenthebackendandgotothestoreconfigurationatStores|Configuration|General.WewillconfiguretheFrenchlanguageforthedefaultstoreview.

Page 192: Magento 2 Development Cookbook

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:

Page 193: Magento 2 Development Cookbook

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.

Page 194: Magento 2 Development Cookbook

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.

Page 195: Magento 2 Development Cookbook
Page 196: Magento 2 Development Cookbook

AddingwidgetstothelayoutMagentohasasetofpredefinedwidgetsthatyoucanconfigureandshowonthedifferentpages.WiththeMagentowidgetinterface,wecanconfiguredifferentwidgetsondifferentpages.

Page 197: Magento 2 Development Cookbook

GettingreadyWewilladdablockofproductsonthecontentareaofthehomepage.GotothebackendandnavigatetoContent|Widgets.

Page 198: Magento 2 Development Cookbook

Howtodoit…Inthefollowingsteps,wewillconfigureawidgetforthecategorypages:

1. ClickonthebuttonAddWidget.2. Intheform,setthefollowingvalues:

Type:CatalogProductsListDesignTheme:Packtcookbook

3. Clickcontinueandthefollowingscreenshowsup:

4. CompletetheStorefrontPropertiestabwiththefollowingvalues:

WidgetInstanceTitle:Widget-home-productsThisisthetitleofthewidgetinthebackend.Astructuralnameiseasywhen

Page 199: Magento 2 Development Cookbook

workingwithalotofwidgetsAssigntoStoreViews:AllStoreViews

5. CompletetheWidgetOptionstabwiththefollowingvalues:

Title:FeaturedproductsNumberofProductstoDisplay:10Conditions:Chooseaspecificcategorylikethefollowingscreenshot:

6. SavethewidgetbyclickingSaveandContinueEdit.Thewidgetinstanceisnowsavedbutnothingwillshowupinthefrontendbecausethereisnolayoutupdateset.

7. Wehavetocreatealayoutupdatebeforethewidgetwillshowupinthefrontend.Onthewidgetpage,opentheFrontendPropertiesandcompletetheLayoutUpdatessectionasfollows:

8. Clearthecacheandreloadthehomepage.Youwillseealistwithproductsfromtheconfiguredcategory.

Page 200: Magento 2 Development Cookbook

Howitworks…IntheMainContentArea,anewblockisaddedtothefrontend.Likeanyotherblockinthefrontend,thisblockhasablockclassandatemplatesimilartootherblocks.

TheonlydifferenceisthatthisblockisnotgeneratedbyanXMLfilebutbyanXMLlayoutinstructioninthedatabase.

ThewidgetinterfacewillgeneratealayoutXMLthatisstoredinthedatabase.TheblockclassandtemplatearesimilartootherblocksintheXMLfiles.

Page 201: Magento 2 Development Cookbook
Page 202: Magento 2 Development Cookbook

CustomizingemailtemplatesMagentohasalotoffunctionalitythatsendsemailsonparticularactions,suchasaneworder,customerinformation,newsletter,andmanymore.

Whencustomizingthelookandfeelofyourwebsite,itisnicethatyouremailtemplateshavethesamelookandfeel.

Inthisrecipe,wewilllearnhowwecandothis.Wewillcustomizethenewaccountemailandwewilllearnhowwecaneditthegenericheaderandfooterofthetransactionalemails.

Page 203: Magento 2 Development Cookbook

GettingreadyWewillcustomizeemailtemplates.Totestthis,makesureyourdevelopmentenvironmentcansendemails.

Likethepreviousrecipes,wewillbuildthecodefurtheronthethingsthatwehavedoneinthepreviousrecipes.Ifyoudon’thavethecompletecode,youcaninstallthestartfilesforthisrecipe.

Page 204: Magento 2 Development Cookbook

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

Page 205: Magento 2 Development Cookbook

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.

Page 206: Magento 2 Development Cookbook
Page 207: Magento 2 Development Cookbook

Chapter4.CreatingaModuleInthischapter,wewillcoverthefollowingrecipes:

CreatingthemodulefilesCreatingacontrollerAddinglayoutupdatesAddingatranslationfileAddingablockofnewproductsAddinganinterceptorAddingaconsolecommand

Page 208: Magento 2 Development Cookbook

IntroductionWhenyoulookintheapp/codefolder(thecoreofMagento),youseethemodulararchitecture.Everyconceptinthee-commerceflowisstoredinamodule.TheMagentoapplicationisacombinationofallthesemodules.

Oneoftheadvantagesofamodulararchitectureistheextendibility.ItiseasytoaddmodulesthataddtoormodifythenativebehaviorofMagento.

Inthischapter,wewillcreateamodulewiththemostimportantthingsyouneedtoknowwhenwritingcodeinMagento.

Page 209: Magento 2 Development Cookbook
Page 210: Magento 2 Development Cookbook

CreatingthemodulefilesWhencreatingamodule,thefirststepistocreatethefilesandfolderstoregisterthemodule.Attheendofthisrecipe,wewillhavearegisteredmodulebutwithoutfunctionality.

Inthenextrecipes,wewilladdextrafeaturestothatmodule.

Page 211: Magento 2 Development Cookbook

GettingreadyOpentherootfolderofyourMagento2website.Theapp/code/folderisthefolderwhereallthemoduledevelopmentneedstobedone.

AccesstoacommandlineisalsorecommendedbecauseMagento2hasabuilt-inconsoletoolwithalotofcommandsthatwecanuseduringthedevelopment.

Page 212: Magento 2 Development Cookbook

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

Page 213: Magento 2 Development Cookbook

list.EnsurethatyouhavecleanedtheMagentocaches.

Page 214: Magento 2 Development Cookbook

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.

Page 215: Magento 2 Development Cookbook
Page 216: Magento 2 Development Cookbook

CreatingacontrollerInyourMagentoroot,createthefollowingfolders:Wewilladdanextrapagethatwecanuseforseveralpurposes.

Page 217: Magento 2 Development Cookbook

GettingreadyWebuildfurtheronthePackt_HelloWorldmodulethatwecreatedinthepreviousrecipe.EnsurethatyouhavethismoduleinyourMagentoinstance.Also,ensurethatthefullpagecacheisdisabledwhenyouaredeveloping.YoucandisablethisinthebackendbynavigatingtoSystem|CacheManagement.

Page 218: Magento 2 Development Cookbook

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

){

Page 219: Magento 2 Development Cookbook

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

Page 220: Magento 2 Development Cookbook

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.

Page 221: Magento 2 Development Cookbook

There’smore…Whenthingsarenotworkingasyouexpect,youcanusethefollowingtipstomakeitwork:

Cleanthecache.Youcandothisusingthephpbin/magentocache:cleancommand.Flushthecache.Youcandothisusingthephpbin/magentocache:flushcommand.Removethevar/generationfolder.Sometimes,thethegeneratedclassesinthisfolderneedstoberegenerated.

Page 222: Magento 2 Development Cookbook
Page 223: Magento 2 Development Cookbook

AddinglayoutupdatesInthepreviousrecipe,wecreatedapagewithoutcontent.Inthisrecipe,wewillmodifythecontentofthatpagewithlayoutupdates.

Withlayoutupdates,wecanarrangethestructureofthepageaswehaveseenintheCustomizingtheHTMLoutputrecipeofChapter3,Theming.Buthere,wewillseehowwecandothatinamodule.

Page 224: Magento 2 Development Cookbook

GettingreadyThisrecipebuildsfurtheronthepreviousrecipe.Youneedtheinstallthemodulethatwecreatedinthepreviousrecipesofthischapter.

Page 225: Magento 2 Development Cookbook

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 226: Magento 2 Development Cookbook

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

Page 227: Magento 2 Development Cookbook

11. Cleanthecacheandreloadthe/helloworldURL.Youwillseesomethinglikethefollowing:

Page 228: Magento 2 Development Cookbook

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.

Page 229: Magento 2 Development Cookbook
Page 230: Magento 2 Development Cookbook

AddingatranslationfileMagentoismadetoruninmultiplelanguages.Thismeansthattheinterfaceandcontentneedstobetranslatableintheconfiguredlanguages.

Inthisrecipe,youwilllearnhowtomakethestringsinourmoduletranslatableindifferentlanguages.

Page 231: Magento 2 Development Cookbook

GettingreadyWewillcreatetranslationfilesforthemodulethatwecreatedinthepreviousrecipesofthischapter.EnsurethatyouhavethecodeinyourMagentoinstance.

Page 232: Magento 2 Development Cookbook

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.

Page 233: Magento 2 Development Cookbook

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.

Page 234: Magento 2 Development Cookbook
Page 235: Magento 2 Development Cookbook

AddingablockofnewproductsInthepreviousrecipes,wepreparedthemodulefortherealwork.Weaddedthemostcommonfeaturestothemodulesothatwecaneasilyextenditwiththenewfunctionality.

Inthisrecipe,wewillcreateablockofnewproductstothepagewecreatedinthepreviousrecipes.

Page 236: Magento 2 Development Cookbook

GettingreadyInourmodule,wewillcreateablockthatwillloadaproductcollection.Thisproductcollectionwillbeusedinthetemplate,whichwillshowthenewestproductsoftheshop.

Ensurethatyouhavethemoduleofthepreviousrecipeinstalled.

Page 237: Magento 2 Development Cookbook

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(){

Page 238: Magento 2 Development Cookbook

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

Page 239: Magento 2 Development Cookbook

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.

Page 240: Magento 2 Development Cookbook
Page 241: Magento 2 Development Cookbook

AddinganinterceptorOneofthemajorthingsthathaschangedinMagento2isthatthereisnoMageclass.Toreplacethis,allobjectsarepassedtotheclasseswithdependencyinjection.

DependencyinjectionisapowerfultoolthataddsalotofflexibilitytoaddorchangebehaviorinMagento.

Page 242: Magento 2 Development Cookbook

GettingreadyToexplorethepossibilitiesofdependencyinjectionofMagento2,weneedthemodulethatwecreatedinthepreviousrecipes.

Page 243: Magento 2 Development Cookbook

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.

Page 244: Magento 2 Development Cookbook

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.

Page 245: Magento 2 Development Cookbook

SeealsoAlotofthingsarepossiblewithDependencyInjection.FormoreinformationhowitisintegratedinMagento,youcanreadthedocumentationontheMagentosite:

http://devdocs.magento.com/guides/v2.0/extension-dev-guide/depend-inj.html.

NoteMoreinformationaboutthedependencyinjectiondesignpatterncanbefoundonthefollowingURL:

https://en.wikipedia.org/wiki/Dependency_injection.

Page 246: Magento 2 Development Cookbook
Page 247: Magento 2 Development Cookbook

AddingaconsolecommandAnothernewthinginMagento2isthebuilt-incommand-linetool.Inthischapter,weusedthistooltocleanthecache,forexample.

Withinamodule,itispossibletoextendthistoolwithcustomcommands,andthisisthethingthatwewilldointhisrecipe.

Page 248: Magento 2 Development Cookbook

GettingreadyThisrecipewillbuildfurtheronthemodulethatwehavecreatedinthischapter.Ifyoudon’thavethecode,youcaninstallthestarterfiles.

Page 249: Magento 2 Development Cookbook

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

Page 250: Magento 2 Development Cookbook

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

Page 251: Magento 2 Development Cookbook

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.

Page 252: Magento 2 Development Cookbook

Seealso…TheMagentoconsoleisbuiltusingtheSymfonyconsolecomponent.MoreinformationabouthowtousetheSymfonyconsolecanbefoundatthefollowingURL:

http://symfony.com/doc/current/components/console/introduction.html

Page 253: Magento 2 Development Cookbook
Page 254: Magento 2 Development Cookbook

Chapter5.DatabasesandModulesInthischapter,wewillcover:

RegisteringdatabaseconnectionsCreatinganinstallandupgradescriptCreatingaflattablewithmodelsWorkingwithMagentocollectionsProgrammaticallyaddingproductattributesRepairingthedatabase

Page 255: Magento 2 Development Cookbook

IntroductionInthepreviouschapter,welearnedhowtocreateamoduleandhowwecandosomebasicstuff.

Inthisrecipe,wewilllearnhowwecanintegrateamodulewiththedatabaseofMagento.Forthis,weneedtheMagentomodulethatwehavecreatedinChapter4,CreatingaModule.

Wewilllearnhowwecanrunqueries,addourownentities,andautomatetheinstallationofconfigurationsinthedatabase.

Page 256: Magento 2 Development Cookbook
Page 257: Magento 2 Development Cookbook

CreatinganinstallandupgradescriptInsomecases,youneedtoupdatethedatabasetofinishyourwork.Commoncasesareanextracolumntoatable,anewproductattribute,orasetting.

Ifyouhaveadevelopment,staging,andproductionenvironment,youhavetomakesurethattherightupdatesaredonewhendeployingtotheseenvironments.

Magentohasawaytoautomaticallytriggertheexecutionofcertainscriptsforanupdate.That’sthethingwewilllearninthisrecipe.CommonthingstoautomatearesettingsintheStore|Configurationpageinthebackend.

Page 258: Magento 2 Development Cookbook

GettingreadyWewillcreateanewmodulewherewewilladdaninstallandupgradescript.Wewillalsousethecommandlinetorunthescripts.

InMagento2,theprincipleofinstallingandupgradingscriptsisthesameasinMagento1butsomethingshavechangedinthewayofworking.Inthisrecipe,wewillseehowitworkstosavesomedatainthedatabasewithaninstallscript.

Page 259: Magento 2 Development Cookbook

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

Page 260: Magento 2 Development Cookbook

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

);

}

}

Page 261: Magento 2 Development Cookbook

}

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.

Page 262: Magento 2 Development Cookbook

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.

Page 263: Magento 2 Development Cookbook
Page 264: Magento 2 Development Cookbook

CreatingaflattablewithmodelsWhenyouwanttosavedatainamodule,youmaywanttostorethatinacustomentity.Thatentityneedsadatabasetableandamodelthattalkswiththatdatabasetable.

Wewillcreateasubscriptionsentitywherewecanstoresubscriptions.

Page 265: Magento 2 Development Cookbook

GettingreadyInthisrecipe,wewillextendthemoduleofChapter4,CreatingaModule,withanentitywithadatabasetable.Makesureyouhavethestarterfilesforthisrecipeinstalled.

Page 266: Magento 2 Development Cookbook

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(

Page 267: Magento 2 Development Cookbook

'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);

Page 268: Magento 2 Development Cookbook

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

Page 269: Magento 2 Development Cookbook

$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');

Page 270: Magento 2 Development Cookbook

}

}

9. OpenthebrowserandgototheURL/helloworld/index/subscriptionofyourMagento.Whenyouseethewordsuccess,thiswillmeanthatanewsubscriptionisaddedtothedatabasetable.

10. Whenyoulookinyourdatabaseandrunthequeryselect*frompackt_helloworld_subscription;,wewillseethefollowingoutput:

Page 271: Magento 2 Development Cookbook

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.

Page 272: Magento 2 Development Cookbook
Page 273: Magento 2 Development Cookbook

WorkingwithMagentocollectionsWhenyouwanttoretrieveasetofentitiesofthesametype,weusuallyuseaquerytogetthedataofatable.

ButinMagentonoteveryentityisstoredinasingletableandthatmeansthataverycomplexqueryisrequiredtogetthedata.Agenericsystem/languagetocreatequeriesforMagentoentitieswasthesolution.

Forthissolution,Magentohascreatedasystemcalledcollections.Acollectionisasetofentitiesofthesametypewhereyoucanaddfilterstoittocustomizeyourresult.

Inthisrecipe,wewillseewhatwecandowithMagentocollections.

Page 274: Magento 2 Development Cookbook

GettingreadyForthisrecipe,itisrequiredtohavethePackt_HelloWorldmoduleinstalledwiththecodeofthepreviousrecipeCreatingaflattablewithmodels.

Page 275: Magento 2 Development Cookbook

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"

Page 276: Magento 2 Development Cookbook

}

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

Page 277: Magento 2 Development Cookbook

])

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

));

Page 278: Magento 2 Development Cookbook

$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

Page 279: Magento 2 Development Cookbook

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();

Page 280: Magento 2 Development Cookbook

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.

Page 281: Magento 2 Development Cookbook
Page 282: Magento 2 Development Cookbook

ProgrammaticallyaddingproductattributesIntherecipeCreatinganinstallandupgradescript,welearnedhowwecanautomatetheexecutionofdatabasechanges.

Intheseinstallscripts,wecanalsoaddattributestoproducts,aswewilllearninthisrecipe.

Page 283: Magento 2 Development Cookbook

GettingreadyWewillworkfurtheronthePackt_HelloWorldmodulethatwehavecreatedinthepreviousrecipes.Makesureyouhavethemoduleinstalled.

Page 284: Magento 2 Development Cookbook

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:

Page 285: Magento 2 Development Cookbook

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

Page 286: Magento 2 Development Cookbook

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.

Page 287: Magento 2 Development Cookbook
Page 288: Magento 2 Development Cookbook

RepairingthedatabaseSometimes,itcanhappenthatyourMagentodatabaseisbrokenorcorrupt.Thiscanbecausedbyvariousreasonssuchasahackoraservercrash.Whenthedatabaseisbroken,everyonewantstorepairitasquicklyaspossible.

Commonexamplesofacorruptdatabasearemissingtablesorcolumnsafterrunninganunsavedquery,ormissingrelationsafteranimportofadumpwithouttherelations.

MagentohasadatabaserepairtoolthatcomparesdatabaseAwithdatabaseBandthistoolcanfixthemissingstructuresbetweenthem.

Inthisrecipe,wewillmakeourdatabasecorruptandwewillrepairitwiththerepairtool.

Page 289: Magento 2 Development Cookbook

GettingreadyToprepareyourselves,downloadthedatabaserepairtoolfromtheMagentositeathttp://www.magentocommerce.comandplacethePHPfileinyourwebroot.

Page 290: Magento 2 Development Cookbook

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.

Page 291: Magento 2 Development Cookbook

Howitworks…ThedatabaserepairtoolofMagentocomparesthestructureoftwodatabaseswitheachotherandwillfixthedifferences.

WecreatedanemptydatabasewhereweinstalledanewemptyMagentowiththeinstallscripts.Thisdatabasewasusedasthereferencedatabase.

Inthedatabaserepairtool,weenteredtheoriginaldatabaseasthecorrupteddatabase.

Thetoolwillcomparethestructureofthereferencedatabasewiththecorrupteddatabase.Whenthecomparisonsaredone,thetoolwillmakethestructureofthecorrupteddatabasethesameasthestructureofthereferencedatabase.

Whenthisisdone,allbrokenstructures(suchasmissingrelations,tables,columns,andmore)arefixedinthecorrupteddatabase.

Thecomparetoolonlyfixesstructuralissueswithyourdatabase.Ifyoumisssomeofyourdata,thisisnottherighttooltogetitback.

Page 292: Magento 2 Development Cookbook
Page 293: Magento 2 Development Cookbook

Chapter6.MagentoBackendInthischapter,wewillcover:

RegisteringabackendcontrollerExtendingthemenuAddinganACLAddingconfigurationparametersCreatingagridofadatabasetableWorkingwithbackendcomponentsAddingcustomerattributesWorkingwithsourcemodels

Page 294: Magento 2 Development Cookbook

IntroductionForastoreowner,thebackendistheinterfacetomanageeverythingintheirstore.Itisveryimportantforeverythingtobesecuredagainstvisitorswithbadintentions.ThebackendofastandardMagentoinstallationisextendibleinmanyways(likethefrontend),soeveryonecanextenditwithcustompages,configuration,roles,andsoon.

ByfollowingthebestpracticesofMagento,allsecurityrisksaremanagedbyMagentoliketheaccessforanonymoususers.

TherecipesinthischapterdescribethemostcommonwaysinwhichyoucanextendyourbackendusingthebestpracticesofMagento2.

Page 295: Magento 2 Development Cookbook
Page 296: Magento 2 Development Cookbook

RegisteringabackendcontrollerThefirstthingthatwewilllearnishowtoextendthebackendwithacustomcontrolleraction.Weneedacontrollerthatissecuredsothatonlylogged-inbackenduserscanseethecontentsofthispage.

AbackendcontrollerisrequiredwhenyouwanttoaddanextrapagetothebackendofMagento.Thisismostlythecasewhenyouareworkingwithacustomformoroverviewthatyouneedforyourmodule.

Page 297: Magento 2 Development Cookbook

GettingreadyFordevelopmentpurposes,itisrecommendedthatweremovethesecretkey(thehashintheURL)fromtheadminURLs.YoucanconfigurethisinStores|Configuration|Advanced|Admin|Security.Changetheconfigurationasshowninthefollowingscreenshot:

Page 298: Magento 2 Development Cookbook

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.

Page 299: Magento 2 Development Cookbook

4. Whenyouaddthefollowingcodeintheexecute()functionofthatclass,youwillseethatthebackendinterfaceistherewithanemptypage:

publicfunctionexecute()

{

$resultPage=$this->resultPageFactory->create();

return$resultPage;

}

Page 300: Magento 2 Development Cookbook

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>

Page 301: Magento 2 Development Cookbook
Page 302: Magento 2 Development Cookbook

ExtendingthemenuInthepreviousrecipe,weaddedanewpagetothebackend,butitisimportantthatyouareabletoeasilynavigatetoyourcustompages.NoteveryoneknowstheURL,andifthesecretkeysareenabledfortheadministratorURLs,itislikelyimpossibletobuildacorrectURLbecauseyouhavetoknowthekey.

Page 303: Magento 2 Development Cookbook

GettingreadyThisrecipebuildsfurtheronthepreviousone.Makesureyouhavethecodeofthepreviousrecipeinstalled.

Page 304: Magento 2 Development Cookbook

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:

Page 305: Magento 2 Development Cookbook

4. Tochangethepositionofthelinks,wecanplaywiththesortOrderattributeoftheconfiguration.

Page 306: Magento 2 Development Cookbook

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

Page 307: Magento 2 Development Cookbook
Page 308: Magento 2 Development Cookbook

AddinganACLInthepreviousrecipesofthischapter,wecreatedabackendcontrolleractionthatisaccessibleusingtheadminmenu.However,whenyouwanttoconfigureacustomadminrole,youcan’trestricttheaccesstothispageforaspecificrole.Inthisrecipe,wewillcreateanAccessControlList(ACL)forthebackendpageandcreatearolewithrestrictedpermissionssoanadminusercanonlyviewarestrictedsetofpages.

Page 309: Magento 2 Development Cookbook

GettingreadyEachadminuserisamemberofanadminrole.Theseroleshaveaccesspermissionsthatmanagetheaccesstocertainpagesinthebackend.

Inthisrecipe,wewilladdnewpermissionsforthepagewecreatedinthepreviousrecipesofthischapter.Makesureyouhaveinstalledthefilesofthepreviousrecipes.

Page 310: Magento 2 Development Cookbook

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:

Page 311: Magento 2 Development Cookbook

4. SubmitthisformtoaddanewrolewiththeHelloWorldindexcheckboxchecked.Let’ssaywecallthisroleTestHelloWorld.

5. CreateanewbackenduserinSystem|Permissions|AllUsers.Fillintherequiredfieldsandaddtheusertotherolethatwehavejustcreated,asshowninthefollowingscreenshot:

Page 312: Magento 2 Development Cookbook

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.

Page 313: Magento 2 Development Cookbook

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"

/>

Page 314: Magento 2 Development Cookbook
Page 315: Magento 2 Development Cookbook

AddingconfigurationparametersWhenyouwanttosavesomeconfigurationparametersforyourmodule,youcanusetheMagentoconfigurationtabletosaveyourconfigurationinit.YoucanfindalltheconfigurationformsunderStores|ConfigurationintheMagentobackend.Inthisrecipe,wewilladdanextrapagewithsomeconfigurationparameters.

Page 316: Magento 2 Development Cookbook

GettingreadyLikethepreviousrecipesinthischapter,wewillbuildfurtheronthemodulethatwehavecreatedinthepreviousrecipes.Makesureyouhavethemodulefilesinstalled.

Page 317: Magento 2 Development Cookbook

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:

Page 318: Magento 2 Development Cookbook

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:

Page 319: Magento 2 Development Cookbook

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.

Page 320: Magento 2 Development Cookbook

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)

Page 321: Magento 2 Development Cookbook

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

Page 322: Magento 2 Development Cookbook
Page 323: Magento 2 Development Cookbook

CreatingagridofadatabasetableInthepreviouschapter,wecreatedaMagentoentitythatwaslinkedtoadatabasetable.Inthisrecipe,wewillcreateabackendinterfacesothatthebackenduserscanseethedatafromthistableinthebackend.

Page 324: Magento 2 Development Cookbook

GettingreadyMakesureyouhavetherightfilesforthisrecipeinstalledinyourMagentoinstance.

WewillcreateanoverviewthatwillusethestandardMagentobackendgridwidget.Thiswidgetisusedforalotofgridsinthebackend,suchastheproducts,orders,CMSpages,andmore.

Page 325: Magento 2 Development Cookbook

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');

Page 326: Magento 2 Development Cookbook

$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

{

/**

Page 327: Magento 2 Development Cookbook

*@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>

Page 328: Magento 2 Development Cookbook

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

Page 329: Magento 2 Development Cookbook

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

Page 330: Magento 2 Development Cookbook

default:

$class='grid-severity-critical';

break;

}

return'<spanclass="'.$class.'"><span>'.$value.'</span>

</span>';

}

14. Reloadthepage,andyoushouldhaveanoutputlikethatshowninthefollowingscreenshot:

Page 331: Magento 2 Development Cookbook

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)

Page 332: Magento 2 Development Cookbook
Page 333: Magento 2 Development Cookbook

WorkingwithbackendcomponentsTheMagentobackendexistswithalotofreusablecomponents,suchasabutton,menus,forms,andmore.Whencreatingbackendextensions,youcanusethesecomponentstobuildyoucustompages.Inthisrecipe,wewillcreateapagewherewecanplaywiththecomponentsavailable.

Page 334: Magento 2 Development Cookbook

GettingreadyWewillextendtheexistingPackt_HelloWorldmodulewithanewpagewherewecanplaywiththebackendcomponents.

Page 335: Magento 2 Development Cookbook

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"

Page 336: Magento 2 Development Cookbook

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:

Page 337: Magento 2 Development Cookbook

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

Page 338: Magento 2 Development Cookbook

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.

Page 339: Magento 2 Development Cookbook

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.

Page 340: Magento 2 Development Cookbook
Page 341: Magento 2 Development Cookbook

AddingcustomerattributesInsomecases,itcouldbethatweneedextraattributesforacustomerlikewedidforproducts.BecauseacustomerisanEAVobject,itispossibletoaddattributestoit,butthereisnointerfaceforthatintheCommunityEditionofMagento.Whenwewanttodothat,weneedtoinstalltheattributesbycode,andthat’sthethingthatwewilldointhisrecipe.Wewilladdanewfieldloyaltynumbertothecustomer.

Page 342: Magento 2 Development Cookbook

GettingreadyToaddacustomerattribute,weneedtocreateaninstallationorupgradescript.Inthisrecipe,wewillcreateanewmodulethatwillinstalltheattributewiththeinstallationscriptsowedon’tneedtoinstallstarterfilesinMagento.

Page 343: Magento 2 Development Cookbook

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

Page 344: Magento 2 Development Cookbook

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

Page 345: Magento 2 Development Cookbook
Page 346: Magento 2 Development Cookbook

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.

Page 347: Magento 2 Development Cookbook
Page 348: Magento 2 Development Cookbook

WorkingwithsourcemodelsMagentoworkswithalotofdropdownfieldsthatyoucanselectintheformsoftheapplication.Wecanseedropdownsintheconfiguration,product,customer,andmanymorepages.

Magentohasasystemtosettheoptionsofthedropdownandmultiselectfields.Magentousesamodelthatreturnsthevaluesandlabelstorendertheoptionsofadropdownormultiselectfield.Thesemodelsarecalledsourcemodels.

Inthisrecipe,wewillseewhichsourcemodelsMagentousesandhowwecancreateacustomsourcemodelforacustomconfigurationfield.

Page 349: Magento 2 Development Cookbook

GettingreadyInthisrecipe,wewillextendthePackt_HelloWorldmodulethatwecreatedinthepreviousrecipes.Makesureyouhavetherightversioninstalledforthisrecipe.

Page 350: Magento 2 Development Cookbook

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,

Page 351: Magento 2 Development Cookbook

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

Page 352: Magento 2 Development Cookbook

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.

Page 353: Magento 2 Development Cookbook
Page 354: Magento 2 Development Cookbook

Chapter7.EventHandlersandCronjobsInthischapter,wewillcoverthefollowingtopics:

UnderstandingeventtypesCreatingyourowneventAddinganeventobserverIntroducingcronjobsCreatingandtestinganewcronjob

Page 355: Magento 2 Development Cookbook

IntroductionIntheMagentoapplication,therearealotofeventsthathappenwhenvisitorsarebrowsingthroughyourwebsite.Thevisitorcanaddsomethingtothecart,login,createanorder,anddoalotmore.

Magentohasaneventsystemthatfireseventswhensomeactionshappensinyourshop.Withaconfiguration,itispossibletoexecutesomecodewhenaneventoccurs.It’slikehookingintoaclickeventinJavaScript.

Theobserverdesignpatternisusedtoimplementtheeventhandlingsystem.Whenaneventistriggered,Magentolooksforeventobserversthathookintothateventandexecutetherightmethodthatisconfiguredforthateventhandler.

AnothersimilarsysteminMagentoisacronjob.Intheconfiguration,youcancreateacronjobthatwillbeexecutedonaspecifictimestamp.Whenitistherighttime,Magentowillexecutethecodethatisconfiguredforthatcronjob.

Inthischapter,wewillexplorethepossibilitiesofusingthesetwosystems(Eventhandlersandcronjobs).

Page 356: Magento 2 Development Cookbook
Page 357: Magento 2 Development Cookbook

UnderstandingeventtypesWorkingwitheventtypesisbetterthanoverwritingafunctionwithdependencyinjection.Whenanalyzingaprocess,itisgoodtothinkabouthowyoucansolveyourproblem.ItisbettertoexecuteextracodeinsteadofrewritingthestandardfunctionsofMagento.

Withevents,itispossibletoexecutecodewhensomethinghappens,butbeforewecandothat,wehavetoknowwhicheventsareavailable,whentheyaredispatched,andwhichparametersareavailable.

Page 358: Magento 2 Development Cookbook

GettingreadyInthisrecipe,wewilldebugtheeventsthatarefiredinMagento.EnsurethatyouhaveaccesstothecommandlinebecausewewilldebugusingtheMagentologfiles.

Page 359: Magento 2 Development Cookbook

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:

Page 360: Magento 2 Development Cookbook

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.

Page 361: Magento 2 Development Cookbook

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.

Page 362: Magento 2 Development Cookbook

SeealsoMagento2iscurrentlynewanddoesnothavealotofdocumentation,butyoucanfindmoreinformationaboutsomeoftheavailableeventsinMagento2athttp://cyrillschumacher.com/magento2-list-of-all-dispatched-events/.

Page 363: Magento 2 Development Cookbook
Page 364: Magento 2 Development Cookbook

CreatingyourowneventWhenwecreateourownevent,wehavetodispatchitwithacustomname.Inthisrecipe,youwilllearnhoweventsaredispatchedandwhatwecandowithparametersthataresentwiththeevent.

Page 365: Magento 2 Development Cookbook

GettingreadyWewillcreateaneventthatisfiredwhenavisitoropensthe/helloworld/index/eventpage.

ThecodeinthisrecipebuildsfurtheronthePackt_HelloWorldmodulethatwecreatedinChapter4,CreatingaModule,Chapter5,DatabasesandModulesandChapter6,MagentoBackend.Ensurethatyouhaveinstalledthestartfiles.

Page 366: Magento 2 Development Cookbook

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

Page 367: Magento 2 Development Cookbook

];

$this->_eventManager->dispatch('helloworld_register_visit',

$parameters);

return$resultPage;

}

NoteEnsurethattheproductIDandcategoryIDexistsinyourinstallation.Ifnot,youcanchooseanotherIDthatexistsinyourinstallation.

Page 368: Magento 2 Development Cookbook

Howitworks…Thedispatch()functionoftheeventManagermethodfiresaneventinMagento.Theeventmanageriscreatedintheconstructofthecontrollerclass(inthisexample,itisthecontrolleraction)andstoredinthe$_eventManagervariable.Thisisaninstanceofthe\Magento\Framework\Event\ManagerInterfaceclass.

Whenthisfunctionisfired,Magentowilllookintotheconfigurationtoseewhicheventlistenersarewatchingforthatevent.Iftherearematchinglisteners,Magentowillexecutethecodeoftheobserver.

Page 369: Magento 2 Development Cookbook
Page 370: Magento 2 Development Cookbook

AddinganeventobserverIfyoureadthepreviousrecipe,youknowhowtofireaneventusingthedispatch()function.Inthisrecipe,youwilllearnhowtoexecutesomecodewhenaneventisdispatchedandwhatwecandowithit.

Page 371: Magento 2 Development Cookbook

GettingreadyInthisrecipe,wewilladdtwoeventobservers.Thefirstonewillcatchtheeventthatwecreatedinthepreviousrecipe.Thesecondeventlistener(observer)willhookintotheaddtocartactionofaproduct.

Inthisrecipe,wewillworkfurtheronthePackt_HelloWorldmodulefromthepreviousrecipes.Ensurethatyouhavetherightcodefilesinstalledforthisrecipe.

Page 372: Magento 2 Development Cookbook

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

Page 373: Magento 2 Development Cookbook

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

Page 374: Magento 2 Development Cookbook

thrownew\Exception('Qtymustbeeven');

}

}

}

9. Cleanthecacheandtrytoaddsomethingtothecartwithanevenandoddquantity.Youwillseethatanerroroccurswhenyouaddanoddquantity.Whenyouaddanevenquantity.

Page 375: Magento 2 Development Cookbook

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.

Page 376: Magento 2 Development Cookbook
Page 377: Magento 2 Development Cookbook

IntroducingcronjobsCronjobs,orscheduledtasks,arebackgroundprocessesthatkeepyourMagentowebshoprunningbyautomatingsometasks.Someexamplesofcronjobsareasfollows:

SendingnewslettersRecalculatingcatalogpromotionrulesCleaningvisitorlogsSendingpriceandstockalerte-mailsUpdatingcurrencyrates

WhentheMagentocronisnotconfiguredcorrectly,youwillseethatsomefeaturesofyourMagentoshopwillnotworkasexpected.

Page 378: Magento 2 Development Cookbook

GettingreadyInthisrecipe,youwilllearnhowyoucanconfigurecronjobsontheserverandhowyoucanverifythattheyareworking.OpenyourSSHclientandchangetotheMagentofolder.

Page 379: Magento 2 Development Cookbook

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:

Page 380: Magento 2 Development Cookbook

4. Whenwewanttoconfigurethatthecroncommandwillbeexecutedeveryminute,wehavetousethecrontabfileoftheLinuxserver.First,switchtothewwwuserofyourproject.ForastandardApache2webserver,itiswww-data.Wecanchangetheuserwiththefollowingcommand:

sudosuwww-data

5. Thenextthingtodoistoopenthecrontabfile.Wecandothisbyrunningthecrontab-ecommand.Thiswillopenafilewherewehavetoputthecontent,asshowninthefollowingscreenshot:

6. Savethefileandyourcronjobwillrunaftereveryminute.

Page 381: Magento 2 Development Cookbook

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.

Page 382: Magento 2 Development Cookbook
Page 383: Magento 2 Development Cookbook

CreatingandtestinganewcronjobInMagento2,cronjobsaredefinedinthecrontab.xmlfileofeachmodule.LikeeveryconfigurationintheMagentomodules,theconfigurationofthecronjobsiseasytoextendincustommodules.Andthat’swhatwewilldointhisrecipe.Wewillcreateanewcronjobforourmodule.

Testingacronjobisabittricky.Youcanwaitwhilethecronwillisexecuted,butinthisrecipe,wewillseehowwecantriggeritfordevelopmentpurposes.

Page 384: Magento 2 Development Cookbook

GettingreadyTheworkflowtocreateanewcronjobismostlythesameasworkingwitheventobservers.Wehavetoconfigureanewcronjobthatwillstartafunctioninaconfiguredclass.

Forthecreationofanewcronjob,wewillusetheexistingPackt_HelloWorldmodule.Makesureyouhavethelatestversioninstalled.

Page 385: Magento 2 Development Cookbook

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'');

}

}

Page 386: Magento 2 Development Cookbook

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>

Page 387: Magento 2 Development Cookbook

</config>

10. Cleanthecacheandyourcronwillruninthedefaultgroup.Thejobwillbescheduledfor2:30a.m.

Page 388: Magento 2 Development Cookbook

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.

Page 389: Magento 2 Development Cookbook
Page 390: Magento 2 Development Cookbook

Chapter8.CreatingaShippingModuleInthischapter,wewillcover:

InitializingmoduleconfigurationsWritinganadaptermodelExtendingtheshippingmethodfeaturesAddingthemoduleinthefrontend

Page 391: Magento 2 Development Cookbook

IntroductionShippingtheorderedproductstothecustomersisoneofthekeypartsofthee-commerceflow.Inmostcases,astoreownerhasacontractwithashippinghandler,andeveryshippinghandlerhastheirownbusinessrules.

InMagento2,thefollowingshippinghandlershaveanextension:

DHLFedExUPSUSPS

Ifyourhandlerisnotonthislist,checkwhetheryourshippinghandlerhasaMagentomodule.Ifnot,youcanconfigureastandardshippingmethodoryoucancreateyourownshippingmethod,asyouwilllearntodointhischapter.

Page 392: Magento 2 Development Cookbook
Page 393: Magento 2 Development Cookbook

InitializingmoduleconfigurationsInChapter4,CreatingaModule,youlearnedhowtocreateacustommodule.Inthisrecipe,wewillcreateanewmodulewherewewilladdtherequiredsettingsforashippingmodule.

Inthelaterrecipesofthischapter,wewillextendthismodulewithmoreshippingfeatures.

Page 394: Magento 2 Development Cookbook

GettingreadyOpenyourIDEwiththeMagento2project.Wewillalsoneedthebackend,wherewewillchecksomeconfigurations.

Page 395: Magento 2 Development Cookbook

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

Page 396: Magento 2 Development Cookbook

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

Page 397: Magento 2 Development Cookbook

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

Page 398: Magento 2 Development Cookbook

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

Page 399: Magento 2 Development Cookbook

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

Page 400: Magento 2 Development Cookbook

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.

Page 401: Magento 2 Development Cookbook

SeealsoInthisrecipe,weusedthesystem.xmlfileofthemoduletocreatetheconfigurationvalues.MoreinformationaboutconfigurationvaluesisexplainedintheAddingconfigurationparametersrecipeofChapter6,MagentoBackend.

Page 402: Magento 2 Development Cookbook
Page 403: Magento 2 Development Cookbook

WritinganadaptermodelInthepreviousrecipe,weenabledanewmodulewiththesettingsforanewshippingmethod.Thiswasapreparationforthebusinesspart,whichwewilldointhisrecipe.

Wewilladdamodelwiththebusinesslogicfortheshippingmethod.ThemodeliscalledanadapterclassbecauseMagentorequiresanadapterclassforeachshippingmethod.

Theadapterclasswillbeusedforthefollowingthings:

MakingtheshippingmethodavailableCalculatingtheshippingcostsSettingthetitleinthefrontendoftheshippingmethods

Page 404: Magento 2 Development Cookbook

GettingreadyEnsurethatyouhaveinstalledthemodulethatwecreatedinthepreviousrecipebecauseweneedthisforcreatingtheadapterclass.

Page 405: Magento 2 Development Cookbook

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

Page 406: Magento 2 Development Cookbook

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

Page 407: Magento 2 Development Cookbook

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.

Page 408: Magento 2 Development Cookbook
Page 409: Magento 2 Development Cookbook

ExtendingtheshippingmethodfeaturesNowthatallthefilesareinstalled,wecanaddmorefeaturestotheshippingmethod.Inthisrecipe,wewilladdacountryconfigurationandenabletrackingcodesfortheshippingmethod.

Page 410: Magento 2 Development Cookbook

GettingreadySimilartoallrecipesinthischapter,wewillbuildfurtheronthemodulethatwecreatedinthepreviousrecipesofthischapter.Ensurethatyouhavetherightfilesinstalled.

Page 411: Magento 2 Development Cookbook

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:

Page 412: Magento 2 Development Cookbook
Page 413: Magento 2 Development Cookbook

Howitworks…Inthisrecipe,weextendedtheshippingmethodwithtwonewfeatures.ThefirstfeaturewastoaddthepossibilitytocreatetrackingcodesfortheShipmeshippingmethod.WeoverwrotetheisTrackingAvailable()function,whichreturnsfalsebydefault.Byoverwritingthisfunctionandreturningtrue,weenablethetrackingcodes.

Thesecondthingthatwedidwastoenablecountry-specificshipping.Weaddedtwofieldswithastandardnamingconvention.Usingthefollowingnamesforthetwofields,Magentorecognisestheconfigurationforcountries:

sallowspecific

specificcountry

Whenweenablethisconfigurationinthebackend,theshippingmethodisonlyavailablewhenthecountryoftheshippingaddressisoneoftheselectedcountriesintheShiptoSpecificCountriesconfigurationoftheshippingmethod.

Page 414: Magento 2 Development Cookbook
Page 415: Magento 2 Development Cookbook

AddingthemoduleinthefrontendInthepreviousrecipes,wecreatedconfigurationsforanewshippingmethod.Wehaveseenthatwecanconfigurethisinthebackend.Now,itistimetotesttheshippingmethodinthefrontend.Wewillcreateatestorderwiththeshippingmethodthatwehavecreatedinthischapter.

Page 416: Magento 2 Development Cookbook

GettingreadyWeneedtheshippingmodulethatwecreatedinthepreviousrecipes.Ensureyouhavetherightfilesinstalled.

Page 417: Magento 2 Development Cookbook

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

Page 418: Magento 2 Development Cookbook

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.

Page 419: Magento 2 Development Cookbook

15. WhenyouclickontheSubmitShipmentbutton,yourshipmentisprocessedandthestatusoftheorderwillchangetoComplete.

Page 420: Magento 2 Development Cookbook

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

Page 421: Magento 2 Development Cookbook
Page 422: Magento 2 Development Cookbook

Chapter9.CreatingaProductSliderWidgetInthischapter,wewillcoverthefollowingrecipes:

CreatinganemptymoduleCreatingawidgetconfigurationfileCreatingtheblockandtemplatefilesCreatingacustomconfigurationparameterFinalizingthetheming

Page 423: Magento 2 Development Cookbook

IntroductionTheMagentowidgetssystemisagraphicalinterfacewhereyoucanconfigureblocksinthefrontend.Foreverywidget,thereisaconfigurationpageavailablewhereyoucansettherequiredvaluesforthatwidget.

WithaMagentowidget,youcanconfigurethelayoutinstructionstoshowthewidgetatseveralplacesinthefrontend.

Inthischapter,wewillcreateanewmoduleinwhichwewillcreateourownwidget.Wewillcreateaproductsliderwiththeproductsofacategorythatwecanconfigureinthatwidget.

Whenwearedonewiththetechnicalpart(configurationpage,blockclass,templateinitialization),wecanfinishwithitsrepresentationinthefrontend.WewillcreateaproductlistthatwewillstylewithajQuerysliderscript.

Page 424: Magento 2 Development Cookbook
Page 425: Magento 2 Development Cookbook

CreatinganemptymoduleAswedidinthepreviouschapterandfullyexplainedinChapter4,CreatingaModule,wewillcreatetherequiredfilestocreateanemptymodulethatwewillextendwithwidgetconfigurationsinfurtherchapters.WewillstartwithanemptyMagentomodulethatwewillcreateinthisrecipe.Wewillcreatealltherequiredfilestoinitializeanewmodulethatcanbeusedforthecreationofawidget.

Page 426: Magento 2 Development Cookbook

GettingreadyWewillcreatenewmodulecalledPackt_ProductSlider.OpenyourIDEtoaddsomecodetoit.

Page 427: Magento 2 Development Cookbook

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

Page 428: Magento 2 Development Cookbook

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.

Page 429: Magento 2 Development Cookbook
Page 430: Magento 2 Development Cookbook

CreatingawidgetconfigurationfileInthisrecipe,wewillextendthefeaturesofthePackt_ProductSlidermodulewithawidgetconfigurationfile.Inthisconfigurationfile,wewilldeclareanewwidgetorfrontendapptype.

Foranewfrontendapp,weneedtoconfigurethefollowingthings:

Nameofthewidget(usedinthebackend)WidgetconfigurationparametersWidgetblocktypeWidgettemplates(the.phtmlfiles)

Page 431: Magento 2 Development Cookbook

GettingreadyWewillextendthemodulethatwecreatedinthepreviousrecipewithawidgetconfiguration.Ensurethatyouhavetherightfilesinstalled.

Page 432: Magento 2 Development Cookbook

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.

Page 433: Magento 2 Development Cookbook

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:

Page 434: Magento 2 Development Cookbook

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.

Page 435: Magento 2 Development Cookbook

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.

Page 436: Magento 2 Development Cookbook
Page 437: Magento 2 Development Cookbook

CreatingtheblockandtemplatefilesInthepreviousrecipe,youlearnedhowtoconfigureanextrawidgettypetoMagento.Now,itistimetodisplaythewidget.

Wewillextendthewidgetconfigurationwiththeoptiontoselecttwodifferenttemplatestorenderthewidgetinthefrontend.

ThesecondthingthatwewilldoiscreateacustomBlockclasswherewecanwriteourownspecificmethodsforthewidget.

Page 438: Magento 2 Development Cookbook

GettingreadyWewillworkfurtheronthewidgetmodulethatwecreatedinthepreviousrecipes.Ensurethatyouhavetherightcodeinstalled.

Page 439: Magento 2 Development Cookbook

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

Page 440: Magento 2 Development Cookbook

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:

Page 441: Magento 2 Development Cookbook

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:

Page 442: Magento 2 Development Cookbook

TipYoucanfindthecategoryIDwhilenavigatingtoacategoryinthebackend.NavigatetoProducts|Categoriesandselectthecategorythatyouwantinthetree.Whenselectingacategory,theIDappearsnearthename.

Page 443: Magento 2 Development Cookbook

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

Page 444: Magento 2 Development Cookbook
Page 445: Magento 2 Development Cookbook

CreatingacustomconfigurationparameterAtthispoint,wehaveaworkingwidget.ItshowsupinthefrontendandtherightproductsaredisplayedforthegivencategoryID.

ToconfigurethecategoryID,wehavetoknowtheIDofthecategory.Wehavetocopyitfromthecategorypageandpasteitinthetextbox.

Forbetterusability,wewillcreateacustomconfigurationfieldtoselectacategory.WewillcreateabuttonthatopensanoverlaywherewecanchoosetherightcategoryID.

Page 446: Magento 2 Development Cookbook

GettingreadyWewillcreateasimilarconfigurationfieldthatisusedfortheCatalogCategoryLinkwidgettypeinthebackend.Youcanlookatthisconfigurationwidget’sconfigurationtoseehowitworks.

Also,ensurethatyouhavetherightstartfilesinstalledbecausewewillbuildfurtheronthemodulethatwecreatedinthepreviousrecipes.

Page 447: Magento 2 Development Cookbook

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:

Page 448: Magento 2 Development Cookbook

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"

Page 449: Magento 2 Development Cookbook

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

Page 450: Magento 2 Development Cookbook

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

}

}

Page 451: Magento 2 Development Cookbook

';

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

'',

[

Page 452: Magento 2 Development Cookbook

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

Page 453: Magento 2 Development Cookbook

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.

Page 454: Magento 2 Development Cookbook

There’smore…Likewedidinthisrecipe,itispossibletocreateconfigurationfieldsthatuseacustomHTMLoutput.

Alotispossibletoshowaconfigurationparameter,butyouhavetoreturnavaluethatwillbesavedinthewidgetconfiguration.Thisisalwaysdonewithainputformelement,whichhasthenamingconvention<inputname="parameters[<parameter_name>]">.

Replacethe<parameter_name>tagwiththenameofyourcustomconfigurationparameterandthevaluesofthiselementwillbehandledlikealltheotherconfigurationparametersofthatwidget.

Page 455: Magento 2 Development Cookbook
Page 456: Magento 2 Development Cookbook

FinalizingthethemingThefrontendrepresentationofthewidgetthatwejustcreatedisnotsomethingthatinvitespeopletobuysomeproducts.Itisjustalistoftheproductnamesforagivencategory.

Thelaststepofthischapteristofinalizethethemingofthewidget.WewillcreateanHTMLoutputthatshowsanimage,name,andpriceofthegivenproducts.

WithajQueryplugin,wewillconverttheHTMLoutputtoaslider,sowecanscrollthroughtheproducts.

Page 457: Magento 2 Development Cookbook

GettingreadyOntheInternet,therearealotofgoodJavaScriptcarouselplugins.Inthisrecipe,wewillusethefollowing:

http://kenwheeler.github.io/slick/

Forthecode,wewillbuildfurtheronthethingsthatarecreatedinthepreviousrecipesofthischapter.Ensurethatyouhavetherightfilesinstalled.

Page 458: Magento 2 Development Cookbook

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

Page 459: Magento 2 Development Cookbook

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,

Page 460: Magento 2 Development Cookbook

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:

Page 461: Magento 2 Development Cookbook

13. TheJavaScriptconfigurationcontainssomebreakpointsforresponsivedevices.Whenyouhaveasmallerscreen,youwillseesomethingasshowninthefollowingscreenshot:

14. Tofinishthischapter,wecanmakethetitleofthiswidgetconfigurable.Inthewidgetconfiguration,thereisatitlefieldthatwecanuseforthis.Whenwechangethefollowinghighlightedcodeofthelist.phtmlfiletothefollowing,thetitleofthatfieldisusedinthefrontend.

...

<divclass="block-title">

<h2><?phpecho$block->getTitle()?></h2>

</div>

...

Page 462: Magento 2 Development Cookbook

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.

Page 463: Magento 2 Development Cookbook
Page 464: Magento 2 Development Cookbook

Chapter10.PerformanceOptimizationInthischapter,wewillcoverthefollowingrecipes:

BenchmarkingawebsiteOptimizingthefrontendofthewebsiteOptimizingthedatabaseandMySQLconfigurationsOptimizingtheApachewebserverFindingperformanceleaksinMagentoConfiguringOPcache,Redis,andMemcachedOptimizingthePHPconfigurations

Page 465: Magento 2 Development Cookbook

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

Page 466: Magento 2 Development Cookbook
Page 467: Magento 2 Development Cookbook

BenchmarkingawebsiteWhenyouhaveahigh-trafficsite,youwouldprobablywanttoknowthelimitsofthewebsite.WhatwillbethecapacityofmywebsitewhenIlaunchamarketingcampaign?Whatisslowingdownmysite?Whichoptimizationshavethemosteffect?

Toknowthelimitsofawebsite,wehavetousebenchmarkingtools.Withabenchmarkingtool,wewillcreatealoadonthewebsiteandlogtheresponsetimetoafile.Byincreasingordecreasingsomevalues,wecandeterminetheloadthatisthelimitofawebsite.

Inthisrecipe,wewillbenchmarktheMagentositebydoingsometestswithApacheBenchandSiege.Withthesetools,wecanmeasuretheperformanceofdifferentpages.

Page 468: Magento 2 Development Cookbook

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

Page 469: Magento 2 Development Cookbook

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:

Page 470: Magento 2 Development Cookbook

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

Page 471: Magento 2 Development Cookbook

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.

Page 472: Magento 2 Development Cookbook

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.

Page 473: Magento 2 Development Cookbook

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.

Page 474: Magento 2 Development Cookbook
Page 475: Magento 2 Development Cookbook

OptimizingthefrontendofthewebsiteWhenyoulookattheperformanceofawebsite,therearemanypointsthatyoucanoptimize.Betweenthestartofarequestandtherenderedpageinthebrowser,alotofoperationsarecarriedout.

Inthisrecipe,youwilllearnhowtospotbottlenecksandoptimizesomethingsthatincreasetheperformance.

Page 476: Magento 2 Development Cookbook

GettingreadyForthisrecipe,wewillusetwobrowserpluginswherewecanmeasuresomemetrics:

app.telemetry:Withthissimplebrowserplugin,wecanmonitortheloadtimeofeachpage.Wecandownloadthepluginfromthefollowingwebsite:

http://www.apptelemetry.com/en/page-speed-monitor.html

Thiswilladdaniconinthebrowserbarwherewecanreadtheresponsetime.

YSlow:Withthisbrowserplugin,canwegenerateareportofthingsthatwecanoptimizeinthewebsite.YoucaninstalltheYSlowplugintoChromeorFirefoxfromthefollowingURL:

http://yslow.org/

Page 477: Magento 2 Development Cookbook

Howitworks…ThefollowingstepsdescribehowwecanfindpitfallsinthefrontendofMagentotodecreasethepageloadtime:

1. Whenyouhavetheapp.telemetryplugininstalled,youwillseeanicononthebrowserbar,asshowninthefollowingscreenshot:

2. Toanalyzeapageload,youhavetoreloadapage,andwhenthepageisloaded,youwillseethetimetakenforloadinginthebrowserbaricon.Whenyouclickontheicon,youcanseemoredetails,asshowninthefollowingscreenshot:

3. Whenwedothesameonaremotewebsite,wewillseedifferentresults.ThetimetakenbytheTCPandDNSstepswillbelonger.

4. WewillcontinuebygeneratingaperformanceanalysiswithYSlow.YSlowisabrowserpluginthatneedstobeinstalledinyourbrowser.Whenyouhaveinstalledthebrowserplugin,youcanclickontheiconinthebrowserbar.

Page 478: Magento 2 Development Cookbook

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

Page 479: Magento 2 Development Cookbook

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:

Page 480: Magento 2 Development Cookbook

12. Whenyourunthetestagain,youwillseethattheoverallscoreisraisedagain.Therearetwopointsthatneedmoreattention:UseaContentDeliveryNetworkandUsecookie-freedomains.YoucanuseanexistingCDNprovidertohostyourstaticfiles,butyoucanalsocreateanotherdomainsuchasstatic.magento2.localthatalsopointstotheMagentoroot.Inthebackend,youcanconfigureMagentotousethisdomainforstaticcontent.YoucanconfigurethisbynavigatingtoStores|Configuration|General|Web.InthebaseURLssection,youcanconfigurethevaluesasshowninthefollowingscreenshot:

Page 481: Magento 2 Development Cookbook
Page 482: Magento 2 Development Cookbook

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

Page 483: Magento 2 Development Cookbook

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.

Page 484: Magento 2 Development Cookbook

There’smore…Ifyougotothesitegtmetrix.com,youcanenteraURLthatyouwanttoanalyze.ThistoolwilldoaPageSpeedandYSlowtestonthegivenURL.

Becausethisisanonlinetool,youhavetoensurethatyoursiteisaccessiblebytheGTmetrixserver.

Page 485: Magento 2 Development Cookbook
Page 486: Magento 2 Development Cookbook

OptimizingthedatabaseandMySQLconfigurationsTheMagentoapplicationsuseadatabasetostoreallthedata,includingproducts,customers,orders,andmore.ThedatabaseisthecentralstorageofallthedatathatisavailableintheMagentoinstance.ScalingMagentotomultiplefrontendserversisnotthathardbutscalingthedatabaseismuchharderbecausethedataissomethingthatneedstobeinsync.

Inthisrecipe,wewillseehowwecanoptimizethedatabaseandtheMySQLserver.

Page 487: Magento 2 Development Cookbook

GettingreadyEnsurethatyouhaveaccesstoadatabaseclientwhereyoucandosomequeriestoyourdatabase.Inthisrecipe,wewillusephpMyAdmin.

Page 488: Magento 2 Development Cookbook

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

Page 489: Magento 2 Development Cookbook

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

Page 490: Magento 2 Development Cookbook

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.

Page 491: Magento 2 Development Cookbook
Page 492: Magento 2 Development Cookbook

OptimizingtheApachewebserverMagentocanberunonApacheorNginx.Configurationfilesforbothsystemsareavailableinthecodebase(.htaccessandnginx.conf.sampleintherootfolder).

Theperformanceofthewebserverdependsonwhathardwaretheserverisrunning.Networkcard,RAM,disk,OS,andCPUarethemostimportanthardwarecomponentsthatyouhavetothinkaboutwhenchoosingaserver.

Page 493: Magento 2 Development Cookbook

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

Page 494: Magento 2 Development Cookbook

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.

Page 495: Magento 2 Development Cookbook

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

Page 496: Magento 2 Development Cookbook
Page 497: Magento 2 Development Cookbook

FindingperformanceleaksinMagentoWhenoptimizingawebsite,wecandolotsofthingstotheserverenvironmentbuttheapplicationthatrunsonitcanalwaysbefaster.

WhenyouhaveaMagentostoreandonekindofpageissignificantlyslowerthanotherpages,itcouldpossiblebethatthereisaperformanceissueonthatpage.

Tofindperformanceissues,wecanusetheMagentoProfiler.WiththeMagentoProfiler,wecanseehowmuchtimeeveryprocesstakestorenderapage.

Page 498: Magento 2 Development Cookbook

GettingreadyToenabletheprofiler,wehavetomodifytheapacheenvironmentvariables.WecandothisintheVirtualHostconfigurationorinthe.htaccessfile.Ensurethatyouhaveaccesstooneofthesefiles.

Page 499: Magento 2 Development Cookbook

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,

Page 500: Magento 2 Development Cookbook

weseethattheLAYOUTpartistheslowestofthewholepage.

Page 501: Magento 2 Development Cookbook

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.

Page 502: Magento 2 Development Cookbook
Page 503: Magento 2 Development Cookbook

ConfiguringOPcache,Redis,andMemcachedThedefaultcachingmechanismofMagentoisbasedonfilecaching.Allthecachefilesarewrittentothevar/cachefolder.Forasimplewebshop,thisisenough,butifyouarescalingyourshopwithmanyproductsandhightraffic,youwillneedtousesomeextracachingsystems.

RedisisareplacementofthestandardfilecachingofMagento.Thissystemworksfasterwhenyouhavealotofcachefiles.

MemcachedissimilartoRedis.Itisasysteminwhichyoucancacheobjects.

ZendOPcacheisanopcodecachingsystem.Whenthesecachingtechniquesareenabled,thePHPcodewillbecachedtothesesystems.

Page 504: Magento 2 Development Cookbook

GettingreadyWhenwewanttoconfigurethecachingtoolsZendOPcache,Memcached,andRedis,wehavetoinstallthemonourserver.Toinstallthese,runthecommandsexplainedinthefollowingtopics:

ZendOPcacheThispackageisnormallystandardinstalledwithPHP5.5.Whenyouexecuteaphpinfo()methodorrunthephp-i|grepopcachecommand,youcansearchfortheopcachesettings.

MemcachedInstallMemcachedwiththesudoapt-getinstallphp5-memcached.command.

RedisInstallRediswiththesudoapt-getinstallredis-servercommand.

Page 505: Magento 2 Development Cookbook

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

),

),

Page 506: Magento 2 Development Cookbook

),

),

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

Page 507: Magento 2 Development Cookbook

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.

Page 508: Magento 2 Development Cookbook
Page 509: Magento 2 Development Cookbook

OptimizingthePHPconfigurationsInthepreviousrecipe,weexplainedhowsomecachingsystemsofPHPwork.SincePHP5.5,wehaveZendOPcachethatisusedastheopcodecache.

Inthisrecipe,wewilltunesomePHPsettingstooptimizethisforMagento.

Page 510: Magento 2 Development Cookbook

GettingreadyWewillmakesomechangesinthephp.inifile,soensurethatyouhaveaccesstoit.

Page 511: Magento 2 Development Cookbook

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

Page 512: Magento 2 Development Cookbook

Howitworks…Thevaluesinthephp.iniconfigurationdependmostlyontheapplicationthatyouarerunningandtheloadthatyouexpectonyoursystem.Ifyourapplicationhassomeprocessesthatwillrunforalongtime(thisispossiblewithsomere-indexingprocesseswithlargeamountsofproducts),itisrequiredtoincreasethevaluesofmax_execution_timeandmax_input_time.Thesamegoesforthememory_limitparameterwheretherecommendedvalueis768MB.

DisablingXdebugwillgiveabetterperformancebecausenodebugdatawillbeprocessed.

Disablingtheerrorreportingonaproductionsystemisrecommendedforwarningsandnoticesbutcriticalerrorsneedstobereportedbecauseyouneedthisinformationwhenyouwanttosolveapossiblebug.

Page 513: Magento 2 Development Cookbook
Page 514: Magento 2 Development Cookbook

Chapter11.DebuggingandUnitTestingInthischapter,wewillcoverthefollowingrecipes:

LoggingintoMagento2GettingstartedwithXdebugRunningautomatedtestsfromMagentoCreatingaMagentotestcase

Page 515: Magento 2 Development Cookbook

IntroductionDebuggingawebsiteinanefficientwayisoneofthemostimportantjobsofPHPdevelopers.Thesedays,awebsiteisalotmorethanafewsimpleHTMLpages.InaMagentostore,youhavealotofcomplexbusinesslogicthatisusedintheflowofane-commercetransaction.

DebugginginPHPisnotoutoftheboxlikeinotherprogramminglanguages,suchas.NETandJava.TherearemanywaystoconfigureaPHPdebugger(suchasXdebug).Withagoodcodeeditoranddebugger,debugginginMagentoismucheasier.

Anotherpartofdebuggingandcodetestingareautomatedtests.Automatedtests,orUnittests,aredevelopedtotesttheoutputoffunctionsforagiveninput.Whensomecodeischanged,youcanrunthetestsandareportwillbegeneratedaboutthefailedandpassedtests.

Page 516: Magento 2 Development Cookbook
Page 517: Magento 2 Development Cookbook

LoggingintoMagento2ThestandarddebuggingtechniquesinPHPareecho$variable,die($variable)andvar_dump($variable).Thesesimpledebuggingtricksdon’talwayswork(whenyouareworkingwithAJAXandJSON)whenitisprintedinahiddenHTMLsection.

IfyouwanttodoasimpledebuggingtrickwithoutchangingtheHTMLoutputofapage,youcanusetheMagentologging.Thiswillwritethedebuggedresultstoalogfile.

Page 518: Magento 2 Development Cookbook

GettingreadyWewillprintsomedatatotheMagentologfiles.Toeasilyviewthecontentofthesefiles,weneedcommandlineaccess.Also,openyourIDEbecausewewilladdsomeloggingstatementsintheMagentocode.

Page 519: Magento 2 Development Cookbook

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.

Page 520: Magento 2 Development Cookbook

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.

Page 521: Magento 2 Development Cookbook
Page 522: Magento 2 Development Cookbook

GettingstartedwithXdebugWitharealdebugger,youcanpausetheexecutionofthescript.Itallowsyoutohavealookatthevariablesandvaluesthattheyhaveatthatpoint.Inadebugger,youcanalsochangevalues,skipstatements,andadomuchmore.

InPHP,thereistheXdebugextensionthatenablesyoutouseadebuggerincombinationwithanIDE.Inthisrecipe,wewillseehowtoinstallXdebugandintegrateitwiththeIDENetBeans.

Page 523: Magento 2 Development Cookbook

GettingreadyInthisrecipe,wewillstartanXdebugsessionwiththeNetBeansIDE.OpenNetBeansandsettheMagentoProjectasMainProject.EnsurethatalltheURLsareconfiguredcorrectlyinthePropertysettingsoftheproject.

ToinstallXdebug,youhavetoensurethatthephp5-devandphp-pearpackagesareinstalledonyourserver.Ifnot,youcaninstallthemusingthefollowingcommands:

sudoapt-getinstallphp5-dev

sudoapt-getinstallphp-pear

Page 524: Magento 2 Development Cookbook

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

Page 525: Magento 2 Development Cookbook

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

Page 526: Magento 2 Development Cookbook

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:

Page 527: Magento 2 Development Cookbook

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:

Page 528: Magento 2 Development Cookbook

17. Whenyoucontinuethedebuggeratthebreakpoint,youwillseethatthepagewillbeloaded.

18. ThedebugsessionsstaysaliveuntilyouhitthestopbuttoninNetBeans.Whenyoubrowsetootherpagesonyourwebsite,thedebuggerwillcontinueaslongasthesessionisalive.

19. Tostopthedebugsession,clickontheStopbuttoninNetBeans.

Page 529: Magento 2 Development Cookbook

Howitworks…WeneedtoinstallXdebugontheserverwherewewanttodebugasiteon.Inthisrecipe,itwillbetheserveronwhichourMagentoinstancewillrun.

TheinstallationoftheXdebugextensionisdonebyPEAR.PEARisanapplicationrepositoryforthePHPplugins.WithPEAR,wedownloadedandinstalledthexdebuglibrary.

WhenXdebugwasinstalledontheserver,weconfiguredthephp.inifiletousethexdebuglibrary.WeaddedsomesettingstomaketheXdebugconfigurationcompatiblewithNetBeans.

TipWhenusingXdebugonaremoteserver,ensurethatyoucanconnecttotheservertroughport9000.ThisismostlydisabledonthefirewalloftheserverandyourlocalPC.

Whentheserverwascorrectlyconfigured,wecheckedtheconfigurationsinNetBeansandwestartedthedebugsession.Whenthissessionwasstarted,wewereabletodebugtheMagentoapplicationlikeadebuggerdoesit.

Thedebuggerenablesustousethefollowingadvanceddebuggerfeatures,amongothers:

SettingbreakpointsExecutingthecodestatementbystatementBrowsingandchangingvaluesofvariables

Page 530: Magento 2 Development Cookbook
Page 531: Magento 2 Development Cookbook

RunningautomatedtestsfromMagentoWhenyoubuildsomefunctionality,youhavetotestthatthefunctionalityworkslikeyouwouldexpectit.Testingisusuallydoneattheendofyourprojectandthiscanbeautomated.

Withaunittest,wecanspecifywhataspecificpartofcodeneedstodo.Whatwillbetheinput,howwillitbeprocessed,whatistheoutput—theseareallthethingsthatyoucanspecifyinaunittest.

AnewadditiontoMagento2isthatunittestsareautomaticallyincludedinthecore.WhenyouwanttocontributetotheMagentocorewithGitHub,itisrequiredthatyourchangespassthroughtheunitandintegrationtests.

Page 532: Magento 2 Development Cookbook

GettingreadyForrunningtheunittests,weneedthecommand-linetoolofMagento.Ensurethatyouhaveaccesstoit.

Page 533: Magento 2 Development Cookbook

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:

Page 534: Magento 2 Development Cookbook

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

Page 535: Magento 2 Development Cookbook

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:

Page 536: Magento 2 Development Cookbook

12. Whenwelookinthetest-reportsfolder,weseethatthefollowinglogfilesaregenerated:

testdox.html

testdox.txt

Page 537: Magento 2 Development Cookbook

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.

Page 538: Magento 2 Development Cookbook
Page 539: Magento 2 Development Cookbook

CreatingaMagentotestcaseAsthisisthelastchapterofthisbook,wewillwriteatestthatwecanexecutewiththeMagentoTestingFramework.ThisframeworkusesPHPUnittoexecutethetests.

ByfollowingthepatternoftheMagentotests(Unittests,Integrationtests,andmore),thetestswillautomaticallyexecutewhenthedev:tests:runconsolecommandwillbeexecuted.

Page 540: Magento 2 Development Cookbook

GettingreadyInthisrecipe,wewillcreateaunittestforthePackt_HelloWorldmodulethatwecreatedinChapters4,CreatingaModule,Chapter5,DatabasesandModules,Chapter6,MagentoBackend,andChapter7,EventHandlersandCronjobs.

Ifyoudon’thavethecompletecode,youcaninstallthestarterfilesforthisrecipe.

Page 541: Magento 2 Development Cookbook

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

Page 542: Magento 2 Development Cookbook

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;

}

Page 543: Magento 2 Development Cookbook

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

Page 544: Magento 2 Development Cookbook

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.

Page 545: Magento 2 Development Cookbook

There’smore…Inthisrecipe,weusedtheassertContains()andassertNull()methodstotesttheoutputofthedecorateStatus()method.However,therearemanymoreassertionmethodsthatyoucanusewithPHPUnit.Forexample,amethodthatcomparesanumber,amethodthatcomparesthetypeofobject,andmanymore.

AfulllistofassertionmethodscanbefoundatthefollowingURL,whichreferstotheoriginaldocumentationofPHPUnit:

https://phpunit.de/manual/current/en/appendixes.assertions.html

Page 546: Magento 2 Development Cookbook

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…

Page 547: Magento 2 Development Cookbook

Bbackendcomponents

workingwith/Workingwithbackendcomponents,Howtodoit…,Howitworks…

backendcontrollerregistering/Registeringabackendcontroller,Gettingready,Howtodoit…,Howitworks…

Blankthemeabout/ExploringthedefaultMagento2themes

blockfilecreating/Creatingtheblockandtemplatefiles,Howtodoit…,Howitworks…

bundleproductabout/Abundleproduct

Page 548: Magento 2 Development Cookbook

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…

Page 549: Magento 2 Development Cookbook

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

Page 550: Magento 2 Development Cookbook

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…

Page 551: Magento 2 Development Cookbook

FFacebook

URL/Gettingreadyfallbackmechanism

about/Howitworks…filepermissions

URL/Howtodoit…FullPageCaching

about/Howtodoit…

Page 552: Magento 2 Development Cookbook

GGitHub

about/RunningautomatedtestsfromMagentoGooglePlus

URL/Gettingreadygrid

creating,ofdatabasetable/Creatingagridofadatabasetable,Howtodoit…,Howitworks…

groupedproductabout/Agroupedproduct

Gruntconfiguring/There’smore…URL/There’smore…

GTmetrixserverabout/There’smore…

Page 553: Magento 2 Development Cookbook

HHTMLobject

embedding/EmbeddinganHTMLobject,Howtodoit,HowitworksHTMLoutput

customizing/CustomizingtheHTMLoutput,Howtodoit…,Howitworks…

Page 554: Magento 2 Development Cookbook

IIDENetBeans

about/GettingstartedwithXdebuginstallscript

creating/Creatinganinstallandupgradescript,Howtodoit…,Howitworks…

IntegratedDevelopmentEnvironment(IDE)about/UsinganIDEusing/UsinganIDE,Howtodoit…

interceptoradding/Addinganinterceptor,Howtodoit…,Howitworks…

Page 555: Magento 2 Development Cookbook

JjQuerysliderscript

about/Introduction

Page 556: Magento 2 Development Cookbook

Llayoutupdates

adding/Addinglayoutupdates,Howtodoit…,Howitworks…LESS

workingwith/WorkingwithLESS,Howtodoit…,Howitworks…,There’smore…

Lumathemeabout/ExploringthedefaultMagento2themes

Page 557: Magento 2 Development Cookbook

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

Page 558: Magento 2 Development Cookbook

creating/Creatingthemodulefiles,Howtodoit…,Howitworks…Multi-ProcessingModules(MPM)

about/Howtodoit…MyISAMtables

about/Howtodoit…MySQL

configurations,optimizing/OptimizingthedatabaseandMySQLconfigurations,Howtodoit…,Howitworks…

Page 559: Magento 2 Development Cookbook

NNetBeans

about/UsinganIDEURL/Gettingready

Nginxabout/OptimizingtheApachewebserver

Page 560: Magento 2 Development Cookbook

OOPcache

configuring/ConfiguringOPcache,Redis,andMemcached,Howtodoit…,Howitworks…

Page 561: Magento 2 Development Cookbook

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…

Page 562: Magento 2 Development Cookbook

simpleproduct/Asimpleproductconfigurableproduct/Aconfigurableproductbundleproduct/Abundleproductgroupedproduct/Agroupedproductvirtualproduct/Avirtualproductdownloadableproduct/Adownloadableproduct

Page 563: Magento 2 Development Cookbook

RRedis

configuring/ConfiguringOPcache,Redis,andMemcached,Howtodoit…,Howitworks…about/ConfiguringOPcache,Redis,andMemcached

RequireJSlibraryabout/Howtodoit…

routingabout/Howitworks

Page 564: Magento 2 Development Cookbook

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…

Page 565: Magento 2 Development Cookbook

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

Page 566: Magento 2 Development Cookbook

Uupgradescript

creating/Creatinganinstallandupgradescript,Howtodoit…,Howitworks…

URLmodifying,ofproductpage/ChangingtheURLofaproductpage,Howitworks,There’smore

Page 567: Magento 2 Development Cookbook

Vvirtualproduct

about/Avirtualproduct

Page 568: Magento 2 Development Cookbook

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…

Page 569: Magento 2 Development Cookbook

XXdebug

disabling/Howitworks…about/GettingstartedwithXdebuginstalling/Howtodoit…,Howitworks…

XMLStyleDefinition(XSD)filesabout/Howtodoit…

Page 570: Magento 2 Development Cookbook

YYSlow

about/Gettingready,There’smore…URL/Gettingready

Page 571: Magento 2 Development Cookbook

ZZendOPcache

about/ConfiguringOPcache,Redis,andMemcached