magento 2 development cookbook

Post on 08-Jul-2016

808 Views

Category:

Documents

339 Downloads

Preview:

Click to see full reader

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

TRANSCRIPT

Magento2DevelopmentCookbook

TableofContents

Magento2DevelopmentCookbook

Credits

AbouttheAuthor

AbouttheReviewers

www.PacktPub.com

Supportfiles,eBooks,discountoffers,andmore

Whysubscribe?

FreeaccessforPacktaccountholders

Preface

Whatthisbookcovers

Whatyouneedforthisbook

Whothisbookisfor

Sections

Gettingready

Howtodoit…

Howitworks…

There’smore…

Seealso

Conventions

Readerfeedback

Customersupport

Downloadingtheexamplecode

Downloadingthecolorimagesofthisbook

Errata

Piracy

Questions

1.UpgradingfromMagento1

Introduction

CreatingaMagento1websitewithsampledata

Gettingready

Howtodoit…

Howitworks…

CreatingaMagento2website

Gettingready

Howtodoit…

Howitworks…

There’smore…

PreparinganupgradefromMagento1

Gettingready

Howtodoit…

Howitworks…

Upgradingthedatabase

Gettingready

Howtodoit…

Howitworks…

There’smore…

Seealso

UsinganIDE

Gettingready

Howtodoit…

There’smore…

WritingcleancodewithPHPMDandPHPCS

Gettingready

Howtodoit…

Howitworks…

There’smore…

2.WorkingwithProducts

Introduction

Configuringthecatalogdefaults

Gettingready

Howtodoit

Howitworks

Workingwithattributesets

Gettingready

Howtodoit

Howitworks

Workingwithproducttypes

Gettingready

Howtodoit

Howitworks…

There’smore…

Asimpleproduct

Aconfigurableproduct

Abundleproduct

Agroupedproduct

Avirtualproduct

Adownloadableproduct

Addingsocialmediabuttons

Gettingready

Howtodoit

Howitworks

EmbeddinganHTMLobject

Gettingready

Howtodoit

Howitworks

ChangingtheURLofaproductpage

Gettingready

Howtodoit

Howitworks

There’smore

3.Theming

Introduction

ExploringthedefaultMagento2themes

Gettingready

Howtodoit…

Howitworks…

CreatingaMagento2theme

Gettingready

Howtodoit…

Howitworks…

There’smore…

CustomizingtheHTMLoutput

Gettingready

Howtodoit…

Howitworks…

Addingextrafilestothetheme

Gettingready

Howtodoit…

Howitworks…

There’smore…

WorkingwithLESS

Gettingready

Howtodoit…

Howitworks…

There’smore…

Changingapagetitle

Howtodoit…

Howitworks…

Workingwithtranslations

Gettingready

Howtodoit…

Howitworks…

Addingwidgetstothelayout

Gettingready

Howtodoit…

Howitworks…

Customizingemailtemplates

Gettingready

Howtodoit…

Howitworks…

4.CreatingaModule

Introduction

Creatingthemodulefiles

Gettingready

Howtodoit…

Howitworks…

Creatingacontroller

Gettingready

Howtodoit…

Howitworks…

There’smore…

Addinglayoutupdates

Gettingready

Howtodoit…

Howitworks…

Addingatranslationfile

Gettingready

Howtodoit…

Howitworks…

Addingablockofnewproducts

Gettingready

Howtodoit…

Howitworks…

Addinganinterceptor

Gettingready

Howtodoit…

Howitworks…

Seealso

Addingaconsolecommand

Gettingready

Howtodoit…

Howitworks…

Seealso…

5.DatabasesandModules

Introduction

Creatinganinstallandupgradescript

Gettingready

Howtodoit…

Howitworks…

Creatingaflattablewithmodels

Gettingready

Howtodoit…

Howitworks…

WorkingwithMagentocollections

Gettingready

Howtodoit…

Howitworks…

Programmaticallyaddingproductattributes

Gettingready

Howtodoit…

Howitworks…

Repairingthedatabase

Gettingready

Howtodoit…

Howitworks…

6.MagentoBackend

Introduction

Registeringabackendcontroller

Gettingready

Howtodoit…

Howitworks…

Extendingthemenu

Gettingready

Howtodoit…

Howitworks…

AddinganACL

Gettingready

Howtodoit…

Howitworks…

Addingconfigurationparameters

Gettingready

Howtodoit…

Howitworks…

Creatingagridofadatabasetable

Gettingready

Howtodoit…

Howitworks…

Workingwithbackendcomponents

Gettingready

Howtodoit…

Howitworks…

Addingcustomerattributes

Gettingready

Howtodoit…

Howitworks…

Workingwithsourcemodels

Gettingready

Howtodoit…

Howitworks…

7.EventHandlersandCronjobs

Introduction

Understandingeventtypes

Gettingready

Howtodoit…

Howitworks…

Seealso

Creatingyourownevent

Gettingready

Howtodoit…

Howitworks…

Addinganeventobserver

Gettingready

Howtodoit…

Howitworks…

Introducingcronjobs

Gettingready

Howtodoit…

Howitworks…

Creatingandtestinganewcronjob

Gettingready

Howtodoit…

Howitworks…

8.CreatingaShippingModule

Introduction

Initializingmoduleconfigurations

Gettingready

Howtodoit…

Howitworks…

Seealso

Writinganadaptermodel

Gettingready

Howtodoit…

Howitworks…

Extendingtheshippingmethodfeatures

Gettingready

Howtodoit…

Howitworks…

Addingthemoduleinthefrontend

Gettingready

Howtodoit…

Howitworks…

9.CreatingaProductSliderWidget

Introduction

Creatinganemptymodule

Gettingready

Howtodoit…

Howitworks…

Creatingawidgetconfigurationfile

Gettingready

Howtodoit…

Howitworks…

Creatingtheblockandtemplatefiles

Gettingready

Howtodoit…

Howitworks…

Creatingacustomconfigurationparameter

Gettingready

Howtodoit…

Howitworks…

There’smore…

Finalizingthetheming

Gettingready

Howtodoit…

Howitworks…

10.PerformanceOptimization

Introduction

Benchmarkingawebsite

Gettingready

Howtodoit…

Howitworks…

Optimizingthefrontendofthewebsite

Gettingready

Howitworks…

Howitworks…

There’smore…

OptimizingthedatabaseandMySQLconfigurations

Gettingready

Howtodoit…

Howitworks…

OptimizingtheApachewebserver

Howtodoit…

Howitworks…

FindingperformanceleaksinMagento

Gettingready

Howtodoit…

Howitworks…

ConfiguringOPcache,Redis,andMemcached

Gettingready

ZendOPcache

Memcached

Redis

Howtodoit…

Howitworks…

OptimizingthePHPconfigurations

Gettingready

Howtodoit…

Howitworks…

11.DebuggingandUnitTesting

Introduction

LoggingintoMagento2

Gettingready

Howtodoit…

Howitworks…

GettingstartedwithXdebug

Gettingready

Howtodoit…

Howitworks…

RunningautomatedtestsfromMagento

Gettingready

Howtodoit…

Howitworks…

CreatingaMagentotestcase

Gettingready

Howtodoit…

Howitworks…

There’smore…

Index

Magento2DevelopmentCookbook

Magento2DevelopmentCookbookCopyright©2015PacktPublishing

Allrightsreserved.Nopartofthisbookmaybereproduced,storedinaretrievalsystem,ortransmittedinanyformorbyanymeans,withoutthepriorwrittenpermissionofthepublisher,exceptinthecaseofbriefquotationsembeddedincriticalarticlesorreviews.

Everyefforthasbeenmadeinthepreparationofthisbooktoensuretheaccuracyoftheinformationpresented.However,theinformationcontainedinthisbookissoldwithoutwarranty,eitherexpressorimplied.Neithertheauthor,norPacktPublishing,anditsdealersanddistributorswillbeheldliableforanydamagescausedorallegedtobecauseddirectlyorindirectlybythisbook.

PacktPublishinghasendeavoredtoprovidetrademarkinformationaboutallofthecompaniesandproductsmentionedinthisbookbytheappropriateuseofcapitals.However,PacktPublishingcannotguaranteetheaccuracyofthisinformation.

Firstpublished:December2015

Productionreference:1171215

PublishedbyPacktPublishingLtd.

LiveryPlace

35LiveryStreet

BirminghamB32PB,UK.

ISBN978-1-78588-219-7

www.packtpub.com

CreditsAuthors

BartDelvaux

Reviewers

KarenKilroy

PankajPareek

DavidParloir

MariusStrajeru

CommissioningEditor

VeenaPagare

AcquisitionEditor

PrachiBisht

ContentDevelopmentEditor

AparnaMitra

TechnicalEditor

AbhishekR.Kotian

CopyEditor

PranjaliChury

ProjectCoordinator

IzzatContractor

Proofreader

SafisEditing

Indexer

MariammalChettiyar

Graphics

DishaHaria

ProductionCoordinator

ConidonMiranda

CoverWork

ConidonMiranda

AbouttheAuthorBartDelvauxisanexperiencedwebdeveloperwithseveralyearsofexperienceinthePHPworld.HehasworkedwiththemostimportantframeworksinPHP,suchasDrupalandZendFramework,butMagentoishisspecialization.

BarthasobtainedalltheMagentodevelopercertifications:FrontEndDeveloper,Developer,aswellasDeveloperPlus.HecurrentlyworksforISAACSoftwareSolutions,acompanythatspecializesinsoftwaresolutionssuchaswebshops,apps,systemintegrations,andmore.

BartfinishedalargevarietyofMagentoprojectsinhisMagentocareerthatstartedin2010withtheprinciple“qualityabovequantity”.Havinggonefromhandlingabasicshoptoshippingmodulesandlarge,complexMagentostores,Magentoholdsnosecretsfromhim.

BarthasalsoworkedonMagento1.8DevelopmentCookbook,PacktPublishing.NowthatMagento2isout,itistimeforthenextone!

Iwanttothankeveryonewhomadeitpossibleformetocompletethisbook.IwouldliketoextendthankstothepeopleatPacktPublishingforthesupportandtomycolleaguesfortheirvisionandsupport.

Lastly,IwanttothankthepeoplewhocontributedtoMagento2.TheydidagoodjobcreatinganewversionofthepopularMagentosystem,whichisfuture-proof!

AbouttheReviewersKarenKilroyisahighlyexperienceddeveloper,administrator,andinstructor.SheisaMagento-certifiedFrontEndDeveloper.Asahands-ondeveloperandsystemsadministratorwithmorethan25yearsofexperienceinIT,whichincludes20yearsinwebdevelopment,KarenhasfocusedprimarilyonMagentoforthepast7years.Currently,sheisemployedatAmplifiasaMagentotechnicalleadandworksonseveralwell-knowncommercesites.

KarengotherstartinMagentoatadirectmarketingcompanysellingEdenPUREHeaters(edenpure.com),asitethatgeneratesmillionsofdollarsinsales.Additionally,shewasacoursewareauthorandinstructorforMagento’sofficialtrainingarm,MagentoU,between2010and2014.KarenisalsoareviewerofMasteringMagento,2ndEdition,PacktPublishing.

PriortobecominginvolvedwithMagento,shecustomizedLAMPcontentmanagementsystems,suchasJoomla,Drupal,andWordPress.Intheearlydaysofwebdevelopment,Karenledherowncompany,wheresheemployed20developersdoingJavaandLotusNotes/Dominoworkforlargeclients.

Inhersparetime,sheisalsoaprofessionaldragonboatcoachandsteersperson.

PankajPareekisacertifiedsoftwareprofessionalwhohasexpertiseinMagento,PHP,andotherframeworks.Hehasprovidedhisprofessionalservicesinthisfieldformorethan7years.

Atrueprofessional,Pankajworkswiththemottothatknowledgeincreaseswhenyoushareitwithothers.Heisapersonwhohasexploreddifferentaspectsofthesoftwarefieldsuomoto.Pankajisaquick,curiouslearnerwhoreceivedvariousrecognizedcertificationsintheITfieldinaveryshortspanoftime,namelyMagentoDeveloper(2013),MagentoSolutionSpecialist(2015),andZendCertifiedEngineer(2014).

Iwouldliketoexpressmygratitudetowardmylovinggrandmother,family,colleagues,andthealmightygod.

DavidParloirhasbeenafreelanceMagentodevelopersincethefirstversionwasreleasedin2008,andthroughthis,hehasalsobeentheleaddeveloperforseverallargeglobalprojects.Priortothis,Davidworkedforseveralcompaniesthatfocusedonthedevelopmentofe-commercewebsitesandevenworkedasateacherofMagentoforashortperiod.Heisaself-taughtdeveloperwhoseeswebdevelopmentasmorethanajob—heseesitasapassion.Davidconsidershimselfacraftsman,keepinguptodatewiththelatesttrendsinthisareawhilebalancingthenewskillshedevelops,withadesireforhiscodetobeefficient,simple,andelegant.

MariusStrajeruis32yearsoldandfinishedasafacultyofcomputerscienceinIasi,Romania,in2006.Sincethen,hehasworkedasaPHPdeveloperforvarioussoftwarecompanies.

Marius’areaofexpertiseisMagento;hehasbeenworkingwithMagentosinceversion1.0

cameoutin2008.HestartedlookingatMagento2assoonasheheardthatthesourcecodeisavailableinadevversion.

www.PacktPub.com

Supportfiles,eBooks,discountoffers,andmoreForsupportfilesanddownloadsrelatedtoyourbook,pleasevisitwww.PacktPub.com.

DidyouknowthatPacktofferseBookversionsofeverybookpublished,withPDFandePubfilesavailable?YoucanupgradetotheeBookversionatwww.PacktPub.comandasaprintbookcustomer,youareentitledtoadiscountontheeBookcopy.Getintouchwithusat<service@packtpub.com>formoredetails.

Atwww.PacktPub.com,youcanalsoreadacollectionoffreetechnicalarticles,signupforarangeoffreenewslettersandreceiveexclusivediscountsandoffersonPacktbooksandeBooks.

https://www2.packtpub.com/books/subscription/packtlib

DoyouneedinstantsolutionstoyourITquestions?PacktLibisPackt’sonlinedigitalbooklibrary.Here,youcansearch,access,andreadPackt’sentirelibraryofbooks.

Whysubscribe?FullysearchableacrosseverybookpublishedbyPacktCopyandpaste,print,andbookmarkcontentOndemandandaccessibleviaawebbrowser

FreeaccessforPacktaccountholdersIfyouhaveanaccountwithPacktatwww.PacktPub.com,youcanusethistoaccessPacktLibtodayandview9entirelyfreebooks.Simplyuseyourlogincredentialsforimmediateaccess.

PrefaceMagentoisoneofthemostpopulare-commerceplatformsonthemarket.Itcontainsalotofe-commercefunctionality,itisstable,anditisfree.ThismeansthatalotofpeoplechooseMagentofortheironlinebusiness.

ThefirststableversionofMagentowasreleasedin2008.ThelaterreleaseswerebasedonthefirstversionofMagento.TechnologychangesquicklyandMagentoneededabigupdate—abigreleaseMagento2isnowready.

DevelopinginMagentoisnotaseasyasyouwouldexpect.EvenifyouhaveknowledgeofMagento1,agoodguidewithpracticalexamplesthatshowsyouthebestpracticeisamusthave,andthisisexactlywhatthisbookwilldo.

WithMagento2DevelopmentCookbook,wewillcoverthemostimportanttopicsthatwillhelpyoubecomeagoodMagento2developer.Wewillstartwiththebasicsandwewillendwiththemoreadvancedtopics.

Thisbookisdividedintoseveralrecipes,whichshowyouwhichstepstotaketocompleteaspecificaction.Ineachrecipe,wehaveasectionthatexplainshoweverythingworks.

Wewillstartthisbookwiththecreationofagooddevelopmentenvironment.Foragooddevelopmentenvironment,weneedtherighttools.WewillinstallMagentoandwewilldiscusshowwecanmigratedatafromaMagento1toaMagento2shop.Next,wewillseesomefunctionalstuff.Youwilllearnhowthecatalogsystemworks,whichproducttypesareavailable,andalotmore.

Afterthis,youwilllearnhowwecancreateaMagentothemetochangethelookandfeeloftheMagentoshop.Butthemainfocusofthisbookwillbethedevelopmentpart.WewillcreateacustommodulethatwewillextendwithalotofcommonfeaturesthatareusedinMagentoprojects,suchasextracontrollerpages,databaseintegrations,customshippingmethods,andextrabackendinterfaces.

Attheendofthisbook,wewillseehowwecanimprovetheperformanceofaMagentoshop.Finally,wewillseesomedebuggingtechniques,suchasXdebugandcreatingunittestsusingtheMagentotestframework.

WhatthisbookcoversChapter1,UpgradingfromMagento1,providesanintroductiontohowyoucaninstallandmigratethedatafromaMagento1toaMagento2shop.Wewillalsoprepareourdevelopmentenvironmentinthischapter.

Chapter2,WorkingwithProducts,givesyouamorefunctionalinformationaboutthepossibilitiesofdisplayingproductsinyourMagentoshop.

Chapter3,Theming,explainshowyoucancustomizethelookandfeelofyourwebshopusingacustomMagentotheme.

Chapter4,CreatingaModule,describeshowtocreateabasicMagentomodule;howtoextendthatmodulewithcustomconfigurations,suchasacustompage,translations,andblocks;andhowtochangebehaviorofstandardMagentoclasses.

Chapter5,DatabasesandModules,demonstrateshowyoucanextendaMagentomodulewithdatabaseinteractions,suchasinstallandupgradescripts,acustomentitythatrepresentsadatabasetable.

Chapter6,MagentoBackend,showsyouhowtointegrateaMagentomodulewiththebackend,suchasaddingconfigurationpages,creatingoverviewpages,andextendingtheadminmenu.

Chapter7,EventHandlersandCronjobs,describeshowtheevent-drivenarchitectureisimplementedinMagentoandhowtointegratethisinyourmodule.Laterinthischapter,youwilllearnhowtocreatecronjobsandhowtotestthem.

Chapter8,CreatingaShippingModule,showsyouhowtocreateamodulewiththeconfigurationsthatarerequiredforanewshippingmethod.

Chapter9,CreatingaProductSliderWidget,willcoverhowtocreateamodulewithacustomwidget,howtobuildthebackendinterface,andhowtoprovideagoodUIinthefrontendofthatwidget.

Chapter10,PerformanceOptimization,describeshowtobenchmarkasitetoexplorethelimitsandhowtoimprovetheperformanceusingdifferenttechniquessuchasRedisandMemcached.

Chapter11,DebuggingandUnitTesting,showsyouhowtousethePHPdebuggerXdebugandhowwecancreateautomatedtestsusingtheMagento2testingframework.

WhatyouneedforthisbookMagento2sourcecodeAvirtualLinuxserver(Ubuntu15.10orhigher)Onthatvirtualserver,youneedthefollowing:

Apache2.4PHP5.5orhigherMySQLServer5.6orhigherSSHaccess

NetBeansIDE(oranyothergoodPHPeditorlikePhpStorm)Adatabaseclient(suchaphpMyAdmin)AstandardwebbrowserXdebugGitSCM

WhothisbookisforThisbookisforwebprogrammerswhoarefamiliarwithPHPandwanttostartwithMagento2.ThisbookisalsoforMagento1developerswhowanttoknowhoweverythingworksinMagento2.

ThisbookwillstartwiththebasicsofMagento2developmentandwillendwiththemoreadvancedtopics.EvenifyouknowledgeaboutMagentodevelopment,thisbookisagoodreferenceifyouwanttomoreaboutaparticulartopicinMagento.

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

Togiveclearinstructionsonhowtocompletearecipe,weusethesesectionsasfollows:

GettingreadyThissectiontellsyouwhattoexpectintherecipe,anddescribeshowtosetupanysoftwareoranypreliminarysettingsrequiredfortherecipe.

Howtodoit…Thissectioncontainsthestepsrequiredtofollowtherecipe.

Howitworks…Thissectionusuallyconsistsofadetailedexplanationofwhathappenedintheprevioussection.

There’smore…Thissectionconsistsofadditionalinformationabouttherecipeinordertomakethereadermoreknowledgeableabouttherecipe.

SeealsoThissectionprovideshelpfullinkstootherusefulinformationfortherecipe.

ConventionsInthisbook,youwillfindanumberoftextstylesthatdistinguishbetweendifferentkindsofinformation.Herearesomeexamplesofthesestylesandanexplanationoftheirmeaning.

Codewordsintext,databasetablenames,foldernames,filenames,fileextensions,pathnames,dummyURLs,userinput,andTwitterhandlesareshownasfollows:“Thewidget.xmlfileisusedtodefinewidgetsintheMagentoinstallation.”

Ablockofcodeissetasfollows:

<?xmlversion="1.0"?>

<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">

<routerid="standard">

<routeid="helloworld"frontName="helloworld">

<modulename="Packt_HelloWorld"/>

</route>

</router>

</config>

Newtermsandimportantwordsareshowninbold.Wordsthatyouseeonthescreen,forexample,inmenusordialogboxes,appearinthetextlikethis:“ClickingtheNextbuttonmovesyoutothenextscreen.”

NoteWarningsorimportantnotesappearinaboxlikethis.

TipTipsandtricksappearlikethis.

ReaderfeedbackFeedbackfromourreadersisalwayswelcome.Letusknowwhatyouthinkaboutthisbook—whatyoulikedordisliked.Readerfeedbackisimportantforusasithelpsusdeveloptitlesthatyouwillreallygetthemostoutof.

Tosendusgeneralfeedback,simplye-mail<feedback@packtpub.com>,andmentionthebook’stitleinthesubjectofyourmessage.

Ifthereisatopicthatyouhaveexpertiseinandyouareinterestedineitherwritingorcontributingtoabook,seeourauthorguideatwww.packtpub.com/authors.

CustomersupportNowthatyouaretheproudownerofaPacktbook,wehaveanumberofthingstohelpyoutogetthemostfromyourpurchase.

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

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

ErrataAlthoughwehavetakeneverycaretoensuretheaccuracyofourcontent,mistakesdohappen.Ifyoufindamistakeinoneofourbooks—maybeamistakeinthetextorthecode—wewouldbegratefulifyoucouldreportthistous.Bydoingso,youcansaveotherreadersfromfrustrationandhelpusimprovesubsequentversionsofthisbook.Ifyoufindanyerrata,pleasereportthembyvisitinghttp://www.packtpub.com/submit-errata,selectingyourbook,clickingontheErrataSubmissionFormlink,andenteringthedetailsofyourerrata.Onceyourerrataareverified,yoursubmissionwillbeacceptedandtheerratawillbeuploadedtoourwebsiteoraddedtoanylistofexistingerrataundertheErratasectionofthattitle.

Toviewthepreviouslysubmittederrata,gotohttps://www.packtpub.com/books/content/supportandenterthenameofthebookinthesearchfield.TherequiredinformationwillappearundertheErratasection.

PiracyPiracyofcopyrightedmaterialontheInternetisanongoingproblemacrossallmedia.AtPackt,wetaketheprotectionofourcopyrightandlicensesveryseriously.IfyoucomeacrossanyillegalcopiesofourworksinanyformontheInternet,pleaseprovideuswiththelocationaddressorwebsitenameimmediatelysothatwecanpursuearemedy.

Pleasecontactusat<copyright@packtpub.com>withalinktothesuspectedpiratedmaterial.

Weappreciateyourhelpinprotectingourauthorsandourabilitytobringyouvaluablecontent.

QuestionsIfyouhaveaproblemwithanyaspectofthisbook,youcancontactusat<questions@packtpub.com>,andwewilldoourbesttoaddresstheproblem.

Chapter1.UpgradingfromMagento1Inthischapter,wewillcover:

CreatingaMagento1websitewithsampledataCreatingaMagento2websitePreparinganupgradefromMagento1UpgradingthedatabaseUsinganIDEWritingcleancodewithPHPMDandPHPCS

IntroductionMagentoisoneofthemostcompletee-commerceplatformsontheopensourcemarket.WithadefaultMagentoinstallation,allthecommone-commercefeatures,suchascatalognavigation,promotionrules,taxsettings,onlinepayments,andsoonareavailable.

ThefirstversionofMagentowasreleasedin2008afteroneyearofdevelopment.Magentowasinitiallydesignedasane-commercesystemthatcouldbeusedforawiderangeofuses.Inlateryears,Magentobecameverypopularasanout-of-the-boxe-commercesystemandalotofminorversionsofthe1.xserieshavebeenreleasedinthelastfewyears.

Tobefutureproof,Magentostartedthedevelopmentofamajorupgradeofthesystem,alsoknownasMagento2.Magento2isabigimprovementoneverypartofMagento.Everyaspectisanalyzedandrewrittenwithup-to-datetechnologiestobereadyforthefuture.Everything,includingthedeveloperexperience,maintainability,performance,andtechnologieswillbeimproved.

Inthischapter,wewillupgradethedataofaMagento1installationtoaMagento2installation.Wewillalsopreparesometoolsthatwecanuseinthefollowingchaptersofthisbook.

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

GettingreadyToinstallaMagento1website,weneedthefollowingstuff:

Awebserver(Linux,Apache2,PHP,orMySQL)TheMagento1.9codebaseTheMagento1.9sampledata

NoteTheMagento1.9codebaseandsampledatacanbedownloadedfromtheMagentositeathttp://www.magentocommerce.com/download.

Thefollowingstuffisrecommendedfortheinstallation:

Command-lineaccessAvirtualhost(domainname)thatisgoingtobeyourwebroot

NoteWerecommendthatyouuseatestserverthatisonyourdevelopmentmachine.IfyouuseaLinuxoraMacoperatingsystem,youcaninstallthewebserveronyourlocalmachine.IfyouhaveaWindowsmachine,youcanuseavirtualLinuxserverforyourdevelopment.

Howtodoit…1. ExtracttheMagentocodearchiveinyourwebroot(thedirectoryofthevirtualhost).

Anls-lacommandshouldgiveyouthefollowingoutput:

api.php

app

cron.php

cron.sh

downloader

errors

favicon.ico

get.php

includes

index.php

index.php.sample

install.php

js

lib

LICENSE_AFL.txt

LICENSE.html

LICENSE.txt

mage

media

php.ini.sample

pkginfo

RELEASE_NOTES.txt

shell

skin

var

2. Extractthesampledataarchivetoadifferentfolderfromthewebroot.Copythecontentsofthemediaandskinfolderstothemediaandskinfoldersinyourwebroot.Wecandothisbyusingthefollowingcpcommand:

cp–R<path_to_sampledata_folder>/media/*

<path_to_magento_folder>/media/

cp–R<path_to_sampledata_folder/skin/*<path_to_magento_folder>/skin/

3. CreateadatabasefortheMagento1installationandnameitmagento1.Wecandothisbyrunningthefollowingcommands:

mysql-u<username>-p

createdatabasemagento1;

exit;

4. Importthesqlfilethatisinthesampledatadirectory.Thisfilecontainsadatabasethatwewillimportintothemagento1database.Wecandothisbyrunningthefollowingcommand:

mysql-u<username>-pmagento1<"path_to_sample_data.sql"

TipToavoidpermissionproblems,ensurethatallfilesandfoldershavetheright

permissions.Forsecurityreasons,itisrecommendedthatallfileshavejustenoughpermissionssothatonlytherightuserscanaccesstherightfiles.Whenyougivealltherights(777),youdon’thavepermissionproblemsbecauseeachusercanread,writeand,executeeachfileofyourapplication.Moreinformationaboutfilepermissionscanbefoundathttp://devdocs.magento.com/guides/m1x/install/installer-privileges_after.html.

5. Whenthefilesareintherightplaceandthedatabaseisimported,wecanruntheMagentoinstaller.Openyourbrowserandgotothedomainthatisconfiguredforyourwebsite.Youshouldseetheinstallerasinthefollowingscreenshot:

6. Continuewiththeinstallationprocessbyacceptingthetermsandconditions.7. Onthenextscreen,choosethecorrectlanguage,locale,andcurrencyforyourstore.8. Ontheconfigurationpage,fillintheformwiththerightdata:

DatabaseType:MySQL.Host:EnterthehostnameorIPaddressofyourdatabaseserver(localhostifitisonthesamemachine).Databasename:Entermagento1inthisfield(oranothernameifyouhaveadifferentnameforyourdatabase).Username:Enteryourdatabaseusername.Userpassword:Enteryourdatabasepassword.Tablesprefix:Leavethisfieldempty(thestringinthisfieldwillbeusedtoprefixalltablesofyourdatabase).BaseURL:EntertheURLofyourwebsiteinthisfield.Adminpath:Enteradmininthisfield.Thiswillbethepathofthebackend.Enablecharts:Fordevelopment,itisrecommendedthatthisbeunchecked.SkipBaseURLValidationBeforetheNextStep:Whenchecked,thewizard

willcheckforavalidURLwhenprocessingthisform.UseWebServer(Apache)rewrites:Checkthiswhentheapachemodulemod_rewriteisenabled.UseSecureURL’s(SSL):Thischeckboxmustbeuncheckedifyoudon’tuseHTTPS.

9. Submitthisformandwewillbeforwardedtothenextstep.Inthisstep,youcanconfiguretheadministratoraccount.Fillintherightdataandremembertheusernameandpasswordbecausethisisrequiredtomanagethestore.Leavetheencryptionkeyfieldempty.

10. Aftersubmittingthisform,theinstallationiscomplete.Optionally,youcansubmittheMagentosurvey.Atthebottomofthepage,therearebuttonstonavigatetothefrontendandbackend.Whengoingtothefrontend,youcanseeademoshopwithsampledataasinthefollowingscreenshot:

11. Thelayoutisresponsive.Whenscalingyourbrowsertoasmallerwidth,thewebsitewillswitchtothemobilelayoutlikeinthefollowingscreenshot:

Howitworks…WehavejustcreatedafullyfunctionalMagento1store.Thewebshopisfullyconfiguredandfilledwithdataaboutproducts,customers,andorders,justthedataweneedtomigratetoMagento2(intheupcomingrecipes).

Wheninstallinganewshop,youhavetofollowtheinstaller.Thisinterfacecreatesaconfigurationfileapp/etc/local.xml.Ifthefiledoesn’texist,Magentowilllaunchtheinstallerwizard.Ifthefileisthere,Magentowillruntheshop.

Withavalidlocal.xmlfile,itistechnicallypossibletoinstallanewMagentoshop,butthisisnotrecommendedbecausesomesettingssuchasabackenduser,timezone,andcurrencyarenotset.Theseareactionsthatyouhavetodomanuallywhenchoosingforthismethod.

CreatingaMagento2websiteInthepreviousrecipe,wecreatedaMagento1websitewithsampledatathatwewilluseforanupgrade.Inthisrecipe,wewilldothesame,butwewillcreateaMagento2websitewiththesampledataforMagento2.

GettingreadyToinstallMagento2,weneedthenewesttoolstorunthatapplication.Makesureyourwebserverhasthefollowingstuffinstalled:

PHP5.5orhigherMySQL5.6orhigherApache2.2orhigherCommandlineaccessComposer

WecaninstallMagento2indifferentways.Inthisrecipe,wewillinstallMagento2usingComposer.TheadvantageofthisisthatwecanuseGITtoaddversioncontroltoourcustomdevelopment.

Howtodoit…1. WewillinstallMagento2withComposer.Forthis,weneedauthenticationkeys.

Withanaccountonthemagento.comsite,gotoDevelopers|SecurekeysintheMyAccountsection.Onthispage,youcangeneratepublicandprivatekeysthatwillbeyourusernameandpasswordinthenextstep.

2. ToinstallMagento2withcomposer,wehavetorunthefollowingcommand:

composercreate-project--repository-url=https://repo.magento.com

magento/project-community-edition<installation_dir>

3. Youwillbepromptedforausernameandpassword.Theusernameisthepublickeyandthepasswordistheprivatekeythatwegeneratedinthepreviousstep.Whenthecommandhasrun,theinstallationdirectorywillhavethefollowingstructure:

app

bin

CHANGELOG.md

composer.json

composer.lock

CONTRIBUTING.md

CONTRIBUTOR_LICENSE_AGREEMENT.html

COPYING.txt

dev

.gitignore

Gruntfile.js

.htaccess

.htaccess.sample

index.php

lib

LICENSE_AFL.txt

LICENSE.txt

nginx.conf.sample

package.json

.php_cs

php.ini.sample

pub

README.md

setup

.travis.yml

update

var

vendor

TipCheckthattheuserandgroupofthesefilesarethesameasyourApacheuser.Onerecommendationistoexecuteallthecommandsasyourapacheuser.

4. Wehaveinstalledthecodebasewithcomposer.Nowwecanruntheinstallationwizard.OpenyourbrowserandentertheURLofyoursite.Youshouldseethefollowingwelcomescreen:

5. HittheAgreeandSetupMagentobuttonandstarttheenvironmentcheck.6. ClickonNextandenteryourdatabaseinformationasfollows:

DatabaseServerHost:ThehostnameorIPaddressofthedatabaseserverDatabaseServerUsername:TheusernameofthedatabaseaccountDatabaseServerPassword:ThepasswordfortheaccountDatabaseName:ThenameofthedatabaseTablePrefix:Optionally,youcangiveaprefixforeachtable

7. GotothenextstepandcheckiftherightinformationisfilledfortheURLpart.Intheadvancedsection,youcanoptionallyconfigureHTTPS,apacherewrites,andyourencryptionkey.Forourtestenvironment,wecanleavethesesettingsastheyareconfigured.

NoteMakesurethatthemod_rewriteoptionisenabledfortheapacheserver.Whennotenabled,theURLrewriteswillnotworkcorrectly.

8. Inthenextstep,youcanconfigureyourtimezone,currency,anddefaultlanguage.9. Inthelaststep,youcanconfigureyouradministrationaccount.Afterclickingonthe

Nextbutton,youarereadytoinstall.ClickontheInstallNowbuttonandtheinstallerwillstart.Thiswilltakesometimebecausetheinstallerwilladdthesampledataduringtheinstallation.YoucanopentheConsoleLogtoseewhatiscurrentlyhappening.

10. Whentheinstallerisready,youwillseethefollowingsuccessmessage:

11. RunthefollowingcommandsinyourMagentoinstallationdirectorytoconfigurethesampledata:

phpbin/magentosampledata:deploy

composerupdate

phpbin/magentosetup:upgrade

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

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

Howitworks…WehavenowinstalledaMagento2website.LikewedidinthepreviousrecipeforMagento1.9,wedownloadedthecodebase(usingcomposer),createdadatabase,andinstalledMagento.

ForMagento2,weusedcomposertodownloadthecodebase.ComposerisaPHPdependencymanager.Allthedependenciesaresetinthecomposer.jsonfile.Forthisrecipe,therearetheMagentoandthemagento-sample-datadependenciesinthecomposer.jsonfile.Thereisalsoacomposer.lockfilegenerated.Inthatfile,theversionsoftheinstalleddependenciesarestored.

NoteWhenworkingwithGIT,weonlyhavetocommitthecomposer.json,composer.lock,and.gitignorefilesforaworkingMagento2project.WhenanotherpersondoesaGitcloneoftherepositoryandrunsthecomposer’sinstallcommand,Magento2willbeinstalledwiththeversionthatisinthecomposer.lockfile.

ThesampledataforMagento2isnowascriptthatwillbeexecutedaftertheinstallationofMagento.Thatscriptwilladdproducts,customers,orders,CMSdata,andmoreconfigurationstopopulatetheshop.

Theshopisinstalledandtheconfigurationsettings(database,encryptionkey,andsoon)arenowstoredinapp/etc/env.phpinsteadofintheapp/etc/local.xmlfileinMagento1.

There’smore…WheninstallingMagento2,herearesomecommonissuesthatcanoccurandtheirfixes:

Whenyoudon’tseeCSSinyourbrowser,youhavetocheckthefollowingthings:

Makesurethepub/folderiswritableRunthecommandphpbin/magentosetup:static-content:deploytogeneratethestaticcontent

Youforgettoinstallthesampledata:

YoucaninstallthesampledataaftertheinstallationofMagentowiththecommandphpbin/magentosampledata:deploy

Theinstallationisnotrespondinganymore:

ThiscouldbecausedbyanApachetimeout.Ifthisoccurs,youcanmaybetrythecommand-lineinstallation.Thisworksasfollows:

ToruntheMagentoinstallerfromthecommandline,wecanusethecommandphpbin/magentosetup:install.Wehavetoaddthefollowingrequiredparameterstothecommandtoconfiguretheinstallation:

base-url:ThebaseURL,forexamplehttp://magento2.local/db-host:ThedatabasehostorIPaddressdb-user:Thedatabaseusernamedb-name:Thedatabasenamedb-password:Thedatabasepasswordadmin-firstname:Thefirstnameoftheadministratoruseradmin-lastname:Thelastnameoftheadminuseradmin-email:Thee-mailaddressoftheadminuseradmin-user:Theusername(loginname)oftheadminuseradmin-password:Thepasswordfortheadminuserlanguage:Thelanguageoftheshopcurrency:Thecurrencycodeoftheshoptimezone:Thetimezoneoftheshopuse-rewrites:Whethertousetheapacherewritesornotuse-sample-data:Installthesampledata(optional)

Lookatthefollowingcodeforaworkingexampleoftheinstallcommand:

phpbin/magentosetup:install--base-url=http://magento2.local/--db-

host=localhost--db-user=magento2--db-name=magento2--db-

password=yourpassword--admin-firstname=John--admin-lastname=Doe--admin-

email=john.doe@example.com--admin-user=admin--language=en_US--

currency=USD--timezone=UTC--use-rewrites=1

PreparinganupgradefromMagento1ThedifferencesbetweenMagento1andMagento2arehuge.Thecodehasawholenewstructurewithalotofimprovementsbutthereisonebigdisadvantage.WhatdoIdoifIwanttoupgrademyMagento1shoptoaMagento2shop?

MagentocreatedanupgradetoolthatmigratesthedatafromaMagento1databasetotherightstructureforaMagento2database.

ThecustommodulesinyourMagento1shopwillnotworkinMagento2.ItispossiblethatsomeofyourmoduleswillhaveaMagento2version,anddependingonthemodule,themoduleauthorwillhaveamigrationtooltomigratethedatathatisinthemodule.

GettingreadyBeforewegetstarted,makesureyouhaveanempty(withoutsampledata)Magento2installationwiththesameversionastheMigrationtoolthatisavailableat:

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

Howtodoit…1. InyourMagento2version(withthesameversionasthemigrationtool),runthe

followingcommands:

composerconfigrepositories.data-migration-toolgit

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

composerrequiremagento/data-migration-tool:2.0.0

2. InstallMagento2withanemptydatabasebyrunningtheinstaller.Makesureyouconfigureitwiththerighttimezoneandcurrencies.

3. Whenthesestepsaredone,youcantestthetoolbyrunningthefollowingcommand:

phpbin/magentomigrate:data--help

4. Thenextthingiscreatingtheconfigurationfiles.Examplesoftheconfigurationfilesareinvendor/magento/data-migration-tool/etc/<version>.Wecancreateacopyofthisfolderwherewecansetourcustomconfigurationvalues.ForaMagento1.9installation,wehavetorunthefollowingcpcommand:

cp–Rvendor/magento/data-migration-tool/etc/ce-to-ce/1.9.1.0/

vendor/magento/data-migration-tool/etc/ce-to-ce/packt-migration

5. Openthevendor/magento/data-migration-tool/etc/ce-to-ce/packt-migration/config.xml.distfileandsearchforthesource/databaseanddestination/databasetags.Changethevaluesofthesedatabasesettingstoyourdatabasesettingslikeinthefollowingcode:

<source>

<databasehost="localhost"name="magento1"user="root"/>

</source>

<destination>

<databasehost="localhost"name="magento2_migration"user="root"/>

</destination>

6. Renamethatfiletoconfig.xmlwiththefollowingcommand:

mvvendor/magento/data-migration-tool/etc/ce-to-ce/packt-

migration/config.xml.distvendor/magento/data-migration-tool/etc/ce-to-

ce/packt-migration/config.xml

Howitworks…Byaddingacomposerdependency,weinstalledthedatamigrationtoolforMagento2inthecodebase.ThismigrationtoolisaMagentoconsolecommandthatwillhandlethemigrationstepsfromaMagento1shop.

Intheetcfolderofthemigrationmodule,thereisasampleconfigurationofanemptyMagento1.9shop.

IfyouwanttomigrateanexistingMagento1shop,youhavetocustomizetheseconfigurationfilessoitmatchesyourpreferredstate.

Inthenextrecipe,wewilllearnhowwecanusethescripttostartthemigration.

UpgradingthedatabaseInthepreviousrecipe,weconfiguredthedatabasemigrationtool.Inthisrecipe,wewillrunthemigrationtoolsothatwecanmigratepartsfromaMagento1shoptoaMagento2shop.

GettingreadyYouneedaMagento1websiteandaMagento2website.TheMagento2websiteneedstohavethedatabasemigrationtoolinstalledandconfiguredasdescribedinthepreviousrecipe.

Inthisrecipe,wewilldoamigrationfromacleanMagento1site,toaMagento2sitewithoutsampledata.

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

Howtodoit…1. Firstweneedtomakesurethatthedatabasesettingsarecorrectinthe

vendor/magento/data-migration-tool/etc/ce-to-ce/packt-

migration/config.xmlfile.Openthatfileandcheckthatthedatabasecredentialsarecorrect.Wecreatedthisfileinthepreviousrecipe:

<sourceversion="1.9.1">

<databasehost="localhost"name="magento1_migration"user="root"/>

</source>

<destinationversion="2.0.0.0">

<databasehost="localhost"name="magento2_migration"user="root"/>

</destination>

NoteIfyouhaveadatabaseprefixinyoursourceordestinationdatabase,youcanoptionallyconfiguresource_prefixanddest_prefixinthe<options>sectionofthesameconfigurationfile.

TipTestthemigrationfirstwithacleanMagento1.9database.ThemappingthatwewilluseinthisrecipeisforacleanMagento1.9installation.Withanexistingshop,youwillhavecustomattributesandentitiesthatneedmoreconfigurationtomakethemigrationwork.

2. Ifthesesettingsarecorrect,wecanruntheupgradetool.Runthefollowingcommand:

phpbin/magentomigrate:data--help

3. Thisgivesusthefollowingoutput:

4. Tostartortestamigration,wehavetorunthefollowingcommand:

phpbin/magentomigrate:datavendor/magento/data-migration-tool/etc/ce-

to-ce/packt-migration/config.xml

5. Themigrationwillstartandwillgivethefollowingoutput:

6. Themigrationisnowcomplete.IfyoucheckyourdatabasefortheMagento2website,youwillseethatthedata(products,categories,andsoon)ismigratedfromMagento1.

TipIfyouwanttorerunthemigrationtool,youhavetoremovethevar/migration-tool-progress.lockfile.

7. WecanalsomigratethesettingsfromtheMagento1website.Todothis,youhavetoreplacethedataparameterinthecommandusingsettings.

8. Tocheckiftheupgradeworks,youhavetolookatthedataoftheMagento2installation.Wecancheckthefollowingthingsinthebackend:

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

9. Youcanalsocheckinthedatabaseifyoulookatthefollowingtables:

sales_order

customer_entity

catalog_product_entity

url_rewrite

Howitworks…Whenthemigrationtoolstarts,itstartscheckingalltheconfigurationsthatareintheconfigurationfilesofthemigrationtool.IftherearemorethingsavailableintheMagento1databasethanthethingsthatareconfigured,themigrationtoolwillgiveanotificationandstopthemigration.

It’slikelythateveryexistingMagento1shopworkswithcustomattributes,customentities,andsoon.Eachentity,attribute,andsoonneedstobedeclaredintheconfigurationfiles.

Themosttime-consumingpartofamigrationistocreateagoodconfigurationfilesothatthemigrationtoolwon’tfailonmissingstuff.Itisonyoutodecidewhattoignoreandwhattomigrate.Iftheconfigurationfilesarevalid,themigrationwillstartandthedatawillcomeintotheMagento2database.Thesameprincipleapplieswhenmigratingthesettings,butyouhavetothinkaboutwhetheryouwantit.

NoteWiththemigrationtool,itisonlypossibletomigratedataandsettings.ThecodeofMagento1moduleswillnotworkinMagento2.Soforyourmodules,youneedtoseeifthereisaMagento2version/alternativeavailable.

There’smore…Inthisrecipe,wedidamigrationofacleanMagento1installationtoacleanMagento2installation.HoweveralmosteveryrunningMagento1shopisnotclean.Itcontainscustomattributes,custommodules,andacustomconfiguration.

Whenmigratingsuchashoptoanewshop,themigrationisabitmorecomplex.Thefirstquestionis:Whatneedstobemigrated?Withthetool,youcanmigrateeveryentity,fromproducts,customers,andorderstoreviews,settings,andmore.

Ifyouwanttoskipdatathatmustbemigrated,youcanusethemap.xmlfile.Ifyouopenthefilevendor/magento/data-migration-tool/etc/ce-to-ce/packt-migration/map.xml,youseethatalotofentitiesareignoredinthemap/source/document_rulestag.

TipIfyouwanttochangesomethinginthemap.xmlfile,youhavetomakesurethattherightmap.xmlfileisloaded.Thisfileisconfiguredintheconfig.xmlfile(whereyoudidyourdatabaseconfiguration).Inthatfile,youhavetolookfortheXMLtagconfig/options/map_file.

IfyouhaveanerrorsuchasSourcedocumentsnotmapped,youhavetoaddtheconfigurationfortheseentitiesinthemap/source/document_rulestagofthemap.xmlfile.IftheerrorissomethinglikeDestinationdocumentsnotmapped,youhavetoaddconfigurationinthemap/destination/documenttagofthemap.xmlfile.

TosolveerrorssuchasSourcefieldsnotmappedyouhavetoaddconfigurationinthemap-eav.xmlfile.

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

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

GettingreadyInstallthelatestversionofNetBeansIDEonyourcomputer.YoucandownloaditfromthefollowingURL:

https://netbeans.org/downloads/

ForPHPdevelopment,youneedtodownloadtheHTML5&PHPbundle.

Howtodoit…1. Tocreateanewproject,openNetBeansandnavigatetoFile|NewProject.2. Awindowliketheoneinthefollowingscreenshotwillappearonyourscreen.Click

onPHPandPHPApplicationwithExistingSources.

3. ClickonNextandconfigurethefollowingsettings:

SourceFolder:ThisfieldissettothelocationofyourMagentocode(like/var/www/html/magento2/)ProjectName:TheNetBeansprojectnameisenteredinthisfieldPHPVersion:ThisfieldissettoPHP5.5DefaultEncoding:ThisfieldissettoUTF-8

4. Inthenextscreenshot,youcanseehoweverythingisconfigured:

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

5. ClickonNextandconfigurethefinalsettings:

Runas:IfyouaredevelopingonalocalPC,chooseLocalWebServerProjectURL:TheURLofyourwebsiteIndexfile:Setthistoindex.php

Thesettingsareshowninthefollowingscreenshot:

6. ClickontheFinishbuttonandyourNetBeansprojectisready.Youcannowstartdeveloping.

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

PHPStormEclipsewithPDT(PHPDevelopmentTools)ZendStudio

WritingcleancodewithPHPMDandPHPCSMaintainingcleancodeismuchmoreefficientthanmaintainingspaghetticode,butwritingcleancodeisnotaseasyasitsounds.Thesedaystherearesometoolsthathelpyouwithwritingcleancode,suchasPHPMDandPHP_CodeSniffer.

PHPMDstandsforPHPMessDetector;thistoolwillcheckyourcodeoncomplexityandhowvariablesareusedandwilldetectsomepossiblebugs.ItgoesabitfurtherthanthesyntaxcheckinyourIDE.

PHP_CodeSnifferorPHPCSchecksyourcodeoncodingstandardssuchasPSR-1andPSR-2.

GettingreadyWewillinstallPHPMDandPHP_CodeSnifferinourdevelopmentenvironment.Makesureyouhavecommand-lineaccesstoyourdevelopmentenvironment.

Howtodoit…1. BeforeinstallingPHPMDandPHP_CodeSniffer,wehavetomakesurethatPHPis

installedonourdevelopmentmachine.Especiallyifyouaredevelopingonaremoteserver,itcouldbethatPHPisnotinstalled.

2. DownloadandinstallPHPMD.DependingonyourOS,theprotocolcouldbedifferent.Youcanfindinstructionsat:

http://phpmd.org/download/index.html

3. DownloadandinstallPHP_CodeSniffer.Youcanfindtheinstallationinstructionsat:

https://github.com/squizlabs/PHP_CodeSniffer

4. Everythingisinstalled,sowecanrunatestforPHPMD.ForthePHPMDcommand,thesearetherequiredoptions:

FilenameordirectoryTheformatofthereportTheruleset

5. Let’srunthefollowingcommandtocheckthefileoncleancodeandoutputtext:

phpmdapp/code/Magento/Cms/Model/Observer.phptextcleancode

6. Itgivesusthefollowingoutput:

/var/www/magento2/app/code/Magento/Cms/Model/Observer.php:70Avoid

usingstaticaccesstoclass'\Magento\Cms\Helper\Page'inmethod

'noCookies'

/var/www/magento2/app/code/Magento/Cms/Model/Observer.php:71Avoid

usingstaticaccesstoclass'\Magento\Store\Model\ScopeInterface'in

method'noCookies'.

/var/www/magento2/app/code/Magento/Cms/Model/Observer.php:77The

methodnoCookiesusesanelseexpression.Elseisnevernecessaryand

youcansimplifythecodetoworkwithoutelse.

7. Therearealotoferrors,butMagento2definesitsownrulesforPHPMD.Torunatestwiththeserules,wecanrunthefollowingcommand:

phpmdapp/code/Magento/Cms/Model/Observer.phptext

dev/tests/static/testsuite/Magento/Test/Php/_files/phpmd/ruleset.xml

8. Thiscommandgivesemptyoutput,whichmeansthatthisfileisvalid.9. WewillnowrunatestonthesamefilewithPHP_CodeSniffer.Withthenext

command,wewillrunatestonthesamefileweusedforPHPMD.

phpcsapp/code/Magento/Cms/Model/Observer.php

10. Thistestgivesusthefollowingoutput:

FILE:/var/www/magento2/app/code/Magento/Cms/Model/Observer.php

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

FOUND22ERRORSAND2WARNINGSAFFECTING12LINES

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

5|WARNING|[]PHPversionnotspecified

5|ERROR|[]Missing@categorytaginfilecomment

5|ERROR|[]Missing@packagetaginfilecomment

5|ERROR|[]Missing@authortaginfilecomment

5|ERROR|[]Missing@licensetaginfilecomment

5|ERROR|[]Missing@linktaginfilecomment

10|ERROR|[]Missing@categorytaginclasscomment

10|ERROR|[]Missing@packagetaginclasscomment

10|ERROR|[]Missing@authortaginclasscomment

10|ERROR|[]Missing@licensetaginclasscomment

10|ERROR|[]Missing@linktaginclasscomment

18|ERROR|[]Protectedmembervariable"_cmsPage"mustnotbe

||prefixedwithanunderscore

25|ERROR|[]Protectedmembervariable"_scopeConfig"mustnot

||beprefixedwithanunderscore

27|ERROR|[]Missingshortdescriptionindoccomment

28|ERROR|[]Missingparametercomment

28|ERROR|[x]Expected27spacesafterparametertype;1found

29|ERROR|[]Missingparametercomment

42|ERROR|[]Missingparametercomment

42|ERROR|[x]Tagvalueindentedincorrectly;expected2spaces

||butfound1

43|ERROR|[]Tagcannotbegroupedwithparametertagsina

||doccomment

62|ERROR|[]Missingparametercomment

62|ERROR|[x]Tagvalueindentedincorrectly;expected2spaces

||butfound1

63|ERROR|[]Tagcannotbegroupedwithparametertagsina

||doccomment

78|WARNING|[]Lineexceeds85characters;contains94

||characters

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

PHPCBFCANFIXTHE3MARKEDSNIFFVIOLATIONSAUTOMATICALLY

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

Time:28ms;Memory:3.75Mb

NoteIfthephpmdcommandisnotworking,youhavetofindthepathtothephpmdexecutableandrunitfromthere.

11. WhenwespecifytherulesetofMagento2,wehavethefollowingcommand:

phpcsapp/code/Magento/Cms/Model/Observer.php--

standard=dev/tests/static/testsuite/Magento/Test/Php/_files/phpcs/rules

et.xml

12. Thiscommandgivesusthefollowingoutput:

FILE:/var/www/magento2/app/code/Magento/Cms/Model/Observer.php

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

FOUND5ERRORSAFFECTING5LINES

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

18|ERROR|Missingvariabledoccomment

25|ERROR|Missingvariabledoccomment

31|ERROR|Missingfunctiondoccomment

45|ERROR|Missingfunctiondoccomment

65|ERROR|Missingfunctiondoccomment

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

Time:35ms;Memory:3.75Mb

Howitworks…PHPMDandPHP_CodeSnifferaretoolsthatchecksPHPfilesoncodestyle.Thesetoolshavedefinedtheirdefaultrulesetsforcommonusage.

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

WhendevelopingcustomcodeinMagento2,itisrecommendedthatyouconfiguretheserulesetswhenworkingwithPHPMDandPHP_CodeSniffer.

There’smore…SomeIDE’shavebuilt-insupportforPHPMDandPHP_CodeSniffer.Thesepluginswillrunatestwhensavingafile.

InNetBeans,youhavethephpcsmdpluginthatallowsyoutointegratethesetoolsinyourIDE.FormoredetailsvisitthefollowingURL:

http://plugins.netbeans.org/plugin/40282/phpmd-php-codesniffer-plugin

InPHPStorm,thereisbuilt-insupportforPHPMDandPHP_CodeSniffer.Ifitisconfigured,thereisacolorindicatorthatsayshowcleanyourcodeis.Moreinformationcanbefoundathttps://www.jetbrains.com/phpstorm/help/using-php-mess-detector.html.

TipWhenconfiguringPHPMDandPHP_CodeSnifferinanIDE,thesetoolsandPHPneedtobeinstalledonthemachineonwhichtheIDEisrunning.

Chapter2.WorkingwithProductsInthischapter,wewillcoverthefollowingrecipes:

ConfiguringthecatalogdefaultsWorkingwithattributesetsWorkingwithproducttypesAddingsocialmediabuttonsEmbeddinganHTMLobjectChangingtheURLofaproductpage

IntroductionHowproductsaredisplayedinthefrontendisveryimportantformakingawebshopwithgoodusability.Convincingthevisitorstobuysomethingfromtheshopisthemaintargetofeveryshopowner.

Productsneedtobesetinsuchawaythatavisitorcanquicklyfindwhatheislookingfor.Withgoodproductcontent,ashoplooksreliable,duetowhichavisitorismorelikelytobuysomething.

Inthischapter,wewillexplainwhatyoucandotodisplayproductsinyourshop,andwewillseesomeextrathings,suchasavideoandaddtocartlinks,toraiseconversionfromaprospectivebuyertoanactualbuyer.

Thegoalofthischapteristomakeyourshopmoreuser-friendlywithjustalittledevelopment.

ConfiguringthecatalogdefaultsOneofthefirstthingsistoconfiguresomedefaultcatalogsettingstothepreferredvalues.WewillcoveralltheconfigurationvaluesthatarepossibleinaMagento2installation.

Wewillgothroughtheavailableconfigurationsandchangesomevaluestotherecommendedsettings.

GettingreadyOpenyourfrontendandlogintothebackendinaseparatebrowsertab.Wewillmodifysomeconfigurationvaluestotherecommendedsettings.Whenchangingaconfigurationvalue,wecancheckwhathappensinthefrontend.

HowtodoitInthenextsteps,wewilltakealookatthecatalogsettings:

1. Inthebackend,navigatetoStores|Settings|Configuration.OpentheCatalogmenu,aswecanseeinthefollowingscreenshot:

2. OpentheProductFieldsAuto-Generationsection.Inthissection,wecanconfigurethebehaviorofthegenerationofSKUandmetadata.WhenweaddthefollowingvaluesfortheMaskforMetaKeywords,theSKU,name,andthestring“Magento”willbegeneratedwhensavingaproduct:

{{sku}},{{name}},Magento

3. OpentheStorefrontsectionandsetthefollowingvalues:

Listmode:grid(bydefault,thisshowstheproductsinagridorlist)ProductsperpageonGridallowedvalues:12,24,36ProductsperpageonGriddefaultvalue:24

NoteWhenchangingtheallowedanddefaultvalueforagridpage,ensurethatyoucandividethenumbersbythenumberofproductsthatfitinarowinyourtheme.Otherwise,thelastrowofproductswillnotbecomplete.

ProductsperpageonListallowedvalues:10,20,30,40ProductsperpageonListdefaultvalue:10

Allowallproductsperpage:No

NoteWhenyouhavealargenumberofproducts,itisnotrecommendedtosettheAllowallproductsperpageoptiontoYes.Whenyouhave2000productsandyouwanttoshowalltheproductsonasinglepage,youwillgenerateanenormousHTMLoutputthatcancausememoryissues.

ProductlistingSortby:priceUseFlatCatalogCategory:NoUseFlatCatalogProduct:NoAllowDynamicMediaURL’sinProductsandCategories:Yes

4. Enabletheproductreviewsforguests.Thisallowseveryonetowriteareviewaboutaproduct.Whenthisisenabled,areviewformwillappearontheproductreviewpage.

5. OpentheProductAlertssectiontoconfigureproductalerte-mailsthatwillbesentwhenthepriceorstockchanges.

6. Wewillconfigureastockalertwiththefollowingsettings:

Allowalertwhenproductpricechanges:NoAllowalertwhenproductcomesbackinstock:Yes

NoteThepreviousconfigurationswillsendstockalerte-mails(astockalertistriggeredwhenaproductbecomesavailableinstock)tothesubscribede-mailaddresses.

7. WecansetthevaluesforProductAlertsRunsettingsinthenextsection.Wewillconfigureadailytaskat04:00hourstosendthealerte-mails:

Frequency:DailyStarttime:04:00:00

8. LeavetheProductImagePlaceholdersoptionsastheyare.Ifwewant,wecansetadefaultimagethatwillbeshownwhenaproducthasnoimageortheimageisnotfound.Thebestwayistosettheplaceholderimagesinthetheme.

9. IntheRecentlyViewed/ComparedProductstab,setthefollowingvalues:

Showforcurrent:Website

NoteThiswillshowtherecentproductsyouviewedoverallstoresandstoreviewsinthewebsite.

Defaultrecentlyviewedcount:5Defaultrecentlycomparedcount:5

10. InthePricetab,settheCatalogPriceScopeoptionasGlobal.Forthistutorial,wedon’tneeddifferentpricesforeachstoreview.WhenPriceScopeissettoGlobal,wecanonlyconfigureoneglobalpriceforaproduct,whichwillbethesameinallstoreviews.

11. IntheLayeredNavigationsection,wewillmodifysomesettingstocustomizetheleftnavigationforthecategorypages:

Displayproductcount:YesPricenavigationstepcalculation:Automatic(thiswillequalizepriceranges)

12. Bymakingthesesettings,thepricestepswillalwayshavethesameincrement.13. OpentheCategoryTopNavigationsection,andsetMaximalDepthto3.This

meansthatthenavigationwillbeshownwithamaximumofthreelevels.14. IntheChangingtheURLofaproductpagerecipe,wewilllookattheSearch

EngingeOptimizationstep.15. ConfiguretheCatalogSearchsectionasfollows:

MinimalQueryLength:3MaximumQueryLength:128SearchEngine:MySQLApplyLayeredNavigationifSearchResultsareLessThan:0

NoteIfasearchresultshowsalotofproducts,thegenerationofthelayerednavigationslowsdownthepageload.Withthissetting,youcandisablethelayerednavigationiftheresultscountishigherthantheconfiguredvalue.

16. Don’tforgettosavetheconfigurationbyclickingontheSaveConfigbutton.

HowitworksAllthesesettingsaresavedintheconfigurationtableofMagento.Thefrontendfilesforthecatalogpageswillpickupthesesettingsandrendertheoutputbasedonthesesettings.

Whenyouaddextrafunctionalitytothecategorypage,youcaneasilyextendtheconfigurationwithextraparameters.MoreinformationaboutextendingtheconfigurationsisgivenintheExtendingthesystemconfigurationrecipeofChapter6,MagentoBackend.

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

Tocoverthis,Magento2comeswithasystemcalledproducttemplates,whichcanbecomparedwithattributesetsinMagento1.

Aproducttemplateisaspecificationofproductattributesthatyoucanassigntoproducts.

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

Wewillcreateanewproductattributeandanewproducttemplate(suchasanattributeset)thatwecanuseinnewproducts.

HowtodoitInthefollowingsteps,wewillcreateanextraproductattributethatwecanuseinaproducttemplate:

1. NavigatetoStores|Attributes|Productinthebackend,andclickontheAddNewAttributebutton.

2. Populatetheformwiththefollowinginstructions:

Defaultlabel:Availablefrom(Thislabelwillbeusedtoidentifytheattribute)CatalogInputTypeforStoreOwner:Date(thisisthetypeoftheattribute)

3. ClickonSaveandContinueEditandtheattributewillbesaved.YouwillseethattheAttributeCodefieldisprepopulatedwithacodethatisgeneratedfromthelabel.

4. Additionally,wecansetthefollowingvalues:

Valuesrequired:NoScope:StoreView(withthissetting,wecreatethepossibilitytospecifyseparatevaluesforeachstoreview)Defaultvalue:LeavethisfieldemptyUniquevalue:No

5. IntheManageLabelstab,wecansetthelabelthatwillbedisplayedontheproductdetailpage.Ifleftempty,theattributelabelwillbeused.

6. IntheStorefrontPropertiestab,wecansetthefollowingproperties:

Useinsearch:NoComparableonStorefront:NoUseforPromoRuleConditions:NoAllowHTMLTagsonfrontend:NoVisibleonCatalogPagesonStorefront:NoUsedinProductListing:NoUsedforSortinginProductListing:No

7. ClickonSaveAttribute;thiswillsavetheattribute.8. ThenextstepistocreateanAttributeSettowhichwillassignaproductto.Inthe

backend,navigatetoStores|Attributes|AttributeSet.9. ClickontheAddAttributeSetbuttonandfillintheform,asfollows:

10. ClickingonSavewillopentheoverviewpage.11. CreateagroupnamedGamespecificdataanddragtheavailable_fromand

manufacturerattributestoit.Theoverviewwilllookasfollows:

12. SavetheAttributeSet.13. NavigatetoProduct|CatalogandcreateanewproductbyclickingontheNew

Productbutton.14. SelecttherightAttributeSetintheform,asshowninthefollowingscreenshot:

15. Whenselectingtheproducttemplate,youwillseethattheGamespecificdatatabappearsintheproducttab,asshowninthefollowingscreenshot:

HowitworksProductattributesandAttributeSetsareusedwhenyouworkwithmultiplefamiliesofproducts.Inthesampledataofourshop,therearemoreattributesetsavailableforbags,clothing,andmore.

WithAttributeSets,youcanmakegroupsofattributesforeveryproductfamily.Whencreatingaproductattribute,youhavetochoosethetypeoftheattribute,whichcanbeoneofthefollowing:

TextfieldTextareaDateYes/NoMultipleselectDropdownPriceMediaimageFixedProductTax

TipWhenyouwanttouseanattributeasafilterintheleftnavigationonthecategorypages,thisattributemusthavethetypeDropdown,MultipleSelectorPrice.

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

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

HowtodoitInthefollowingsteps,wewillcreateaproductwherewecanspecifyasizeontheproductdetailpage:

1. NavigatetoProducts|Catalog,clickonthearrowneartheAddProductbutton,andchooseConfigurableProductasshowninthefollowingscreenshot:

2. Chooseaname,SKU,andpricefortheproduct.3. Thenextstepistodecidetheattributeonwhichwewanttoconfigureourproduct.

ScrolldownthroughtheNewProductspage,andclickontheCreateConfigurationsbutton.Inthegrid,selecttheSizeattributeandclickonNext.

4. SelectthesizesforyourproductsandclickonNext.5. Onthenextscreen,youcanaddimages,prices,andquantitiesfortheproducts,or

youcandothislater.6. WhenyouclickonNext,youwillgetanoverview.WhenyouclickonGenerate

Products,theproductswillbedisplayedasshowninthefollowingscreenshot:

7. ClickontheSavebuttonandapopupwillshowuptocreateaspecificattributesetforthisconfiguration.SelecttheAddconfigurableattributestothenewsetbasedoncurrentoptionandselectaSizeforthename.

8. ClickonConfirmtosavetheproductsinthedatabase.9. OntheProductEditpage,selectthecategorywhereyouwanttoaddtheproduct,

clickonSave,andsearchfortheproductintherightcategoryinthefrontend.Theproductdetailpagewilllookasfollows:

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

Thisisthereasonwhywehavegeneratedsimpleproductstobeshownasoptionsinourconfigurableproduct.Theconfigurableproductisaparentwrapperthatisusedtodisplayinthefrontend.ThesimpleproductsarehiddeninthefrontendbytheVisibilityattribute.

Whenaproductissold,theSKUoftheselectedchildproductwillbeusedtoprocesstheorder.That’sthereasonwhywehavetoconfigureastockonthechildproducts.

There’smore…InMagento,wecancreatesixtypesofproduct.Thefollowingoverviewgivesashortdescriptionofwhatispossiblewiththedifferentproducttypes.

AsimpleproductAsimpleproductisjustaproductthatyoucansellinyourwebshop.EveryproductinMagentohasauniqueID(SKU)thatmostlyhasthesamevalueasthearticlecodeofthesuppliers.

AconfigurableproductInthisrecipe,wecreatedaconfigurableproduct.Thisproducthaschildproductsthatyoucanconfigureontheproductpage(forexample,toconfigurethesize).Thechildproductsaresimpleproducts.

AbundleproductAbundledproductislikeaconfigurableproduct,butwiththisone,youcan(optionally)specifymoreoptions.

Inthesampledata,youcanfindagoodexamplebynavigatingtoGear|FitnessEquipment|SpriteYogaCompanionKit.

NoteEveryoptionofabundlerepresentsanotherproductintheshop.Whenyouaddabundleproducttothecart,theproductsofthechosenconfigurationwillalsobeaddedtothecartinthebackground.

AgroupedproductAgroupedproductisaproductthatrepresentsasetinwhichyoucanspecifythenumberofchildproducts.AgoodexampleofthiscanbefoundbynavigatingtoGear|FitnessEquipment|SetofSpriteYogaStraps.

Inagroupedproduct,youassignsimpleproducts.Whenaddingagroupedproducttothecart,thechildproductswillbeaddedasseparateproductswiththeconfiguredquantity.

AvirtualproductAvirtualproductislikeasimpleproductbutitisnotphysical.Ithasnoinventoryandcan’tbeshipped.Agoodexampleofavirtualproductisasoftwarelicense.

AdownloadableproductAdownloadableproductisaproductthatisnotphysical.Whenacustomerbuysadownloadableproduct,adownloadlinkwillbesenttothecustomersothattheycandownloadtheirproduct,whichisintheformofaPDF,MP3,ZIPfile,oranyothertypeoffile.

AddingsocialmediabuttonsThesedays,anincreasingnumberofpeoplearesharingtheirmindsthroughdifferentsocialmedia,suchasFacebook,Twitter,andmanymore.

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

GettingreadyInthisrecipe,wewilladdsharebuttonsforthefollowingplatforms.Youcantakealookatthedeveloperdocumentationofeachplatformasapreparatorystep:

Facebook(https://developers.facebook.com/docs/plugins/like-button)Twitter(https://about.twitter.com/resources/buttons)GooglePlus(https://developers.google.com/+/web/+1button/)

Toshowabuttononeveryproductpage,wehavetodosomecodechanges.EnsurethatyouhaveaccesstothecodewithyourIDE.

HowtodoitThefollowingstepsshowyouhowtoaddsocialmediabuttonstothedescriptionofyourproduct:

1. Openthepageoftheproductwhereyouwanttoaddthesocialmediabuttons.2. WewillstartwiththeLikebuttonofFacebook.Visit

https://developers.facebook.com/docs/plugins/like-buttonandconfigurethefollowingformwithyourdata:

3. WhenclickingtheGetCodebutton,youwillseethecodeofthebutton.Placethiscodeintheproductdescriptionfield.

4. SavetheproductandopentheProductDetailpageofthatproductinthefrontend.YouwillseeaLikebuttoninthedescriptionfieldoftheproduct.

5. Wecanalsoaddbuttonsforsharingonothersocialmediausingthesameprinciple.ForTwitter,wecangetthecodeofabuttonathttps://about.twitter.com/resources/buttons.Fromhere,copythecodeandpasteitafterthecodeoftheFacebookLikebutton.

6. Similarly,forGooglePlus,wecangetabuttonathttps://developers.google.com/+/web/+1button/fromwherewecancopythecodeandpasteitinthedescriptionfieldoftheproduct.

7. Whenreloadingthefrontend,wecanseethebuttonsonthedetailpageofthatproduct.Ifwewanttoshowitoneverypage,wewillhavetomakeachangeinthecodeoftheproductdetailtemplate.

8. Ifyouhaveacustomtheme,copyandpastetheapp/code/Magento/Catalog/view/frontend/templates/product/view/description.phtml

filetotheconfiguredapp/design/frontend/<package>/<theme>/Magento_Catalog/templates/product/view/description.phtml

theme.

Note

Ifyouhavenocustomthemeinstalled,takealookattheCreatingaMagento2themerecipeofChapter3,Theming.Inthatrecipe,allthestepsareexplainedonhowtodothis.

9. Addthecodeofthesocialmediabuttonsattheendofthefile.YougeneratetheproductURLwiththefollowingcode:$block->getProduct()->getProductUrl().UsethiscodetogeneratetheURLforthebutton.ThecodefortheFacebookLikebuttoncodewilllookasfollows:

<divclass="fb-like"

data-href="<?phpecho$block->getProduct()->getProductUrl()?>"

data-width="450"

data-layout="standard"

data-action="like"

data-show-faces="true"

data-share="true">

</div>

10. Savethefile,cleanthecache,andyouwillseetheLikebuttononeverypage.

HowitworksAsocialmediabuttonismostlyapieceofexternalHTMLthatwillrenderinyourwebsite.Withthisbutton,apagecanbeshared.

Torenderthesebuttons,externalstaticresourcesfromthesocialmediawebsitewillbeloaded.Whenyoureadthecodeofthebuttons,youcanseethatadditionalJavaScriptlibrariesareincludedinthecode.

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

ThesetagsaregeneratedbyMagentoonaproductdetailpage,soagoodtitle,image,anddescriptionwillbedisplayedforthepost.

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

Inthisrecipe,wewillfocusonhowtoaddaYouTubevideoinaproductdescription.

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

HowtodoitThenextstepsshowsyouhowtoembedaYouTubevideoonaproductdetailpage.

1. OntheYouTubevideopage,clickontheEmbedbutton.Whenyouclickthisbutton,thefollowingscreenshowsup:

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

HowitworksTheabilitytouseHTMLtagsinproductdescriptionsgivesalotofflexibilityforthisfield.ItispossibletouseaWYSIWYGeditorforthecontentbecausethisallowsustousewidgets,suchasaYouTubevideoorotherthird-partywidgets.

OnaYouTubevideopage,weopenedtheembedoptionswherewecanconfigurethecodethatwewillincludeinoursite.Wecanspecifyoptionssuchaswidth,height,colorandmore.

Wheneverythingisconfigured,wecanpastetheEmbedcodeintheHTMLoftheproductdescription,andthevideowillbevisibleinthedescriptionoftheproduct.

ChangingtheURLofaproductpageWhenyouareonaproductpage,theURLofeveryproductalwayslooksclean.ThenameintheURLmakesitverySEOfriendly.

Inthisrecipe,wewillexplorethepossibilitiesofURLrewritesinMagento.

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

HowtodoitInthefollowingsteps,wewillseetheprocedureforchangingaURLofaproductdetailpage.

1. Findtheappropriateproductinthefrontend.YoucanfindbynavigatingtoGear|Bags.Ifyouopentheproductdetailpage,youwillseetheURL/endeavor-daytrip-backpack.html.

2. Inthebackend,changetheURLkeyattributetobuy-now-endeavor-daytrip-backpack.

3. Reloadtheproductinthefrontend.TheURLwillchangetotheonewehavejustenteredinthebackend.

TipWhenyouselecttheCreatePermanentRedirectoptionforanoldURLcheckbox,Magentowillcreateapermanent301redirectresponsefortheoldURLoftheproduct.ThecheckboxislocatedintheproducteditpageinthebackendundertheURLKeyattribute.

4. EmptytheURLkeyattributeatthebackendandsavetheproductagain.YouwillseethatMagentoautogeneratestheURLkeyattributebasedonthenameoftheproduct.

5. Atthebackend,navigatetoStores|Configuration|Catalog|SearchEngineOptimizations.CleartheproductURLSuffixfieldandsavetheconfiguration.

6. ClearthecachebynavigatingtoSystem|CacheManagement.7. Reloadtheproductinthefrontend,andyouwillseethatthe.htmlsuffixisgone.

NoteInMagento1,therewasaURLrewriteindex,inMagento2,thisindexisreplacedbyanewsystemtogeneratetheURLs.

HowitworksInMagento,thereisaURLrewritesystemthatmapsanSEO-friendlyURLtothesystem’sURL.Intechnicalterms,thisisalsocalledrouting.Inthebackend,youcanseealltheURLrewritesthatareavailableintheinstallation.

YoucanseethisbynavigatingtoMarketing|SEO&Search|URLRewritesinthebackend.Onthispage,youcanseethecompletelistoftheURLsthatareavailableinthewebshop.Ifwesearchforendeavor-daytrip-backpack,wewillseealistofalltheURLs,whichlookslikethis.

Whatweseeisthefollowing:

Permanent301redirectresponses(rowswheretheRedirectTypecolumnisPermanent(301))TheProductURLTheURLofaproductineverycategory

AlltheURLsaregeneratedseparatelyforeachstoreviews.Whenaproductisenabledinmultiplestores,itisnormalthataproducthasmorethanoneURL.

There’smoreOntheURLrewritepage,itisalsopossibletoaddcustomURLrewrites.Forexample,aURLrewriteforthecontactpage.

WhenaddingtheAddnewURLRewritebutton,aformshowsup.Thefollowingscreenshotshowsushowwecancreateanaliasfromthecontact-us.htmlURLtothe/contactpage:

IntheStoreoption,youcanconfigurethestorefortheURLrewrite.

ThevalueintheRequestPathfieldisthepaththatyouwanttorewrite;inthiscase,wewanttorewritesomethingonthe/contact-us.htmlpath.

ThevalueintheTargetPathfieldisthepathwheretherequestwillend;inthiscase,itisthe/contactpage.

IfthevalueinRedirectTypeissettoNo,thetargetpathwillberenderedontherequestpath(so,theURLdoesn’tchange).Youalsohavethechoicetoredirectthepagewithapermanent(301)ortemporary(302)redirect.

Chapter3.ThemingInthischapter,wewillcover:

ExploringthedefaultMagento2themesCreatingaMagento2themeCustomizingtheHTMLoutputAddingextrafilestothethemeWorkingwithLESSChangingapagetitleWorkingwithtranslationsAddingwidgetstothelayoutCustomizingemailtemplates

IntroductionThemostcommoncustomizationonaMagentoshopisthetheming.Makingagoodfirstimpressiontoyourcustomersisanimportantpointtoraisetheconversion.Almosteverystoreownerwantsalookandfeeloftheircompanyintheirshop.

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

ExploringthedefaultMagento2themesWhenMagento2isinstalled,therearetwothemesavailable.YouhavetheLumathemethatyoucanseeinthesampledata,andyouhavetheBlankthemethatisdevelopedasastartingpointtocustomizeyourtheme.

GettingreadyLogintothebackendandopenthedesignconfiguration.WecanfindthisinStores|Configuration|Design.

Howtodoit…ThefollowinginstructionsdescribehowwecanconfigurethethemesettingsforaMagento2store:

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

2. WehavenowconfiguredtheMagentoBlankthemeforthestore.TheMagentoBlankthemeisthedefaultthemewheretheMagentoLumathemewillextendfrom.

3. OpenthepageContent|ThemesinthebackendandyouwillseeanoverviewoftheavailablethemesinMagento.WhenMagento2isinstalledwithoutmodules,thefollowingthemesareavailable:

MagentoBlank(usedasthedefaulttheme)

MagentoLuma(usedinthesampledatastore)

4. WhenclickingonMagentoLuma,wewillseethedetailsofthethemelikethefollowingscreenshot:

5. WeseethattheParentThemeisMagentoBlank.ThismeansthatthisthemewillextendtheMagentoBlanktheme.

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

Howitworks…InMagento2theconceptofthemesismucheasierthaninMagento1.Therearenopackagesanymore,andnoskins.

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

LikeinMagento1,youcanoverridethethemesettingsindifferentwayssuchasthefollowing:

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

CreatingaMagento2themeWewillstartcustomizingthelookandfeeloftheshopbycreatingacustomtheme.Thepurposeofacustomthemeisthatwedon’thavetomodifythecorefilesdeliveredbyMagento.

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

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

Howtodoit…Thefollowingprocedureshowsyouwhichactionsarerequiredtocreateacustomtheme:

1. Forourtheme,wewillcreateavendornamespace.Wecandothisbycreatingthefolderapp/design/frontend/Packt.

NoteAthemenamespaceisalwayswritteninCamelCasesuchasMagento,Packt.

2. Inthatnamespace,wewillcreateathemecalledcookbook.First,createthefolderapp/design/frontend/Packt/cookbook.

NoteAthemeisalwayswritteninsmallletterssuchasluma,cookbook,blank.

3. Toregisterthetheme,wehavetocreatethefilewiththefollowingcontent:

<?php

\Magento\Framework\Component\ComponentRegistrar::register(

\Magento\Framework\Component\ComponentRegistrar::THEME,

'frontend/Packt/cookbook',

__DIR__

);

4. Atthispoint,wehavetocreateathemeconfigurationfile.Wecandothisbycreatingthefileapp/design/frontend/Packt/cookbook/theme.xmlwiththefollowingcontent:

<themexmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation="urn:magento:framework:Config/etc/theme.x

sd">

<title>PacktCookbook</title>

<parent>Magento/blank</parent>

</theme>

5. Itisalsopossibletoaddapreviewimagetothetheme.Inthethemefolder,createamediafolderinthethemefolderandaddanimageofyourchoicethatwewanttouseasapreview.

6. Tosetthepreviewimage,wehavetoaddthehighlightedXMLconfigurationtothetheme.xmlfile:

...

<title>PacktCookbook</title>

<parent>Magento/blank</parent>

<media>

<preview_image>media/preview.png</preview_image>

</media>

</theme>

7. Clearthecacheandrunthecomposerinstalltoregisterthetheme.8. Totestwhetherthethemeexists,navigatetothepageContent|Themesinthe

Magentobackend.Youshouldseethethemeinthelist.Whenweclickonthetheme,wecanseethedetailsthatwehaveconfiguredinthetheme.xmlfile.

9. Thelaststepistoconfigurethetheme.GotothepageStores|Configuration|Designandconfigurethethemelikethefollowingscreenshot:

10. Flushthecacheandreloadthefrontend.YourshopwillnowhavethelookandfeeloftheBlanktheme.

NoteIfyouwanttouninstallatheme,youcanusethecommandphpbin/magentotheme:uninstall.

Howitworks…WehavejustcreatedanemptythemethatinheritseverythingfromtheMagentobasetheme.Whenwewanttochangestuffonthetheme,wecandoitinthisthemewithoutoverridingthecore.

Whenwewanttocustomizesomeaspectsofthetheme,wecancopyafilefromtheMagento/Blankthemeandpasteitinourtheme.Whencopyingafile,thedirectorystructureneedstobethesameasthestructureoftheparenttheme.

NoteChangingcodeintheMagentocorewillalsowork,butthisisnotrecommended.Whenyouwanttoupgradeyourstore,allthecorefileswillbeoverwrittenandthenallyourchangeswillbelost.

Wecreatedatheme.xmlfileinourthemewherewehavesetthedefaultsettingsofthethemesuchasthenameandparenttheme.Magentowillstorethisdatainthethemetableofthedatabase.

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

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

Toenablethis,navigatetoStores|Configuration|Advanced|DeveloperandchangetheconfigurationscopedropdowntoMainWebsitelikethefollowingscreenshot:

IntheDebugsection,youcanenablethehintsbysettingthedropdownvaluestoYesforthefollowingfields:TemplatePathHintsandAddBlockNamestoHints.

Whenthisisdone,reloadyourfrontendandyouwillseeredbordersaroundeachblockofHTML.

CustomizingtheHTMLoutputYoucancustomizeathemeintwoways.Wecanonlychangesomestylestomakeashoplookdifferent.ThesecondwayistocustomizetheHTMLoutput,whichiswhatwewillcoverinthisrecipe.

ItisverycommontowanttochangesomestuffontheHTMLstructuretomakeyourshoplookunique.

GettingreadyMakesureyouhavethethemeinstalledandconfiguredlikewehavedoneinthepreviousrecipe.

Ifyoudon’thavethethemeinstalled,youcaninstallthestartfilesforthisrecipe.

Howtodoit…Inthenextsteps,wewillchangethelogo,changeatemplate,andwewilladdextrablockstothefooter:

1. Firstwewillchangethelogo.Ifyoulookatthefrontendofthewebshop,youwillseethatthedefaultMagentologoisused.ThisisanSVGimagebutwhatifIhaveaPNGimage?Createalogo.pngfileinthefolderapp/design/frontend/Packt/cookbook/web/images.Ifthisfolderdoesn’texist,createone.

2. Thesecondstepistocreatealayoutconfigurationfileinthefolderapp/design/frontend/Packt/cookbook/Magento_Theme/layout.

3. Inthatfolder,createadefault.xmlconfigurationwiththefollowingcontent:

<?xmlversion="1.0"?>

<pagexmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/pa

ge_configuration.xsd">

<body>

<referenceBlockname="logo">

<actionmethod="setLogoFile">

<argumentname="logo_file"

xsi:type="string">images/logo.png</argument>

</action>

</referenceBlock>

<referenceBlockname="logo"remove="true"/>

</body>

</page>

4. Flushthecacheandreloadthefrontend.Youshouldseethatthelogoischangedtothespecifiedfile.

5. Nextwewillchangethetoolbaronthecategorypages.Inthefrontend,navigatetoacategorypagewithproductssuchasWomen|Tops.

6. Tochangetheoutputofacategorypage,wehavetooverridethetemplate.Toknowwhattemplateisused,wecanenablethetemplatehints.EnablingthetemplatehintsisdescribedintheThere’smoresectionofthepreviousrecipeCreatingaMagento2theme.

7. Withthetemplatehintsenabled,weseethetemplatelocatedinapp/code//Magento/Catalog/view/frontend/templates/product/list.phtml.Tooverridethistemplate,copyandpastethatfileinapp/design/frontend/Packt/cookbook/Magento_Catalog/templates/product/.Ifthatfolderdoesn’texist,createit.

8. Cleanthecacheandreloadthepage.Inthetemplatehints,youwillseethatthefilewejustcopiedisusedinsteadofthedefaultone.

TipIfyoudon’tseeanychangestothefrontend,youhavetoflushtheMagentocache.Alsodisablethefull-pagecachewhendeveloping.

9. Inthisfile,wecanchangewhatwewantwithoutoverridingthecore.10. Atlast,wewilladdanextramenuwithlinksinthefooter.Forthis,weneedtoedit

thefile:app/design/frontend/Packt/cookbook/Magento_Theme/layout/default.xml.

11. PastethefollowinghighlightedcodeinthatXMLfile:

...

</action>

</referenceBlock>

<referenceBlockname="footer">

<blockclass="Magento\Framework\View\Element\Html\Links"

name="footer_links_account"after="footer_links">

<arguments>

<argumentname="css_class"xsi:type="string">footer

links</argument>

</arguments>

<block

class="Magento\Framework\View\Element\Html\Link\Current"name="my-

account-link">

<arguments>

<argumentname="label"xsi:type="string">My

account</argument>

<argumentname="path"

xsi:type="string">customer/account</argument>

</arguments>

</block>

<block

class="Magento\Framework\View\Element\Html\Link\Current"name="my-cart-

link">

<arguments>

<argumentname="label"xsi:type="string">My

cart</argument>

<argumentname="path"

xsi:type="string">checkout/cart</argument>

</arguments>

</block>

</block>

</referenceBlock>

</body>

</page>

12. Youhavetopastethehighlightedcodeasachildofthe<body>tag.13. Cleanthecacheandreloadthefrontend.Youshouldnowseeanextracolumninthe

footerwithtwolinksinit.14. Toendthisrecipe,wewilladdalinktothemenuwehavejustcreatedbutthatmenu

itemisonlyvisibleonthecartpage.Torealizethis,wehavetoaddthefileapp/design/frontend/Packt/cookbook/Magento_Theme/layout/checkout_cart_index.xml

15. Inthatfile,addthefollowingcontent:

<?xmlversion="1.0"?>

<pagexmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

layout="1column"

xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/pa

ge_configuration.xsd">

<body>

<referenceContainername="footer_links_account">

<block

class="Magento\Framework\View\Element\Html\Link\Current"

name="checkout-link">

<arguments>

<argumentname="label"xsi:type="string">Goto

checkout</argument>

<argumentname="path"

xsi:type="string">checkout/onepage</argument>

</arguments>

</block>

</referenceContainer>

</body>

</page>

16. Cleanthecacheandgotothecartpage.Youwillseethatthereisanextralinkinthemenu.

Howitworks…Withthetemplatehintson,weseethatalotofHTMLblocksareusedtobuildthepage.AlltheseblocksareconfiguredinthelayoutXMLfiles.

Everyblockinthepageisfromaspecifictype.Thistypeistheclassthatisusedtogeneratetheblock.Theblockclasscontainsfunctionsthatcouldbecalledinthetemplatebycallingthe$blockvariable.

InthelayoutXMLfiles,thetypeoftheblockisspecifiedwiththeclassattribute.

Forchangingthelogofile,wehavechangedaparameteroftheblockclassthatisusedforthelogo.WiththeXMLconfigurationinthedefault.xmlpage,wehavemodifiedthelogo_fileparameterofthatclassforallpages.Everyblockhasanameandbyusingthe<referenceBlockname="block_name">tag,wecanmodifythecontentsoftheblock.Wecanmodifypageargumentslikewehavedonewiththelogoorwecanaddchildblockslikewehavedonewiththeextrafootermenu.

Theextrafootermenuisanewblockthatwehavespecifiedasachildofthefooterblock.Inthefootermenublock,wehavespecifiedthelinksthatarealsoimplementedasablock.

Weaddthenewblockinthedefault.xmlfile.Thismeansthattheconfigurationisloadedonallpages.

Ifyouwanttoaddaconfigurationthatisonlyforaspecificpage,youhavetoputthatconfigurationinadifferentfile.Thenameofthefileisthelayouthandlethatisusedsuchascheckout_cart_indexforthecartpage.

AddingextrafilestothethemeInthepreviousrecipe,welearnedhowwecanmakestructuralchangestotheHTMLoutput.HoweveranHTMLoutputisnotacompletedwebsite.WithJavaScriptandCSS,wecanthemetheHTMLoutput.

GettingreadyWewilllearnhowwecanaddCSSandJavaScriptfilestoourtheme.ThisisacommoncasewhenintegratinganexternalJavaScriptplugin.Todothis,wehavetoworkinthethemefolderthatwecreatedintherecipeCreatingaMagento2themeearlierinthischapter.

Howtodoit…Thefollowingstepsdescribehowwecanaddextrafilestoatheme:

1. OpenthefrontendandopentheHTMLsourceofapage,andhavealookatthe<head>section.WeseethattheincludedJSandCSSfilesarelocatedinthepub/staticmap.

2. IfwewanttoaddanextraCSSfilewehavetoconfigurethatinthelayoutXMLfiles.Openorcreatethefileapp/design/frontend/Packt/cookbook/Magento_Theme/layout/default_head_blocks.xml

3. Inthatfileaddthefollowingcontent:

<?xmlversion="1.0"?>

<pagexmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/pa

ge_configuration.xsd">

<head>

<csssrc="css/cookbook.css"/>

</head>

</page>

4. Createthefileapp/design/frontend/Packt/cookbook/web/css/cookbook.cssandaddthefollowingcontentinit:

body{

background-color:#dcdcdc;

}

5. Cleanthecacheandreloadthefrontend.Youwillseethatthebackgroundcolorischangedtolightgrayandthatthecookbook.cssfileisincludedinthe<head>section.

NoteInthenextrecipe,wewillseehowwecangenerateCSSusingtheLESSpre-processorinMagento2.Thismethodisrecommendedwhenyouwanttoadda3rdpartyCSSsuchastheCSSofajQueryplugin.

6. ThenextthingistoaddaJavaScriptfiletothe<head>section.ThisworksthesamewayaswiththeCSSfile.

7. Openthefileapp/design/frontend/Packt/cookbook/Magento_Theme/layout/default_head_blocks.xml

andaddthehighlightedcodeasachildofthe<head>tag:

<head>

<csssrc="css/cookbook.css"/>

<scriptsrc="js/cookbook.js"/>

</head>

8. Createthefilecookbook.jsinthefolderapp/design/frontend/Packt/cookbook/web/js/.

9. Cleanthecacheandreloadthesourceofthefrontend.Youwillseethatthe

cookbook.jsfileisaddedinthe<head>.

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

WhenrenderingtheHTMLoutput,Magentowilllookforall<head>configurationentriesandwillgeneratealistofCSSandJSfiles.

WecreatedtheCSSandJavaScriptfilesinthethemefolder.ButifwelookattheHTMLsource,weseethatMagentoloadsthefilefromadifferentlocation.Thefilesareloadedfromthepub/staticfolder.

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

There’smore…InthisrecipewelearnedhowwecanaddCSSandJavaScriptfilestotheHTMLhead,butwiththesamesystemwecanalsoaddmetalinkandtitletagstothehead.Thefollowingcodesnippetshowsyouhowthisworks.Thisisanexampleofthedefault_head_blocks.xml:

<?xmlversion="1.0"?>

<pagexmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Fr

amework/View/Layout/etc/page_configuration.xsd">

<head>

<csssrc="css/cookbook.css"/>

<scriptsrc="js/cookbook.js"/>

<linkrel="publisher"src="https://www.packtpub.com/"/>

<metaname="author"content="PacktPublishing"/>

</head>

</page>

The<title>configurationismissinginthisconfiguration.HowtochangeapagetitleisexplainedintherecipeChangingapagetitleinthischapter.

TipIfyouwanttoexplorewhichconfigurationsarepossibleinaparticularXMLfile,havealookattheXSDfilethatisdeclaredatthetopofthefileandyouwillseewhichconfigurationsyoucanuseinyourconfigfile.

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

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

MagentohasaLESSpre-processorthatgeneratesaCSSfilefromtheLESSfilesinthetheme.

GettingreadyOpenyourfavoriteIDEandnavigatetothethemefolderofthePackt/cookbookthemethatwehavecreatedinthepreviousrecipes.

AccesstoacommandlineisalsousefultoworkwithGrunt.

Howtodoit…Thefollowingstepsdescribehowwecanchangethelayout,suchashowMagento2doesit:

1. Copythefileapp/design/frontend/Magento/blank/web/css/source/_theme.lesstothefolderapp/design/frontend/Packt/cookbook/web/css/source/_theme.less.

NoteIfyouinstalledMagento2usingcomposer,youhavetocopythe_theme.lessfilefromthefoldervendor/magento/theme-frontend-blank/web/css/source/.

2. Inthatfile,addthefollowingcontent:

@cookbook_primary_color:#FECA5C;

@cookbook_dark_color:#373C40;

@text__color:@cookbook_dark_color;

@navigation__background:@cookbook_primary_color;

@button-primary__background:@cookbook_primary_color;

@button-primary__border:1pxsolid@cookbook_dark_color;

@button-primary__color:@cookbook_dark_color;

3. Reloadthepage.Normallyyouwillseenolayoutchangesbecausewehavetoclearthepre-generatedfile.Removethefollowingfolderswithpre-generatedCSS:

rm–rfpub/static/*

rm–rfvar/view_preprocessed

Alsodon’tforgettoflushthecachebeforerenderingthepage.

4. Reloadthepageandyourpagewilllooklikethefollowingscreenshot.TheloadtimeofthepageisabitlongerbecausetheCSSneedstobegenerated:

NoteIfyougetapagewithoutstyling,anerroroccurredwhenrenderingtheCSSfile.Ifthishappens,flushthecacheandremovethefoldersagain,asdescribedinstep3.

Anotherthingthatcanhappenisthatthepubandvarfoldersdonothaveenoughfilepermissions.

5. WhenwewanttoaddsomeCSSentriestotheheader,wehavetocopythefileapp/design/frontend/Magento/blank/Magento_Theme/web/css/source/_module.less

toapp/design/frontend/Packt/cookbook/Magento_Theme/web/css/source/_module.less

6. Addthefollowinghighlightedcodeinthatfilearoundline293.Thiswillgivethetopmenuadarkercolor:

...

.page-header{

border:0;

margin-bottom:0;

.panel.wrapper{

background-color:@cookbook_dark_color;

li{

a{

color:@color-white;

}

}

border-bottom:1pxsolid@secondary__color;

}

.header.panel{

padding-top:@indent__s;

padding-bottom:@indent__s;

&:extend(.abs-add-clearfix-desktopall);

}

.switcher{

display:inline-block;

}

}

...

7. Cleanthecacheandremovethefolderspub/static/*andvar/view_preprocessed,andreloadthepage.Youwillseethatthetopbarisnowinadarkcolor.

Howitworks…WhenworkingwithLESS,thereareafewwaysofdoingit.ThegoalistoavoidduplicateCSScodethatisloadedinthebrowser.

First,wedidsomestylingbychangingsomevariablesthataredefinedbyMagento.Thepurposeofthetheme.lessfileintheweb/css/sourcedirectoryistooverridethedefaultvariablesoftheMagentotheme.TheLESScompilerconvertstherightvaluestotherightCSSentries.

Ifyouwanttoknowwhichvariablesareavailable,havealookinthefolderlib/web/css/source/lib/variables.Inthisfolder,thevariablesofthedefaultMagentocomponentsareinitialized.Onefolderaboveintheweb/css/source/lib,theCSSstructureofthesecomponentsisdefined.

ThesecondthingwedidwastooverrideathemeLESSfilefromtheMagento_Thememodule.Wecopiedtheoriginalfiletoourtheme.Thismeansthatthefileofourthemeisloadedinsteadofthedefaulttheme.

Thisprocessiscalledthefallbackmechanism.Whenafileisloaded,Magentowilllookforitinthefollowingorder:

Themefolder(app/design/frontend/<Vendor>/<Theme>/web/css)Parentthemefolders(app/design/frontend/<Vendor>/<Theme>/web/css)Modulefolder(app/code/<Vendor>/<Module>/view/frontend/web/css)

NoteIfyouinstalledMagento2usingcomposer,thedefaultandLumathemeofMagentoareinthevendor/magentofolder.

WhenthereisachangeinaLESSfile,wehavetocleartwofolders.Thefirstoneisthevar/view_preprocessedfolder.Inthisfolder,alltheparticularLESSfilesaremergedinalargefile.

Thatlargefilewillbecompiledinthefolderpub/static/frontend.Sothat’sthereasonwhywehadtoclearboththefolders.

WhenMagentoloadsfilesfromthepub/static/frontendfolderthatdoesn’texist,Magentowilllookinitscorefolderstomakethosefilesavailableinthisfolder.ForCSSfiles,Magentowillstartthegenerationofthem.Forotherfilessuchasimages,Magentowillcreatesymlinkstothesourcefile.

Changingfilesinthepubfolderisnotagoodideabecauseyouhavetodoitintheappfolderandregeneratethefilesinpub.

TipItisalsopossibletousetheJavaScriptcompiler.Inthebackend,youcanenableitonthepageStores|Configuration|Advanced|Developer|Front-enddevelopmentworkflow.

There’smore…WhenworkinginLESS,itisnotconvenienttoalwaysclearthecontentsofthepub/staticandvar/view_preprocessedfolderswhentestingtheresultofachange.

FornormalLESS,youcaninstallaLESSwatcher,butwiththemodulararchitectureofMagento,wecanusetheJavaScripttaskrunnerGrunt.

WithGrunt,wecanconfigureawatcherforourtheme.WhenthereisachangeinaLESSfile,GruntwillregeneratethepublicfilefollowingtherulesofMagento.

TouseGrunt,wefirsthavetoinstallnodejsandgrunt-cli.Wecandothiswiththefollowingcommands(onaUbuntuserver):

sudoapt-getupdate

apt-getinstallnodejs

sudoapt-getinstallnpm

sudonpminstall-ggrunt-cli

WhenGruntisinstalled,wecanconfigureitforourdemoshop.OpenyourterminalandchangetotheMagentoprojectroot.Inthisdirectory,runthefollowingcommands:

npminstallgrunt--save-dev

npminstall

npmupdate

npminstallgrunt-contrib-less

NoteTheseinstallationinstructionsaretestedonanUbuntuServer.Ifyouwanttoinstallthisonotheroperatingsystems,youcanfindmoreinformationonthefollowingURL:http://gruntjs.com/installing-grunt.

Addyourthemebyaddingthefollowingconfigurationtothefiledev/tools/grunt/configs/themes.js:

cookbook:{

area:'frontend',

name:'Packt/cookbook',

locale:'en_US',

files:[

'css/styles-m',

'css/styles-l'

],

dsl:'less'

},

Whenrunningthecommandgruntexec:cookbook,thefileswillbegeneratedforthecookbooktheme.

Whenrunninggruntwatch,thesefileswillautomaticallybegeneratedafterafilechange.

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

Inthisrecipe,wewillchangethepagetitleofthecontactpagethatisavailableatthe/contactpath.

Howtodoit…Tochangethepagetitleofthecontactpage,havealookatthefollowingsteps:

1. Gotothecontactpageinthefrontend.Thisisavailableatthepath/contact.2. YouseethatthepagetitleissettoContactUs.WewillchangethistoGiveusa

message.3. Createthefilecontact_index_index.xmlinthefolder

app/design/frontend/Packt/cookbook/Magento_Contact/layout.Ifthatfolderdoesn’texist,createit.

4. Inthecontact_index_index.xmlfile,pastethefollowingcontent:

<?xmlversion="1.0"?>

<pagexmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

layout="1column"

xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/pa

ge_configuration.xsd">

<head>

<title>Giveusamessage!</title>

</head>

</page>

5. Cleanthecacheandreloadthefrontend.YouwillseethatthepagetitleischangedtoGiveusamessage!.

Howitworks…The<title>andothertagsintheheadaregeneratedintheclasslib/internal/Magento/Framework/Page/Config/Renderer.php.WhenyoulookfortherenderTitle()function,youseethefollowingcode:

publicfunctionrenderTitle()

{

return'<title>'.$this->pageConfig->getTitle()->get().'</title>'.

"\n";

}

ThisfunctionlooksinthepageConfigforatitletag.ThepageConfiglooksinthe<head>tagoftheXMLconfigurationfile.

Forothertypesofpages,suchasaproductdetailpagewherewecansetthetitleinthebackend,thepageConfigvalueswillbeoverwrittenafterloadingthelayoutXMLconfiguration.Butforthecontactpage,thisisnotthecase.

WorkingwithtranslationsMagentohastheabilitytorunastoreinmultiplelanguages.Everystoreviewhasalanguage,andMagentohasasystemtotranslatetheinterfaceintothatparticularlanguage.

GettingreadyOpenthebackendandgotothestoreconfigurationatStores|Configuration|General.WewillconfiguretheFrenchlanguageforthedefaultstoreview.

Howtodoit…ThefollowingstepsshowhowwecantranslatetheinterfaceofMagentoinaspecificlanguage:

1. First,wewillconfigurethedefaultlanguageofthestoreview.Inthebackend,gotoStores|Configuration|Generalandchangethelocaletothepreferredvalue.InMagento,thefollowinglanguagepacksareinstalled:

en_US—English(UnitedStates)de_DE—German(Germany)es_ES—Spanish(Spain)fr_FR—French(France)nl_NL—Dutch(Netherlands)pt_BR—Portuguese(Brazil)zh_CN—Chinese(China)

ThisrecipeworkswithashopintheFrenchlanguage.

2. Whencleaningthecacheandreloadingthefrontend,youwillseethattheinterfacetextsaretranslatedintoFrench.

3. Whenyouwanttotranslateexistingstrings,wecandothisbytheInlineTranslationtoolandwecandoitinthetheme.WecanenabletheInlineTranslationtoolinthebackendonthepageStores|Configuration|Advanced|Developer|TranslateInline.

4. Reloadthefrontendandyouwillseeredframesaroundeachinterfacetext.Whenhoveringoveraframe,aniconwillappear.Clickonitandapop-upwindowwillbeshownwiththetranslationform:

5. Whensubmittingthisform(byclickingSOUMETTRE),andafterclearingthecache,youwillseethatthevalueyouenteredinthisformisused.

6. Thesecondwayoftranslatingtheinterfaceistouseathemetranslation.Foraddingcustomtranslationsforatheme,wehavetocreatethefollowingfile:app/design/frontend/Packt/cookbook/i18n/fr_FR.csv.

7. WhenwewanttotranslatethestringPanierontheshoppingcartpage,wehavetoknowtheoriginalstring.YoucanfindtheoriginalstringintheInlineTranslationpopuporinthetemplates:

TheoriginalstringforPanierisShoppingCart.Whenweaddthe

followinglineintheCSVfilewehavejustcreated,thestringwillbe

translatedtothatvalueShoppingCart,Votrepanier.

8. Cleanthecacheandreloadtheshoppingcartpage.YouwillseethatthetitleischangedfromPaniertoVotrepanier.

Howitworks…Magentohasapowerfultranslatefunction.Tomakeastringtranslatablethroughthatfunction,wehavetousethefollowingsyntax:

__('Translatablestring')

Whenusingthissyntax,thefollowingfallbackmechanismwillbeused:

First,Magentowilllookinthedatabasetabletranslation.ThistableisusedtosavethevaluestranslatedwithInlineTranslation.Ifthestringisnotfoundinthetranslationtable,Magentowilllookforitinthetheme.Itwillsearchtoseewhetherthestringispresentinthefileapp/design/frontend/<VendorName>/<ThemeName>/i18n/<locale>.csv.Thelastfallbackisthetranslationfilesofthemodules.Thesearelocatedini18n/<locale.csvofthemodule.Ifnomatchingstringisfound,thetranslatefunctionwillreturntheoriginalstringthatispassedasanargumenttothetranslatefunction.

AddingwidgetstothelayoutMagentohasasetofpredefinedwidgetsthatyoucanconfigureandshowonthedifferentpages.WiththeMagentowidgetinterface,wecanconfiguredifferentwidgetsondifferentpages.

GettingreadyWewilladdablockofproductsonthecontentareaofthehomepage.GotothebackendandnavigatetoContent|Widgets.

Howtodoit…Inthefollowingsteps,wewillconfigureawidgetforthecategorypages:

1. ClickonthebuttonAddWidget.2. Intheform,setthefollowingvalues:

Type:CatalogProductsListDesignTheme:Packtcookbook

3. Clickcontinueandthefollowingscreenshowsup:

4. CompletetheStorefrontPropertiestabwiththefollowingvalues:

WidgetInstanceTitle:Widget-home-productsThisisthetitleofthewidgetinthebackend.Astructuralnameiseasywhen

workingwithalotofwidgetsAssigntoStoreViews:AllStoreViews

5. CompletetheWidgetOptionstabwiththefollowingvalues:

Title:FeaturedproductsNumberofProductstoDisplay:10Conditions:Chooseaspecificcategorylikethefollowingscreenshot:

6. SavethewidgetbyclickingSaveandContinueEdit.Thewidgetinstanceisnowsavedbutnothingwillshowupinthefrontendbecausethereisnolayoutupdateset.

7. Wehavetocreatealayoutupdatebeforethewidgetwillshowupinthefrontend.Onthewidgetpage,opentheFrontendPropertiesandcompletetheLayoutUpdatessectionasfollows:

8. Clearthecacheandreloadthehomepage.Youwillseealistwithproductsfromtheconfiguredcategory.

Howitworks…IntheMainContentArea,anewblockisaddedtothefrontend.Likeanyotherblockinthefrontend,thisblockhasablockclassandatemplatesimilartootherblocks.

TheonlydifferenceisthatthisblockisnotgeneratedbyanXMLfilebutbyanXMLlayoutinstructioninthedatabase.

ThewidgetinterfacewillgeneratealayoutXMLthatisstoredinthedatabase.TheblockclassandtemplatearesimilartootherblocksintheXMLfiles.

CustomizingemailtemplatesMagentohasalotoffunctionalitythatsendsemailsonparticularactions,suchasaneworder,customerinformation,newsletter,andmanymore.

Whencustomizingthelookandfeelofyourwebsite,itisnicethatyouremailtemplateshavethesamelookandfeel.

Inthisrecipe,wewilllearnhowwecandothis.Wewillcustomizethenewaccountemailandwewilllearnhowwecaneditthegenericheaderandfooterofthetransactionalemails.

GettingreadyWewillcustomizeemailtemplates.Totestthis,makesureyourdevelopmentenvironmentcansendemails.

Likethepreviousrecipes,wewillbuildthecodefurtheronthethingsthatwehavedoneinthepreviousrecipes.Ifyoudon’thavethecompletecode,youcaninstallthestartfilesforthisrecipe.

Howtodoit…Thefollowingstepsshowhowwecancustomizetheemailtemplateswithoutoverridingthestandardtemplates:

1. Theemailtemplatesarelocatedintheviewfolderofeachmodule.Ifwewanttochangethenewaccountemail,wehavetocopytheoriginalfiletoyourtheme.Copythefileapp/code/Magento/Customer/view/email/account_new.htmlandpastethisinthefolderapp/design/frontend/Packt/cookbook/Magento_Customer/email/.

NoteIfyouinstalledMagentowithcomposer,youhavetocopythefilevendor/magento/module-customer/view/frontend/email/account_new.html.

2. Whenopeningthenewfile,youcanmodifythecontentoftheemailinanywayyouwant.Wecanusethefollowingvariablestomakethefiledynamic:

{{storeurl='<path>'}}TheURLofthestore.Youcanalsogiveapathbetweenthequotes.{{varcustomer.<field>}}Thecustomerobject.Afterthedot,youcanspecifytheattributeofthecustomerobject.{{varstore.<field>}}Thestoreobjectwheretheemailissentfrom.{{configpath='<configpath>'}}Avalueoftheconfigurationtablecore_config_data.

3. Savethefileandcreateanewaccountinthefrontendwithyouremailaddress.Youwillreceiveanemailofanewaccount.Ifyouopenit,youwillseethatthevariablesarereplacedbytheinformationofMagento.

4. Whenwelookinthetemplate,weseethatanemailheaderisincludedinthetemplate.Ifwewanttochangetheheader,wehavetocopythefileapp/code/Magento/Email/view/frontend/email/header.htmltothefolderapp/design/frontend/Packt/cookbook/Magento_Email/email/.

5. Tomodifythefooter,wehavetocopytheapp/code/Magento/Email/view/frontend/email/footer.htmlfiletothesamefolder.

NoteIfyouinstalledMagentowithcomposer,youcanfindtheheaderandfooterfilesinthefoldervendor/magento/module-email/view/frontend/email/.

Howitworks…WhenanemailissentinMagento,thegeneralemailfunctionisused.Thisfunctionsendsanemailwiththerighttemplateandemailheaderssuchassubject,sender.

TheemailfunctionneedsatemplateandthattemplateisconfiguredintheconfigurationXMLfilesandusesafilefromtheemailfolder.

WhenanemailtemplateisconfiguredthroughtheconfigurationXML,itisalsopossibletooverwritethisemailtemplateinthebackend.ThiscanbemanagedonthepageMarketing|EmailTemplates.

NewinMagento2istheabilitytoworkwithaheaderandfootertemplatethatisusedinallthetransactionalemails.IntheolderversionsofMagento1,thiswasnotpossibleandwhenyouwantedtochangesomethingintheemailheader,youhadtoeditalltheemailtemplatefiles.

AlsonewinMagento2isthatyoudon’thaveanemailtemplateforeachlocale.Withthe{{trans"Yourstringtotranslate"}}code,youcanusetheMagentotranslationfilestomakeyouremailsmultilingual.

ThesetwofeaturesmakeitaloteasiertodevelopthetransactionalemailsinMagento2.

Emailtemplatesarenowpartofamodule.Intheview/<area>/emailfolderaretheemailtemplatesstored.Tomodifyatemplate,youcancopythefileandpasteitinthe<modulename>/emailfolderinthetheme.

Chapter4.CreatingaModuleInthischapter,wewillcoverthefollowingrecipes:

CreatingthemodulefilesCreatingacontrollerAddinglayoutupdatesAddingatranslationfileAddingablockofnewproductsAddinganinterceptorAddingaconsolecommand

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

Oneoftheadvantagesofamodulararchitectureistheextendibility.ItiseasytoaddmodulesthataddtoormodifythenativebehaviorofMagento.

Inthischapter,wewillcreateamodulewiththemostimportantthingsyouneedtoknowwhenwritingcodeinMagento.

CreatingthemodulefilesWhencreatingamodule,thefirststepistocreatethefilesandfolderstoregisterthemodule.Attheendofthisrecipe,wewillhavearegisteredmodulebutwithoutfunctionality.

Inthenextrecipes,wewilladdextrafeaturestothatmodule.

GettingreadyOpentherootfolderofyourMagento2website.Theapp/code/folderisthefolderwhereallthemoduledevelopmentneedstobedone.

AccesstoacommandlineisalsorecommendedbecauseMagento2hasabuilt-inconsoletoolwithalotofcommandsthatwecanuseduringthedevelopment.

Howtodoit…Inthefollowingsteps,wewillcreatetherequiredfilestoregisteraMagentomodule:

1. WewillcreateaHelloWorldmoduleinthePacktnamespace.InyourMagentoroot,createthefollowingfolders:

app/code/Packt

app/code/Packt/HelloWorld

app/code/Packt/HelloWorld/etc

2. Intheetcfolderofthemodule,createafilecalledmodule.xmlwiththefollowingcontent:

<?xmlversion="1.0"?>

<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.

xsd">

<modulename="Packt_HelloWorld"setup_version="2.0.0">

<sequence>

<modulename="Magento_Catalog"/>

</sequence>

</module>

</config>

NoteInMagento2,thereareXMLStyleDefinition(XSD)filesthatdescribesthestructureoftheconfigurationXMLfiles.Inthe<config>tag,thecorrectXSDfileisconfigured.

3. Toregisterthemodule,wehavetocreatearegistration.phpfileintheapp/code/Packt/HelloWorld/folderwiththefollowingcontent:

<?php

\Magento\Framework\Component\ComponentRegistrar::register(

\Magento\Framework\Component\ComponentRegistrar::MODULE,

'Packt_HelloWorld',

__DIR__

);

4. OpenyourterminalandgototheMagentodirectory.Inthisdirectory,runthefollowingcommands:

composerinstall

phpbin/magentocache:clean

phpbin/magentosetup:upgrade

5. WheneverythingisOK,youcanseethenameofthemoduleintheoutputofthelastcommand.

6. Totestthatthemoduleisinstalled,openthebackendandnavigatetoStores|Configuration|Advanced|Advanced,andcheckthatthemoduleispresentinthe

list.EnsurethatyouhavecleanedtheMagentocaches.

Howitworks…ModuledevelopmentinMagento2ismucheasierthaninMagento1.Theconceptofcodepoolsisgone,everythingisstoredinasinglefolder(code,translations,templates,CSS,andmore).ThesethingsmakeitaloteasiertodevelopandmaintainaMagentomodule.

Toinitialize,wehavetocreatethefoldersandthemodule.xmlfileintheetcfolderofthemodule.Inthemodule.xmlfile,weinitializethePackt_HelloWorldname,theversionnumber,andthesequence.

Whenwecreatedthemodulefiles,weexecutedthesetup:upgradecommand.Byrunningthiscommand,wewillruntheinstallorupgradeprocedureofallthemodules.Inthisprocess,alotofgeneratedclassesarecreatedinthevar/generationfolder.

Weusedthebin/magentotoolforcleaningthecacheandrunningtheupgradescripts.ThistoolwasintroducedinMagento2andisareplacementofthird-partytoolsfromMagento1(suchasn98magerunandwiz).

Whenrunningthephpbin/magentocommand,youcanseealistofallavailablecommands.Itiseasytoaddyourowncommandsinamodule.

CreatingacontrollerInyourMagentoroot,createthefollowingfolders:Wewilladdanextrapagethatwecanuseforseveralpurposes.

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

Howtodoit…Thefollowingstepsshowhowtoaddextrapagesusingcontrollersandcontrolleractions:

1. Createthefollowingfolders:

app/code/Packt/HelloWorld/etc/frontend

app/code/Packt/HelloWorld/Controller

app/code/Packt/HelloWorld/Controller/Index

2. Intheapp/code/Packt/HelloWorld/etc/frontendfolder,createaroutes.xmlfilewiththefollowingcontent:

<?xmlversion="1.0"?>

<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd

">

<routerid="standard">

<routeid="helloworld"frontName="helloworld">

<modulename="Packt_HelloWorld"/>

</route>

</router>

</config>

3. Inthelastfolder,thatis,app/code/Packt/HelloWorld/Controller/Index,createtheIndex.phpfilewiththefollowingcontent:

<?php

namespacePackt\HelloWorld\Controller\Index;

classIndexextends\Magento\Framework\App\Action\Action

{

/**

*Indexaction

*

*@return$this

*/

publicfunctionexecute()

{

}

}

4. Cleanthecacheusingthephpbin/magentocache:cleancommand.5. Openyourbrowserandnavigatetothe/helloworldURLoftheshop.Youwillseea

whitepage.Thisisnormalbecausethecontrolleractionisempty.6. Toloadthelayoutoftheshop,addthefollowingcodeintheindex.phpfile:

/**@var\Magento\Framework\View\Result\PageFactory*/

protected$resultPageFactory;

publicfunction__construct(

\Magento\Framework\App\Action\Context$context,

\Magento\Framework\View\Result\PageFactory$resultPageFactory

){

$this->resultPageFactory=$resultPageFactory;

parent::__construct($context);

}

publicfunctionexecute()

{

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

return$resultPage;

}

NoteIfyoustillseeawhitepage,thepageiscached.Youhavetoflushthecacheusingthephpbin/magentocache:flushcommand.ItisrecommendedthatyoudisabletheFullPageCache,asexplainedinthebeginningofthisrecipe.

7. WewillnowcreateanextraactionthatredirectsustotheHelloWorldpageandcreatetheapp/code/Packt/HelloWorld/Controller/Index/Redirect.phpfile.

8. Inthisfile,addthefollowingcontent:

<?php

namespacePackt\HelloWorld\Controller\Index;

classRedirectextends\Magento\Framework\App\Action\Action

{

publicfunctionexecute()

{

$this->_redirect('helloworld');

}

}

9. CleanthecacheandgototheURL/helloworld/index/redirect.Wewillberedirectedtotheindexaction.

10. Wecanalsochangethecontentoftheexecute()methodto$this->_forward('index').WewillseethesameoutputbuttheURLdoesn’tchangeinthebrowserbar.

Howitworks…AllpagesinMagentoareexecutedbycontrolleractions.Allthecontrollersareplacedinmodules,andeachcontrollercanhavemultiplecontrolleractions.ThisgivesusthefollowingstructureoftheURL:<modulenameorfrontname>/<controllerName>/<actionName>.

WhenyoucomparethecontrollerpartwithMagento1,alotofthingshavebeenchangedandmadeeasier.

InMagento2,everycontrolleractioniswritteninaseparateclass.ThisclassextendstheMagento\Framework\App\Action\Actionclass.Thecontrolleristhefolderwheretheactionsareplaced.

NoteItisalsopossiblethatthecontrollerisinaseparateclass,butthisisonlydonewhentherearegenericfunctionsthattheactionswilluse.AgoodexamplecanbefoundintheProductControlleroftheMagento_Catalogmodule.

Inacontrolleraction,theexecute()methodisusedtostarttherenderingofthepage.Whenwehavenothinginthismethod,thepagewillhaveanemptyoutput(blankscreen).

Ifwewanttorenderthelayout,wewillinitializetheresultPageFactoryinstanceinthe__construct()methodofthecontroller.Thisfactoryclassisusedtostartthelayoutrenderingofthepage.

Thesecondcontrolleractionwecreatedwasonethatdoesaredirecttoanotherpage.Whencallingthe_redirect()methodinacontrolleraction,a301redirectwillbereturnedtothegivenURL.

The_forward()methoddoeslikelythesame,butthisinternallyforwardstheactiontoanothercontroller.ThismeansthattheoutputofanothercontrolleractionwillberenderedonthepagebuttheURLwon’tchange.ThismethodisusedtotranslateanSEO-friendlyURL(suchasaproductURL)totherightcontrolleractionwiththerightparameters.

There’smore…Whenthingsarenotworkingasyouexpect,youcanusethefollowingtipstomakeitwork:

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

AddinglayoutupdatesInthepreviousrecipe,wecreatedapagewithoutcontent.Inthisrecipe,wewillmodifythecontentofthatpagewithlayoutupdates.

Withlayoutupdates,wecanarrangethestructureofthepageaswehaveseenintheCustomizingtheHTMLoutputrecipeofChapter3,Theming.Buthere,wewillseehowwecandothatinamodule.

GettingreadyThisrecipebuildsfurtheronthepreviousrecipe.Youneedtheinstallthemodulethatwecreatedinthepreviousrecipesofthischapter.

Howtodoit…Inthenextsteps,wewillseehowwecanmodifytheblocklayoutwithourmodule:

1. Createtheapp/code/Packt/HelloWorld/view/frontend/layoutfolder.2. Inthisfolder,createafilecalleddefault.xmlwiththefollowingcontent:

<?xmlversion="1.0"?>

<pagexmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/pa

ge_configuration.xsd">

<body>

<referenceBlockname="footer_links">

<block

class="Magento\Framework\View\Element\Html\Link\Current"

name="helloworld-link">

<arguments>

<argumentname="label"translate="true"

xsi:type="string">Helloworldlanding</argument>

<argumentname="path"

xsi:type="string">helloworld/index/index</argument>

</arguments>

</block>

</referenceBlock>

</body>

</page>

3. Cleanthecacheusingthephpbin/magentocache:cleancommandandreloadthefrontend.Inthefooter,youwillseeanextralinkleadingtothepagethatwecreatedinthepreviousrecipe.

4. Thelayoutupdatewejustcreatedisappliedtoallpages.Ifwewantupdatesonthehelloworldindexpage,wehavetocreatetheapp/code/Packt/HelloWorld/view/frontend/layout/helloworld_index_index.xml

file.5. Inthisfile,pastethefollowingcontent:

<?xmlversion="1.0"?>

<pagexmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

layout="2columns-left"

xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/pa

ge_configuration.xsd">

<head>

<title>HelloworldLandingspage</title>

</head>

<body>

<removename="wishlist_sidebar"/>

</body>

</page>

6. Wealsoneedtoregisterthepage.Forthis,createtheapp/code/Packt/HelloWorld/etc/frontend/page_types.xmlfilewiththefollowingcontent:

<?xmlversion="1.0"?>

<page_typesxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/pa

ge_types.xsd">

<typeid="helloworld_index_index"label="HelloWorldlandingpage"/>

</page_types>

7. Cleanthecacheandreloadthe/helloworldpage.YouwillseethatthetitleissimilartowhatweconfiguredintheXMLfileandthewishlistblockisnotpresentintheleft-handsidecolumn.

8. Tofinishthisrecipe,wewilladdacustomtemplatewithacustomBlockclass.Createtheapp/code/Packt/HelloWorld/Block/Landingspage.phpfilewiththefollowingcontent:

<?php

namespacePackt\HelloWorld\Block;

useMagento\Framework\View\Element\Template;

classLandingspageextendsTemplate

{

publicfunctiongetLandingsUrl()

{

return$this->getUrl('helloworld');

}

publicfunctiongetRedirectUrl()

{

return$this->getUrl('helloworld/index/redirect');

}

9. Now,wehavetocreatethetemplatewherewewillcallthemethodfromtheLandingspageclass.Createtheapp/code/Packt/HelloWorld/view/frontend/templates/landingspage.phtmlfilewiththefollowingcontent:

<h2>HelloWorld</h2>

<p>

<ahref="<?phpecho$block->getLandingsUrl();?>">Gotolandings

URL</a>

</p>

<p>

<ahref="<?phpecho$block->getRedirectUrl();?>">Gotoredirect

URL</a>

</p>

10. Asthelaststep,wehavetoaddtheblockwithourlayoutXML.Addthefollowingconfigurationtotheapp/code/Packt/HelloWorld/view/frontend/layout/helloworld_index_index.xml

fileasachildofthe<body>tag:

<referenceContainername="content">

<blockclass="Packt\HelloWorld\Block\Landingspage"

name="landingsblock"template="Packt_HelloWorld::landingspage.phtml"/>

</referenceContainer>

11. Cleanthecacheandreloadthe/helloworldURL.Youwillseesomethinglikethefollowing:

Howitworks…Layoutupdatescanbeplacedinmodulesandthemes.IntheCustomizingtheHTMLoutputofChapter3,Theming,weexplainedhowtolayoutupdatesworkinMagentothemes,butitisalsopossibletodothesameprincipleinamodule.

EveryMagento2folderhasaviewfolder.Intheviewfolder,allthestufftorenderthepageisstored,suchasLESS(CSS),JavaScript,templates,andlayoutfiles.

Intheviewfolder,wecanhavethefollowingsubfolders:

adminhtml

base

frontend

Asthenamesuggests,theadminhtmlfolderisusedfortheMagentobackend,thefrontendfolderisusedforthefrontend,andthebasefolderisusedforboth(frontendandbackend).

Inthesefolders,thefollowingstructureistheinternalfolderstructurethatisused:

layout(forlayoutupdateXMLfiles)templates(for.phtmltemplates)web(forstaticfiles,suchasLESS,JavaScript,andimages)

Inthelayoutfolder,wecanplacelayoutXMLfiles.Foreverylayouthandle,wecanapplylayoutupdatesinaseparatefile.

Wehaveplacedalayoutfileforthedefaulthandle(theseinstructionsareloadedonallpages).Everypagealsohasitsownhandleinthestructure<modulefrontname>_<controllername>_<actionname>.Forthehelloworldlandingspage,isthishelloworld_index_indexfile.Inthehelloworld_index_index.xmlfile,wehaveplacedthelayoutinstructionsofthatpage.Thedefaulthandle,default.xml,isloadedonallpages.

Inthatfile,wecreatedalayoutinstructionthatdefinesacustomblockwithtemplateonapage.Thelandingspage.phtmltemplateofthePackt_HelloWorldmoduleisusedtorendertheoutput.Withthe$blockvariable,wecancallthemethodsofthePackt\HelloWorld\Block\Landingspageclass.

TipInMagento1,weusedthe$thiscommandtocallmethodsfromtheblockclass.InMagento2,wewillusethe$blockvariableforthis.

Theguidelineistousethe.phtmlfilesfortherenderingoftheHTMLoutput.ThesefilesmaynotcontainalogofthePHPcode.ThePHPcodeiswrittenintheblockfilesandtheHTMLcodeinthe.phtmlfiles.Inthe.phtmlfiles,wecancallmethodsfromtheblockclass.

AddingatranslationfileMagentoismadetoruninmultiplelanguages.Thismeansthattheinterfaceandcontentneedstobetranslatableintheconfiguredlanguages.

Inthisrecipe,youwilllearnhowtomakethestringsinourmoduletranslatableindifferentlanguages.

GettingreadyWewillcreatetranslationfilesforthemodulethatwecreatedinthepreviousrecipesofthischapter.EnsurethatyouhavethecodeinyourMagentoinstance.

Howtodoit…Thefollowingproceduredemonstrateshowwecanmanagetranslationsinourmodule:

1. Tomakeatesttranslation,wecancreateatesttranslationinthetemplatefilethatwecreatedinthepreviousrecipe.Addthefollowingcodeattheendofthefileapp/code/Packt/HelloWorld/view/frontend/templates/landingspage.phtml:

<p>

<?phpecho__('Testtranslation')?>

</p>

2. Gotothe/helloworldpageandyouwillseethatthetextTesttranslationisaddedonthepage.

3. Totranslatethisstring,wehavetocreatetheapp/code/Packt/HelloWorld/i18nfolder.

4. Inthisfolder,createtheen_US.csvfile.5. AddthefollowinglineintheCSVfile:

"Testtranslation","Translationtotest"

6. Cleanthecacheandreloadthepage.IfthelanguageofyourshopissettoEnglish(UnitedStates),youwillseethattheoutputissettoTranslationtotest.

7. Ifwewant,forexample,aFrenchtranslation,wehavetocreatethefr_FR.csvfilewiththefollowingcontent:

"Testtranslation","Testtraduction"

8. ChangethelanguageofthestoretoFrench,cleanthecache,andyouwillseetheFrenchtranslation.

TipIfyouwanttoknowallthetranslationsofamodule,youcanrunthephpbin/magentoi18n:collect-phrasesapp/code/<Vendorname>/<Modulename>

commandandyouwillgetaCSVlistofallthetranslations.

Howitworks…Whencallingthe__('translatestring')function,Magentowillsearchforatranslationforthatstringinthecurrentlanguage.Magentowilllookforthestringsinthefollowingorder:

ThedatabasetranslationtableThethemetranslationfiles(app/design/fronted/<Package>/<theme>/i18n/<locale_code>.csv)Themoduletranslationfiles(app/code/<Vendor>/<Module>/i18n/<locale_code>.csv)

Whenastringisfound,Magentodoesn’tlookfurtherforothermatchingstrings.Ifnomatchingstringisfoundforthecurrentlanguage,Magentowillreturnthestringthatispresentinthefirstparameterofthetranslatefunction(thatis,theuntranslatedstring).

TheimplementationoftranslationsinMagento2ismucheasierthaninMagento1.Everythingisstoredinthemodulefolder,andyoudon’thavetoaddconfigurationXMLinstructionstothemodulewhereyoucandomistakeswith.

Also,thetranslatefunctionhasnowbeenmovedtoaglobalfunction.Youdon’tneedahelperclasstocallthe__()function.The__()functionisimplementedasaglobalfunctionthatisavailableeverywhereintheapplication.

AddingablockofnewproductsInthepreviousrecipes,wepreparedthemodulefortherealwork.Weaddedthemostcommonfeaturestothemodulesothatwecaneasilyextenditwiththenewfunctionality.

Inthisrecipe,wewillcreateablockofnewproductstothepagewecreatedinthepreviousrecipes.

GettingreadyInourmodule,wewillcreateablockthatwillloadaproductcollection.Thisproductcollectionwillbeusedinthetemplate,whichwillshowthenewestproductsoftheshop.

Ensurethatyouhavethemoduleofthepreviousrecipeinstalled.

Howtodoit…Thefollowingstepsdemonstratehowtostartwithaddingtheblockwithnewproducts:

1. Tocreatetheblockclass,wehavetocreatetheNewproducts.phpfileintheapp/code/Packt/HelloWorld/Block/folder.

2. Addthefollowingcontenttothatfile:

<?php

namespacePackt\HelloWorld\Block;

useMagento\Framework\View\Element\Template;

classNewproductsextendsTemplate

{

}

3. Createatemplateinthemodulefolder.Wecandothisbycreatingthenewproducts.phtmlfileintheapp/code/Packt/HelloWorld/view/frontend/templates/folder.

4. AddsomeHTMLcontenttothattemplatefilesuchas<h2>NewProducts</h2>.5. Toaddtheblocktothepage,wehavetocreatealayoutupdate.Inthe

app/code/Packt/HelloWorld/view/frontend/layout/helloworld_index_index.xml

file,addthefollowingcodeasachildof<referenceContainername="content">:

<blockclass="Packt\HelloWorld\Block\Newproducts"name="new_products"

template="Packt_HelloWorld::newproducts.phtml"/>

6. Cleanthecacheandreloadthe/helloworldpage.YouwillseethattheNewProductstitleisvisible.

7. Createaconstructorintheblockclassthatinitializestheproductcollectionfactory.Wecandothisbyaddingthefollowingcodeinthatclass(theapp/code/Packt/HelloWorld/Block/Newproducts.phpfile):

private$_productCollectionFactory;

publicfunction__construct(

Template\Context$context,

\Magento\Catalog\Model\ResourceModel\Product\CollectionFactory

$productCollectionFactory,

array$data=[])

{

parent::__construct($context,$data);

$this->_productCollectionFactory=$productCollectionFactory;

}

8. CreatethegetProducts()methodinthesameblockclass.Thismethodwillreturnthefivelatestproductsoftheshop.ThecodeforthegetProducts()methodwilllookasfollows:

publicfunctiongetProducts(){

$collection=$this->_productCollectionFactory->create();

$collection

->addAttributeToSelect('*')

->setOrder('created_at')

->setPageSize(5);

return$collection;

}

9. ThelaststepistocallthemethodinthetemplateandgenerateanHTMLfileforit.Thecodeofthetemplateisasfollows:

<h2>Newproducts</h2>

<ul>

<?phpforeach($block->getProducts()as$product):?>

<li><?phpecho$product->getName()?></li>

<?phpendforeach;?>

</ul>

10. Reloadthe/helloworldpageandyouwillseealistwiththenamesofthelatestproducts.

Howitworks…WhatwehavedoneinthisrecipeisabasicextensionofMagento.WeaddedacustomblockthatusestheMagentoframeworktorenderthecontent.

WecreatedablockclassthathasthegetProducts()method.Thismethodreturnsthelatestfiveproductsofthewebshop.Inthismethod,wecreatedaquerythatusestheMagentocollections.WithMagentocollections,wecangetdatafromthedatabase.AcollectionbuildsaSQLqueryinthebackground.

Thepurposeofcollectionsisthatthereisaneasyinterfacetogettherightentities.AproductisnotstoredinonedatabasetablebecauseitusestheEntityAttributeValuesystem(EAV).TheMagentocollectionsgenerateanSQLquerythatreturnsthevaluesofthattables.ThissavesustheprogrammingofverycomplexSQLqueries.

Toworkwiththecollections,weusedtheCollectionFactoryproducttoworkwiththecollectionfunctions.WeinitializedthisclassintheconstructoranduseditinthegetProducts()method.

Whenwerunthecreate()functiononCollectionFactory,aproductcollectionwillbereturned.ItislikedoingacollectionusingthegetCollection()methodonaMagentomodel,butbecausethismethodisdeprecated,wehavetouseCollectionFactory.

AddinganinterceptorOneofthemajorthingsthathaschangedinMagento2isthatthereisnoMageclass.Toreplacethis,allobjectsarepassedtotheclasseswithdependencyinjection.

DependencyinjectionisapowerfultoolthataddsalotofflexibilitytoaddorchangebehaviorinMagento.

GettingreadyToexplorethepossibilitiesofdependencyinjectionofMagento2,weneedthemodulethatwecreatedinthepreviousrecipes.

Howtodoit…Thefollowingstepsdescribehowwecanmodifythebehaviorofsomeclasses,whichisanewconceptinMagento2thatreplacestherewritingofclassesinMagento1:

1. Createtheapp/code/Packt/HelloWorld/etc/di.xmlfileandpastethefollowingcontentinit:

<?xmlversion="1.0"?>

<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/

config.xsd">

<typename="Magento\Catalog\Model\Product">

<pluginname="Packt_HelloWorld::productName"

type="Packt\HelloWorld\Plugin\Catalog\ProductAround"sortOrder="1"/>

</type>

</config>

NoteThelettersdiinthedi.xmlfilestandsforDependencyInjection.

2. Createapluginclassbycreatingtheapp/code/Packt/HelloWorld/Plugin/Catalog/ProductAround.phpfilewiththefollowingcontent:

<?php

namespacePackt\HelloWorld\Plugin\Catalog;

useMagento\Catalog\Model\Product;

classProductAround

{

publicfunctionaroundGetName($interceptedInput)

{

return"Nameofproduct";

}

}

NoteItishighlyrecommendedthatyouusearoundinthemethodnamebecauseyoucanalsowriteinterceptorsthatareexecutedbeforeorafteramethod.

3. Cleanthecachesandregeneratetheclassesbyremovingthevar/generationfolder.4. ReloadaproductpageandyouwillseethateveryproductnameischangedtoName

ofproduct.5. Toundothis,commentthe<type>tagandcontentsofthedi.xmlfileandregenerate

theclassesbyremovingthevar/generationfolder.Also,don’tforgettocleanthecache.

6. Reloadtheproductpageandyouwillseethenormalproductnames.

Howitworks…Inthisrecipe,weaddedadependencyinjectionintotheMagento\Catalog\Model\Productclass.WedidanoverrideofanexistingmethodinMagento.

Withinterception,wecanexecutethecodebefore,after,andaroundanymethodofaclass.ThisgivesalotofpossibilitiestoaddbehaviortoMagento.

Inthedi.xmlfile,weinitializedapluginthatcouldoverridemethodsoftheMagento\Catalog\Model\Productclass.

TheoverridesaredoneinthePackt\HelloWorld\Plugin\Catalog\ProductAroundclass.Inthisclass,wedidamodificationofthegetName()methodoftheoriginalclassusingthearoundGetName()method.

Totestourcode,wehadtocreatethegeneratedclasses.Wecandothisbyremovingthevar/generationfolderorbyrunningthephpbin/magentosetup:di:compilecommand.ThecachealsoneedstobecleanedbecausewechangedthingsintheconfigurationXMLfiles.

Thiscommandcreatesgeneratedclassesthatwillbeplacedinthevar/generationfolder.Withoutgeneratingtheclasses,theconfigurationinthedi.xmlfilewillnotload.Thisisalsothereasonwhyyouhavetodothiswheninstallingorupgradinganewmodule.

DependencyinjectionreplacestheclassrewritesysteminMagento1.Withdependencyinjection,youcanintercepteverymethodthatiscalledinaclass.WiththerewritesystemofMagento1,youcouldnotdothiswithabstractclasses.

Itisalsopossibletoexecutecodebeforeandafteramethodiscalled.

SeealsoAlotofthingsarepossiblewithDependencyInjection.FormoreinformationhowitisintegratedinMagento,youcanreadthedocumentationontheMagentosite:

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

NoteMoreinformationaboutthedependencyinjectiondesignpatterncanbefoundonthefollowingURL:

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

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

Withinamodule,itispossibletoextendthistoolwithcustomcommands,andthisisthethingthatwewilldointhisrecipe.

GettingreadyThisrecipewillbuildfurtheronthemodulethatwehavecreatedinthischapter.Ifyoudon’thavethecode,youcaninstallthestarterfiles.

Howtodoit…Inthenextsteps,wewillcreateasimpleconsolecommandthatwillprintsomeoutputtotheconsole.Usingthisprinciple,youcancreateyourowncommandstoautomatesometasks:

1. Foracustomconsolecommand,wehavetoaddthefollowingconfigurationinthedi.xmlfileofthemodule.Pastethefollowingcodeinthatfileaschildofthe<config>tag:

<typename="Magento\Framework\Console\CommandList">

<arguments>

<argumentname="commands"xsi:type="array">

<itemname="helloWorldCommand"

xsi:type="object">Packt\HelloWorld\Console\Command\HelloWorldCommand</i

tem>

</argument>

</arguments>

</type>

2. Next,wewillcreatetheapp/code/Packt/HelloWorld/Console/Command/HelloWorldCommand.phpfilewiththefollowingcontent:

<?php

namespacePackt\HelloWorld\Console\Command;

useSymfony\Component\Console\Command\Command;

useSymfony\Component\Console\Input\InputInterface;

useSymfony\Component\Console\Output\OutputInterface;

useSymfony\Component\Console\Input\InputOption;

classHelloWorldCommandextendsCommand

{

}

3. Inthepreviousstep,wecreatedtheclassforthecommand.Toinitializethecommand,wehavetoaddthefollowingcontenttoit:

constINPUT_KEY_EXTENDED='extended';

protectedfunctionconfigure()

{

$options=[

newInputOption(

self::INPUT_KEY_EXTENDED,

null,

InputOption::VALUE_NONE,

'Getextendedinfo'

),

];

$this->setName('helloworld:info')

->setDescription('Getinfoaboutinstallation')

->setDefinition($options);

parent::configure();

}

protectedfunctionexecute(InputInterface$input,OutputInterface

$output)

{

$output->writeln('<error>'.'writelnsurroundedbyerrortag'.

'</error>');

$output->writeln('<comment>'.'writelnsurroundedbycommenttag'

.'</comment>');

$output->writeln('<info>'.'writelnsurroundedbyinfotag'.

'</info>');

$output->writeln('<question>'.'writelnsurroundedbyquestion

tag'.'</question>');

$output->writeln('writelnwithnormaloutput');

if($input->getOption(self::INPUT_KEY_EXTENDED)){

$output->writeln('');

$output->writeln('<info>'.'Extendedparameteris

given'.'</info>');

}

$output->writeln('');

}

4. Cleanthecache,removethevar/generationfolder,andrunthephpbin/magentocommand.Youwillseethatthehelloworld:infocommandwillbeinthelist.

5. Whenyourunthecommand,youwillseethefollowingoutput:

6. Whenyourunthecommandwiththeextendedparameter,youwillseesomeextraoutput.Todothis,wehavetorunthecommandasfollows:

phpbin/magentohelloworld:info--extended

Howitworks…Toregistertheconsoleclasstothecommandlist,wehadtocreateanextraargumentfortheMagento\Framework\Console\CommandListclassinthedi.xmlfile.Inthisfile,werefertothePackt\HelloWorld\Console\Command\HelloWorldCommandclassforourcustomcommand.

Intheconfigure()method,weregisteredthenameofthecommand,thedescription,andtheotheroptions.Inthiscase,weinitializedanoptionalinputoption.

Theexecute()methodismadetoexecutethecommand.The$inputparametercontainstheinputofthecommand,suchastheoptionsandarguments.Withthe$outputparameter,wecanmodifytheoutputofthecommand.Thisparameterisusedtowriteoutputtotheconsolewiththewrite()andwriteln()methods.

Inthisrecipe,weworkedwithsomecolorstostyletheconsoleoutput.Thetextbetweentheerror,comment,information,andquestiontagswillberenderedinadifferentcolor,aswehaveseeninthisrecipe.

Wealsohadanoptionalparametercalledextended.Togetthevalueofthisparameter,wecanusethegetOption()methodofthe$inputparameter.Whentheparameterissetwithoutvalue,itwillreturntrue.Iftheparameterisn’tset,itwillreturnfalse.Ifatextisgiventotheparameter,itwillreturnthetext.

Seealso…TheMagentoconsoleisbuiltusingtheSymfonyconsolecomponent.MoreinformationabouthowtousetheSymfonyconsolecanbefoundatthefollowingURL:

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

Chapter5.DatabasesandModulesInthischapter,wewillcover:

RegisteringdatabaseconnectionsCreatinganinstallandupgradescriptCreatingaflattablewithmodelsWorkingwithMagentocollectionsProgrammaticallyaddingproductattributesRepairingthedatabase

IntroductionInthepreviouschapter,welearnedhowtocreateamoduleandhowwecandosomebasicstuff.

Inthisrecipe,wewilllearnhowwecanintegrateamodulewiththedatabaseofMagento.Forthis,weneedtheMagentomodulethatwehavecreatedinChapter4,CreatingaModule.

Wewilllearnhowwecanrunqueries,addourownentities,andautomatetheinstallationofconfigurationsinthedatabase.

CreatinganinstallandupgradescriptInsomecases,youneedtoupdatethedatabasetofinishyourwork.Commoncasesareanextracolumntoatable,anewproductattribute,orasetting.

Ifyouhaveadevelopment,staging,andproductionenvironment,youhavetomakesurethattherightupdatesaredonewhendeployingtotheseenvironments.

Magentohasawaytoautomaticallytriggertheexecutionofcertainscriptsforanupdate.That’sthethingwewilllearninthisrecipe.CommonthingstoautomatearesettingsintheStore|Configurationpageinthebackend.

GettingreadyWewillcreateanewmodulewherewewilladdaninstallandupgradescript.Wewillalsousethecommandlinetorunthescripts.

InMagento2,theprincipleofinstallingandupgradingscriptsisthesameasinMagento1butsomethingshavechangedinthewayofworking.Inthisrecipe,wewillseehowitworkstosavesomedatainthedatabasewithaninstallscript.

Howtodoit…Thefollowingstepsdescribehowwecancreateanautomatedscript:

1. CreateanewmodulecalledPackt_SEO.Wecandothisbycreatingthefileapp/code/Packt/SEO/etc/module.xmlwiththefollowingcontent:

<?xmlversion="1.0"?>

<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.

xsd">

<modulename="Packt_SEO"setup_version="2.0.0">

<sequence>

<modulename="Magento_Backend"/>

</sequence>

</module>

</config>

2. Next,createafilecalledregistration.phpinthefolderapp/code/Packt/SEO/withthefollowingcontent:

<?php

\Magento\Framework\Component\ComponentRegistrar::register(

\Magento\Framework\Component\ComponentRegistrar::MODULE,

'Packt_SEO',

__DIR__

);

3. Thefollowingstepistocreatetheinstallscript.Wecandothisbycreatingthefileapp/code/Packt/SEO/Setup/InstallData.phpwiththefollowingcontent:

<?php

namespacePackt\SEO\Setup;

useMagento\Framework\Setup\InstallDataInterface;

useMagento\Framework\Setup\ModuleContextInterface;

useMagento\Framework\Setup\ModuleDataSetupInterface;

classInstallDataimplementsInstallDataInterface{

protected$resourceConfig;

publicfunction

__construct(\Magento\Config\Model\ResourceModel\Config$resourceConfig)

{

$this->resourceConfig=$resourceConfig;

}

publicfunctioninstall(ModuleDataSetupInterface$setup,

ModuleContextInterface$context){

$setup->startSetup();

$this->resourceConfig->saveConfig(

'catalog/seo/category_canonical_tag',

true,

\Magento\Config\Block\System\Config\Form::SCOPE_DEFAULT,

0

);

$this->resourceConfig->saveConfig(

'catalog/seo/product_canonical_tag',

true,

\Magento\Config\Block\System\Config\Form::SCOPE_DEFAULT,

0

);

$setup->endSetup();

}

}

4. Whenwewanttorunthescript,wehavetoinstallthemodule.Wecandothisbyrunningthefollowingcommand:

phpbin/magentosetup:upgrade

NoteThepreviousscriptwillenablethecanonicaltagsontheproductandcategorypages.

5. Ifwewanttoaddanewscripttotheexistingmodule,wehavetocreateanupgradescript.Wecandothisbycreatingthefileapp/code/Packt/SEO/Setup/UpgradeData.phpwiththefollowingcontent:

<?php

namespacePackt\SEO\Setup;

useMagento\Framework\Setup\UpgradeDataInterface;

useMagento\Framework\Setup\ModuleContextInterface;

useMagento\Framework\Setup\ModuleDataSetupInterface;

classUpgradeDataimplementsUpgradeDataInterface{

protected$resourceConfig;

publicfunction

__construct(\Magento\Config\Model\ResourceModel\Config$resourceConfig)

{

$this->resourceConfig=$resourceConfig;

}

publicfunctionupgrade(ModuleDataSetupInterface$setup,

ModuleContextInterface$context){

if(version_compare($context->getVersion(),'2.0.1')<0){

$this->resourceConfig->saveConfig(

'web/cookie/cookie_lifetime',

'7200',

\Magento\Config\Block\System\Config\Form::SCOPE_DEFAULT,

0

);

}

}

}

6. Thisscriptwillrunforversion2.0.1sowehavetoconfigureourmoduletothatversion.Wecandothisbyraisingtheversionnumberinthefileapp/code/Packt/SEO/etc/module.xmlinthesetup_versionattribute.Thefilewillhavethefollowingcontent:

<?xmlversion="1.0"?>

<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.

xsd">

<modulename="Packt_SEO"setup_version="2.0.1">

<sequence>

<modulename="Magento_Backend"/>

</sequence>

</module>

</config>

7. Torunthescript,wehavetorunthesamecommandphpbin/magentosetup:upgrade.

8. Whentheupgradescripthasfinished,lookatthepageStores|Configuration|Web|DefaultCookieSettingsandyouwillseethatthelifetimehasthevalue7200.

Howitworks…EveryMagentomodulehasaversionnumberthatisstoredinthemodule.xmlfile.Whenwelookinthedatabasetablesetup_module,weseetheversionnumbersofeverymodule.

Whenrunningthecommandphpbin/magentosetup:upgrade,Magentowillcheck,foreverymodule,theversionnumberinthemodule.xmlfileandthedatabasetablesetup_module.

NoteIfyouwanttore-runaninstallorupgradescript,youcandeleteorchangetheversionnumberofthemoduleinthetablesetup_module.

Ifamoduleisnotinthelist,theInstallData.phpscriptwillbeexecuted.Afterthat,theUpgradeData.phpscriptisalwaysexecuted.

Withanifstatement,wecanmanagewhatthingsneedtobeexecutedinaspecificupgrade.

Inthedatabasetablesetup_module,weseethefollowingthreecolumns:

module(thenameofthemodule)schema_version(theinstalledschemascript)data_version(theinstalleddatascript)

Therearetwodifferenttypesofinstallscripts.Aschemainstallandadatainstall.Aschemainstallisusedtoinstalldatabasestructuressuchasnewtables,columns,andrelations.

Adatainstallorupgradeisusedtoadddatatothedatabasesuchasasetting,page,category,stores,andmanymore.

Alltheschemascriptsareexecutedbeforethedataupgradescriptswillstart.ThismeansthatwecanusetheMagentomodelsinthedatascripts.Thisisnotalwayspossibleinaschemascriptbecauseitcouldbethatthedatabasestructureisnotcompletelyinstalledwhenrunningyourscript.

Inthescripts,weusedtheresourceConfigobjecttosaveconfigurationparameters.Withthiscode,dataisupdatedintheconfigurationpagesthatyoucanfindinthebackendatStores|Configuration.

Thevaluesofthesepagesaresavedinthetablecore_config_data.

CreatingaflattablewithmodelsWhenyouwanttosavedatainamodule,youmaywanttostorethatinacustomentity.Thatentityneedsadatabasetableandamodelthattalkswiththatdatabasetable.

Wewillcreateasubscriptionsentitywherewecanstoresubscriptions.

GettingreadyInthisrecipe,wewillextendthemoduleofChapter4,CreatingaModule,withanentitywithadatabasetable.Makesureyouhavethestarterfilesforthisrecipeinstalled.

Howtodoit…Inthenextsteps,wewilllearnhowwecanaddentitiestoanexistingmodule:

1. Wheninstallinganewentity,wehavetocreatearesourcemodel.Wecandothisbycreatingthefileapp/code/Packt/HelloWorld/Model/ResourceModel/Subscription.phpwiththefollowingcontent:

<?php

namespacePackt\HelloWorld\Model\ResourceModel;

classSubscriptionextends

\Magento\Framework\Model\ResourceModel\Db\AbstractDb{

publicfunction_construct(){

$this->_init('packt_helloworld_subscription','subscription_id');

}

}

2. Thesecondstepistocreatethetablewithaschemaupgradescript.Wehavetocreatethescriptapp/code/Packt/HelloWorld/Setup/UpgradeSchema.phpwiththefollowingcontent:

<?php

namespacePackt\HelloWorld\Setup;

useMagento\Framework\Setup\UpgradeSchemaInterface;

useMagento\Framework\Setup\ModuleContextInterface;

useMagento\Framework\Setup\SchemaSetupInterface;

classUpgradeSchemaimplementsUpgradeSchemaInterface{

publicfunctionupgrade(SchemaSetupInterface$setup,

ModuleContextInterface$context){

if(version_compare($context->getVersion(),'2.0.1')<0){

$installer=$setup;

$installer->startSetup();

$connection=$installer->getConnection();

//Installnewdatabasetable

$table=$installer->getConnection()->newTable(

$installer->getTable('packt_helloworld_subscription')

)->addColumn(

'subscription_id',

\Magento\Framework\DB\Ddl\Table::TYPE_INTEGER,

null,[

'identity'=>true,

'unsigned'=>true,

'nullable'=>false,

'primary'=>true

],

'SubscriptionId'

)->addColumn(

'created_at',

\Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP,

null,[

'nullable'=>false,

'default'=>\Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT

],

'Createdat'

)->addColumn(

'updated_at',

\Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP,

null,

[],

'Updatedat'

)->addColumn(

'firstname',

\Magento\Framework\DB\Ddl\Table::TYPE_TEXT,

64,

['nullable'=>false],

'Firstname'

)->addColumn(

'lastname',

\Magento\Framework\DB\Ddl\Table::TYPE_TEXT,

64,

['nullable'=>false],

'Lastname'

)->addColumn(

'email',

\Magento\Framework\DB\Ddl\Table::TYPE_TEXT,

255,

['nullable'=>false],

'Emailaddress'

)->addColumn(

'status',

\Magento\Framework\DB\Ddl\Table::TYPE_TEXT,

255,[

'nullable'=>false,

'default'=>'pending',

],

'Status'

)->addColumn(

'message',

\Magento\Framework\DB\Ddl\Table::TYPE_TEXT,

'64k',[

'unsigned'=>true,

'nullable'=>false

],

'Subscriptionnotes'

)->addIndex(

$installer->getIdxName('packt_helloworld_subscription',

['email']),

['email']

)->setComment(

'CronSchedule'

);

$installer->getConnection()->createTable($table);

$installer->endSetup();

}

}

3. Raisethemoduleversionnumberinthefileapp/code/Packt/HelloWorld/etc/module.xmlto2.0.1.

4. Runthefollowingcommandtoexecutetheupgradescript:

phpbin/magentosetup:upgrade

5. Checkthatthetableisinstalledinthedatabase.Wecandothisbyrunningthequerydescribepackt_helloworld_subscription;onaMySQLcommandline.Thiswillgivethefollowingoutput:

6. Thenextpartistocreateamodelthatinteractswiththedatabasetable.Tocreateamodel,wehavetocreatethefileapp/code/Packt/HelloWorld/Model/Subscription.phpwiththefollowingcontent:

<?php

namespacePackt\HelloWorld\Model;

classSubscriptionextends\Magento\Framework\Model\AbstractModel{

constSTATUS_PENDING='pending';

constSTATUS_APPROVED='approved';

constSTATUS_DECLINED='declined';

publicfunction__construct(

\Magento\Framework\Model\Context$context,

\Magento\Framework\Registry$registry,

\Magento\Framework\Model\ResourceModel\AbstractResource$resource=

null,

\Magento\Framework\Data\Collection\AbstractDb$resourceCollection=

null,

array$data=[]

){

parent::__construct($context,$registry,$resource,

$resourceCollection,$data);

}

publicfunction_construct(){

$this->_init('Packt\HelloWorld\Model\ResourceModel\Subscription');

}

}

7. Thelastclassthatwehavetocreateisthecollectionclass.Createthefileapp/code/Packt/HelloWorld/Model/ResourceModel/Subscription/Collection.php

withthefollowingcontent:

<?php

namespacePackt\HelloWorld\Model\ResourceModel\Subscription;

/**

*SubscriptionCollection

*/

classCollectionextends

\Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection

{

/**

*Initializeresourcecollection

*

*@returnvoid

*/

publicfunction_construct(){

$this->_init('Packt\HelloWorld\Model\Subscription',

'Packt\HelloWorld\Model\ResourceModel\Subscription');

}

}

8. Allthefilesareintherightplacetostartatest.Toperformasimpletest,wecancreateacontrolleractionintheIndexControlleroftheHelloWorldmodulethatwehavecreatedinthepreviousrecipe.Addthefileapp/code/Packt/HelloWorld/Controller/Index/Subscription.phpwiththefollowingcontent:

<?php

namespacePackt\HelloWorld\Controller\Index;

classSubscriptionextends\Magento\Framework\App\Action\Action{

publicfunctionexecute(){

$subscription=$this->_objectManager-

>create('Packt\HelloWorld\Model\Subscription');

$subscription->setFirstname('John');

$subscription->setLastname('Doe');

$subscription->setEmail('john.doe@example.com');

$subscription->setMessage('Ashortmessagetotest');

$subscription->save();

$this->getResponse()->setBody('success');

}

}

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

10. Whenyoulookinyourdatabaseandrunthequeryselect*frompackt_helloworld_subscription;,wewillseethefollowingoutput:

Howitworks…Whenweworkwiththeprevioussetupofentities,theMagentoORMmakesthelinkbetweentheentityandthedatabase.AlltheSQLqueries,security,andmorearemanagedbytheORM.

Thefirststepwedidwastocreatearesourcemodel.Aresourcemodelisusedforthelinkbetweenthemodelandthedatabase.Thisclassisinitializedbyspecifyingthedatabasetableandtheprimarykeyofthattable.

Thesecondpartwastocreateascriptthatautomaticallycreatesadatabasetable.Adatabasetableiscreatedwiththeschemainstaller.Becausethismodulewasalreadyinstalled,wehadtocreateanupgradescriptandwehadtoraisetheversionnumber.

Thethirdstepwasthecreationofthemodel.Inamodel,alotofbusinesslogicisavailableinfunctionsandvariables.Animportantthingtomentionistheinitializationoftheresourcemodelthatisdoneinthe_construct()methodofthemodel.

Thelastparttofinishthemodelsetupwastocreatethecollectionclass.ThisclassisresponsibleforallowingustoworkwithMagentocollectionsinourmodel.TheprincipleofcollectionsisexplainedinthenextrecipeWorkingwithMagentocollections.

Totestthemodel,wecreatedatest()methodinacontrolleraction.ToloadaMagentoentity,wehavetousetheObjectManagerinterface.Inacontrolleraction,thisinterfaceisavailableinthevariable_objectManager.

NoteInthecontrolleraction,weusedtheobjectmanagertogetthemodelandsavearecordtothetable.Thiswasjustfordebuggingpurposes.Intherealworld,thecontrollerisusedforcontrolleractionssuchasredirects,addingmessages,renderingthepage,andmore.Thesaveofanentityismostlydoneinaseparatemodelbut,forsimplicity,wediditinthecontroller.

TocreateaMagentoentity,wehavetousethecreate()methodoftheobjectmanager.Theparameterisastringwiththenamespaceandclassnameoftheentitysuchasthefollowing:

$this->_objectManager->create('Packt\HelloWorld\Model\Subscription');

WithourMagentoentity,wecanusethefollowingmethodstointeractwiththedatabase:

load($entityId)

save()

delete()

Allthesemethodsareimplementedinthe\Magento\Framework\Model\AbstractModelclass.AlltheentitiesthatusetheMagentoORMframeworkwillextendthisclass.

WorkingwithMagentocollectionsWhenyouwanttoretrieveasetofentitiesofthesametype,weusuallyuseaquerytogetthedataofatable.

ButinMagentonoteveryentityisstoredinasingletableandthatmeansthataverycomplexqueryisrequiredtogetthedata.Agenericsystem/languagetocreatequeriesforMagentoentitieswasthesolution.

Forthissolution,Magentohascreatedasystemcalledcollections.Acollectionisasetofentitiesofthesametypewhereyoucanaddfilterstoittocustomizeyourresult.

Inthisrecipe,wewillseewhatwecandowithMagentocollections.

GettingreadyForthisrecipe,itisrequiredtohavethePackt_HelloWorldmoduleinstalledwiththecodeofthepreviousrecipeCreatingaflattablewithmodels.

Howtodoit…ThenextexamplesshowthepossibilitiesofworkingwithMagentocollections:

1. CreateaCollectioncontrolleractionbycreatingthefileapp/code/Packt/HelloWorld/Controller/Index/Collection.phpwiththefollowingcontent:

<?php

namespacePackt\HelloWorld\Controller\Index;

classCollectionextends\Magento\Framework\App\Action\Action{

publicfunctionexecute(){

}

}

2. Pastethefollowingcodeintheexecute()methodofthatclass:

publicfunctionexecute(){

$productCollection=$this->_objectManager

->create('Magento\Catalog\Model\ResourceModel\Product\Collection')

->setPageSize(10,1);

$output='';

foreach($productCollectionas$product){

$output.=\Zend_Debug::dump($product->debug(),null,false);

}

$this->getResponse()->setBody($output);

}

3. Whenloadingthepage/helloworld/index/collection,weseeadumpofthefirst10productslikethefollowingarray:

array(9){

["entity_id"]=>

string(1)"1"

["attribute_set_id"]=>

string(2)"15"

["type_id"]=>

string(6)"simple"

["sku"]=>

string(7)"24-MB01"

["has_options"]=>

string(1)"0"

["required_options"]=>

string(1)"0"

["created_at"]=>

string(19)"2015-07-1612:36:24"

["updated_at"]=>

string(19)"2015-07-1612:36:24"

["is_salable"]=>

string(1)"1"

}

4. Whenwelookatthearray,weseenoproductattributesinthedump.Toaddanattributetoacollection,wehavetousethemethodaddAttributeToSelect('<attribute_code>').Addthefollowingcodeintheexecute()methodandwewillseethename,price,andimageoftheproducts:

publicfunctionexecute(){

$productCollection=$this->_objectManager

->create('Magento\Catalog\Model\ResourceModel\Product\Collection')

->addAttributeToSelect([

'name',

'price',

'image',

])

->setPageSize(10,1);

$output='';

foreach($productCollectionas$product){

$output.=\Zend_Debug::dump($product->debug(),null,false);

}

$this->getResponse()->setBody($output);

}

5. Whenreloadingthepage,wewillseeanarrayofeachproductwiththethreeattributesinitlikethefollowingcode:

array(12){

["entity_id"]=>string(1)"2"

["attribute_set_id"]=>string(2)"15"

["type_id"]=>string(6)"simple"

["sku"]=>string(7)"24-MB04"

["has_options"]=>string(1)"0"

["required_options"]=>string(1)"0"

["created_at"]=>string(19)"2015-07-1612:36:25"

["updated_at"]=>string(19)"2015-07-1612:36:25"

["name"]=>string(20)"StriveShoulderPack"

["image"]=>string(33)"/sample_data/m/b/mb04-black-0.jpg"

["price"]=>string(7)"32.0000"

["is_salable"]=>string(1)"1"

}

6. WewillnowcreateafilterontheproductcollectionwiththeaddAttributeToFilter()method.ThenextcodeshowshowyoucanfiltertheproductswiththenameOvernightDuffle:

publicfunctionexecute(){

$productCollection=$this->_objectManager

->create('Magento\Catalog\Model\ResourceModel\Product\Collection')

->addAttributeToSelect([

'name',

'price',

'image',

])

->addAttributeToFilter('name','OvernightDuffle');

$output='';

foreach($productCollectionas$product){

$output.=\Zend_Debug::dump($product->debug(),null,false);

}

$this->getResponse()->setBody($output);

}

NoteThecodeinthisstatementwillcreateaWHEREname='OvernightDuffle'statementtothequery,soalltheitemswherethenameisOvernightDufflewillbereturned.

7. WiththeaddAttributeToFilter()method,wecandomore.ThefollowingcodeshowsyouhowyoucancreateaWHEREentity_idIN(159,160,161)statement:

publicfunctionexecute(){

$productCollection=$this->_objectManager

->create('Magento\Catalog\Model\ResourceModel\Product\Collection')

->addAttributeToSelect([

'name',

'price',

'image',

])

->addAttributeToFilter('entity_id',array(

'in'=>array(159,160,161)

));

$output='';

foreach($productCollectionas$product){

$output.=\Zend_Debug::dump($product->debug(),null,false);

}

$this->getResponse()->setBody($output);

}

8. Thenextfilterthatwewilluseisthelikefilter.AddthefollowingcodetomakeaquerywiththeWHEREnameLIKE'%Sport%'statement:

publicfunctionexecute(){

$productCollection=$this->_objectManager

->create('Magento\Catalog\Model\ResourceModel\Product\Collection')

->addAttributeToSelect([

'name',

'price',

'image',

])

->addAttributeToFilter('name',array(

'like'=>'%Sport%'

));

$output='';

foreach($productCollectionas$product){

$output.=\Zend_Debug::dump($product->debug(),null,false);

}

$this->getResponse()->setBody($output);

}

9. Whenthequeriesbecomemorecomplex,sometimesitisnicetoknowwhatSQLquerywillbegeneratedtogetthecollection.ToprinttheSQLquery,whichisusedforacollection,wecanusethefollowinglineofcode:

$productCollection->getSelect()->__toString();

10. Whenweaddthefollowingcode,wecanseethequeryforthepreviouscollection:

publicfunctionexecute(){

$productCollection=$this->_objectManager

->create('Magento\Catalog\Model\ResourceModel\Product\Collection')

->addAttributeToSelect([

'name',

'price',

'image',

])

->addAttributeToFilter('name',array(

'like'=>'%Sport%'

));

$output=$productCollection->getSelect()->__toString();

$this->getResponse()->setBody($output);

}

11. ThiscodewilloutputthefollowingSQLquery:

SELECT

e.*,

IF(at_name.value_id>0,at_name.value,at_name_default.value)AS`name`

FROMcatalog_product_entity`ASe

INNERJOINcatalog_product_entity_varcharAS`at_name_default

ON(at_name_default.entity_id=e.entity_id)

AND(at_name_default.attribute_id='69')

ANDat_name_default.store_id=0

LEFTJOINcatalog_product_entity_varcharASat_name

ON(at_name.entity_id=e.entity_id)

AND(at_name.attribute_id='69')

AND(at_name.store_id=1)

WHERE(IF(at_name.value_id>0,at_name.value,at_name_default.value)

LIKE'%Sport%')

12. WhenyourunthepreviousqueryinanSQLpromptsuchasphpMyAdmin,wecanseetheresponsewithalltheattributesthatareusedfortheproductcollection.

13. Withthepreviouscodeexamples,wecanonlyreaddatafromthedatabase.ByusingthesetDataToAll()method,wecanupdatesomeattributesforalltheentitiesinthe

collection.Usethenextcodetoupdateallthepricesinthecollection:

publicfunctionexecute(){

$productCollection=$this->_objectManager

->create('Magento\Catalog\Model\ResourceModel\Product\Collection')

->addAttributeToSelect([

'name',

'price',

'image',

])

->addAttributeToFilter('entity_id',array(

'in'=>array(159,160,161)

));

$output='';

$productCollection->setDataToAll('price',20);

foreach($productCollectionas$product){

$output.=\Zend_Debug::dump($product->debug(),null,false);

}

$this->getResponse()->setBody($output);

}

14. WhenyouusethesetDataToAll()method,nothingwillbechangedinthedatabaseuntilyouhavecalledthesave()method.AddthefollowingcodeafterthesetDataToAll()methodtosavethecollection:

$productCollection->save();

Howitworks…WhenwewantacollectionofMagentoentities,wecandothisbycallingthecollectionclassofthemodel.Thisclassislocatedintheresourcemodelfolderofamodule(Model/ResourceModel/<Modelname>/Collection.php).

Togetacollection,wecanusethegetCollection()methodofamodel.Whenweusethatmethod,aninstanceofthecollectionclassisreturned.Abetterwayistodirectlycallthecollectionclasslikewedidinthisrecipe.

Acollectionclassalwaysextendsfromthe\Magento\Framework\Data\Collectionclass,whichgivesaccesstoallthemethodstoworkwithcollections.Theresultofacollectionisalwaysreturnedasaniterableobjectsowecanloopthroughtheobjectsinthecollection.

Whenworkingwithcollections,wehavetwotypesofcollections.Thecollectionsworkingwithflatentities,andthecollectionsworkingwithEAVentities(suchasproducts).

ThedifferencebetweenaflatandanEAVcollectionisthat,withaflatcollection,allthefieldsofthetableareincludedinthecollection.WithanEAVcollection,wehavetousethemethodaddAttributeToSelect()toincludetheEAVattributesthatwewantinourcollection.

ThisisbecauseEAVdataisstoredinmultipletablesforasingleentity.Foraproduct,thevaluesofeverytypeofattributearestoredinadifferenttable(thecatalog_product_entity_*tables).

WiththeaddAttributeToFilter()method,wecancreateconditionsontheSQLstatement.Withflatcollections,wecanalsousetheaddFieldToFilter()method.

Forthisrecipe,weusedthe\Zend_Debug::dump()methodtocreatedumpsofourobjects.Weusethismethodbecausethisadds<pre>tagsaroundthedumpsowecaneasilyreaditinabrowser.

Inthatdumpstatement,wesetthethirdparametertofalse.Thismeansthatthismethodwillreturnthedatainsteadofprintingit.Tocorrectlyaddaresponsetoacontroller,weneedtousethe$this->getResponse()->setData()methodattheendofthecontrolleraction.

ProgrammaticallyaddingproductattributesIntherecipeCreatinganinstallandupgradescript,welearnedhowwecanautomatetheexecutionofdatabasechanges.

Intheseinstallscripts,wecanalsoaddattributestoproducts,aswewilllearninthisrecipe.

GettingreadyWewillworkfurtheronthePackt_HelloWorldmodulethatwehavecreatedinthepreviousrecipes.Makesureyouhavethemoduleinstalled.

Howtodoit…Thefollowingstepsdescribetheproceduretocreateanupgradescriptthataddsaproductattributetoallproducts:

1. ThemodulePackt_HelloWorldisalreadyinstalledinoursystemsowehavetocreateanupgradescript.CreatethefileUpgradeData.phpinthefolderapp/code/Packt/HelloWorld/Setup.Addthefollowingcontentinthatfile:

<?php

namespacePackt\HelloWorld\Setup;

useMagento\Framework\Setup\UpgradeDataInterface;

useMagento\Framework\Setup\ModuleContextInterface;

useMagento\Framework\Setup\ModuleDataSetupInterface;

classUpgradeDataimplementsUpgradeDataInterface{

protected$categorySetupFactory;

publicfunction

__construct(\Magento\Catalog\Setup\CategorySetupFactory

$categorySetupFactory){

$this->categorySetupFactory=$categorySetupFactory;

}

publicfunctionupgrade(ModuleDataSetupInterface$setup,

ModuleContextInterface$context){

if(version_compare($context->getVersion(),'2.0.2')<0){

$categorySetup=$this->categorySetupFactory->create(['setup'=>

$setup]);

$entityTypeId=$categorySetup-

>getEntityTypeId(\Magento\Catalog\Model\Product::ENTITY);

$categorySetup->addAttribute($entityTypeId,'helloworld_label',

array(

'type'=>'varchar',

'label'=>'HeloWorldlabel',

'input'=>'text',

'required'=>false,

'visible_on_front'=>true,

'apply_to'=>

'simple,configurable,virtual,bundle,downloadable',

'unique'=>false,

'group'=>'HelloWorld'

));

}

}

}

2. Updatetheversionnumberinthemodule.xmlfile.Openthefileapp/code/Packt/HelloWorld/etc/module.xmlandchangethesetup_versionattributeofthe<module>tagto2.0.2.Thisfilewilllookasfollows:

<?xmlversion="1.0"?>

<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.

xsd">

<modulename="Packt_HelloWorld"setup_version="2.0.2">

<sequence>

<modulename="Magento_Catalog"/>

</sequence>

</module>

</config>

3. Runthecommandphpbin/magentosetup:upgradeinyourcommandlinetoruntheupgradescripts.

4. Logintothebackendandlookforaproduct.YouwillseethattheattributeisaddedtotheHelloWorldtablikeinthefollowingscreenshot:

NoteThefirstversionofMagento2hasanissuethattheproducteditpagewillnotloadcorrectlyafteraddinganewattribute.Iftheproducteditpagedoesn’tloadcorrectly,youhavetosavetheproducttemplate(attributeset)oftheproductmanually.Aftersavingthis,thepagewillloadcorrectly.

Howitworks…Installingaproductattributeisdoneinthedatainstallorupgradescripts.Inthatscript,weusethecategorySetupclasstoaddtheattributes.Inthisclass,theaddAttribute()methodisavailableforaddingattributes.

TheaddAttribute()methodwillcreateanEAVattributethatwillbestoredinthetableeav_attribute.EveryEAVattributeisfromaspecificentitytype.That’sthereasonwhywehaveretrievedtheentitytypewiththemethod$categorySetup->getEntityTypeId(\Magento\Catalog\Model\Product::ENTITY);.

Foraproduct,someinformationoftheattribute(suchasthevisible_on_front,used_in_product_listing,andmore)isstoredinaseparatetable.Thistableisthecatalog_eav_attributetablewherealltheextendedinformationofthecatalogattributesissaved.Withtheattribute_idparameter,everyrowisrelatedtoanEAVattributefromthetableeav_attribute.

RepairingthedatabaseSometimes,itcanhappenthatyourMagentodatabaseisbrokenorcorrupt.Thiscanbecausedbyvariousreasonssuchasahackoraservercrash.Whenthedatabaseisbroken,everyonewantstorepairitasquicklyaspossible.

Commonexamplesofacorruptdatabasearemissingtablesorcolumnsafterrunninganunsavedquery,ormissingrelationsafteranimportofadumpwithouttherelations.

MagentohasadatabaserepairtoolthatcomparesdatabaseAwithdatabaseBandthistoolcanfixthemissingstructuresbetweenthem.

Inthisrecipe,wewillmakeourdatabasecorruptandwewillrepairitwiththerepairtool.

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

Howtodoit…Thenextstepsshowyouhowyoucanbreakyourdatabaseandhowtofixitusingthedatabaserepairtool:

1. CreateabackupofyourexistingMagento2database.2. Createanewemptydatabasethatwewilluseasthereferencedatabase.Let’ssaywe

callitmagento2_dev_repair.3. ConfigureMagentotousethisemptydatabaseintheapp/etc/env.phpfile.4. Runthecommandphpbin/magentosetupsetupsetup:upgradeintheMagento

root.ThiswillinstallanemptyMagentointhedatabase.5. Makeyouroriginaldatabasecorrupt.Youcandothisbyrunningthefollowing

queriesthatremoveaforeignkeyandatable:

ALTERTABLEstoreDROPFOREIGNKEY

STORE_WEBSITE_ID_STORE_WEBSITE_WEBSITE_ID;

DROPTABLEcatalog_product_index_price;

6. Browsetotherepairtoolinyourbrowser,andconfigureyouroriginalandreferencedatabaseasshowninthefollowingscreenshot:

7. Submittheformandthescriptwillrepairyourdatabase.Onthenextpage,youwillseewhatchangesaremadetoyouroriginaldatabase.

8. Switchthedatabasebacktotheoriginaloneinapp/etc/env.php,flushthecache,andyourstoreisbackupandrunning.

Howitworks…ThedatabaserepairtoolofMagentocomparesthestructureoftwodatabaseswitheachotherandwillfixthedifferences.

WecreatedanemptydatabasewhereweinstalledanewemptyMagentowiththeinstallscripts.Thisdatabasewasusedasthereferencedatabase.

Inthedatabaserepairtool,weenteredtheoriginaldatabaseasthecorrupteddatabase.

Thetoolwillcomparethestructureofthereferencedatabasewiththecorrupteddatabase.Whenthecomparisonsaredone,thetoolwillmakethestructureofthecorrupteddatabasethesameasthestructureofthereferencedatabase.

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

Thecomparetoolonlyfixesstructuralissueswithyourdatabase.Ifyoumisssomeofyourdata,thisisnottherighttooltogetitback.

Chapter6.MagentoBackendInthischapter,wewillcover:

RegisteringabackendcontrollerExtendingthemenuAddinganACLAddingconfigurationparametersCreatingagridofadatabasetableWorkingwithbackendcomponentsAddingcustomerattributesWorkingwithsourcemodels

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

ByfollowingthebestpracticesofMagento,allsecurityrisksaremanagedbyMagentoliketheaccessforanonymoususers.

TherecipesinthischapterdescribethemostcommonwaysinwhichyoucanextendyourbackendusingthebestpracticesofMagento2.

RegisteringabackendcontrollerThefirstthingthatwewilllearnishowtoextendthebackendwithacustomcontrolleraction.Weneedacontrollerthatissecuredsothatonlylogged-inbackenduserscanseethecontentsofthispage.

AbackendcontrollerisrequiredwhenyouwanttoaddanextrapagetothebackendofMagento.Thisismostlythecasewhenyouareworkingwithacustomformoroverviewthatyouneedforyourmodule.

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

Howtodoit…Whenyouwanttoaddanextrapagetoyourbackend,youhavetofollowthesesteps:

1. Createthefileroutes.xmlinthefolderapp/code/Packt/HelloWorld/etc/adminhtmlwiththefollowingcontent:

<?xmlversion="1.0"?>

<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd

">

<routerid="admin">

<routeid="helloworld"frontName="helloworld">

<modulename="Packt_HelloWorld"before="Magento_Backend"/>

</route>

</router>

</config>

2. Inthefolderapp/code/Packt/HelloWorld/Controller/Adminhtml/Index,createafilecalledIndex.phpwiththefollowingcontent:

<?php

namespacePackt\HelloWorld\Controller\Adminhtml\Index;

useMagento\Backend\App\Action\Context;

useMagento\Framework\View\Result\PageFactory;

classIndexextends\Magento\Backend\App\Action

{

protected$resultPageFactory;

publicfunction__construct(

Context$context,

PageFactory$resultPageFactory

){

parent::__construct($context);

$this->resultPageFactory=$resultPageFactory;

}

publicfunctionexecute()

{

}

}

NoteMakesurethatyourcontrolleractionextendsfromthe\Magento\Backend\App\Actionclass.Thiswillcovertheaccessforauthorizedusers.

3. Cleanthecacheusingthecommandphpbin/magentocache:cleanandnavigatetotheURL/admin/helloworld/index.Youwillseeawhitepage,whichisnormalbecausetheactionisempty.

4. Whenyouaddthefollowingcodeintheexecute()functionofthatclass,youwillseethatthebackendinterfaceistherewithanemptypage:

publicfunctionexecute()

{

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

return$resultPage;

}

Howitworks…Backendcontrollersworksimilartofrontendcontrollers.Themaindifferencebetweenthemisthatabackendcontrolleractionwillextendfromtheclass\Magento\Backend\App\Actionwhereafrontendcontrollerwillextendfromtheclass\Magento\Framework\App\Action\Action.

Theclasswherethebackendactionextendsfrommanagesallthesecuritysothatthepageisonlyaccessibleforauthenticatedbackendusers.

Therouteforthebackendcontrollerisinitializedintheroutes.xmlfileforadminhtml.Inthisfile,theroutenameisconfiguredforthePackt_HelloWorldmodule.Wecalledthisroutehelloworld.

ThecontrolleractionfilesareplacedintheAdminhtmlsubfolderoftheControllerfolderofthemodule.MagentoknowsthatithastolookintheAdminhtmlfolderforbackendcontrollersandactions.

Intheconfiguration,wedisabledthesecretkeystotheadminURLssoitiseasytodebugnewURLs.ThestructureofabackendURLislikelythesameasinthefrontend.Theonlydifferenceisthatbackendcontrolleractionsalwaysstartwiththeadminhtmlprefix.Whenthisprefixisadmin,theURLswillhavethefollowingstructure:<path_to_backend>/<route_name>/<controller_name>/<action_name>

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

GettingreadyThisrecipebuildsfurtheronthepreviousone.Makesureyouhavethecodeofthepreviousrecipeinstalled.

Howtodoit…ThefollowingstepsdescribehowwecanaddextramenuitemstotheAdminmenu:

1. FirstwehavetothinkaboutwherewewillgetanextramenuitemintheAdminmenu.Forthistutorial,wewilladdanextraitemintheMarketingmenu.ForthisweneedtoknowtheIDofthemarketingmenu.

2. Createthefileapp/code/Packt/HelloWorld/etc/adminhtml/menu.xmlwiththefollowingcontent:

<?xmlversion="1.0"?>

<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Backend:etc/m

enu.xsd">

<menu>

<add

id="Packt_HelloWorld::helloworld"

title="HelloWorld"

module="Packt_HelloWorld"

sortOrder="50"

parent="Magento_Backend::marketing"

resource="Packt_HelloWorld::helloworld"

/>

<add

id="Packt_HelloWorld::index"

title="HelloworldIndex"

module="Packt_HelloWorld"

sortOrder="55"

parent="Packt_HelloWorld::helloworld"

action="helloworld/index/"

resource="Packt_HelloWorld::index"

/>

</menu>

</config>

3. Cleanthecacheandreloadthebackend.Whenopeningthemarketingmenu,youwillseethatthereisaHelloWorldgroupwithalink,likeinthefollowingscreenshot:

4. Tochangethepositionofthelinks,wecanplaywiththesortOrderattributeoftheconfiguration.

Howitworks…TheAdminmenuofMagentoisbasedonallthemenu.xmlfilesofallthemodules.Thestandardmenucontainsthefollowingrootitems:

DashboardSalesProductsCustomersMarketingContentReportsStoresSystem

Alltheseitemsaredefinedinthefileapp/code/Magento/Backend/etc/adminhtml/menu.xml,soifyouneedanidentifieroftherootitems,youhavetolookinthatfile.Ifalinkisnotconfiguredinthatfile,youhavetolookinthemenu.xmlfilesoftheothermodules,suchascatalog,customer,andreports.

Weaddedanitemtothemenuwithacustommenu.xmlfile.Inthatfile,weconfiguredtwostatementsthataddamenuitem.ThefirstoneisusedtoconfiguretheHelloWorldheadingofthemenu.Thisitemhasnoactionattribute,andtheparentisMagento_Backend::marketing(theIDoftheparentmenuitem).Thesecondoneisusedtoconfigurethelink.Intheactionattribute,theURLisconfigured.TheparentattributeistheIDoftheHelloWorldheading(Packt_HelloWorld::helloworld).

An<add/>statementcanhavethefollowingattributes:

id:Theidentifierofthemenuitemtitle:Thetitleofthemenuitemmodule:ThemodulethatthemenuitemwillrefertosortOrder:Thesequencebywhichallthemenuitemswillbeorderedparent:Theparentmenuitemaction:Iftheitemcontainsalink,thisistheURLleadingtothecontrolleractionresource:TheidentifieroftheACLresourceexplainedintherecipeAddinganACL

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

GettingreadyEachadminuserisamemberofanadminrole.Theseroleshaveaccesspermissionsthatmanagetheaccesstocertainpagesinthebackend.

Inthisrecipe,wewilladdnewpermissionsforthepagewecreatedinthepreviousrecipesofthischapter.Makesureyouhaveinstalledthefilesofthepreviousrecipes.

Howtodoit…Thefollowingstepsdescribehowyoucanlimittheaccesstoapageforaroleofusers:

1. OpenthebackendandnavigatetoSystem|Permissions|UserRoles.ClickonthebuttonAddNewRoleandopentheRoleResourcestab.YouwillseeatreewithalltheavailableACLsinthisMagentoinstallation.

2. ThesecondstepistoaddanextraACLtothatpage.Forthisweneedtocreatethefileapp/code/Packt/HelloWorld/etc/acl.xmlwiththefollowingcontent:

<?xmlversion="1.0"?>

<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation="urn:magento:framework:Acl/etc/acl.xsd">

<acl>

<resources>

<resourceid="Magento_Backend::admin">

<resourceid="Magento_Backend::marketing">

<resourceid="Packt_HelloWorld::helloworld"

title="HelloWorld"sortOrder="60">

<resourceid="Packt_HelloWorld::index"

title="HelloWorldindex"/>

</resource>

</resource>

</resource>

</resources>

</acl>

</config>

3. CleanthecacheandnavigatetoSystem|Permissions|UserRoles,clickontheAddNewRolebutton,andopentheRoleResourcestab.WhenyousearchforHelloWorld,youwillseethatthattheACLswehavejustcreatedareinthelist,asyoucanseeinthefollowingscreenshot:

4. SubmitthisformtoaddanewrolewiththeHelloWorldindexcheckboxchecked.Let’ssaywecallthisroleTestHelloWorld.

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

6. WehavejustcreatedanACLforthepagethatisavailableat/admin/helloworld/index/index.WehavetoaddthefollowingfunctiontothatcontrollersothecontrolleractionknowswhichACLisactiveforthatpage.Openapp/code/Packt/HelloWorld/Controller/Adminhtml/Index/Index.phpandaddthefollowingfunctioninclassofthatfile:

protectedfunction_isAllowed()

{

return$this->_authorization->isAllowed('Packt_HelloWorld::index');

}

7. Logintothebackendwiththenewuserandyouwillseethatthisuserhasonlyaccesstothepagesthatwehaveconfiguredforthespecificroleofthatuser.

Howitworks…WiththeMagentoACLs,itispossibletorestricttheaccessofbackendpagestoaspecificuserrole.Forexample,theproductmanagerrolecanonlyaccesstheproducts,categories,andpromotionrulespages,andthelogisticpartneronlyhasaccesstotheorderpages.

Whenyoudon’tcreatetheACLsforcustompages,onlytherolesthathaveaccesstoalltheresourcescanaccessthepages.Inmostcases,thisistheadministrator.Forotherroles,itisnotpossibletoaccesspageswithnoACLs.

TipIntheMagentoCommunityEditions,itisnotpossibletorestricttheaccesstothedataofaspecificstore.Forexample,alogisticpartnercanonlyseetheordersofstore1.TheACLrestrictionsareonlybasedoncontrolleractions.

That’sthereasonthatwehaveconfiguredtheACLsintheacl.xmlfileofthemodule.Inthisfile,wedefinenewACLsinatreestructure.TheseACLshavesanidentifierthatisusedtomanagetheaccesstothemenuandcontrollerpage.

Inthecontrolleraction,wehavecreatedan_isAllowed()functionthatcheckstheaccesstothegivenACL.

Inthemenu.xmlfileofthemodule,theACLforeverymenuitemisconfiguredintheresourceattributeoftheconfigurations(likethehighlightedcodeshownhere):

<add

id="Packt_HelloWorld::helloworld"

title="HelloWorld"

module="Packt_HelloWorld"

sortOrder="50"

parent="Magento_Backend::marketing"

resource="Packt_HelloWorld::helloworld"

/>

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

GettingreadyLikethepreviousrecipesinthischapter,wewillbuildfurtheronthemodulethatwehavecreatedinthepreviousrecipes.Makesureyouhavethemodulefilesinstalled.

Howtodoit…Thefollowinginstructionsdescribewhatyouhavetodowhenyouwanttoaddsomecustomconfigurationparameters:

1. Thesystemconfigurationisalwaysconfiguredinthesystem.xmlfileofthemodule.Socreatethefileapp/code/Packt/HelloWorld/etc/adminhtml/system.xmlwiththefollowingcontent:

<?xmlversion="1.0"?>

<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/sy

stem_file.xsd">

<system>

</system>

</config>

2. Toaddanewtabtotheconfigurationmenu,wehavetoaddthefollowingconfigurationXMLinthesystem.xmlfile.Pastethefollowingcodeasachildofthe<system>tag:

<tabid="packt"translate="label"sortOrder="500">

<label>Packt</label>

</tab>

3. ThenextthingtodoistocreateanACLforthenewconfigurationpage.Addthefollowingconfigurationinthefileapp/code/Packt/HelloWorld/etc/acl.xml.Pastethefollowingcodeasachildofthe<resourceid="Magento_Backend::admin">tag:

<resourceid="Magento_Backend::stores">

<resourceid="Magento_Backend::stores_settings">

<resourceid="Magento_Config::config">

<resourceid="Packt_HelloWorld::config_helloworld"

title="HelloWorldSection"/>

</resource>

</resource>

</resource>

4. CleanthecacheandopenSystem|Permissions|UserRoles.ClickontheAddNewRolebuttonandopentheRoleResourcestab.IfeverythingisOK,youwillseeHelloWorldSectioninthelist,likeinthefollowingscreenshot:

5. WhenHelloWorldSectionisinthelist,itmeansthattheACLisadded.Don’tsavethenewrole;thisstepwasjusttoverifythattheACLsettingsareright.

6. WiththerightACLsettingsinstalled,wecanaddtherightcodeforanewMagentoconfigurationpage.Openthefileapp/code/Packt/HelloWorld/etc/adminhtml/system.xmlandaddthefollowingcodeasachildofthe<system>tag:

<sectionid="helloworld"translate="label"type="text"sortOrder="100"

showInDefault="1"showInWebsite="1"showInStore="1">

<label>HelloWorld</label>

<tab>packt</tab>

<resource>Packt_HelloWorld::config_helloworld</resource>

<groupid="hellopage"translate="label"type="text"sortOrder="1"

showInDefault="1"showInWebsite="1"showInStore="1">

<label>HelloWorldpagesettings</label>

<fieldid="is_enabled"translate="label"type="select"

sortOrder="10"showInDefault="1"showInWebsite="1"showInStore="1">

<label>IsEnabled</label>

<source_model>Magento\Config\Model\Config\Source\Yesno</source_model>

</field>

<fieldid="header_title"translate="label"type="text"

sortOrder="20"showInDefault="1"showInWebsite="1"showInStore="1">

<label>Headertitle</label>

</field>

</group>

</section>

7. CleanthecacheandopenStores|Configuration.Ifeverythingwentwell,youwillseethePACKTgroupinthemenu.Whenyouclickonit,youwillseetheconfigurationfieldsIsEnabledandHeadertitle,asshowninthefollowingscreenshot:

8. Whenwewanttoconfigureadefaultvalueforthisfield,wehavetocreatethefileapp/code/Packt/HelloWorld/etc/config.xmlwiththefollowingcontent:

<?xmlversion="1.0"?>

<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/con

fig.xsd">

<default>

<helloworld>

<hellopage>

<is_enabled>1</is_enabled>

<header_title>HelloWorldtitle</header_title>

</hellopage>

</helloworld>

</default>

</config>

9. Cleanthecacheandreloadtheconfigurationpage.Youwillseethatthedefaultvaluesarefilledasspecifiedintheconfig.xmlfilethatwejustcreated.

Howitworks…AllthevaluesoftheMagentoconfigurationarespecifiedinthesystem.xmlfileofthemodule.Magentoreadseachfileandmergesthemtogetherwhenbuildingtheconfigurationpage.

Thefirstthingwedidwastocreateatabinthemenuoftheconfigurationpages.ThetabhasanIDattributethatisusedtolinkasectiontoatab.

Thesecondthingwastoaddanewpagewithconfigurationparameters.Becauseanewpagerequiresaccesspermissions,wehadtocreateanewACLforthatpage.Weextendedtheacl.xmlfilewithanACLforthenewconfigurationpage.

AftertheACL,wecreatedtheconfigurationtoshowthetwoconfigurationfields.TheXMLtreestartstodefinethesection.Asectionisusedtodefineanewconfigurationpage.Inthistutorial,wehavecreatedasectionwiththeidentifierhelloworld.

Eachsectionisdividedintogroups.Thesearetheaccordionsthatyoucanopenandcloseontheconfigurationpage.WecreatedagroupwiththeIDhellopage.

Inagroup,wecanconfiguretheconfigurationfields.Inthisrecipe,wecreatedtwofields.Thefirstfieldwasafieldwithadropdowntosaywhetheritisenabledornot.Thesecondonewasatextfieldtoconfigurethetitle.

Withthe<source_model>tag,weconfiguredthevaluesforthedropdownmenubyspecifyingtheclassofthesourcemodel.MoreinformationaboutsourcemodelscanbefoundintherecipeWorkingwithsourcemodelsinthischapter.

Thelastthingwedidinthisrecipewastoprovidesomedefaultvaluesfortheconfigurationparameters.Thedefaultvaluesaresetintheconfig.xmlfileofthemodule.Inthe<default>tag,youcanspecifyadefaultvalueforaconfigurationparameter.Let’stakealookatthefollowingcodesnippet:

<section_id>

<group_id>

<field_id>Value</field_id>

</group_id>

</section_id>

Makesureyoureplacesection_id,group_idandfield_idsoitmatchestheconfigurationforyourfield.

Eachsection,group,andfieldhastheattributesshowInDefault,showInWebsite,andshowInStore.Withtheseattributes,youcanspecifythattheconfigurationoptionisvisiblewhenconfiguringsomethingforthatscope.

InMagento,therearethreelevelsofconfigurationscopesavailable.ThesescopesareusedwhenyourunmultiplestoresonthesameMagentoinstallation.ThefollowingconfigurationscopesareavailableinMagento:

Globalconfiguration(showInDefault)Websiteconfiguration(showInWebsite)

Storeviewconfiguration(showInStore)

Whenyouwanttochangethescopeoftheconfigurationpage,youcanusetheStoreViewdropdownontheconfigurationpage,asshowninthefollowingscreenshot:

Whenyousavetheconfigurationform,thevaluesarestoredinthedatabasetablecore_config_data.Whenyouopenthattable,youseethefollowingcolumns:

config_id:TheconfigurationIDscope:Bydefault,thisiswebsiteorstorescope_id:TheIDofthewebsiteorstorepath:Theconfigurationpathvalue:Theconfigurationvalue

Thepathcolumnisthecombinationofthesectionid,groupid,andfieldidcolumns.Forthefieldsthatwecreatedinthisrecipe,thepathswillbeasshownhere:

helloworld/hellopage/is_enabled

helloworld/hellopage/header_title

CreatingagridofadatabasetableInthepreviouschapter,wecreatedaMagentoentitythatwaslinkedtoadatabasetable.Inthisrecipe,wewillcreateabackendinterfacesothatthebackenduserscanseethedatafromthistableinthebackend.

GettingreadyMakesureyouhavetherightfilesforthisrecipeinstalledinyourMagentoinstance.

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

Howtodoit…Followthesestepstocreatebackendgrids:

1. Whenwewantanewpage,weneedacontrollerwithacontrolleraction.SoweneedtoaddanACLforthatpage.Openthefileapp/code/Packt/HelloWorld/etc/acl.xml,andaddthefollowinglineofcodeasachildofthe<resourceid="Packt_HelloWorld::helloworld"title="HelloWorld"sortOrder="60">tag:

<resourceid="Packt_HelloWorld::subscription"title="HelloWorld

subscription"/>

2. Weneedsomenavigationtothenewpage.Wecandothisbycreatingamenuitemforthepage.Openapp/code/Packt/HelloWorld/etc/adminhtml/menu.xmlandaddthefollowingcodeasachildofthe<menu>tag:

<add

id="Packt_HelloWorld::subscription"

title="Subscriptions"

module="Packt_HelloWorld"

sortOrder="70"

parent="Packt_HelloWorld::helloworld"

action="helloworld/subscription/"

resource="Packt_HelloWorld::subscription"

/>

3. Aspreviously,weneedtocreatethecontrolleraction.Createthefileapp/code/Packt/HelloWorld/Controller/Adminhtml/Subscription/Index.php

withthefollowingcontent:

<?php

namespacePackt\HelloWorld\Controller\Adminhtml\Subscription;

useMagento\Backend\App\Action\Context;

useMagento\Framework\View\Result\PageFactory;

classIndexextends\Magento\Backend\App\Action

{

protected$resultPageFactory;

publicfunction__construct(

Context$context,

PageFactory$resultPageFactory

){

parent::__construct($context);

$this->resultPageFactory=$resultPageFactory;

}

publicfunctionexecute()

{

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

$resultPage->setActiveMenu('Packt_HelloWorld::subscription');

$resultPage->addBreadcrumb(__('HelloWorld'),__('HelloWorld'));

$resultPage->addBreadcrumb(__('ManageSubscriptions'),

__('ManageSubscriptions'));

$resultPage->getConfig()->getTitle()-

>prepend(__('Subscriptions'));

return$resultPage;

}

protectedfunction_isAllowed()

{

return$this->_authorization-

>isAllowed('Packt_HelloWorld::subscription');

}

}

4. CleanthecacheandopentheMarketingmenuinthebackend.YouwillseethatthereisanewmenuitemaddedundertheHelloWorldsection.WhenyouclickonthelinkSubscriptions,youwillberedirectedtothepagethatwehavejustcreated.

5. Thebackendpageisnowupandrunning.Inthenextsteps,wewillcompletethisrecipebyaddingagridonthispage.

6. Toaddagrid,wehavetoaddthegridcontainerblock.Tocreatethisblock,wehavetocreatethefileapp/code/Packt/HelloWorld/Block/Adminhtml/Subscription.phpwiththefollowingcontent:

<?php

namespacePackt\HelloWorld\Block\Adminhtml;

classSubscriptionextends\Magento\Backend\Block\Widget\Grid\Container

{

protectedfunction_construct()

{

$this->_blockGroup='Packt_HelloWorld';

$this->_controller='adminhtml_subscription';

parent::_construct();

}

}

7. Inthegridcontainerblock,wehavetoaddthegridblock.Forthis,weneedtocreatethefileapp/code/Packt/HelloWorld/Block/Adminhtml/Subscription/Grid.phpwiththefollowingcontent:

<?php

namespacePackt\HelloWorld\Block\Adminhtml\Subscription;

useMagento\Backend\Block\Widget\GridasWidgetGrid;

classGridextends\Magento\Backend\Block\Widget\Grid\Extended

{

/**

*@var\Packt\HelloWorld\Model\Resource\Subscription\Collection

*/

protected$_subscriptionCollection;

/**

*@param\Magento\Backend\Block\Template\Context$context

*@param\Magento\Backend\Helper\Data$backendHelper

*@param

\Packt\HelloWorld\Model\ResourceModel\Subscription\Collection

$subscriptionCollection

*@paramarray$data

*/

publicfunction__construct(

\Magento\Backend\Block\Template\Context$context,

\Magento\Backend\Helper\Data$backendHelper,

\Packt\HelloWorld\Model\ResourceModel\Subscription\Collection

$subscriptionCollection,

array$data=[]

){

$this->_subscriptionCollection=$subscriptionCollection;

parent::__construct($context,$backendHelper,$data);

$this->setEmptyText(__('NoSubscriptionsFound'));

}

/**

*Initializethesubscriptioncollection

*

*@returnWidgetGrid

*/

protectedfunction_prepareCollection()

{

$this->setCollection($this->_subscriptionCollection);

returnparent::_prepareCollection();

}

}

8. Theblocksarecreated,butwehavetoaddthemtothecontrollerwithalayoutupdate.Createthefileapp/code/Packt/HelloWorld/view/adminhtml/layout/helloworld_subscription_index.xml

withthefollowingcontenttocreatethelayoutupdate:

<?xmlversion="1.0"?>

<pagexmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/pa

ge_configuration.xsd">

<body>

<referenceContainername="content">

<block

class="Packt\HelloWorld\Block\Adminhtml\Subscription"

name="adminhtml.block.helloworld.subscription.container">

<block

class="Packt\HelloWorld\Block\Adminhtml\Subscription\Grid"

name="adminhtml.block.helloworld.subscription.grid"as="grid"/>

</block>

</referenceContainer>

</body>

</page>

9. CleanthecacheandreloadtheSubscriptionspageinthebackend.Youwillseesomethinglikethefollowingscreenshot:

10. Inthescreenshot,weseethefilterbuttonsofthegrid,butifwewanttoshowthegrid,weneedtodefinethecolumns.Wecandothisbyaddingthefollowingfunctiontothefileapp/code/Packt/HelloWorld/Block/Adminhtml/Subscription/Grid.php.Pastethefunctionintheexistingclass.

/**

*Preparegridcolumns

*

*@return$this

*/

protectedfunction_prepareColumns()

{

$this->addColumn(

'subscription_id',

[

'header'=>__('ID'),

'index'=>'subscription_id',

]

);

$this->addColumn(

'firstname',

[

'header'=>__('Firstname'),

'index'=>'firstname',

]

);

$this->addColumn(

'lastname',

[

'header'=>__('Lastname'),

'index'=>'lastname',

]

);

$this->addColumn(

'email',

[

'header'=>__('Emailaddress'),

'index'=>'email',

]

);

$this->addColumn(

'status',

[

'header'=>__('Status'),

'index'=>'status',

]

);

return$this;

}

11. Whenyoureloadthepage,youwillseethatagridappearswiththecontentofthedatabasetable.

12. Whenwewanttodecoratethestatuscolumntoimprovethevisibilityofthestatuses,wehavetocreateaframe_callbackparameter.Addtheframe_callbackparametertotheaddColumn()functionofthestatussoitlookslikethis:

$this->addColumn(

'status',

[

'header'=>__('Status'),

'index'=>'status',

'frame_callback'=>[$this,'decorateStatus']

]

);

TipAddthehighlightedlineofcode.

13. frame_callbackreferstothefunctiondecorateStatus().Addthefollowingfunctiontotheclassthatwe’reediting:

publicfunctiondecorateStatus($value){

$class='';

switch($value){

case'pending':

$class='grid-severity-minor';

break;

case'approved':

$class='grid-severity-notice';

break;

case'declined':

default:

$class='grid-severity-critical';

break;

}

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

</span>';

}

14. Reloadthepage,andyoushouldhaveanoutputlikethatshowninthefollowingscreenshot:

Howitworks…ThebackendgridisoneofthebackendwidgetsthatisavailableinMagento.Otherwidgetsthatarewidelyusedaretheformstoeditdataorthetabbedmenuontheleft-handside.

Thegridwidgetismadetodisplaythecontentofacollectioninagrid.ByusingthegridwidgetofMagento,itispossibletosortandfilterthecolumnsthatarevisibleinthegrid.Apagerisautomaticallyincludedwhenyouhavealotofdata.Thispreventsout-of-memoryexceptionswhentherearealargenumberofrecordsinacollection.

Agridwidgetisalwaysplacedinsideagridcontainer.Westartedthisrecipebycreatingacontainerwidgetinwhichwewillplaceagrid.ThecontainerwidgetisusedtoshowtheheadingandbuttonsliketheAddNewbutton(whichhascurrentlynoaction).Agridcontainerblockalwaysextendsfromtheclass\Magento\Backend\Block\Widget\Grid\Container.

Inthatcontainer,wecreatedaGridblockthatisresponsibleforshowingthegrid.Agridblockalwaysextendsfromtheclass\Magento\Backend\Block\Widget\Grid.Inthisrecipe,ourclassextendsfrom\Magento\Backend\Block\Widget\Grid\Extended.Thisclassaddsthefilterstothegrid.Agridneedsacollection.Thiscollectionispassedasaparameterinthe__construct()methodoftheclass.Inthe_prepareCollection()method,thecollectionisassignedtotheclass.Agridneedsalsoaspecificationofthecolumnstobeshown.Thecolumnsarespecifiedinthe_prepareColumns()functionofthatclass.Inthatfunction,theaddColumn()functionisusedtospecifythecolumns.

TheaddColumn()functionhastwoparameters.Thefirstoneistheidentifier,andthesecondoneisakey-valuearraywiththespecificationsofthecolumn.Inthisrecipe,weusedthefollowingparameters:

header:Thenameofthecolumnindex:Thecolumninthedatabase

Thepreviousoptionsarerequired,whilethefollowingonesareoptional:

frame_callback(Afunctioncalltorenderthevalueofacell)type(Thisdefinesthefilterwidgetlikenumber,datetime,andoptions)options(Thisdefinesasourcemodelwhenthetypeisoptions)

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

GettingreadyWewillextendtheexistingPackt_HelloWorldmodulewithanewpagewherewecanplaywiththebackendcomponents.

Howtodoit…Inthefollowingsteps,wewillcreateapagethatusessomebackendcomponents:

1. Wewillmakesometestsinanewcontrolleraction.Createafileapp/code/Packt/HelloWorld/Controller/Adminhtml/Component/Index.phpwiththefollowingcontent:

<?php

namespacePackt\HelloWorld\Controller\Adminhtml\Component;

useMagento\Backend\App\Action\Context;

useMagento\Framework\View\Result\PageFactory;

classIndexextends\Magento\Backend\App\Action

{

protected$resultPageFactory;

publicfunction__construct(

Context$context,

PageFactory$resultPageFactory

){

parent::__construct($context);

$this->resultPageFactory=$resultPageFactory;

}

publicfunctionexecute()

{

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

$resultPage->setActiveMenu('Packt_HelloWorld::component');

$resultPage->addBreadcrumb(__('HelloWorld'),__('HelloWorld'));

$resultPage->addBreadcrumb(__('Components'),__('Components'));

$resultPage->getConfig()->getTitle()-

>prepend(__('Components'));

return$resultPage;

}

protectedfunction_isAllowed()

{

return$this->_authorization-

>isAllowed('Packt_HelloWorld::helloworld');

}

}

2. CreateamenuitemforthepagebyaddingthefollowingXMLinthefileapp/code/Packt/HelloWorld/etc/adminhtml/menu.xml.Pastethecodeasachildofthe<menu>tag:

<add

id="Packt_HelloWorld::component"

title="Components"

module="Packt_HelloWorld"

sortOrder="80"

parent="Packt_HelloWorld::helloworld"

action="helloworld/component/"

resource="Packt_HelloWorld::helloworld"

/>

3. Cleanthecacheandreloadthebackend.Inthemenu,youwillseeanitemthatleadstothecomponentpagethatwehavejustcreated.ThismenuitemisvisibleintheMarketingmenu.Whenyouopenthatpage,weseeanemptypage.

4. Toaddsomebuttonstothepage,wehavetocreatethefileapp/code/Packt/HelloWorld/view/adminhtml/templates/component/toolbar/buttons.phtml

withthefollowingcontent:

<divclass="page-actions">

<divclass="page-actions-inner">

<divclass="page-actions-buttons">

<button

class="action-primary"

title="<?phpecho__('Primarybutton')?>">

<?phpecho__('Primarybutton')?>

</button>

<button

class="action-secondary"

title="<?phpecho__('Secondarybutton')?>">

<?phpecho__('Secondarybutton')?>

</button>

<button

class="action-secondaryback"

title="<?phpecho__('Back')?>">

<?phpecho__('Back')?>

</button>

</div>

</div>

</div>

5. Toaddthisfiletothecomponentspage,wehavetoaddthefileapp/code/Packt/HelloWorld/view/adminhtml/layout/helloworld_component_index.xml

withthefollowingcontent:

<?xmlversion="1.0"?>

<pagexmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/pa

ge_configuration.xsd">

<body>

<referenceContainername="page.main.actions">

<blockclass="Magento\Backend\Block\Template"

name="component_buttons"

template="Packt_HelloWorld::component/toolbar/buttons.phtml"/>

</referenceContainer>

</body>

</page>

6. Cleanthecacheandreloadthepage.Youwillnowseesomethinglikeinthefollowingscreenshot:

7. Wewillcontinuewithaddingthecontentofthepage.Thefollowingcodeisanexampleofthepossibilitiesforformcomponents.Additinthefileapp/code/Packt/HelloWorld/view/adminhtml/templates/component/index.phtml

<divclass="col-m-4">

<fieldsetclass="fieldsetadmin__fieldset"id="theme">

<divclass="admin__fieldfield"data-ui-id="theme-edit-tabs-

tab-general-section-fieldset-element-form-field-theme-path">

<labelclass="labeladmin__field-label"><span>Label</span>

</label>

<divclass="admin__field-controlcontrol">

<divclass="control-value">Avalue</div>

</div>

</div>

<divclass="admin__fieldfield">

<labelclass="labeladmin__field-label"for="test_input">

<span>Testinput</span></label>

<divclass="admin__field-controlcontrol">

<inputname="design[test_input]"id="test_input"

value=""title="Testinput"type="text"class="input-text

admin__control-text"/>

</div>

</div>

</fieldset>

</div>

8. Toaddthisfiletothepage,addthefollowinglineofcodetothefileapp/code/Packt/HelloWorld/view/adminhtml/layout/helloworld_component_index.xml

asachildofthe<body>tag:

<referenceContainername="content">

<blockclass="Magento\Backend\Block\Template"

name="adminhtml.block.helloworld.component"

template="Packt_HelloWorld::component/index.phtml"/>

</referenceContainer>

9. Cleanthecacheandreloadthepage.Youwillseethataformappearsonthescreen.

NoteMagentoalsohasaformAPIthatgeneratestheelementsforyou.ThisAPIiswidelyusedforbackendformssuchasfortheCMSpages(andmanyotherforms).

10. Thefollowingcodeshowshowtoaddasmalldatatabletoapage.Addittotheendofthefileapp/code/Packt/HelloWorld/view/adminhtml/templates/component/index.phtml

<divclass="col-m-4">

<tableclass="admin__table-secondary">

<tr>

<th><?phpecho__('Firstname')?></th>

<td>John</td>

</tr>

<tr>

<th><?phpecho__('Lastname')?></th>

<td>Doe</td>

</tr>

<tr>

<th><?phpecho__('Email')?></th>

<td>john.doe@example.com</td>

</tr>

<tr>

<th><?phpecho__('Phone')?></th>

<td>000000000</td>

</tr>

</table>

</div>

11. Reloadthepage,andyouwillseeasmalltableneartheform.

Howitworks…Inthisrecipe,wecreatedsomeHTMLthatisusedtorenderbackendcomponents.TheMagentobackendisacombinationofmanyofthesecomponents,suchasthebuttonbar,forms,tables,grids,andmanymore.

Inthefirststeps,wecreatedacontrolleractionthatusesanexistingACL.Onthiscontrolleraction,weplacedablockthatcontainsthebuttons.Thebuttonbarisplacedinthepage.main.actionsblock.Thisblockisthegrayactionbarthatappearsonmanypagesinthebackend.

Later,wecreatedatemplatethatcontainssomebackendcomponents.Thefirstonewasaformwithafieldsetelement.Thisfieldsetelementcontainsatextbox,alabel,andanon-offbutton.

Thelastthingwedidwastocreateasmalltablewithdatainanewcolumn.Forthesecolumns,weusedtheclasscol-m-4.Byusingthisclass,thedivinstancewillhaveawidthoffourcolumns.ThestandardMagentobackendisdividedinto12columns,sofourcolumnsisathirdofthewidthofthebackendpage.

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

GettingreadyToaddacustomerattribute,weneedtocreateaninstallationorupgradescript.Inthisrecipe,wewillcreateanewmodulethatwillinstalltheattributewiththeinstallationscriptsowedon’tneedtoinstallstarterfilesinMagento.

Howtodoit…Inthefollowingsteps,wewillcreateasmallmodulethataddsacustomerattribute:

1. CreateamodulePackt_CustomerAttributebycreatingafileapp/code/Packt/CustomerAttribute/etc/module.xmlwiththefollowingcontent:

<?xmlversion="1.0"?>

<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.

xsd">

<modulename="Packt_CustomerAttribute"setup_version="2.0.0">

<sequence>

<modulename="Magento_Customer"/>

</sequence>

</module>

</config>

2. Inapp/code/Packt/HelloWorld/,createafilecalledregistration.phpwiththefollowingcontent:

<?php

\Magento\Framework\Component\ComponentRegistrar::register(

\Magento\Framework\Component\ComponentRegistrar::MODULE,

'Packt_CustomerAttribute',

__DIR__

);

3. Createadatainstallationscriptbycreatingafileapp/code/Packt/CustomerAttribute/Setup/InstallData.phpwiththefollowingcontent:

<?php

namespacePackt\CustomerAttribute\Setup;

useMagento\Framework\Setup\InstallDataInterface;

useMagento\Framework\Setup\ModuleContextInterface;

useMagento\Framework\Setup\ModuleDataSetupInterface;

classInstallDataimplementsInstallDataInterface

{

private$customerSetupFactory;

publicfunction

__construct(\Magento\Customer\Setup\CustomerSetupFactory

$customerSetupFactory)

{

$this->customerSetupFactory=$customerSetupFactory;

}

publicfunctioninstall(ModuleDataSetupInterface$setup,

ModuleContextInterface$context)

{

/**@varCustomerSetup$customerSetup*/

$customerSetup=$this->customerSetupFactory->create(['setup'

=>$setup]);

$setup->startSetup();

$customerSetup->addAttribute('customer','loyaltynumber',[

'label'=>'Loyaltynumber',

'type'=>'static',

'frontend_input'=>'text',

'required'=>false,

'visible'=>true,

'position'=>105,

]);

$loyaltyAttribute=$customerSetup->getEavConfig()-

>getAttribute('customer','loyaltynumber');

$loyaltyAttribute->setData('used_in_forms',

['adminhtml_customer']);

$loyaltyAttribute->save();

$setup->endSetup();

}

}

4. Toaddtheattributetothebackend,wehavetocreateaui_componentXMLfile.Createafileapp/code/Packt/CustomerAttribute/view/base/ui_component/customer_form.xml

withthefollowingcontent:

<?xmlversion="1.0"encoding="UTF-8"?>

<formxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_con

figuration.xsd">

<fieldsetname="customer">

<fieldname="loyaltynumber">

<argumentname="data"xsi:type="array">

<itemname="config"xsi:type="array">

<itemname="dataType"xsi:type="string">text</item>

<itemname="formElement"

xsi:type="string">input</item>

<itemname="source"

xsi:type="string">customer</item>

</item>

</argument>

</field>

</fieldset>

</form>

5. Toinstalltheattributeinthedatabase,runthecommandphpbin/magentosetup:upgrade.

6. Inthebackend,createanewcustomer.YouwillseetheLoyaltynumberattributeintheformlikeinthefollowingscreenshot:

Howitworks…WestartedtocreateasimplemodulewithadatainstallationscriptlikewedidinChapter4,CreatingaModule,andChapter5,DatabasesandModules.

Intheinstallationscript,weplacedsomecodethatinstallsacustomerattribute.Thisworksinlikelythesamewayasaddingaproductattribute.ThemaindifferenceisthefirstparameteroftheaddAttribute()function.

Toaddacustomerattribute,wehavetoplacethevaluecustomerinthatparameter.Theoptionsofthethirdparameterarealsodifferentwhenyoucompareitwithaproduct.

Theconfigurationofacustomerattributeissavedinthefollowingtwotables:

eav_attribute

customer_eav_attribute

Inthetableeav_attribute,thegenericdataoftheattribute,suchastheentitytypeandtheattributecode,issaved.

Inthetablecustomer_eav_attribute,thespecificdataforacustomerattribute,suchasthepositionandvalidationrules,issaved.

TheattributeisaddedtothedatabasewiththeaddAttribute()function.Toaddtheattributetothecustomerform,wehavetosavethatconfigurationinthedatabasewiththefollowingcode:

$loyaltyAttribute->setData('used_in_forms',['adminhtml_customer']);

Withtheprecedingcode,theattributewillbelinkedtotheadminhtml_customerform.Thisisthecustomerformofthebackend.

Anotherrequiredstepistoaddtheattributeintheui_componentXMLconfiguration.ThisconfigurationworkslikethelayoutXMLfiles.ThislayoutXMLfileworksasalayouthandle.Whenyoulookinthefileapp/code/Magento/Customer/view/adminhtml/layout/customer_index_edit.xml,youwillseethefollowingcodethatinitializesthelayoutupdateoftheui_component:

<uiComponentname="customer_form"/>

Withourcustomcustomer_formUIcomponent,weextendedthefieldsofthecustomerentity.

WorkingwithsourcemodelsMagentoworkswithalotofdropdownfieldsthatyoucanselectintheformsoftheapplication.Wecanseedropdownsintheconfiguration,product,customer,andmanymorepages.

Magentohasasystemtosettheoptionsofthedropdownandmultiselectfields.Magentousesamodelthatreturnsthevaluesandlabelstorendertheoptionsofadropdownormultiselectfield.Thesemodelsarecalledsourcemodels.

Inthisrecipe,wewillseewhichsourcemodelsMagentousesandhowwecancreateacustomsourcemodelforacustomconfigurationfield.

GettingreadyInthisrecipe,wewillextendthePackt_HelloWorldmodulethatwecreatedinthepreviousrecipes.Makesureyouhavetherightversioninstalledforthisrecipe.

Howtodoit…Thefollowingstepsdescribehowyoucancreateyourcustomsourcemodelsforyourcustomformfields:

1. Firstwewillcreateanextrafieldinthesystemconfigurationtorunsometests.ThefollowingcodeaddsanewfieldtotheHelloWorldconfigurationpage.Addittothefileapp/code/Packt/HelloWorld/etc/adminhtml/system.xmlasachildofthe<groupid="hellopage"translate="label"type="text"sortOrder="1"

showInDefault="1"showInWebsite="1"showInStore="1">tag.

<fieldid="source_model_test"translate="label"type="text"

sortOrder="30"showInDefault="1"showInWebsite="1"showInStore="1">

<label>Sourcemodeltest</label>

</field>

2. Inthebackend,openStores|Configuration|Packt|HelloWorld.YouwillseeanewtextfieldcalledSourcemodeltest.

3. Ifwewanttochangethatfieldtoadropdown,wehavetochangethetypeattributetoselectlikeinthefollowingcodesnippet:

<fieldid="source_model_test"translate="label"type="select"

sortOrder="30"showInDefault="1"showInWebsite="1"showInStore="1">

<label>Sourcemodeltest</label>

</field>

4. Cleanthecacheandreloadthepage.Youwillnowseeafieldwithanemptydropdown.

5. Toaddoptionstothedropdown,wehavetospecifyasourcemodel.Wecandothisbyaddingthe<source_model>tagasinthefollowingcode:

<fieldid="source_model_test"translate="label"type="select"

sortOrder="30"showInDefault="1"showInWebsite="1"showInStore="1">

<label>Sourcemodeltest</label>

<source_model>Magento\Config\Model\Config\Source\Locale</source_model>

</field>

6. Ifwewanttocreateacustomsourcemodel,wehavetocreateacustommodelclass.Createafileapp/code/Packt/HelloWorld/Model/Config/Source/Relation.phpwiththefollowingcontent:

<?php

namespacePackt\HelloWorld\Model\Config\Source;

classRelationimplements\Magento\Framework\Option\ArrayInterface

{

publicfunctiontoOptionArray()

{

return[

[

'value'=>null,

'label'=>__('--PleaseSelect--')

],

[

'value'=>'bronze',

'label'=>__('Bronze')

],

[

'value'=>'silver',

'label'=>__('Silver')

],

[

'value'=>'gold',

'label'=>__('Gold')

],

];

}

}

7. Toassignthepreviouslycreatedsourcemodeltothetestconfigurationfield,wehavetochangetheline<source_model>inthesystem.xmlfile.Changeittothefollowing:

<source_model>Packt\HelloWorld\Model\Config\Source\Relation</source_mod

el>

8. Cleanyourcacheandyouwillseethattheoptionsofthefieldarechangedbasedontheoutputfromthesourcemodel.Youcanseethisinthefollowingscreenshot:

Howitworks…AsourcemodelisamodelinstancewithatoOptionArray()function.Thisfunctionreturnsanarraywithalltheitemsofthesourcearray.Thisarrayhasthefollowingformat:

[

'value'=>'0',

'label'=>'Labeloption0'

],

[

'value'=>'1',

'label'=>'Labeloption1'

]

Thevaluekeyisthevalueofthe<option>elementinthedropdownlist.Thelabelkeyisthetextthatappearsinthedropdownlist.

Inthisrecipe,weconfiguredasourcemodelforaconfigurationfield.Wecanalsousesourcemodelsinthefollowingcases:

AproductattributeinthebackendAcustomerattributeinthebackendDropdownfiltersinbackendgridsMagentoformsinthefrontendandbackend(likethecountrydropdownatcheckoutandsoon)

TheconfigurationofthesourcemodelismostlydoneintheXMLconfigurationofthefield.ForEAVfields,theinformationofthesourcemodelisstoredintheattributeconfiguration,whichisstoredinthedatabase.

Whenadropdownfieldormultiselectfieldissaved,itisalwayssavedinasinglefieldofthedatabase.Ifafieldisadropdown,thevaluewillbestoredforthatfield.Whenthefieldisamultiselectfield,acomma-separatedlistoftheselectedvalueswillbesavedinthatfield.

Chapter7.EventHandlersandCronjobsInthischapter,wewillcoverthefollowingtopics:

UnderstandingeventtypesCreatingyourowneventAddinganeventobserverIntroducingcronjobsCreatingandtestinganewcronjob

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

Magentohasaneventsystemthatfireseventswhensomeactionshappensinyourshop.Withaconfiguration,itispossibletoexecutesomecodewhenaneventoccurs.It’slikehookingintoaclickeventinJavaScript.

Theobserverdesignpatternisusedtoimplementtheeventhandlingsystem.Whenaneventistriggered,Magentolooksforeventobserversthathookintothateventandexecutetherightmethodthatisconfiguredforthateventhandler.

AnothersimilarsysteminMagentoisacronjob.Intheconfiguration,youcancreateacronjobthatwillbeexecutedonaspecifictimestamp.Whenitistherighttime,Magentowillexecutethecodethatisconfiguredforthatcronjob.

Inthischapter,wewillexplorethepossibilitiesofusingthesetwosystems(Eventhandlersandcronjobs).

UnderstandingeventtypesWorkingwitheventtypesisbetterthanoverwritingafunctionwithdependencyinjection.Whenanalyzingaprocess,itisgoodtothinkabouthowyoucansolveyourproblem.ItisbettertoexecuteextracodeinsteadofrewritingthestandardfunctionsofMagento.

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

GettingreadyInthisrecipe,wewilldebugtheeventsthatarefiredinMagento.EnsurethatyouhaveaccesstothecommandlinebecausewewilldebugusingtheMagentologfiles.

Howtodoit…ThefollowingstepsdescribehowwecancreatealistfromthedispatchedeventsinaMagentorequest:

1. Magentoeventsaredispatchedusingthedispatch()methodoftheeventManagerinterface.Whenwewanttodebugthisfunction,wehavetomodifytheMagento\Framework\Event\Managerclass.Thefirstthingtodoistoenabletheloggerinterface.Createthe$_loggervariableinthelib/internal/Magento/Framework/Event/Manager.phpfile,asshownbythehighlightedcode.

...

protected$_eventConfig;

/**

*Loggerinterface

*@var\Psr\Log\LoggerInterface$logger

*/

protected$_logger;

/**

*@paramInvokerInterface$invoker

*@paramConfigInterface$eventConfig

*/

publicfunction__construct(InvokerInterface$invoker,ConfigInterface

$eventConfig){

$this->_invoker=$invoker;

...

NoteIfyouhaveinstalledMagentowithcomposer,youwillhavetoeditthevendor/magento/framework/Event/Manager.phpfile.

2. Addtheloggerinterfacetotheconstructorandinitializetheparameterthatwehavejustcreated.Replacetheconstructorofthatclasswiththefollowingcode:

/**

*@paramInvokerInterface$invoker

*@paramConfigInterface$eventConfig

*@param\Psr\Log\LoggerInterface$logger

*/

publicfunction__construct(InvokerInterface$invoker,ConfigInterface

$eventConfig,\Psr\Log\LoggerInterface$logger){

$this->_invoker=$invoker;

$this->_eventConfig=$eventConfig;

$this->_logger=$logger;

}

3. Inthesameclass,gotothedispatch()function.Inthefirstline,wewilladdalogstatementtoprinttheeventnameinthelogfilewhenaneventisfired.Addthehighlightedlineofcodetothatfunction:

publicfunctiondispatch($eventName,array$data=[]){

$this->_logger->debug($eventName);

\Magento\Framework\Profiler::start('EVENT:'.$eventName,['group'=>

'EVENT','name'=>$eventName]);

foreach($this->_eventConfig->getObservers($eventName)as

$observerConfig){

$event=new\Magento\Framework\Event($data);

$event->setName($eventName);

$wrapper=newObserver();

$wrapper->setData(array_merge(['event'=>$event],$data));

\Magento\Framework\Profiler::start('OBSERVER:'.

$observerConfig['name']);

...

4. Takealookatthelogfilethatyoucanfindinthevar/log/debug.logfolder.Usingthetail-fcommand,youcanseewhattextwillbeaddedtothatfile.InyourMagentoinstallation,changethedirectorytoMagentorootandrunthefollowingcommand:

tail-fvar/log/debug.log

5. Whenyouloadapage,youwillseealotofoutputinthelogfileasshowninthefollowingscreenshot.Thesearealltheeventsthataredispatchedwhenloadingapage:

6. Enoughdebuggingfornow.Itistimetorevertthelib/internal/Magento/Framework/Event/Manager.phpfile.Undoyourchanges,orifyouuseGit,youcanusethegitcheckoutlib/internal/Magento/Framework/Event/Manager.phpcommandtoundothelocalchangesinthatfile.

Howitworks…Inthisrecipe,weusedtheMagentodebugloggertoprintthenamesofthefiredevents.Thisisanalternativeforprintingthenamesdirectlyinthebrowser.Themainadvantageofthismethodisthatthedebugmessagesdoesn’taffecttheoutputofyourbrowser.

IfyouareawareaboutthesystemofMagento1,youwillnoticethatlogginginMagento2ischanged.InMagento1,wehadtheMage::log()function,butthisfunctiondoesn’texistinMagento2.InMagento2,wehavetousetheloggerinterfacethatweinitializeintheconstructorofaclass.

Todebugtheeventnames,weusedthedebug()functionoftheloggerinterfacethatwritesamessagetothevar/log/debug.logfile.Intheloggerinterface,thefollowingfunctionsareavailable:

alert()

critical()

debug()

emergency()

error()

info()

log()

notice()

warning()

Thedebug()functionwritestothevar/log/debug.logfile,andtheexception()functionwritestothevar/log/exception.logfile.Theotherfunctionswritetovar/log/system.log.

Weplacedthelogstatementinthedispatch()functionofthelib/internal/Magento/Framework/Event/Manager.phpclass.Thisfunctioniscalledeverytimeaneventisdispatched.Byloggingthename,wecanseealltheeventsthatarecalledinaMagentorequest.

Onalltheseevents,wecanwritehooksthatexecutethecodewhenaneventisdispatched.Asyoucansee,thisfunctiondoesn’treturnsomethingtotheparent,soitisnotpossibletosenddatabacktotheinitiatoroftheevent.However,youcanmodifytheobjectsthataresentwiththeevent.

Fordebuggingpurposes,wemodifiedacoreclassofMagento.Attheend,werevertedtheclasstotheoriginalcode.ModifyingthecoreofMagentoisnotrecommended,butfordebugging,youcandoitifyourevertyourcodewhenthedebuggingisdone.

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

CreatingyourowneventWhenwecreateourownevent,wehavetodispatchitwithacustomname.Inthisrecipe,youwilllearnhoweventsaredispatchedandwhatwecandowithparametersthataresentwiththeevent.

GettingreadyWewillcreateaneventthatisfiredwhenavisitoropensthe/helloworld/index/eventpage.

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

Howtodoit…Thefollowingstepsdescribehowwecandispatchourownevent:

1. First,wewillcreatetheeventpage.Forthis,weneedacontrolleraction.Createtheapp/code/Packt/HelloWorld/Controller/Index/Event.phpfilewiththefollowingcontent:

<?php

namespacePackt\HelloWorld\Controller\Index;

classEventextends\Magento\Framework\App\Action\Action{

/**@var\Magento\Framework\View\Result\PageFactory*/

protected$resultPageFactory;

publicfunction__construct(

\Magento\Framework\App\Action\Context$context,

\Magento\Framework\View\Result\PageFactory$resultPageFactory

){

$this->resultPageFactory=$resultPageFactory;

parent::__construct($context);

}

publicfunctionexecute(){

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

return$resultPage;

}

}

2. Cleanthecacheandnavigatetothe/helloworld/index/eventpage.YouwillseeaMagentopagewithoutanycontent.

3. Whenwewanttodispatchaneventonthepageview,wehavetoaddthehighlightedcodeintheexecute()action:

publicfunctionexecute(){

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

$this->_eventManager->dispatch('helloworld_register_visit');

return$resultPage;

}

4. Itisalsopossibletosendsomeparameterswiththeevent.Wewillsendaproductandcategorywiththeevent.Tosendtheevent,wehavetopassthefollowingkey-valuearraytotheevent:

publicfunctionexecute(){

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

$parameters=[

'product'=>$this->_objectManager-

>create('Magento\Catalog\Model\Product')->load(50),

'category'=>$this->_objectManager-

>create('Magento\Catalog\Model\Product')->load(10),

];

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

$parameters);

return$resultPage;

}

NoteEnsurethattheproductIDandcategoryIDexistsinyourinstallation.Ifnot,youcanchooseanotherIDthatexistsinyourinstallation.

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

Whenthisfunctionisfired,Magentowilllookintotheconfigurationtoseewhicheventlistenersarewatchingforthatevent.Iftherearematchinglisteners,Magentowillexecutethecodeoftheobserver.

AddinganeventobserverIfyoureadthepreviousrecipe,youknowhowtofireaneventusingthedispatch()function.Inthisrecipe,youwilllearnhowtoexecutesomecodewhenaneventisdispatchedandwhatwecandowithit.

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

Inthisrecipe,wewillworkfurtheronthePackt_HelloWorldmodulefromthepreviousrecipes.Ensurethatyouhavetherightcodefilesinstalledforthisrecipe.

Howtodoit…Inthistutorial,youwilldiscoverhowtolistenforaneventandexecuteyourcodebyperformingthesesteps:

1. Whenwewanttoaddaneventobserverforthehelloworld_register_visitevent,wehavetoaddthefollowingconfigurationtotheapp/code/Packt/HelloWorld/etc/events.xmlfilewiththefollowingcontent:

<?xmlversion="1.0"?>

<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.x

sd">

<eventname="helloworld_register_visit">

<observername="register_helloworld_visit"

instance="Packt\HelloWorld\Observer\RegisterVisitObserver"/>

</event>

</config>

2. TheconfigurationofthepreviousfileexecutestheregisterVisit()functioninthePackt\HelloWorld\Observer\RegisterVisitObserverclass.Tocreatethisclass,wehavetoaddtheapp/code/Packt/HelloWorld/Model/Observer.phpfilewiththefollowingcontent:

<?php

namespacePackt\HelloWorld\Observer;

useMagento\Framework\Event\ObserverInterface;

classRegisterVisitObserverimplementsObserverInterface

{

/**@var\Psr\Log\LoggerInterface$logger*/

protected$logger;

publicfunction__construct(\Psr\Log\LoggerInterface$logger)

{

$this->logger=$logger;

}

publicfunctionexecute(\Magento\Framework\Event\Observer

$observer)

{

$this->logger->debug('Registered');

}

}

3. Now,itistimetotestourevent.Cleanthecacheandopenyourterminalandexecutethetail-fvar/log/debug.logcommandinyourMagentoroot.Whenyouloadthe/helloworld/index/eventpage,youwillseethattheRegisteredmessageappearsinthelogfile.

4. Let’slookattheparametersthataresentwiththeevent.Theseparametersarestoredinthe$observervariablethatispassedtotheexecute()method.Addthefollowing

codetothismethodtodebugtheproductandcategorythatispassedtothatevent:

publicfunctionexecute(\Magento\Framework\Event\Observer$observer)

{

$product=$observer->getProduct();

$category=$observer->getCategory();

$this->logger->debug(print_r($product->debug(),true));

$this->logger->debug(print_r($category->debug(),true));

}

5. Whenyoureloadthe/helloworld/index/eventpage,youwillseethatadumpofproductsandcategoriesappearsinthelogfile.

TipWhennothingappearsinyourlogfile,itispossiblethatyourfullpagecacheisactive.Ifthisisso,youcandisableitandflushthecacheafterdisablingit.Youcanalsoflushthecacheeachtimeyouaretesting.

6. Forthenextpart,wewillhookintotheaddtocartevent.Whenauseraddsaproducttothecart,weneedtocheckthatthequantityisodd.Ifnot,wewillhavetoshowanerrormessagethattheproductcan’tbeaddedtothecart.

7. Wecandothisbycreatinganeventobserverforthecheckout_cart_product_add_afterevent.Tocreateaneventlistenerforthisevent,addthefollowingcodeintheapp/code/Packt/HelloWorld/etc/frontend/events.xmlfile:

<?xmlversion="1.0"?>

<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.x

sd">

<eventname="checkout_cart_product_add_after">

<observername="check_cart_qty"

instance="Packt\HelloWorld\Observer\CheckCartQtyObserver"/>

</event>

</config>

8. Createtheapp/code/Packt/HelloWorld/Observer/CheckCartQtyObserver.phpfilewiththefollowingcontent:

<?php

namespacePackt\HelloWorld\Observer;

useMagento\Framework\Event\ObserverInterface;

classCheckCartQtyObserverimplementsObserverInterface

{

publicfunctionexecute(\Magento\Framework\Event\Observer

$observer)

{

if($observer->getProduct()->getQty()%2!=0){

//Oddqty

thrownew\Exception('Qtymustbeeven');

}

}

}

9. Cleanthecacheandtrytoaddsomethingtothecartwithanevenandoddquantity.Youwillseethatanerroroccurswhenyouaddanoddquantity.Whenyouaddanevenquantity.

Howitworks…Eventlistenersareconfiguredintheevents.xmlfile.WhenwelookatthestructureoftheXMLfile,wecansaythefollowingthingsabouttagsandattributeswhenwehavethefollowingcode:

<?xmlversion="1.0"?>

<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">

<eventname="checkout_cart_product_add_after">

<observername="check_cart_qty"

instance="Packt\HelloWorld\Observer\CheckCartQtyObserver"/>

</event>

</config>

Theroottagisthe<config>tag.Thistagcanhaveoneormore<event>subtags.

The<event>subtagdefinesaneweventobserver.Withthenameattribute,weconfigurethenameoftheeventthatwillbeobserved.Inthisrecipe,weusedthecheckout_cart_product_add_aftereventtocheckthequantity.Thiseventisdispatchedintheapp/code/Magento/Checkout/Model/Cart.phpfile.

The<event>tagcanhaveoneormore<observer>subtags.Withthistag,weconfiguretheeventobserver.Thenameattributeisthenameoftheobserver.Thisnamemustbeunique.Theinstanceattributeistheclassthatwillbecalled.

Forthepreviousexample,thismeans:

Thecheckout_cart_product_add_aftereventwillbeobservedThenameoftheobserverischeck_cart_qtyTheexecute()methodwillbeexecutedfromtheclassPackt\HelloWorld\Observer\CheckCartQtyObserver

Withaneventobserver,thereisoneobjectpassedthatcontainsanarrayofdatathatissentwiththeeventinthedispatch()function.

Inthepreviousrecipe,wedispatchedaneventcalledhelloworld_register_visit.Withthatdispatch,weaddedaproductandcategorytothatevent.

Inthisrecipe,weextractedaproductandcategoryfromthe$observerparameter,whichisthefirstargumentoftheobservedeventfunction.

IntroducingcronjobsCronjobs,orscheduledtasks,arebackgroundprocessesthatkeepyourMagentowebshoprunningbyautomatingsometasks.Someexamplesofcronjobsareasfollows:

SendingnewslettersRecalculatingcatalogpromotionrulesCleaningvisitorlogsSendingpriceandstockalerte-mailsUpdatingcurrencyrates

WhentheMagentocronisnotconfiguredcorrectly,youwillseethatsomefeaturesofyourMagentoshopwillnotworkasexpected.

GettingreadyInthisrecipe,youwilllearnhowyoucanconfigurecronjobsontheserverandhowyoucanverifythattheyareworking.OpenyourSSHclientandchangetotheMagentofolder.

Howtodoit…Usingthefollowingsteps,wewillconfigurecronjobsontheserver:

1. TheMagentocronneedstobeexecutedonperiodictimestamps,forexample,everyfiveminutes.Torunthecrons,thefollowingcommandisused:

phpbin/magentocron:run

TipToavoidpermissionproblems,youhavetorunthiscommandastheuserthatApacheusestoserveHTTPrequests.Onabasicapachesetup,thisuseriswww-data,butthiscanbedifferentonothersetups.

2. Whenyouexecutethepreviouscommand,thecronjobtable,cron_schedule,isupdatedwiththerecentcronjobs.Whenyourunthefollowingcommandinyourdatabaseclient,youcanseethecontentofthattable:

SELECT*FROMcron_schedule;

Thisquerygivesthefollowingoutput:

3. Inthescheduled_atcolumn,wecanseewhenthecronjobisplannedtorun.Runthelastcommandagainafteraminute.Magentowillrunthecronjobswherethescheduled_attimeisinthepastandthestatusispending.Thecontentofthecron_scheduletablewilllookasfollows:

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

sudosuwww-data

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

6. Savethefileandyourcronjobwillrunaftereveryminute.

Howitworks…TheMagentocronwillbeexecutedwiththeMagento2command-linetool.Thecommandtorunthecron(thecron.shscriptinMagento1)isasfollows:

phpbin/magentocron:run

ThiswillstarttheMagentocronprocess.ThecroncommandwillexecutethePHPcodeovertheCommandLineInterface(CLI).Thismeansthatthephp-clisettingsareusedtoexecutethecronjobsinsteadoftheapachePHPsettingsthatareusedbyapache.

Whenthecronprocessisinitialized,Magentolooksatthecron_scheduletable.Everyscheduledcronjobwiththescheduled_atfieldinthepastandwiththestatuspendingwillbeexecuted.Whenajobstarts,theexecuted_atfieldwillbeupdatedwiththecurrenttimestampandthestatuswillbechanged.

Whenajobisfinished,thefinished_atfieldisupdatedwiththecurrenttimestamp.Also,thestatuswillbeupdated.Whenthestatusisanerror,themessagefieldwillbeupdatedwiththeerrormessage.

Whentheprocessisfinished,Magentowillcreateaqueueforthenextperiod.Basedontheconfigurationfiles,Magentoknowswhenithastoscheduleeachcronjob.

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

Testingacronjobisabittricky.Youcanwaitwhilethecronwillisexecuted,butinthisrecipe,wewillseehowwecantriggeritfordevelopmentpurposes.

GettingreadyTheworkflowtocreateanewcronjobismostlythesameasworkingwitheventobservers.Wehavetoconfigureanewcronjobthatwillstartafunctioninaconfiguredclass.

Forthecreationofanewcronjob,wewillusetheexistingPackt_HelloWorldmodule.Makesureyouhavethelatestversioninstalled.

Howtodoit…Inthenextsteps,wewillcreatetheconfigurationforanewcronjob:

1. Thefirstthingistocreatetheconfigurationfile.Createtheapp/code/Packt/HelloWorld/etc/crontab.xmlfilewiththefollowingcontent:

<?xmlversion="1.0"?>

<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Cron:etc/cron

tab.xsd">

<groupid="default">

<jobname="helloworld_check_subscriptions"

instance="Packt\HelloWorld\Model\Cron"method="checkSubscriptions">

<schedule>*****</schedule>

</job>

</group>

</config>

2. Thenextthingistocreatethecronclasswiththefunctionthatwillbeexecuted.Createtheapp/code/Packt/HelloWorld/Model/Cron.phpfilewiththefollowingcontent:

<?php

namespacePackt\HelloWorld\Model;

classCron{

/**@var\Psr\Log\LoggerInterface$logger*/

protected$logger;

/**@var\Magento\Framework\ObjectManagerInterface*/

protected$objectManager;

publicfunction__construct(

\Psr\Log\LoggerInterface

$logger,\Magento\Framework\ObjectManagerInterface$objectManager

){

$this->logger=$logger;

$this->objectManager=$objectManager;

}

publicfunctioncheckSubscriptions(){

$subscription=$this->objectManager-

>create(''Packt\HelloWorld\Model\Subscription'');

$subscription->setFirstname(''Cron'');

$subscription->setLastname(''Job'');

$subscription->setEmail(''cron.job@example.com'');

$subscription->setMessage(''Createdfromcron'');

$subscription->save();

$this->logger->debug(''Testsubscriptionadded'');

}

}

3. Whenwewanttotestourcronjob,wecancreateacrongrouptorunthetestwith.Tocreateacrongroup,wehavetocreatetheapp/code/Packt/HelloWorld/etc/cron_groups.xmlfilewiththefollowingcontent:

<?xmlversion="1.0"?>

<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Cron:etc/cron

_groups.xsd">

<groupid="packt">

<schedule_generate_every>1</schedule_generate_every>

<schedule_ahead_for>4</schedule_ahead_for>

<schedule_lifetime>2</schedule_lifetime>

<history_cleanup_every>10</history_cleanup_every>

<history_success_lifetime>60</history_success_lifetime>

<history_failure_lifetime>600</history_failure_lifetime>

<use_separate_process>1</use_separate_process>

</group>

</config>

4. Thenextthingtodoistolinkthecronjobtothegroup.Intheapp/code/Packt/HelloWorld/etc/crontab.xmlfile,changetheidattributeofthe<group>tagtothefollowing:

<groupid="packt">

5. Cleanthecacheandrunthefollowingcommand:

phpbin/magentocron:run--group="packt"

6. Whenyoulookatthecontentsofthecron_scheduletable,youcanseethatanewpendingjobisadded.Thejob_codeinstanceofthisishelloworld_check_subscriptionsaswehaveconfiguredinthecrontab.xmlfile.

7. Runthesamecommandagainandthejobwillbeexecuted.Whenyoulookintothevar/log/debug.logfile,youwillseethattheTestsubscriptionaddedmessageisloggedattheendofthefile.

8. WhenwelookatthesubscriptionstableinthebackendofMagento(Marketing|HelloWorld|Subscriptions),weseethatanewsubscriptionwithnameCronJobisadded.

9. Now,weknowthatthecronjobisworking.Tofinishit,wecanaddthejobtothedefaultgroupandscheduleitinsuchawaythatitrunseverynightat2:30a.m.Forthis,wehavetochangethehighlightedlinesofcodefromtheapp/code/Packt/HelloWorld/etc/crontab.xml:

<?xmlversion="1.0"?>

<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Cron:etc/cron

tab.xsd">

<groupid="default">

<jobname="helloworld_check_subscriptions"

instance="Packt\HelloWorld\Model\Cron"method="checkSubscriptions">

<schedule>302***</schedule>

</job>

</group>

</config>

10. Cleanthecacheandyourcronwillruninthedefaultgroup.Thejobwillbescheduledfor2:30a.m.

Howitworks…Cronjobsarealwaysconfiguredinthecrontab.xmlfile.LikeinMagento1,acronjobhasanamewithaclassandmethodthatwillbeexecuted.Foreverycronjob,youhavetogiveascheduleinthecrontimeformattospecifytheinterval.

TipThenameofacronjobisalwayswrittenusinglowercaseandunderscores.

AnothernewthinginMagento2isthatyouneedtoassociateacronjobtoagroup.Agroupisalwaysspecifiedinthecron_groups.xmlfile.Bydefault,Magento2hasthefollowingcrongroups:

default

index

Inacrongroup,youcanspecifysettingsforhowcronswillbehandledinthecron_scheduletable.Youcanmakesettings,suchasthelifetimeoferrors,cleanup,andschedule.Forthedevelopmentgroup,weusedasettingthatalwaysschedulesanewcronjobwhenthepreviouscronjobisexecuted.

Youhavetwoadvantageswhenyouuseacrongroupfordevelopmentstuff.

Thefirstoneisthatyoucanusespecificsettingsabouttheschedulingofthecronjob.Thesecondoneisthatyoucanusethecroncommandtorunonlythecronjobsofaspecificgroup.

Withthe--groupparameter,youcanspecifythegroupforwhichthejobsneedtobeexecutedandscheduled.Whenthisparameterisempty,allthegroupswillbeexecutedandscheduled.

Ineverycronjobtag(the<job>tag),thereisa<schedule>subtag.Inthissubtag,youcanconfiguretheintervalofthecronjob.Thisconfigurationcontainsfiveparametersthatrepresentthefollowingconfigurations:

MinuteHourDayMonthYear

Whenyouhaveaconfigurationlike010***,thismeansthatthiscronrunsatminute0,hour10,alldays,allmonths,allyears.Sothetimeatwhichthisrunsisat10a.m.

Chapter8.CreatingaShippingModuleInthischapter,wewillcover:

InitializingmoduleconfigurationsWritinganadaptermodelExtendingtheshippingmethodfeaturesAddingthemoduleinthefrontend

IntroductionShippingtheorderedproductstothecustomersisoneofthekeypartsofthee-commerceflow.Inmostcases,astoreownerhasacontractwithashippinghandler,andeveryshippinghandlerhastheirownbusinessrules.

InMagento2,thefollowingshippinghandlershaveanextension:

DHLFedExUPSUSPS

Ifyourhandlerisnotonthislist,checkwhetheryourshippinghandlerhasaMagentomodule.Ifnot,youcanconfigureastandardshippingmethodoryoucancreateyourownshippingmethod,asyouwilllearntodointhischapter.

InitializingmoduleconfigurationsInChapter4,CreatingaModule,youlearnedhowtocreateacustommodule.Inthisrecipe,wewillcreateanewmodulewherewewilladdtherequiredsettingsforashippingmodule.

Inthelaterrecipesofthischapter,wewillextendthismodulewithmoreshippingfeatures.

GettingreadyOpenyourIDEwiththeMagento2project.Wewillalsoneedthebackend,wherewewillchecksomeconfigurations.

Howtodoit…Thefollowingstepsdescribehowwecancreatetheconfigurationforashippingmodule:

1. First,wehavetocreatethefollowingfolders:

app/code/Packt/

app/code/Packt/Shipme/

app/code/Packt/Shipme/etc/

app/code/Packt/Shipme/Model/

app/code/Packt/Shipme/Model/Carrier/

2. Createamodule.xmlfileintheapp/code/Packt/Shipme/etc/folderwiththefollowingcontent:

<?xmlversion="1.0"?>

<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.

xsd">

<modulename="Packt_Shipme"setup_version="2.0.0">

<sequence>

<modulename="Magento_Shipping"/>

</sequence>

</module>

</config>

3. Createaregistration.phpfileintheapp/code/Packt/Shipme/folderwiththefollowingcontent:

<?php

\Magento\Framework\Component\ComponentRegistrar::register(

\Magento\Framework\Component\ComponentRegistrar::MODULE,

'Packt_Shipme',

__DIR__

);

4. Withthisfile,wecaninstallthemodulebyrunningthephpbin/magentosetup:upgradecommand.

5. Tocheckthatthemoduleisactive,openthebackendandnavigatetoStores|Configuration|Advanced|AdvancedandcheckwhetherPackt_Shipmeisinthelistandisenabled.

6. Atthispoint,themoduleisinitializedandactive.Wecannowcreateasystem.xmlfileintheapp/code/Packt/Shipme/etc/adminhtml/folder.

7. Whenthepreviousfileiscreated,addthefollowingcontenttoit.Thiswillcreatetheshippingconfigurationparametersnearalltheothershippingmethods:

<?xmlversion="1.0"?>

<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/sy

stem_file.xsd">

<system>

<sectionid="carriers">

<groupid="shipme"translate="label"type="text"sortOrder="50"

showInDefault="1"showInWebsite="1"showInStore="1">

<label>Shipme</label>

<fieldid="active"translate="label"type="select"

sortOrder="10"showInDefault="1"showInWebsite="1"showInStore="0">

<label>Enabled</label>

<source_model>Magento\Config\Model\Config\Source\Yesno</source_model>

</field>

<fieldid="name"translate="label"type="text"sortOrder="20"

showInDefault="1"showInWebsite="1"showInStore="1">

<label>MethodName</label>

</field>

<fieldid="title"translate="label"type="text"sortOrder="20"

showInDefault="1"showInWebsite="1"showInStore="1">

<label>MethodTitle</label>

</field>

</group>

</section>

</system>

</config>

8. Next,wecanconfiguresomedefaultsettings.Createaconfig.xmlfileintheapp/code/Packt/Shipme/etc/folderwiththefollowingcontent:

<?xmlversion="1.0"?>

<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/con

fig.xsd">

<default>

<carriers>

<shipme>

<model>Packt\Shipme\Model\Carrier\Shipme</model>

<active>1</active>

<name>ShipmeShipping</name>

<title>ShipmeShipping</title>

</shipme>

</carriers>

</default>

</config>

9. CleanthecacheandnavigatetoStores|Configuration|Sales|ShippingMethods.Youwillseethatanextragroupisadded,asshowninthefollowingscreenshot:

10. Now,whentheconfigurationisworking,wecanextendtheconfigurationwithsomeextravalues.Modifythesystem.xmlfilesothatitlooksasfollows(thehighlightedcodeisaddedinthisstep):

<?xmlversion="1.0"?>

<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/sy

stem_file.xsd">

<system>

<sectionid="carriers">

<groupid="shipme"translate="label"type="text"sortOrder="50"

showInDefault="1"showInWebsite="1"showInStore="1">

<label>Shipme</label>

<fieldid="active"translate="label"type="select"

sortOrder="10"showInDefault="1"showInWebsite="1"showInStore="0">

<label>Enabled</label>

<source_model>Magento\Config\Model\Config\Source\Yesno</source_model>

</field>

<fieldid="name"translate="label"type="text"sortOrder="20"

showInDefault="1"showInWebsite="1"showInStore="1">

<label>MethodName</label>

</field>

<fieldid="title"translate="label"type="text"sortOrder="20"

showInDefault="1"showInWebsite="1"showInStore="1">

<label>MethodTitle</label>

</field>

<fieldid="express_enabled"translate="label"type="select"

sortOrder="30"showInDefault="1"showInWebsite="1"showInStore="0">

<label>Enableexpress</label>

<source_model>Magento\Config\Model\Config\Source\Yesno</source_model>

</field>

<fieldid="express_title"translate="label"type="text"

sortOrder="40"showInDefault="1"showInWebsite="1"showInStore="1">

<label>Titleexpress</label>

</field>

<fieldid="express_price"translate="label"type="text"

sortOrder="50"showInDefault="1"showInWebsite="1"showInStore="1">

<label>Priceexpress</label>

</field>

<fieldid="business_enabled"translate="label"type="select"

sortOrder="60"showInDefault="1"showInWebsite="1"showInStore="0">

<label>Enablebusiness</label>

<source_model>Magento\Config\Model\Config\Source\Yesno</source_model>

</field>

<fieldid="business_title"translate="label"type="text"

sortOrder="70"showInDefault="1"showInWebsite="1"showInStore="1">

<label>Titlebusiness</label>

</field>

<fieldid="business_price"translate="label"type="text"

sortOrder="80"showInDefault="1"showInWebsite="1"showInStore="1">

<label>Pricebusiness</label>

</field>

<fieldid="specificerrmsg"translate="label"type="textarea"

sortOrder="90"showInDefault="1"showInWebsite="1"showInStore="1">

<label>DisplayedErrorMessage</label>

</field>

</group>

</section>

</system>

</config>

11. Toconfigurethedefaultvalues,addthehighlightedcodeintotheconfig.xmlfileofthemodule:

<?xmlversion="1.0"?>

<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/con

fig.xsd">

<default>

<carriers>

<shipme>

<model>Packt\Shipme\Model\Carrier\Shipme</model>

<active>1</active>

<name>ShipmeShipping</name>

<title>ShipmeShipping</title>

<express_enabled>1</express_enabled>

<express_title>Expressdelivery</express_title>

<express_price>4</express_price>

<business_enabled>1</business_enabled>

<business_title>Businessdelivery</business_title>

<business_price>5</business_price>

<specificerrmsg>Thisshippingmethodiscurrentlyunavailable.

Ifyouwouldliketoshipusingthisshippingmethod,pleasecontact

us.</specificerrmsg>

</shipme>

</carriers>

</default>

</config>

12. Cleanthecacheandreloadtheshippingmethodconfigurationpageinthebackend.YouwillseethattheextrafieldsareavailableundertheShipmesection,asshowninthefollowingscreenshot:

Howitworks…Inthefirststepsofthisrecipe,wecreatedthenecessaryfilestoinitializethemodule.Toinitializeamodule,weneedamodule.xmlfileintheetcfolderofthemodule.

Inthemodule.xmlfile,weconfiguredthenameandversioninthe<module>tag.Inthe<sequence>subtag,weconfiguredthedependenciesofthemodule.

The<sequence>configurationcheckswhethertheMagento_ShippingandMagento_OfflineShippingmodulesareactive.Otherwise,themodulecannotbeinstalled.

Whenthemodulewasinstalled,weextendedthemodulewiththeconfigurationsforashippingmethod.AlltheMagentoshippingmethodsareconfigurableontheShippingMethodspageintheconfiguration.So,tocreateanextrashippingmethod,wehavetocreateanextrasectiononthatpage.

WecreatedashippinghandlerthathasaMethodNameoption.Inthathandler,wecreatedtwoshippingoptions:expressandbusiness.Forthosetwooptions,wecreatedthename,enabled,andpricefields.

Theconfigurationparametersareconfiguredinthesystem.xmlfileofthemodule.Foreveryconfigurationparameter,thereisadefaultvalue.Thesevaluesareconfiguredintheconfig.xmlfile.

Inthisconfig.xmlfile,thereisalsoamodelconfigured.Thisistheadaptermodelthatwewillcreateinthenextrecipe,Writinganadaptermodel.

SeealsoInthisrecipe,weusedthesystem.xmlfileofthemoduletocreatetheconfigurationvalues.MoreinformationaboutconfigurationvaluesisexplainedintheAddingconfigurationparametersrecipeofChapter6,MagentoBackend.

WritinganadaptermodelInthepreviousrecipe,weenabledanewmodulewiththesettingsforanewshippingmethod.Thiswasapreparationforthebusinesspart,whichwewilldointhisrecipe.

Wewilladdamodelwiththebusinesslogicfortheshippingmethod.ThemodeliscalledanadapterclassbecauseMagentorequiresanadapterclassforeachshippingmethod.

Theadapterclasswillbeusedforthefollowingthings:

MakingtheshippingmethodavailableCalculatingtheshippingcostsSettingthetitleinthefrontendoftheshippingmethods

GettingreadyEnsurethatyouhaveinstalledthemodulethatwecreatedinthepreviousrecipebecauseweneedthisforcreatingtheadapterclass.

Howtodoit…Thefollowingstepsdescribehowyoucanwriteanadapterclassforashippingmethod:

1. Createtheapp/code/Packt/Shipme/Model/Carrier/folderifitdoesn’talreadyexist.

2. Inthisfolder,createafilecalledShipme.phpwiththefollowingcontent:

<?php

namespacePackt\Shipme\Model\Carrier;

useMagento\Shipping\Model\Rate\Result;

classShipmeextends\Magento\Shipping\Model\Carrier\AbstractCarrier

implements

\Magento\Shipping\Model\Carrier\CarrierInterface{

protected$_code='shipme';

/**

*@var\Magento\Shipping\Model\Rate\ResultFactory

*/

protected$_rateResultFactory;

/**

*@var\Magento\Quote\Model\Quote\Address\RateResult\MethodFactory

*/

protected$_rateMethodFactory;

publicfunction__construct(

\Magento\Framework\App\Config\ScopeConfigInterface$scopeConfig,

\Magento\Quote\Model\Quote\Address\RateResult\ErrorFactory

$rateErrorFactory,

\Psr\Log\LoggerInterface$logger,

\Magento\Shipping\Model\Rate\ResultFactory$rateResultFactory,

\Magento\Quote\Model\Quote\Address\RateResult\MethodFactory

$rateMethodFactory,

array$data=[]

){

$this->_rateResultFactory=$rateResultFactory;

$this->_rateMethodFactory=$rateMethodFactory;

parent::__construct($scopeConfig,$rateErrorFactory,$logger,

$data);

}

publicfunction

collectRates(\Magento\Quote\Model\Quote\Address\RateRequest$request){

if(!$this->getConfigFlag('active')){

returnfalse;

}

$result=$this->_rateResultFactory->create();

//Checkifexpressmethodisenabled

if($this->getConfigData('express_enabled')){

$method=$this->_rateMethodFactory->create();

$method->setCarrier($this->_code);

$method->setCarrierTitle($this->getConfigData('name'));

$method->setMethod('express');

$method->setMethodTitle($this->getConfigData('express_title'));

$method->setPrice($this->getConfigData('express_price'));

$method->setCost($this->getConfigData('express_price'));

$result->append($method);

}

//Checkifbusinessmethodisenabled

if($this->getConfigData('business_enabled')){

$method=$this->_rateMethodFactory->create();

$method->setCarrier($this->_code);

$method->setCarrierTitle($this->getConfigData('name'));

$method->setMethod('business');

$method->setMethodTitle($this->getConfigData('business_title'));

$method->setPrice($this->getConfigData('business_price'));

$method->setCost($this->getConfigData('business_price'));

$result->append($method);

}

return$result;

}

publicfunctiongetAllowedMethods(){

return['shipme'=>$this->getConfigData('name')];

}

}

3. Savethefileandclearthecache.Youradaptermodelisnowcreated.

Howitworks…Theclassthatwecreatedinthisrecipehandlesallthebusinesslogicthatisneededfortheshippingmethod.Becausethisadapterclassextendsfromthe\Magento\Shipping\Model\Carrier\AbstractCarrierclass,wecanoverwritesomemethodstocustomizethebusinesslogicofthestandardclass.

Thisclassimplementsthe\Magento\Shipping\Model\Carrier\CarrierInterfaceinterface.Whenwelookinthisclass,weseethatthefollowingmethodsmustbeavailableintheadapterclass:

isTrackingAvailable()

getAllowedMethods()

ThesemethodsaresetintheAbstractCarrierclass—theclasswheretheadaptermodelextendsfrom.Thismeansthattheclassisvalid.Inthatclass,thereisalsotheisAvailable()method.Inthismethod,thereisdecidedthattheshippingmethodisactiveornot.Ifyouwant,youcanoverwritethismethodwithyourcustomcode.

ThesecondandmostimportantfunctionisthecollectRates()function.Thisfunctiondecideswhichmethodsareavailableandtheshippingcosts.

InMagento,ashippingmethodhasacarrier.ThecarrierfromthemethodsofthisrecipeisShipme.Everycarriercanhavemultiplemethods,suchasExpressandBusinessdelivery.

InthecollectRates()function,wecreatearateResultvariabletowhichyoucanassignshippingmethods.Inthisrecipe,weaddedtwomethodstothis:ExpressandBusinessdelivery.

AmethodiscreatedformtherateMethodFactoryinstanceandthiscanhavethefollowingoptions:

Carrier(shipme)TitleofthecarrierMethod(codeofthemethod)MethodtitlePriceCost

Whentheseoptionsareset,themethodisaddedtotherateResultinstanceusingtheappend()function.

ThenextfunctionthatweusedisthegetAllowedMethods()function.Inthisfunction,weaddthemethodsoftheShipmecarriertotheallowedshippingmethods.

ExtendingtheshippingmethodfeaturesNowthatallthefilesareinstalled,wecanaddmorefeaturestotheshippingmethod.Inthisrecipe,wewilladdacountryconfigurationandenabletrackingcodesfortheshippingmethod.

GettingreadySimilartoallrecipesinthischapter,wewillbuildfurtheronthemodulethatwecreatedinthepreviousrecipesofthischapter.Ensurethatyouhavetherightfilesinstalled.

Howtodoit…Inthefollowingsteps,youwilllearnhowwecanenabletrackingcodesandcountryconfigurationsfortheshippingmethod:

1. Opentheshippingadapterfile,app/code/Packt/Shipme/Model/Carrier/Shipme.php.

2. Addthefollowingfunctiontothatclasstoenabletrackingcodes:

publicfunctionisTrackingAvailable(){

returntrue;

}

3. Next,wewillenablethecountry-specificoptions.Intheapp/code/Packt/Shipme/etc/adminhtml/system.xmlfile,addthefollowingcontent:

<fieldid="sallowspecific"translate="label"type="select"

sortOrder="100"showInDefault="1"showInWebsite="1"showInStore="0">

<label>ShiptoApplicableCountries</label>

<frontend_class>shipping-applicable-country</frontend_class>

<source_model>Magento\Shipping\Model\Config\Source\Allspecificcountries

</source_model>

</field>

<fieldid="specificcountry"translate="label"type="multiselect"

sortOrder="110"showInDefault="1"showInWebsite="1"showInStore="0">

<label>ShiptoSpecificCountries</label>

<source_model>Magento\Directory\Model\Config\Source\Country</source_mod

el>

<can_be_empty>1</can_be_empty>

</field>

4. Cleanthecacheandopentheconfigurationpageoftheshippingmethod.Youwillseethattherearetwonewconfigurationoptions.WhenyouchangethevalueoftheShiptoApplicableCountriesfieldtoSpecificCountries,youcanselectfrommultiplecountries,asyoucanseeinthefollowingscreenshot:

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

Thesecondthingthatwedidwastoenablecountry-specificshipping.Weaddedtwofieldswithastandardnamingconvention.Usingthefollowingnamesforthetwofields,Magentorecognisestheconfigurationforcountries:

sallowspecific

specificcountry

Whenweenablethisconfigurationinthebackend,theshippingmethodisonlyavailablewhenthecountryoftheshippingaddressisoneoftheselectedcountriesintheShiptoSpecificCountriesconfigurationoftheshippingmethod.

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

GettingreadyWeneedtheshippingmodulethatwecreatedinthepreviousrecipes.Ensureyouhavetherightfilesinstalled.

Howtodoit…ThefollowingstepsdescribehowtheorderflowworksinMagento:

1. Logintothebackend.2. Navigatetotheconfigurationoftheshippingmethod.Youcanfindthisbynavigating

toStores|Configuration|Sales|ShippingMethods|Shipme.3. CheckwhetherallthevaluesarecorrectfortheShipme-Expressmethod.Ensure

thateverythingisenabled.4. Savetheconfiguration.5. Inthefrontend,addaproducttotheshoppingcartandproceedtocheckout.

NoteInChapter7,EventHandlersandCronjobs,wecreatedaneventthatcheckswhetherthequantityisoddorevenwhenaddingsomethingtothecart.WhenyougettheWecan’taddthisitemtoyourshoppingcartrightnow.message,youhavetoaddanevenquantityoryouhavetodisablethatcode.

6. Whenyouareonthecheckoutpage,fillintherightdatafortheshippingaddress.7. IntheShippingMethodssection,thenewmethodswillappearasshowninthe

followingscreenshot:

8. SelectoneoftheShipmeShippingmethodsandclickontheNextbutton.9. Checkyourpaymentinformation.EnsurethatyouhavecheckedtheCheck/Money

ordermethodifthereismorethanonemethodavailable.

TipIfyoudon’tseetheCheck\Moneyorderpaymentmethod,youhavetoenableitin

thestoresconfiguration.

10. ClickonthePlaceOrderbuttonandyourorderwillbecreated.Youwillseetheordersuccesspagewhereyouoptionallycancreateanaccountforthewebsite.

11. WhenyoulookintothebackendbynavigatingtoSales|Orders,youcanseetheorderthatwehavecreated.ClickontheViewlinkofthatorderandyouwillseeallthedetailsofthatorder.

12. Toprocesstheorder,wecancreateaninvoiceforittoconfirmthattheorderispaid.WhenyouclickontheInvoicebutton,youwillbeforwardedtotheformwhereyoucansubmittheinvoiceforthatorder.

13. Whentheinvoiceissaved,youwillseethatthestatusoftheorderischangedtoProcessing.Tocreateashipmentforthisorder,wecanclickontheShipbutton.Youwillseethefollowingscreen:

14. WhenyouclickontheAddTrackingNumberbutton,youcancreateatrackingcodeforthatshipment.IntheCarrierdropdown,selecttheShipmeShippingoptionandaddasampletrackingcode,suchas1234567890.

15. WhenyouclickontheSubmitShipmentbutton,yourshipmentisprocessedandthestatusoftheorderwillchangetoComplete.

Howitworks…Inthisrecipe,wetestedtheshippingmethodthatwecreatedinthischapter.Weplacedanorderwiththenewshippingmethodtocheckthateverythingworksasexpected.

Whentheorderisplaced,itisthetaskofthestoreownertocompletetheorder.ThepaymentmethodoftheorderisCheck/Moneyorder.Thismeansthatthepaymentwillhappenlater.

Whentheorderispaid,youcansetthepaidamountbycreatinganinvoice.ThestatuswillchangefromPendingtoProcessing.Processingmeansthattheorderisreadytobeprocessed.Whenyoulookattheordertotalsontheorderpage,youseethatTotalPaidisthesameastheGrandTotal(ifyouhavemarkedtheinvoiceaspaid).

Whentheordercanbeshipped,wecancreateashipmentoftheorder.Alltheshippinginformationcanbestoredintheshipmentsuchasthetrackingcode(s),comments,statusupdates,andmuchmore.

Whenthereisaninvoiceandshipmentforalltheorderitems,theorderisCompleteinMagento.Whentheorderiscomplete,itisalwayspossibletocreateaCreditMemoreceiptforspecialcasesintheorderflow(damagedshipping,areturningorder,andsoon).

Chapter9.CreatingaProductSliderWidgetInthischapter,wewillcoverthefollowingrecipes:

CreatinganemptymoduleCreatingawidgetconfigurationfileCreatingtheblockandtemplatefilesCreatingacustomconfigurationparameterFinalizingthetheming

IntroductionTheMagentowidgetssystemisagraphicalinterfacewhereyoucanconfigureblocksinthefrontend.Foreverywidget,thereisaconfigurationpageavailablewhereyoucansettherequiredvaluesforthatwidget.

WithaMagentowidget,youcanconfigurethelayoutinstructionstoshowthewidgetatseveralplacesinthefrontend.

Inthischapter,wewillcreateanewmoduleinwhichwewillcreateourownwidget.Wewillcreateaproductsliderwiththeproductsofacategorythatwecanconfigureinthatwidget.

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

CreatinganemptymoduleAswedidinthepreviouschapterandfullyexplainedinChapter4,CreatingaModule,wewillcreatetherequiredfilestocreateanemptymodulethatwewillextendwithwidgetconfigurationsinfurtherchapters.WewillstartwithanemptyMagentomodulethatwewillcreateinthisrecipe.Wewillcreatealltherequiredfilestoinitializeanewmodulethatcanbeusedforthecreationofawidget.

GettingreadyWewillcreatenewmodulecalledPackt_ProductSlider.OpenyourIDEtoaddsomecodetoit.

Howtodoit…Usingthefollowingsteps,wewillcreateanemptymodulecalledPackt_ProductSlider:

1. CreatethefollowingfoldersinyourMagentoroot:

app/code/Packt/

app/code/Packt/ProductSlider/

app/code/Packt/ProductSlider/etc/

2. Intheapp/code/Packt/ProductSlider/etc/folder,createafilecalledmodule.xml.3. Inthisfile,pastethefollowingcode:

<?xmlversion="1.0"?>

<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.

xsd">

<modulename="Packt_ProductSlider"setup_version="2.0.0">

<sequence>

<modulename="Magento_Catalog"/>

<modulename="Magento_Widget"/>

</sequence>

</module>

</config>

4. Intheapp/code/Packt/ProductSlider/folder,createaregistration.phpfilewiththefollowingcontent:

<?php

\Magento\Framework\Component\ComponentRegistrar::register(

\Magento\Framework\Component\ComponentRegistrar::MODULE,

'Packt_ProductSlider',

__DIR__

);

5. Toinstallthemodule,runthefollowingcommand:

phpbin/magentosetup:upgrade

6. Tocheckwhetherthemoduleisinstalled,openthebackendandnavigatetotheconfigurationpage(Stores|Configuration|Advanced|Advanced).CheckwhetherthePackt_ProductSlidermoduleisinthelist.

7. Youcanalternativelyrunthefollowingcommandtogetalistofallinstalledandenabledmodules:

phpbin/magentomodule:status

Howitworks…Wehavejustcreated,installed,andenabledanewmodulecalledPackt_ProductSlider.Toinitializeamodule,weneedamodule.xmlfileintheetcfolderofthemodule.

Inthemodule.xmlfile,weconfiguredthenameandversioninthe<module>tag.Inthe<sequence>subtag,weconfiguredthedependenciesofthemodule.

The<sequence>configurationcheckswhethertheMagento_WidgetandMagento_Catalogmodulesareactive.Otherwise,themodulecannotbeinstalled.

Practically,thismoduledoesnothing,butwewillextendthisinthenextrecipesofthischapter.

CreatingawidgetconfigurationfileInthisrecipe,wewillextendthefeaturesofthePackt_ProductSlidermodulewithawidgetconfigurationfile.Inthisconfigurationfile,wewilldeclareanewwidgetorfrontendapptype.

Foranewfrontendapp,weneedtoconfigurethefollowingthings:

Nameofthewidget(usedinthebackend)WidgetconfigurationparametersWidgetblocktypeWidgettemplates(the.phtmlfiles)

GettingreadyWewillextendthemodulethatwecreatedinthepreviousrecipewithawidgetconfiguration.Ensurethatyouhavetherightfilesinstalled.

Howtodoit…Usingthefollowingsteps,youcanexplorethepurposeofawidget.xmlconfigurationfile:

1. Createtheapp/code/Packt/ProductSlider/etc/widget.xmlfileusingthefollowingcode:

<?xmlversion="1.0"encoding="UTF-8"?>

<widgetsxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Widget:etc/wi

dget.xsd">

<widget

id="category_product_slider"

class="Magento\Catalog\Block\Product\List"

is_email_compatible="false"

placeholder_image="Magento_Widget::placeholder.gif">

<labeltranslate="true">Categoryproductslider</label>

<descriptiontranslate="true">ListofProductsforagivencategory

inasliderwidget</description>

</widget>

</widgets>

2. Cleanthecacheandcheckwhethertheconfigurationworks.Wecancheckthisintwoways:

1. ThefirstmethodistonavigatetoContent|Widgetsinthebackend.WhenyouclickontheAddWidgetbutton,youcanseethenewwidgettypeinthelist,asshowninthefollowingscreenshot:

2. ThesecondwaytotestthewidgettypesistoaddawidgettoaCMSpage.NavigatetoContent|Elements|PagesandclickontheAddNewPagebutton.

IntheContenttab,clickonthehighlightedbuttonintheWYSIWYGeditor,asshowninthefollowingscreenshot:

3. Whenclickingonthatbutton,anoverlayshowsupwhereyoucanchooseawidget.IntheWidgetTypedropdown,youcanselectCategoryproductsliderwhentheconfigurationisright.

4. Nowthatthewidgetworks,it’stimetoaddsomeconfigurationparameterstothewidget.Whenweaddthefollowinghighlightedcodetothewidget.xmlfile,wewillcreateaparameterforthetitle:

<widget

id="category_product_slider"

class="Magento\Catalog\Block\Product\List"

is_email_compatible="false"

placeholder_image="Magento_Widget::placeholder.gif">

<labeltranslate="true">Categoryproductslider</label>

<descriptiontranslate="true">ListofProductsforagivencategory

inasliderwidget</description>

<parameters>

<parametername="title"xsi:type="text"required="true"

visible="true">

<labeltranslate="true">Title(frontend)</label>

</parameter>

</parameters>

</widget>

5. Cleanthecacheandopenthewidgetconfigurationpage.WecandothisbynavigatingtoContent|Elements|Widgets.ClickontheAddWidgetbuttonandchoosethefollowingconfigurationintheform:

Type:CategoryproductsliderDesignTheme:MagentoLuma(thethemeofyourshop)

6. WhenyouclickontheContinuebutton,youwilllandonthewidgetconfigurationpage.WhenyouclickonWidgetOptions,youwillseethetitleparameter,asshowninthefollowingscreenshot:

7. Whenwewanttoshowproductsforacategory,wehavetocreateaconfigurationfieldwherewecansetthecategoryID.WhenwewanttoaddatextfieldwherewecanconfiguretherightcategoryID,weneedtoaddthehighlightedcodetothewidget.xmlfile.Thehighlightedcodeneedstobepastedasthechildofthe<parameters>tag:

<parameters>

<parametername="title"xsi:type="text"required="true"

visible="true">

<labeltranslate="true">Title(frontend)</label>

</parameter>

<parametername="category_id"xsi:type="text"required="true"

visible="true">

<labeltranslate="true">CategoryID</label>

</parameter>

</parameters>

8. Cleanthecacheandreloadthefrontend.Youwillseethatasecondtextboxisaddedtotheconfigurationpage.

Howitworks…Thewidget.xmlfileisusedtodefinewidgettypesinyourMagentoinstallation.Allwidgettypesaredefinedaschild<widget>tagsoftheglobal<widgets>tag.

Awidgettypeisdeclaredinthe<widget>tagandthiselementhasthefollowingthreerequiredattributes:

id(theuniqueidentifierofawidget)class(theBlockclassforthewidget)is_email_compatible(theBooleanthatthewidgetcanbeusedine-mailtemplates)placeholder_image(animagethatisusedwhenthewidgetisinsertedinaWYSIWYGeditor)

ThewidgettypethatwecreatedinthisrecipeusestheMagento\Catalog\Block\Product\Listblockclass.InMagento,thisclassisusedtorenderallproductlists,justlikeitisusedonthecategorypage.

Inthechildtagsofthe<widget>tag,wecanconfiguretheadditionalfieldsforthewidgettype.

Withthe<name>tag,weconfiguredthenameforthewidgettypethatisdisplayedinthedropdownoftheconfigurationpage.

Inthe<description>tag,wecanconfigureadescriptionforthewidgettype.ThisdescriptionisshownwhenyouinsertanewwidgetinaCMSpage.

Finally,weusedthe<parameters>tagtodefinetheadditionalconfigurationparameters.Inthisrecipe,weaddednameandcategory_idastextconfigurationfields.

Everyconfigurationparameterisdefinedasachild<parameter>tagfromthe<parameters>tag.The<parameter>taghasthefollowingattributes:

name(theIDofthefield)xsi:type(thetypeofthefield,suchastext,dropdown,andsoon)required(canbesettotrueorfalse)visible(canbesettotrueorfalse)

Every<parameter>taghasa<label>subtagwhereyoucanconfigurethelabelofthefieldpage.

CreatingtheblockandtemplatefilesInthepreviousrecipe,youlearnedhowtoconfigureanextrawidgettypetoMagento.Now,itistimetodisplaythewidget.

Wewillextendthewidgetconfigurationwiththeoptiontoselecttwodifferenttemplatestorenderthewidgetinthefrontend.

ThesecondthingthatwewilldoiscreateacustomBlockclasswherewecanwriteourownspecificmethodsforthewidget.

GettingreadyWewillworkfurtheronthewidgetmodulethatwecreatedinthepreviousrecipes.Ensurethatyouhavetherightcodeinstalled.

Howtodoit…Usingthefollowingsteps,youwilllearnhowwecanconfigureacustomBlockclasswithcustomtemplatesforawidgetinstance:

1. ThefirstthingthatwewilldoiscreatetheBlockclassforthewidget.TheBlockclasswillextendMagento\Catalog\Block\Product\Listclassbecauseweneedthefunctionalityofthatclassinourwidgettype.CreateafilecalledProductSlider.phpintheapp/code/Packt/ProductSlider/Block/Catalog/Product/folder.

2. Addthefollowingcontenttothatfile:

<?php

namespacePackt\ProductSlider\Block\Catalog\Product;

classProductSliderextends\Magento\Catalog\Block\Product\ListProduct

{

}

3. ConfigurethewidgetconfigurationtousetheBlockclassthatwejustcreated.Opentheapp/code/Packt/ProductSlider/etc/widget.xmlfileandchangetheclassattributeasshowninthefollowinghighlightedcode:

...

<widget

id="category_product_slider"

class="Packt\ProductSlider\Block\Catalog\Product\ProductSlider"

is_email_compatible="false"

placeholder_image="Magento_Widget::placeholder.gif">

<labeltranslate="true">Categoryproductslider</label>

<descriptiontranslate="true">ListofProductsforagivencategory

inasliderwidget</description>

<parameters>

...

4. NowwhentheBlockclassiscreatedandconfigured,itistimetocreatetemplatesfortheblock.Forthiswidget,wewillconfiguretwotemplates.Thefirstonewillcontaintheimage,price,andtitleoftheproducts,andthesecondoneisasimplifiedversionthatonlyshowstheimageandanAddToCartbutton.

5. Tostorethetemplates,createthefollowingfolders:

app/code/Packt/ProductSlider/view/

app/code/Packt/ProductSlider/view/frontend/

app/code/Packt/ProductSlider/view/frontend/templates/

app/code/Packt/ProductSlider/view/frontend/templates/product/

app/code/Packt/ProductSlider/view/frontend/templates/product/slider/

6. Inthelastfolder,createthefollowingfiles:

list.phtml

teaser.phtml

7. Inthelist.phtmlfile,addthefollowingcontent:

<divclass="blockblock-product-sliderslider-list">

<divclass="block-title">

<h2>List</h2>

</div>

<divclass="block-content">

Productslider

</div>

</div>

8. Intheteaser.phtmlfile,addthefollowingcontent:

<divclass="blockblock-product-sliderslider-teaser">

<divclass="block-title">

<h2>Teaser</h2>

</div>

<divclass="block-content">

Productslider

</div>

</div>

9. Nowwhenthefilesarecreated,wecancreatetheconfigurationinthewidget.xmlfileforthetwotemplates.Addthefollowinghighlightedcodetothewidget.xmlfile.Thecodeneedstobepastedaschildofthe<parameters>tag:

...

</parameter>

<parametername="template"xsi:type="select"required="true"

visible="true">

<labeltranslate="true">Template</label>

<options>

<optionname="default"value="product/slider/list.phtml"

selected="true">

<labeltranslate="true">Productlistslider</label>

</option>

<optionname="teaser"value="product/slider/teaser.phtml">

<labeltranslate="true">Productteaserslider</label>

</option>

</options>

</parameter>

</parameters>

...

10. Cleanthecacheandgotothewidgetconfigurationpage.WhenyouclickontheAddLayoutUpdatebutton,youcanseethetwoconfiguredtemplates,asshowninthefollowingscreenshot:

11. Whenyoucompletetheformwiththelayoutupdateasshown,thewidgettemplatewillappearonthehomepage.

TipEnsurethatyouhavecleanedthecacheandconfiguredthewidgetfortherightthemeandstoreview.

12. Thelastthingthatwewilldoiscreatealoopthatshowsthenameoftheproducts.Addthefollowingcodetothelist.phtmlfile:

<?php$productCollection=$block->getLoadedProductCollection()?>

<divclass="blockblock-product-sliderslider-list">

<divclass="block-title">

<h2>List</h2>

</div>

<divclass="block-content">

<?phpif(count($productCollection)):?>

<ul>

<?phpforeach($productCollectionas$product):?>

<li><?phpecho$product->getName()?></li>

<?phpendforeach;?>

</ul>

<?phpendif;?>

</div>

</div>

13. Onthewidgetconfiguration,configureavalidcategoryID.Whenyousavetheconfigurationandopenthehomepage,youwillseealistofproductnamesofthatcategory,asshowninthefollowingscreenshot:

TipYoucanfindthecategoryIDwhilenavigatingtoacategoryinthebackend.NavigatetoProducts|Categoriesandselectthecategorythatyouwantinthetree.Whenselectingacategory,theIDappearsnearthename.

Howitworks…ThefirstthingthatwedidwastocreateaBlockclassthatextendstheproductlistclass(Magento\Catalog\Block\Product\ListProduct)fromtheMagento_Catalogmodule.

Whentheclassiscreated,weupdatedtheconfigurationinthewidget.xmlfiletousethisclass.

WithonlyaBlockclass,wecan’tdisplaytheoutputtothefrontend.So,wecreatethetemplatefiles.Wecreatedtwotemplatefilesthatwecanusetogenerateadifferentoutputwiththesamedata.

Toconfigurethetemplatesinthewidget,wehavetoaddanextra<parameter>configuration.Templatesarealwaysconfiguredwitha<parametername="template">configuration.Inthe<options>childtag,thedifferenttemplatesaresetasoptionsfromthedropdown.

Thewidgetconfigurationisnowfinished,sobycompletingtheform,thewidgetwillbeplacedonthefrontend.

Toshowawidgetonthefrontend,youhavetocreatealayoutupdateinthewidgetconfigurationpage.Inalayoutupdate,youcanconfigurethepagetype,container,andtemplatewherethewidgetneedstobedisplayed.Inthisexample,thedisplayedisshowninthecontentareaofthehomepage.

Thelastthingwedidwasdisplayingtheproductnamesoftheconfiguredcategory.Wecreatedaloopthatshowstheproductnamesforacategory.UsingthegetLoadedProductCollection()method,alltheproductsthatareinaconfiguredcategoryarereturned.ThecategoryIDneedstobeconfiguredinthecategory_idfieldofthatblock(thisissomethingthatisdoneusingthecategory_idwidgetparameter).

CreatingacustomconfigurationparameterAtthispoint,wehaveaworkingwidget.ItshowsupinthefrontendandtherightproductsaredisplayedforthegivencategoryID.

ToconfigurethecategoryID,wehavetoknowtheIDofthecategory.Wehavetocopyitfromthecategorypageandpasteitinthetextbox.

Forbetterusability,wewillcreateacustomconfigurationfieldtoselectacategory.WewillcreateabuttonthatopensanoverlaywherewecanchoosetherightcategoryID.

GettingreadyWewillcreateasimilarconfigurationfieldthatisusedfortheCatalogCategoryLinkwidgettypeinthebackend.Youcanlookatthisconfigurationwidget’sconfigurationtoseehowitworks.

Also,ensurethatyouhavetherightstartfilesinstalledbecausewewillbuildfurtheronthemodulethatwecreatedinthepreviousrecipes.

Howtodoit…Usingthefollowingsteps,wewillcreateacategorychooserthatwillbeusedonthewidgetconfigurationpage.

1. WhenwelookattheCatalogCategoryLinkwidget,weseethattheyuseacustomwidgettoselectthecategoryID.Wewillusethiswidgetinourmodule.

2. Openthewidget.xmlfilethatisplacedinthe/etcfolderofthePackt_ProductSlidermodule.Replacetheparameterofthecategory_idparameterwiththefollowinghighlightedcode:

...

<labeltranslate="true">Title(frontend)</label>

</parameter>

<parametername="category_id"xsi:type="block"visible="true"

required="true">

<labeltranslate="true">Category</label>

<block

class="Magento\Catalog\Block\Adminhtml\Category\Widget\Chooser">

<data>

<itemname="button"xsi:type="array">

<itemname="open"xsi:type="string">SelectCategory.</item>

</item>

</data>

</block>

</parameter>

<parametername="template"xsi:type="select"required="true"

visible="true">

<labeltranslate="true">Template</label>

...

3. Cleanthecacheandopenthewidgetconfigurationpagefortheproductsliderwidgettype.WhenyouclickontheSelectCategorybutton,apopupopenswiththecategorytree,asshowninthefollowingscreenshot:

4. WhenyouinspecttheSelectCategorybuttonandnavigatetothehiddenformfieldintheHTMLcode,youseethatthevalueissimilartothefollowingpattern:category/<category_id>

5. ThiswidgetrequiresacategoryIDthatisthenumberaftertheslash.Now,wehavethecategorypaththatisusedtogenerateURLs.Tofixthisproblem,wehavethechoicetoimplementoneofthefollowingthings:

ExtracttheIDfromthepathwithstringfunctionsEnsurethataproperIDissetinthewidgetconfigurationpageThemoststableoptionisthesecondone,sowewillimplementit

6. WewillcreateanewBlockclassthatextendsthestandardonesothatwecaninheritalotoffunctionality.Createtheapp/code/Packt/ProductSlider/Block/Adminhtml/Catalog/Category/Widget/Chooser.php

filewiththefollowingcontent:

<?php

namespacePackt\ProductSlider\Block\Adminhtml\Catalog\Category\Widget;

classChooserextends

\Magento\Catalog\Block\Adminhtml\Category\Widget\Chooser{

}

7. Tousethepreviouslycreatedblockintheconfigurationfield,wehavetoupdatethewidget.xmlfile.Thewidget.xmlfilewilllookasshowninthefollowingcode.Thehighlightedcodeisthelinethatyouhavetochange:

<?xmlversion="1.0"encoding="UTF-8"?>

<widgetsxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Widget:etc/wi

dget.xsd">

<widget

id="category_product_slider"

class="Packt\ProductSlider\Block\Catalog\Product\ProductSlider"

is_email_compatible="false"

placeholder_image="Magento_Widget::placeholder.gif">

<labeltranslate="true">Categoryproductslider</label>

<descriptiontranslate="true">ListofProductsforagivencategory

inasliderwidget</description>

<parameters>

<parametername="title"xsi:type="text"required="true"

visible="true">

<labeltranslate="true">Title(frontend)</label>

</parameter>

<parametername="category_id"xsi:type="block"visible="true"

required="true">

<labeltranslate="true">CategoryID</label>

<block

class="Packt\ProductSlider\Block\Adminhtml\Catalog\Category\Widget\Choo

ser">

<data>

<itemname="button"xsi:type="array">

<itemname="open"xsi:type="string">SelectCategory…</item>

</item>

</data>

</block>

</parameter>

<parametername="template"xsi:type="select"required="true"

visible="true">

<labeltranslate="true">Template</label>

<options>

<optionname="default"value="product/slider/list.phtml"

selected="true">

<labeltranslate="true">Productlistslider</label>

</option>

<optionname="teaser"value="product/slider/teaser.phtml">

<labeltranslate="true">Productteaserslider</label>

</option>

</options>

</parameter>

</parameters>

</widget>

</widgets>

8. Whenyoucleanthecacheandreloadtheconfigurationpageofthewidget,youwillseethatnothinghaschangedbecausetheclassthatwecreatedcontainsnofunctionality.Tochangethebehaviorthatwewant,wewilladdthreemethodstotheapp/code/Packt/ProductSlider/Block/Adminhtml/Catalog/Category/Widget/Chooser.php

file.Thehighlightedcodeshowsthedifferencesbetweenthemethodsfromtheextendedclass:

<?php

namespacePackt\ProductSlider\Block\Adminhtml\Catalog\Category\Widget;

classChooserextends

\Magento\Catalog\Block\Adminhtml\Category\Widget\Chooser{

protectedfunction_construct(){

$this->setModuleName('Magento_Catalog');

parent::_construct();

}

publicfunction

prepareElementHtml(\Magento\Framework\Data\Form\Element\AbstractElement

$element){

$uniqId=$this->mathRandom->getUniqueHash($element->getId());

$sourceUrl=$this->getUrl(

'productslider/catalog_category_widget/chooser',

['uniq_id'=>$uniqId,'use_massaction'=>false]

);

$chooser=$this->getLayout()->createBlock(

'Magento\Widget\Block\Adminhtml\Widget\Chooser'

)->setElement(

$element

)->setConfig(

$this->getConfig()

)->setFieldsetId(

$this->getFieldsetId()

)->setSourceUrl(

$sourceUrl

)->setUniqId(

$uniqId

);

if($element->getValue()){

$categoryId=$element->getValue();

$label=$this->_categoryFactory->create()->load($categoryId)-

>getName();

$chooser->setLabel($label);

}

$element->setData('after_element_html',$chooser->toHtml());

return$element;

}

publicfunctiongetNodeClickListener(){

if($this->getData('node_click_listener')){

return$this->getData('node_click_listener');

}

if($this->getUseMassaction()){

$js='

function(node,e){

if(node.ui.toggleCheck){

node.ui.toggleCheck(true);

}

}

';

}else{

$chooserJsObject=$this->getId();

$js='

function(node,e){

'.

$chooserJsObject.

'.setElementValue(node.attributes.id);

'.

$chooserJsObject.

'.setElementLabel(node.text);

'.

$chooserJsObject.

'.close();

}

';

}

return$js;

}

}

9. ThecodethatweaddedinthepreviousstepcontainsacalltoanAJAXcontrollerthatwewillcreate.Toregistertheadminrouterforthismodule,wewillcreatetheapp/code/Packt/ProductSlider/etc/adminhtml/routes.xmlfilewiththefollowingcontent:

<?xmlversion="1.0"?>

<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation="

urn:magento:framework:App/etc/routes.xsd">

<routerid="admin">

<routeid="productslider"frontName="productslider">

<modulename="Packt_ProductSlider"before="Magento_Backend"/>

</route>

</router>

</config>

10. ThenextstepistocreatethecontrolleractionthatmatchestheURLthatwecallintheprepareElementHtml()method.Createtheapp/code/Packt/ProductSlider/Controller/Adminhtml/Catalog/Category/Widget/Chooser.php

filewiththefollowingcontent:

<?php

namespace

Packt\ProductSlider\Controller\Adminhtml\Catalog\Category\Widget;

classChooserextends

\Magento\Catalog\Controller\Adminhtml\Category\Widget\Chooser{

protectedfunction_getCategoryTreeBlock(){

return$this->layoutFactory->create()->createBlock(

'Packt\ProductSlider\Block\Adminhtml\Catalog\Category\Widget\Chooser',

'',

[

'data'=>[

'id'=>$this->getRequest()->getParam('uniq_id'),

'use_massaction'=>$this->getRequest()-

>getParam('use_massaction',false),

]

]

);

}

}

Thehighlightedcodeshowsthedifferencesbetweenwhatischangedinthecode.

11. Cleanthecacheandreloadtheconfigurationpageofthewidget.WhenyouclickontheSelectCategorybutton,apopupopenswiththecategorytree.Whenyouselectacategoryandyouinspecthiddeninputfield,youwillseethatanumber(thecategoryID)issetinsteadofthepath.

12. Savethewidgetandreloadthehomepage.Youwillseethattherightproductsareshownforthechosencategory.

Howitworks…Inthisrecipe,wecreatedacustomconfigurationparametertodevelopabetteruserexperiencefortheadminusers.

Webasedthisconfigurationfieldontheexistingcategorytreepop-upwindowthatisusedinotherwidgettypes,suchastheCatalogCategoryLinktype.Touseanexistingfield,wejusthavetomodifysomeconfigurationsinthewidget.xmlfileandthefieldisreadytouse.

However,thistypeofconfigurationfieldisnotexactlywhatwearelookingfor.ThefrontendrepresentationwasOK,butawronglyformattedcategoryIDwasreturnedinthebackground.

Tosolvethis,wecreatedacustomconfigurationfieldthatextendsthebehaviorfromthestandardcategorychooser.WeonlyhadtochangesomethingsthatareresponsibleforreturningacorrectlyformattedcategoryID.

ThefirstthingwedidwascreatinganewBlockclassthatextendsfromthestandardclassattheMagento\Catalog\Block\Adminhtml\Category\Widget\Chooserlocation.Inthisclass,weoverridethreemethods.First,weaddedthesetModuleName()methodinthe_construct()method.WecalledthismethodsothatwecanusethetemplatesfromtheMagento_CatalogmoduleinthePackt_ProductSlidermodule.

IntheprepareElementHtml()method,weconfiguredanAJAXURLtorendertherightblockwhenthepopupshowsup.Laterinthismethod,westrippedsomelogictoextractthecategoryIDfromthepath.

Inthelastmethod,getNodeClickListener(),wedidachangesothatonlythecategoryIDisreturnedtotheforminsteadofthecategorypath.

WhentheSelectCategorybuttonisclicked,anAJAXcallisdoneandablockwillberenderedtoshowthecategorytree.WealsoneededtochangetheblockthatisrenderedinthatAJAXcall,sowehadtooverwritethiscall.

Wedidthisbycreatinganewcontrolleractioninourmodulethatextendsfromthestandardcontrolleraction,Magento\Catalog\Controller\Adminhtml\Category\Widget\Chooser.Inthisaction,wechangedtheBlockclasstoourcustomclassusingthe_getCategoryTreeBlock()method.

There’smore…Likewedidinthisrecipe,itispossibletocreateconfigurationfieldsthatuseacustomHTMLoutput.

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

Replacethe<parameter_name>tagwiththenameofyourcustomconfigurationparameterandthevaluesofthiselementwillbehandledlikealltheotherconfigurationparametersofthatwidget.

FinalizingthethemingThefrontendrepresentationofthewidgetthatwejustcreatedisnotsomethingthatinvitespeopletobuysomeproducts.Itisjustalistoftheproductnamesforagivencategory.

Thelaststepofthischapteristofinalizethethemingofthewidget.WewillcreateanHTMLoutputthatshowsanimage,name,andpriceofthegivenproducts.

WithajQueryplugin,wewillconverttheHTMLoutputtoaslider,sowecanscrollthroughtheproducts.

GettingreadyOntheInternet,therearealotofgoodJavaScriptcarouselplugins.Inthisrecipe,wewillusethefollowing:

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

Forthecode,wewillbuildfurtheronthethingsthatarecreatedinthepreviousrecipesofthischapter.Ensurethatyouhavetherightfilesinstalled.

Howtodoit…Inthefollowingsteps,thelastactionsaredescribedtocompletethewidgetwithagood-lookingcarousel:

1. ThefirstthingistogenerateagoodHTMLoutputthatisusableforthejQueryplugin.Addthefollowingcodetothelist.phtmlfileofthePackt_ProductSlidermodule:

<?php

$productCollection=$block->getLoadedProductCollection();

$_helper=$this->helper('Magento\Catalog\Helper\Output');

?>

<divclass="blockblock-product-sliderslider-list">

<divclass="block-title">

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

</div>

<divclass="block-content">

<?phpif(count($productCollection)):?>

<divclass="product-slider">

<?phpforeach($productCollectionas$product):?>

<divclass="product">

<divclass="product-image">

<ahref="<?phpecho$product->getProductUrl()?>">

<?phpecho$block->getImage($product,

'category_page_grid')->toHtml()?>

</a>

</div>

<strongclass="product-name">

<a

href="<?phpecho$product->getProductUrl()?>">

<?phpecho$_helper->productAttribute($product,

$product->getName(),'name');?>

</a>

</strong>

<?phpecho$block->getProductPrice($product)?>

</div>

<?phpendforeach;?>

</div>

<?phpendif;?>

</div>

</div>

2. Whenwereloadthefrontend,weseeasimplelistofproductswiththeirname,price,andimage.

3. Thenextstepistoinitializethecarouselscript.GotothefollowingURLanddownloadthelatestversionoftheplugin:

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

4. UnzipthearchiveonyourlocalPC.Forthisrecipe,weneedthefollowingtwofilesofthearchive:

slick/slick.js

slick/slick.css

5. WhenyouwanttoaddtheCSSfiletothemodule,createtheapp/code/Packt/ProductSlider/view/frontend/web/css/source/module/_slick.less

fileandcopythecontentoftheslick/slick.cssfiletothatfile.6. ToloadtheLESSfile,linkitinthe_module.lessfile.Createthe

app/code/Packt/ProductSlider/view/frontend/web/css/source/_module.less

filewiththefollowingcontent:

@import'module/_slick.less';

7. ThenextstepistoaddtheJavaScriptfile.InMagento2,weusetheRequireJSlibrarytoincludethefile.First,wecopytheslick/slick.jsfiletothefollowinglocation:app/code/Packt/ProductSlider/view/frontend/web/js/slick.js.

8. ToregistertheJavaScriptfile,wehavetocreateaRequireJSconfigurationfile.Createthefileatapp/code/Packt/ProductSlider/view/frontend/requirejs-config.jswiththefollowingcontent:

varconfig={

map:{

'*':{

slick:'Packt_ProductSlider/js/slick'

}

}

};

9. WeaddedJavaScriptandLESSfiles,sothismeansthatwehavetoclearthepreviouslygeneratedstaticcontent.Wecandothisbyremovingthefollowingfolders:

pub/static/frontend/

pub/static/_requirejs/

var/view_preprocessed/

10. Cleanthecacheandreloadthefrontend.Becausethestaticcontentneedstobegenerated,itcantakesometimetoloadthefrontend.

11. Thelastthingthatwehavetodoistoinitializetheslickcarousel.Addthefollowingcodeattheendoftheapp/code/Packt/ProductSlider/view/frontend/templates/product/slider/list.phtml

file:

<scripttype="text/javascript">

require(['jquery','slick'],function($){

$(function(){

$('.product-slider').slick({

dots:false,

infinite:false,

speed:300,slidesToShow:5,

slidesToScroll:5,

responsive:[

{

breakpoint:1024,

settings:{

slidesToShow:4,

slidesToScroll:4,

infinite:true

}

},

{

breakpoint:770,

settings:{

slidesToShow:3,

slidesToScroll:2

}

},

{

breakpoint:600,

settings:{

slidesToShow:2,

slidesToScroll:2

}

},

{

breakpoint:400,

settings:{

slidesToShow:1,

slidesToScroll:1

}

}

]

});

});

});

</script>

12. Reloadthehomepageandyouwillseethatthecarouselisinitialized,asshowninthefollowingscreenshot:

13. TheJavaScriptconfigurationcontainssomebreakpointsforresponsivedevices.Whenyouhaveasmallerscreen,youwillseesomethingasshowninthefollowingscreenshot:

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

...

<divclass="block-title">

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

</div>

...

Howitworks…Inthefirststep,wecreatedagoodHTMLoutputthatiscompatiblewiththeslickJavaScriptplugin.Foreveryproduct,weshowanimage,name,andprice.Behindeveryimageandnameisalinkthatredirectstotherespectiveproductdetailpage.

TheproductsliderscriptcontainsaJavaScriptandCSSfilethatwehavetoincludeintheshop.FortheCSSfile,weconvertedthecontenttoaLESSfilesothatitisrenderedwiththeCSSofthewholeshop.

Foreverymodule,Magentolooksfora_module.lessfileintheview/frontend/web/css/source/directory.Inthatfile,weincludeda_slick.lessfilethatcontainstheCSScodeoftheplugin.

FortheJavaScriptfile,weusedtheRequireJSsystemofMagentotoincludethefile.Weplacedtheslick.jsfileintheview/frontend/web/js/folder.

Thescriptisinitializedintheview/frontend/requirejs-config.jsfile.Withthecodeinthatfile,theslick.jsfileisinitializedbutnotloaded.Toloadthefile,wehadtousetherequirefunctionasshowninthefollowingcode:

require(['jquery','slick'],function($){

});

Withthepreviouscode,thejQuerylibraryandtheSlicklibraryareloadedbeforethecodebetweenthebracketsisexecuted.

Toinitializetheproductslider,weusedtheJavaScriptcodethatisusedinthedocumentationoftheslickplugin.Withthatconfiguration,wecreatedsomebreakpointstomakethepluginresponsive.

Chapter10.PerformanceOptimizationInthischapter,wewillcoverthefollowingrecipes:

BenchmarkingawebsiteOptimizingthefrontendofthewebsiteOptimizingthedatabaseandMySQLconfigurationsOptimizingtheApachewebserverFindingperformanceleaksinMagentoConfiguringOPcache,Redis,andMemcachedOptimizingthePHPconfigurations

IntroductionInasportcompetition,everysecond,millisecond,decidewhetheraplayerwinsacompetitionornot.Everysmallaspectthatimprovestheperformanceisastepintherightdirectiontowinacompetition.Forwebsites,thisisthesame.Thefasterawebsiteis,thebetteritis.AfastwebsitegivesabetteruserexperienceanditisbetterforSEO.So,thefaster,thebetter.

Magentoisaframeworkthatcallsalotofoperationswhenloadingapage.Alltheseoperationstakesometime.ThismeansthatMagentoisnotoneofthebestperformingsystemsintheworld,especiallywhenyouareworkingwithalotofproducts,attributes,multiplestoreviews,andmore.

However,withagoodsetupandtherighttools,youcanimprovetheperformanceofyourshopsothatitwillperformveryfast.

Theperformanceofawebsitehasalotofimpactonyourvisitors.Herearesomefactsabouttheperformanceofawebsite:

Whenyoursiteis100millisecondsslower,youlose1%ofthetotalsales.Whenasiteisslowerthan2-3seconds,userswillleavebecauseyoursiteisslow.AquicklyloadingpagehasapositiveinfluenceonyourSEOresults.MoreandmorepeoplehavemobiledeviceswithoutthefastestInternetconnection.

Asyoucansee,theperformanceisanimportantthingwhenyouwanttoimprovetheconversionofyourwebsite.Customerswillleaveyoursitewhenitisslow,andsearchengineswillgiveyoualowerrankwhenyoursiteisslow.

Theimprovementoftheperformanceismostlyoneofthelaststepsinthedevelopmentworkflowofasite.Peoplebuildsomethingandwhenitisready,theybegintolookathowthattheycanoptimizesomepartstomakeitfaster.

Inthischapter,wewillexplore,detect,andfixperformanceleaksinaMagentowebshopusingsomeperformancetools.

Magentodeliverssometoolsbydefault,butwehavetolookatthewholepicture.TwoidenticalMagentoinstallationscanhaveadifferentperformancethatcanbecausedbythefollowingreasons:

HardwareNetworkLoadDeviceoftheclient

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

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

Inthisrecipe,wewillbenchmarktheMagentositebydoingsometestswithApacheBenchandSiege.Withthesetools,wecanmeasuretheperformanceofdifferentpages.

GettingreadyForthisrecipe,weneedsometoolsthatneedtobeinstalledonthewebserver.Ensurethatyouhavethefollowingtoolsinstalled:

ApacheBench(ab):Thistoolcanbeinstalledusingthesudoapt-getinstallapache2-utilscommand.Whenthisisinstalledontheserver,youcanusetheab-hcommandtodisplaytheusageinformation.Siege:WewilldosomebenchmarkingtestswithSiegethatisinstalledonthesameserverastheMagentoinstance.Toseeifitisinstalled,youcanrunthefollowingcommand:

siege-V

Ensurethatthe-Voptionisinuppercase.WhenSiegeisinstalled,youwillseeitsversionnumber,asshowninthefollowingscreenshot:

Ifitisnotinstalled,youcanrunthefollowingcommandwhenyouareusingaDebian-basedLinuxdistribution:

sudoapt-getinstallsiege

Anotheroptionistodownloadtheinstallationfileandinstallitusingthefollowingsteps:

1. Downloadthearchiveusingthefollowingwgetcommand:

wgethttp://download.joedog.org/siege/siege-3.1.0.tar.gz

2. Extractthefilebyrunningthefollowingcommand:

tarxfzsiege-3.1.0.tar.gz

3. Movethefoldertothepreferredlocationandgotothatdirectoryusingthecdcommand.

4. Whenyouareinthatfolder,youcaninstallSiegeusingthefollowingcommand:

sudo./configure

Howtodoit…1. Togetanideaoftheresponsetimewithaloadofanumberofconcurrentusers,we

canuseApacheBenchtoperformsomesimpletests.Withthefollowingcommand,wewillrunatestthatwritestheresulttoaCSVfile:

ab–c10–n50–eapachebench.csvhttp://magento2.local

2. Inthepreviouscommand,wedidaloadtestwiththefollowingparameters:

-c:Thisparameterrepresentsthenumberofconcurrentusers.Inthistest,weran10requestsatthesametimethroughout.-n:Thisparameterrepresentsthenumberofrequests,whichis50inthiscase.So,wewillhave50resultsinthefile.-e:Thisparameterrepresentstheoutputfile.TheoutputiswrittentothegivenCSVfile.

TipThe-goptionmeansthesameasthe-eoption,buta-goptionwillgenerateaTSV(TabSeparatedValue)file,alsoknownasagnuplotfile.

3. Whenyouruntheprecedingcommand,itwillreturnanoutputasshowninthefollowingscreenshot:

4. Thisreportshowsthegeneralstatisticsofthetest.ThespecificresultsofeachrequestaresavedintheCSVfile(apachebench.csv).

5. LoadtestingwithSiege.

SiegeisanotherloadtestingtoollikeApacheBench.ThedifferencebetweenSiegeandApacheBenchisthatSiegehasmorefunctionsthanApacheBench.Itisdesignedtoperformastresstestwithanumberofconcurrentusers.SiegealsoprovidestheabilitytoworkwithHTTPauthentication,cookies,sessions,andmore.Whenyouwriteagoodscript,youcansimulatearealstresssituation.

6. ForaloadtestwithSiege,wewilluseatextfilewherewewillconfiguresomeURLsthatwillbeusedduringtheSiegeloadtest.WhenwedoatestwithdifferentURLs,wewilltestmorepages,andwecanfindmorepitfallsonthewebsite.Createafilecalledsiege_url.txtwiththefollowingcontent:

http://magento2.local/

http://magento2.local/pub/static/frontend/Magento/luma/en_US/mage/calendar.css

http://magento2.local/pub/static/frontend/Magento/luma/en_US/css/styles-

m.css

http://magento2.local/pub/static/frontend/Magento/luma/en_US/images/logo.svg

http://magento2.local/pub/static/frontend/Magento/luma/en_US/requirejs/require.js

http://magento2.local/women/tops-women.html

http://magento2.local/women/tops-women.html?cat=28

http://magento2.local/checkout/cart/add/?product=1

http://magento2.local/checkout/

http://magento2.local/pub/static/frontend/Magento/luma/en_US/js-

translation.json

http://magento2.local/pub/static/frontend/Magento/luma/en_US/Magento_Ui/templates/block-

loader.html

http://magento2.local/pub/static/frontend/Magento/luma/en_US/Magento_Ui/templates/modal/modal-

popup.html

http://magento2.local/pub/static/frontend/Magento/luma/en_US/Magento_Checkout/template/onepage.html

http://magento2.local/pub/static/frontend/Magento/luma/en_US/Magento_Checkout/template/progress-

bar.html

http://magento2.local/pub/static/frontend/Magento/luma/en_US/images/loader-

1.gif

http://magento2.local/pub/static/frontend/Magento/luma/en_US/images/select-

bg.svg

http://magento2.local/customer/account/login/

http://magento2.local/contact/

http://magento2.local/catalog/product_compare/add/?product=1

http://magento2.local/pub/static/frontend/Magento/luma/en_US/js/theme.js

http://magento2.local/catalog/product_compare/index/

http://magento2.local/catalogsearch/result/?q=watch

http://magento2.local/catalogsearch/advanced/result/?

name=Watch&sku=&description=analog&short_description=&price%5Bfrom%5D=50&price%5Bto%5D=100&tax_class_id=

http://magento2.local/pub/media/catalog/product/cache/1/small_image/240x300/e9c3970ab036de70892d86c6d221abfe/sample_data/m/g/mg05-

br-0.jpg

http://magento2.local/customer/account/forgotpassword/

http://magento2.local/search/term/popular/

7. ChangetheURLssothattheymatchyourMagentoconfigurations.EnsurethatyouaretestingyourdevelopmentenvironmentwithvalidURLs.

8. Withthefollowingcommand,wecanstartaloadtestwith50concurrentusersbasedontheURLsthatweenteredinthesiege_url.txtfile:

siege-c50-i-t1M-d3-fsiege_url.txt

9. Thetimetakenbythiscommandtocomplete,dependsonthewebshop’sperformance.Theoutputofthecommandwillbesimilartothefollowing:

$siege-c50-i-t1M-d3-fsiege_url.txt

**SIEGE3.0.5

**Preparing50concurrentusersforbattle.

Theserverisnowundersiege…[alert]socket:1772328704selecttimed

out:Connectiontimedout

[alert]socket:1998931712selecttimedout:Connectiontimedout

[alert]socket:1671616256selecttimedout:Connectiontimedout

socket:2024109824selecttimedout:Connectiontimedout

[alert]socket:1713579776selecttimedout:Connectiontimedout

Liftingtheserversiege…done.

Transactions:171hits

Availability:86.80%

Elapsedtime:59.44secs

Datatransferred:1.02MB

Responsetime:6.08secs

Transactionrate:2.88trans/sec

Throughput:0.02MB/sec

Concurrency:17.49

Successfultransactions:190

Failedtransactions:26

Longesttransaction:27.51

Shortesttransaction:0.00

NoteIntheprecedingoutput,wecanseesometimeouts.Thismeansthattherearerequeststhatarenotsuccessful.

10. Withthesiege-hcommand,youcanseealltheavailableoptionsofthatcommand.

Howitworks…WestartedthisrecipewithaloadtestusingApacheBench.ThisisatoolthatwaspreviouslyapartoftheApacheWebServer.Currently,itispackagedintheapache2-utilsbundle.Thispackagealsocontainstoolssuchaslogrotation,generationofhtpasswdfiles,andmore.

WithApacheBench,weperformedaloadtestwithanumberofconcurrentusers.Aconcurrentuserisauserthatisgeneratingloadonthewebsite.Whenwetestwith10concurrentusers,wewillsimulateacontinuousloadof10processesduringthetimeofthetest.Whenoneoftherequestsisfinished,anewoneisfired,sotherearealways10requestsrunning.

Whenweyoualimitof30seconds,wecanseehowmanyrequestsaresuccessfullyfinishedduringthattime.Thiscangiveyouagoodideaofthecapacityofyourwebsite.

WithSiege,wecandothesameaswithApacheBench.ThemaindifferencebetweenApacheBenchandSiegeisthatSiegehasmorefeatureswhenyoucompareitwithApacheBench.

Inthisrecipe,weperformedaloadtestwithalistofgenericMagentoURLssuchassomepages,staticcontent,productimages,andmore.WealsoaddedsomeproductstothecartwiththequerystringURLsothatwecansimulateahumanworkflowonthewebsite.

AlotofpagesarecachedinMagento,butwealsohavetotestthesession-specificpages,suchasthecart,checkout,search,andmore.

NoteWithApacheBenchandSiege,youcancreateloadonawebsite.Whenyoudothisonaremotesite,itispossiblethatyouwillbeblockedbyafirewallbecausealotofrequestsfromthesameIPwillgivetheimpressionofanattack.

OptimizingthefrontendofthewebsiteWhenyoulookattheperformanceofawebsite,therearemanypointsthatyoucanoptimize.Betweenthestartofarequestandtherenderedpageinthebrowser,alotofoperationsarecarriedout.

Inthisrecipe,youwilllearnhowtospotbottlenecksandoptimizesomethingsthatincreasetheperformance.

GettingreadyForthisrecipe,wewillusetwobrowserpluginswherewecanmeasuresomemetrics:

app.telemetry:Withthissimplebrowserplugin,wecanmonitortheloadtimeofeachpage.Wecandownloadthepluginfromthefollowingwebsite:

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

Thiswilladdaniconinthebrowserbarwherewecanreadtheresponsetime.

YSlow:Withthisbrowserplugin,canwegenerateareportofthingsthatwecanoptimizeinthewebsite.YoucaninstalltheYSlowplugintoChromeorFirefoxfromthefollowingURL:

http://yslow.org/

Howitworks…ThefollowingstepsdescribehowwecanfindpitfallsinthefrontendofMagentotodecreasethepageloadtime:

1. Whenyouhavetheapp.telemetryplugininstalled,youwillseeanicononthebrowserbar,asshowninthefollowingscreenshot:

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

3. Whenwedothesameonaremotewebsite,wewillseedifferentresults.ThetimetakenbytheTCPandDNSstepswillbelonger.

4. WewillcontinuebygeneratingaperformanceanalysiswithYSlow.YSlowisabrowserpluginthatneedstobeinstalledinyourbrowser.Whenyouhaveinstalledthebrowserplugin,youcanclickontheiconinthebrowserbar.

5. AwindowwillshowupandwhenyouclickontheRunTestbutton,thereportwillbegenerated.

6. WhenthereportisreadyandyouclickontheGradetab,youwillseethereport,asshowninthefollowingscreenshot:

7. WithadefaultMagentoinstance,youwillhaveagoodscore.Butwecanincreasethiswithsomesimpleoptimizations.

8. ThefirstthingthatwecanoptimizeistoaddExpiresheaders.Withthefollowingcode,wecanaddExpiresheaderstosomestaticfiles.Addthiscodeinthe.htaccessfilebyreplacingthe<IfModulemod_expires.c>sectionwiththefollowingcode:

<IfModulemod_expires.c>

############################################

##AdddefaultExpiresheader

##http://developer.yahoo.com/performance/rules.html#expires

ExpiresDefault"accessplus1year"

ExpiresActiveOn

ExpiresByTypeimage/gif"access1year"

ExpiresByTypeimage/jpg"access1year"

ExpiresByTypeimage/jpeg"access1year"

ExpiresByTypeimage/png"access1year"

ExpiresByTypeimage/x-icon"access1year"

ExpiresByTypetext/css"access1month"

ExpiresByTypeapplication/x-javascript"access1month"

</IfModule>

9. Whenweaddthiscode,wewillhavetoreloadthewebsiteinordertocheckthatthesiteisnotbrokenafterthechangeinthe.htaccessfile.Whenthisisdone,wecanrunthetestagain.WewillseethattheAddExpiresHeadersisnowchangedtoanA.

10. AnotherimprovementistominifyJavaScriptandCSS.TherearealotoftoolstominifyJavaScriptandCSSfilesbutthisalsoasettingintheconfigurationofMagento.Inthebackend,navigatetoStores|Configuration|Advanced|DeveloperandchangethefollowingsettingstoYes:

MergeJavaScriptfiles:YesMinifyJavaScriptfiles:YesMergeCSSfiles:YesMinifyCSSfiles:Yes

11. Aftersavingthesesettings,theconfigurationwilllookasshowninthefollowingscreenshot:

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

Howitworks…Westartedthisrecipeusingtheapp.telemetryplugintoshowthedifferentpartsofapageload.Whenweclickontheiconofthatplugin,weseethefollowingparameters:

Redirect:Thisisthetimethatistakentoredirectthispage(ifthereisaredirect).AppCache:Thisisthetimeofyourlocalcache.DNSLookup:ThisisthetimetakentoresolvetheIPaddressforthegivendomainname.TCPConnection:Thisisthetimetakentosendarequesttotheserver.ThisismostlylongerwhensendingalargePOSTrequest.TCPRequest:Thisisthetimethattheserverneedstoprocessyourresponse.Inthistimeframe,theserverwillbuildtheHTMLfileofthepage.TCPResponse:Thisisthetimetakentodownloadtheresponsetoyourlocaldevice.Withslownetworks,thistimewillbemore.Processing:ThisisthetimeittakestorenderyourHTML,CSS,JavaScript,andotherstuff.Onloadevent:Thisisthetimethattheonloadeventtakes.Afterthis,thepageisfullyloaded.

Inthefirstcolumn,Offset,youseethetimefromthestartoftherequest.Inthesecondcolumn,Duration,youcanseehowmuchtimeistakenforeachstep.

Inthenextstep,wedidananalysisofthepagewithYSlow.YSlowwilltestawebsiteondifferenttopics.Allthesetopicsarebasedonaruleset.WiththeChromeplugin,theYSlow(v2)rulesetisautomaticallyselected.YoucanalsochoosetheClassic(v1)ruleset.

TheYSlow(v2)rulesetwilltestonthefollowingtopics:

MinimizingHTTPrequestsUsingaContendDeliveryNetworkAvoidingemptysrcorhrefinstancesAddingExpiresHeadersoracache-controlheaderUsingcompressionforstaticfilesPlacingstylesheetsatthetopPlacingJavaScriptatthebottomAvoidingCSSexpressionMakingJavaSriptandCSSexternalReducingDNSlookupsMinifyingJavaSriptandCSSAvoidingredirectsRemovingduplicateJavaScriptandCSSConfiguringETagsMakingAJAXcacheableUsingGETfoAJAXrequestsReducingtheNumberofDOMelementsReducingthenumberof404errors

ReducingthecookiesizeUsingcookie-freedomainsforstaticfilesAvoidingfiltersNoscalingofimagesinHTMLMakingfavicon.icosmallandcacheable

WhenwedidthetestonastandardMagento2shop,theoverallscorewasquitegood.Whenyoudevelopyourownstuff,youwillhavetokeepthepreviousrulesetsinmindbecausethesepointsreducetheloadofthepage.

TheYSlowrulesetmostlyincreasestheperformanceofthefrontend(theloadingofJavaScript,CSS,images,andsoon).Itdoesn’tgiveadviceonhowtooptimizeyourPHPprocesses.

First,weaddedsomeExpiresheadersinstancetothestaticfiles.Withtheseheaders,weconfiguredhowlongstaticfileswillbecachedinthebrowsersofthevisitors.ForeveryMIMEtype,wecanspecifyadifferenttime.

ThesecondpointtooptimizeistomergetheJavaScriptandCSSfiles.Loadingonebigfileisfasterthanloading100smallfiles.Thiscanbedeclaredbythefollowingreasons:

Foreveryfile,anHTTPrequestwillbecreated.Also,headersandcookiesaresentforeveryrequest.SomewebserverscanonlyhandlealimitednumberofHTTPrequestsatatime.So,iftherearealotofrequests,theywillbequeued.Sometimes,itmayhappensthatoneoftherequestshangs,soyourpagekeepsloading.Withfewerrequests,youhavelesserchancesthatthishappens.

Finally,weconfiguredadifferentdomaintothestaticcontent.Whenyouhaveadifferentdomain(oracookie-freedomain),thecookiesarenotsenttotheserverforeveryrequest,sothisreducesthebandwidth.

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

Becausethisisanonlinetool,youhavetoensurethatyoursiteisaccessiblebytheGTmetrixserver.

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

Inthisrecipe,wewillseehowwecanoptimizethedatabaseandtheMySQLserver.

GettingreadyEnsurethatyouhaveaccesstoadatabaseclientwhereyoucandosomequeriestoyourdatabase.Inthisrecipe,wewillusephpMyAdmin.

Howtodoit…Inthefirstpartofthisrecipe,wewilloptimizethetablestructuresoftheMagentodatabase.Takealookatthefollowingsteps:

1. OpenphpMyAdminandclickontheMagentodatabase.Youwillseeanoverviewofallthetables.

2. Atthebottomofthispage,clickontheCheckAllbutton.3. Whenyouclickonthedropdownlist,youcanrepairthetableandoptimizeit,as

showninthefollowingscreenshot:

TipEnsurethatyourunthisactionforallthetablesintheMagentodatabase.Itcanhappenthatthelistisdividedinmultiplepages.AMagento2shophasover300tables.ThephpMyAdmininstallationshowsbydefault250tablesperpage.

4. WehavenowoptimizedthetablesofMagento.Theotherthingthatyoucandoisrunthedatabaserepairtooltocheckwhethersomerelationsaremissing.

Wearenowatthesecondpartofthisrecipe.Inthispart,wewillseesomeoptimizationsthatwecandoontheMySQLserver:

1. AgoodMySQLserverstartswithgoodhardwareandtherightoperatingsystem.TorunMagento,itisrecommendedthatyouuseadedicatedserveroraVPS.Youcanalsouseasharedhostingenvironment,butthisisnotthebestoptionbecausetheRAMandCPUloadissharedbetweenotherusersonthatserver.WithaVPSordedicatedserver,youhaveafixednumberofCPUsandRAMavailable.

2. WhenyourserverhasenoughRAMavailable,youcanturnofftheswappeddevices.Sometimes,theswapoptionwillbeautomaticallyusedevenwhenthereisenoughmemoryavailable.

3. Onyourserver,openthe/etc/mysql/my.cnffile.Lookfortheskip-external-lockingsettingunderthe[mysqld]section.Ifthesettingisn’tthere,youcanaddthis

onanewline.4. ToshowthesizeofthekeybufferforMyISAMtables,wecanrunthefollowing

queryontheMySQLprompt:

mysql>SHOWVARIABLESLIKE'%key_buffer%';

5. ForMagento,therecommendedvalueis512MB.Tosetthisvalue,wecanrunthefollowingcommandontheMySQLprompt:

mysql>SETGLOBALkey_buffer_size=536870912;

6. Next,wewillchangesomedefaultconfigurationparameters.WecansettheseinthemainconfigurationfileoftheMySQLserverthatislocatedat/etc/mysql/my.cnf.

7. Openthatfileandpastethefollowingconfigurationunderthe[mysqld]section:

key_buffer=512M

max_allowed_packet=64M

thread_stack=192K

thread_cache_size=32

table_cache=512

query_cache_type=1

query_cache_size=52428800

tmp_table_size=128M

expire_logs_days=10

max_binlog_size=100M

sort_buffer_size=4M

read_buffer_size=4M

read_rnd_buffer_size=2M

myisam_sort_buffer_size=64M

wait_timeout=300

max_connections=400

8. SavethefileandrestartyourMySQLserver.Youcandothisbyrunningthefollowingcommand:

sudoservicemysqlrestart

Howitworks…WhenoptimizingaMySQLserver,youhavetoknowthecapabilitiesofyourserverandthetrafficthatyouexcept.Withtheseparameters,youcancalculateagoodvalueforthekey_buffer,query_cacheandtable_cache.

Withthefollowingcommands,youcanviewtheMySQLserverstatus:

Command Description

mysql>SHOWSTATUS;ThiscommandshowsthecurrentstatusoftheMySQLserver.ItisavailableinMySQL5.0andlater,andisthestandardquerytoshowalltheglobalvariables.

mysql>SHOW

VARIABLES;ThiscommandshowsalltheMySQLvariables.

mysql>SHOWENGINE

INNODBSTATUS;ThiscommandshowsthecurrentstatusoftheINNODBengine.

mysql>SHOWGLOBAL

STATUS;Thisqueryshowsvaluesofthecurrentloadonthedatabaseserverforallconnections.

mysql>SHOWLOCAL

STATUS;Thiscommandshowsthesameasthelastonebutnowonlyforthecurrentconnection.

$mysqladmin

extended-i100-r

YouneedtorunthiscommandinaLinuxprompt.ThiscommandshowswhatishappeningwiththeMySQLserver.

DatabaseoptimizationisoneofthekeyaspectstotuneyourMagentowebshop.Databaseprocessesareresponsibleforabigpartofthepageload,soagoodperformingdatabaseisveryimportant.

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

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

Howtodoit…1. Thefirstthingtothinkaboutisagoodoperatingsystemtorunyourwebserver.Itis

highlyrecommendedthatyouuseaLinuxdistributionbecauseitisthestandardforPHPapplications.

Intherecipesofthisbook,weusedanUbuntuserver(aDebian-basedLinuxdistribution).

TipDon’tuseaWindowsservertorunMagento.Itwillwork,butitislessefficientandyoucanhaveissueswithfilepermissions,code,andmore.

2. Updatetheoperatingsystemtothelateststableversion.Anupdatedsoftwareissaferandfaster.UseatleasttheApache2.4.Atthetimeofwriting,thisisthelateststablereleaseandhassomeimprovementsforperformance.

3. Installonlytherequiredsoftwareonyourwebserver.Lessismore!Whenalotofsoftwareisinstalledthatyoudon’tuse,youwillhavebackgroundtasksthatarerunningfornothing.

4. Useafastfilesystem.UseanSSDinyourserverbecausethisismuchfasterthanadisk.Neveruseafilesystemthatissharedoveranetworkbecausethisisveryslow.

5. Youhavetoconfigureyourwebserversothatitdoesn’tswap.Whenyourwebserverbeginstoswap,allrequestswillbeservedslower.ThefirstthingtodoistocomparethevolumeofRAMontheserverwiththeaveragememoryloadofarequestandanumberofrequests.ThesecondthingthatyoucandoisconfiguretheMaxClientssetting.Thissettingcontrolsthenumberofchildprocesseswhentheserverisswapping.

6. LookattheHostnameLookupssettingandcheckwhetherthisisconfiguredwiththeOffvalue.

7. EnabletheApachemodulesmod_deflateandmod_headers.Wecandothiswiththefollowingcommands:

sudoa2enmoddeflate

sudoa2enmodheaders

8. Openthe.htaccessfileintheMagentorootandgotothemod_deflateconfigurationtag.Uncommentsomelinessothattheblocklookslikethefollowingcode:

<IfModulemod_deflate.c>

############################################

##enableapacheservedfilescompression

##http://developer.yahoo.com/performance/rules.html#gzip

#Insertfilteronallcontent

SetOutputFilterDEFLATE

#Insertfilteronselectedcontenttypesonly

AddOutputFilterByTypeDEFLATEtext/htmltext/plaintext/xmltext/css

text/javascript

#Netscape4.xhassomeproblems…

BrowserMatch^Mozilla/4gzip-only-text/html

#Netscape4.06-4.08havesomemoreproblems

BrowserMatch^Mozilla/4\.0[678]no-gzip

#MSIEmasqueradesasNetscape,butitisfine

BrowserMatch\bMSIE!no-gzip!gzip-only-text/html

#Don'tcompressimages

SetEnvIfNoCaseRequest_URI\.(?:gif|jpe?g|png)$no-gzipdont-vary

#Makesureproxiesdon'tdeliverthewrongcontent

HeaderappendVaryUser-Agentenv=!dont-vary

</IfModule>

TipWhenyougetaninternalservererrorafterthechange,itispossiblethattheheadersmoduleisnogenabled.Runthesudoa2enmodheaderscommandandrestarttheservertofixthis.

9. TakealookattheKeepAlivesettingofyourApacheserver.Whenthissettingisactive,theApacheservercanhandlemultiplerequeststroughthesameTCPconnection.

10. ConfiguretheMulti-ProcessingModules(MPM)foryourcase.Thevaluesoftheseconfigurationsdependontheresourcesandloadthatyouexpectonyourserver:

StartServers50

MinSpareServers15

MaxSpareServers30

MaxClients225

MaxRequestsPerChild4000

11. Whenyourunsomeloadtestsagain,youcancomparethecurrentresultwiththeresultsbeforetheoptimization.Usually,youwillseesomedifferences.

Howitworks…Theperformanceofawebserverdependsonmanyfactors.Thekeypartsaretheapplication,hardware,operatingsystem,andthenetwork.

Application:Ensurethattheapplicationthatyouarerunningisworkingefficientlywiththeresourcesonyourserver.Ifyourapplicationexpectsalotofresourcesforasimpletask,maybeyoucanoptimizethis.Hardware:Ensurethatthehardwareresourcesarehighenoughtoservetheexpectedloadandpeaks.Operatingsystem:Theoperatingsystemandthewebserverversionareamongtheimportantfactors.UseaLinuxservertorunMagentowithanApacheorNginxwebserveronit.Alwaysusethelateststableversionsofthesoftwarebecausetheyarefasterandmoresecure.Network:Thewebserversendstheresponsetroughthenetworktotheclient.Whenthatnetworkisslow,thedownloadtimeofarequestwillbelong.Hostyourwebserverwithagoodnetworkconnectionandhostitgeographicallyintheregionofyourtargetaudience.Forexample,hostyourwebsiteinItalyforanItalianwebsite.

Asyoucansee,youcanoptimizealotofthingsonyourwebserver.Buteverycaseisdifferent.Normally,peoplehaveastandardserversetup.Theydeploytheapplicationonitandthentheystartoptimizing.Everyapplicationisdifferent(intermsofcode,load,andsoon).

FindingperformanceleaksinMagentoWhenoptimizingawebsite,wecandolotsofthingstotheserverenvironmentbuttheapplicationthatrunsonitcanalwaysbefaster.

WhenyouhaveaMagentostoreandonekindofpageissignificantlyslowerthanotherpages,itcouldpossiblebethatthereisaperformanceissueonthatpage.

Tofindperformanceissues,wecanusetheMagentoProfiler.WiththeMagentoProfiler,wecanseehowmuchtimeeveryprocesstakestorenderapage.

GettingreadyToenabletheprofiler,wehavetomodifytheapacheenvironmentvariables.WecandothisintheVirtualHostconfigurationorinthe.htaccessfile.Ensurethatyouhaveaccesstooneofthesefiles.

Howtodoit…Inthefollowingsteps,wewillspecifyhowwecanusetheMagentoprofiler:

1. Toenabletheprofiler,addthefollowingcodeintheVirtualHostorthe.htaccessfile:

SetEnvMAGE_PROFILERhtml

2. IfyouchangedthissettingintheVirtualHostfile,reloadtheApacheserver.3. Reloadapageinthefrontendandyouwillseethattheprofileroutputisshownatthe

endofthepage,asshowninthefollowingscreenshot:

4. ItispossiblethattheprofileroutputwillbreaksomefunctionalitywhenyouhaveJSONresponseswithAJAX.Forthisreason,itisalsopossibletowritetheprofileroutputtoa.csvfile.Toenablethis,wehavetochangetheSetEnvrulein.htaccessorVirtualHosttothefollowing:

SetEnvMAGE_PROFILERcsvfile

5. Theprofileroutputwillbewrittentothevar/log/profiler.csvfile.6. WhenweswitchtheprofilerbacktotheHTMLoutput,wecananalyzethepage.7. OpenaproductdetailpageandtakealookattheTimeandCountcolumn.Wesee

thestepsofapageloadinatree.Whenwelookforthemosttimeconsumingparts,

weseethattheLAYOUTpartistheslowestofthewholepage.

Howitworks…WhenyouenabletheMagentoprofiler,theprofilingresultswillbeshowedintheHTMLfile,CSVfile,orFirebug.

Intheprofilingresults,youhavethefollowingcolumns:

TimerId:Thisisthecodeofaprofiledstatement.Inthecode,youcanprofileablockofcodebysurroundingitwiththefollowingcode:

\Magento\Framework\Profiler::start('profiler_name');

//Yourcodetoprofile

\Magento\Framework\Profiler::stop('profiler_name');

Time:Thisisthetimetakentocompletethecodebetweenaprofilerstartandstop.Avg:Whenaprofilerstatementisexecutedmorethanonetime,thiswillshowtheaveragetimetocompleteonestatement.Cnt:Thisshowsthenumberoftimesaprofilerstatementiscalled.

NoteTheAvgcolumnmultipliedbytheCntcolumngivesthesamevalueastheTimecolumn.

Emalloc:Thisistheamountofmemorythatisallocatedfortheprofilerstatement.RealMem:ThisisthesameastheEmallocoptionbutthisistheamountofmemorythatisallocatedtothesystem.

Itispossibletorunprofilerstatementsineachother.Whenthisisdone,theHTMLoutputwillshowdotsfortheitemsthatareexecutedinaparentstatement.Thiswillgiveyouatreeoverviewsoyoucanseehowapageisexecuted.

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

RedisisareplacementofthestandardfilecachingofMagento.Thissystemworksfasterwhenyouhavealotofcachefiles.

MemcachedissimilartoRedis.Itisasysteminwhichyoucancacheobjects.

ZendOPcacheisanopcodecachingsystem.Whenthesecachingtechniquesareenabled,thePHPcodewillbecachedtothesesystems.

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

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

MemcachedInstallMemcachedwiththesudoapt-getinstallphp5-memcached.command.

RedisInstallRediswiththesudoapt-getinstallredis-servercommand.

Howtodoit…Inthefollowingsteps,wewilluseZendOPcacheandMemcachedforMagento:

1. Whenyourunaphpinfo()methodinthebrowserorexecutethephp-i|grepopcachecommand,youwillseetheOPcachesettings.ThefollowingsettingsmustbesettoOntoensurethatOPcacheisenabled:

opcache.enable

opcache.enable_cli

2. WhenthesesettingsarenotsettoOn,wehavetosetitinthephp.inifile.Normally,thisislocatedinthe/etc/php5/apache2and/etc/php5/clifolders.ThisconfigurationisforaUbuntuserver.

3. ToenableRedis,wehavetomodifytheapp/etc/env.phpfile.Inthisfile,wehavetoaddthecacheconfigurationsothatMagentoknowswhichcachetypeithastouse.Addthefollowingcodetothisfile.Youhavetopastethiscodeintheexistingarray:

'cache'=>array(

'frontend'=>array(

'default'=>array(

'backend'=>'Cm_Cache_Backend_Redis',

'backend_options'=>array(

'server'=>'127.0.0.1',

'port'=>'6379',

'persistent'=>'',

'database'=>'0',

'force_standalone'=>'0',

'connect_retries'=>'1',

'read_timeout'=>'10',

'automatic_cleaning_factor'=>'0',

'compress_data'=>'1',

'compress_tags'=>'1',

'compress_threshold'=>'20480',

'compression_lib'=>'gzip',

),

),

'page_cache'=>array(

'backend'=>'Cm_Cache_Backend_Redis',

'backend_options'=>array(

'server'=>'127.0.0.1',

'port'=>'6379',

'persistent'=>'',

'database'=>'1',

'force_standalone'=>'0',

'connect_retries'=>'1',

'read_timeout'=>'10',

'automatic_cleaning_factor'=>'0',

'compress_data'=>'0',

'compress_tags'=>'1',

'compress_threshold'=>'20480',

'compression_lib'=>'gzip',

),

),

),

),

4. CleanalltheMagentocachesandrestartyourApacheserver.WhenyoudoaloadtestwithApacheBench,youwillseethatthereisanimprovementwiththeperformance.

NoteMagentousesFullPageCachingtocacheeverycontentpage.Ifyouwanttotesttheperformanceofuncachedpages,youcandisableit.

5. Toendthisrecipe,wewillconfigureMemcachedtostoretheMagentosessions.Inyourphpinfo()method,ensurethatMemcachedisenabled.

6. Opentheapp/etc/env.phpfileandaddthefollowingcodeinthatfile.Youhavetopasteitintheexistingarray:

'session'=>array(

'save'=>'memcached',

'save_path'=>'127.0.0.1:11211?

persistent=1&weight=2&timeout=10&retry_interval=10',

),

7. Thelastthingthatwecandoisstorethevar/cachefolderinmemory.WecandothisbymountingthefolderusingTMPFS:

mounttmpfs/var/www/magento2/var/cache-ttmpfs-osize-64m

Howitworks…ZendOPcacheisanopcodecachingmechanism.ThiswillcachePHPfilessothattheydon’thavetoloadfromthediskeverytimetheyarecalled.ZendOPcacheisthereplacementofAPC,whichisavailableinolderversionsofPHP.

APCisdeprecatedinPHP5.4andisnotavailablefromPHP5.5.Toreplacethis,ZendOPcacheisthetoolthatwehavetouse.That’salsothereasonwhyitisstandardenabledwhenyouinstallPHP5.5orhigher.

ThesecondthingthatweconfiguredisRedis.Redisisacachingtoolinwhichwecancacheobjects.ItisareplacementofthedefaultfilecachingofMagento.TheadvantageofRedisisthatitusescachetags.Whenyouhavealotofcachefiles,afilecachingmechanismwillopeneveryfiletoseethatitisrelevant.WithRedis,everycacheobjectistagged,soitcanbeopenedquicklywhenthereisalotofcache.

Thisisalsothereasonthatafilecachingsystembecomesslowwhenthereisalotofcache.Thecachingmechanismwillbecomeslowbecauseithastoopenlotsoffiles.

Atlast,weusedMemcachedtostorethesessionsin.MemcachedisasystemsimilartoRedis.Memcacheddoesn’tusecachetags,sointheory,itisslowerthanRedis,butpractically,youhavetoseewhichofthesetwosystemsisthebestforyou.

OptimizingthePHPconfigurationsInthepreviousrecipe,weexplainedhowsomecachingsystemsofPHPwork.SincePHP5.5,wehaveZendOPcachethatisusedastheopcodecache.

Inthisrecipe,wewilltunesomePHPsettingstooptimizethisforMagento.

GettingreadyWewillmakesomechangesinthephp.inifile,soensurethatyouhaveaccesstoit.

Howtodoit…Inthefollowingsteps,wegivesometipstooptimizePHP:

1. Atthismoment,PHP7isinBetaversion.Butwhenitisstable,itisrecommendedthatyouusethisversionbecausethisismuchfasterthanPHP5.x.

2. AlwaysusethelateststablePHPversionbecausethisismoresecureandfast.3. TryrunningPHPwithanefficientprocessmanagersuchasphp-fpmthatrunsonan

impressivespeedwithFastCGI.4. Usetherealpath_cache_sizeconfigurationsettingtoconfigurethesizeofthereal

pathcacheinPHP.OnsystemswherePHPopensandclosesalotoffiles,thisvalueneedstobeincreased.YoucanusethefollowingsettingforMagento:

realpath_cache_size=1M

realpath_cache_ttl=86400

5. ThefollowingsettingscanimprovetheperformanceofPHP:

Setting Description Recommendedvalue

max_execution_timeThissettingsetsthemaximumtime(inseconds)thataprocesscanexecute. 120

max_input_timeWiththisproperty,wesetthetime(inseconds).Ascriptwillwaitforinputdata. 240

memory_limitThissettingsetstheamountofmemorythataprocesscanuse.

ForMagento,itisrecommendedtouse768MB

output_bufferingWiththissetting,youcansettheamountofbytestobufferbeforesendingtheresponsetotheclient. 4096

6. EnsurethatXdebugisnotenabledonaproductionenvironment.7. Finally,wecandisablesomeerrorreportinglevelswhenyoursiteislive.Thiscanbe

configuredwiththefollowingsetting:

error_reporting=E_COMPILE_ERROR|E_ERROR|E_CORE_ERROR

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

DisablingXdebugwillgiveabetterperformancebecausenodebugdatawillbeprocessed.

Disablingtheerrorreportingonaproductionsystemisrecommendedforwarningsandnoticesbutcriticalerrorsneedstobereportedbecauseyouneedthisinformationwhenyouwanttosolveapossiblebug.

Chapter11.DebuggingandUnitTestingInthischapter,wewillcoverthefollowingrecipes:

LoggingintoMagento2GettingstartedwithXdebugRunningautomatedtestsfromMagentoCreatingaMagentotestcase

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

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

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

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

IfyouwanttodoasimpledebuggingtrickwithoutchangingtheHTMLoutputofapage,youcanusetheMagentologging.Thiswillwritethedebuggedresultstoalogfile.

GettingreadyWewillprintsomedatatotheMagentologfiles.Toeasilyviewthecontentofthesefiles,weneedcommandlineaccess.Also,openyourIDEbecausewewilladdsomeloggingstatementsintheMagentocode.

Howtodoit…PerformthefollowingstepstodescribehowwecanuselogginginMagento2:

1. Ifwewanttodebugsomedataonacategorypage,wecanusetheloggerinterfacetowritesomethingtoafile.Openthecategorypagecontroller,whichisinthefollowingfile:app/code/Magento/Catalog/Controller/Category/View.php.

NoteIfyouinstalledMagentowiththecomposer,youwillhavetoeditthevendor/magento/module-catalog/Controller/Category/View.phpfile.

2. Inthatfile,wehavetoaddthefollowinghighlightedcodetothe_initCategory()function:

try{

$this->_eventManager->dispatch(

'catalog_controller_category_init_after',

['category'=>$category,'controller_action'=>$this]

);

}

catch(\Magento\Framework\Exception\LocalizedException$e){

$this->_objectManager->get('Psr\Log\LoggerInterface')->critical($e);

returnfalse;

}

$this->_objectManager->get('Psr\Log\LoggerInterface')->debug(

print_r($category->debug(),true)

);

return$category;

3. Weneedtoaddthehighlightedcodebeforethereturnstatementofthatcode.4. Openacategorypageandthedataofthecategorypagewillbewrittentothe

var/log/debug.loglogfile.

Withthefollowingcommand,wecanfollowthenewlinesthatareaddedtothelogfile:

tail-fvar/log/debug.log

NoteMakesurethatthefullpagecachingisnotenabled.Ifitisenabled,thecachewillskiptheexecutionoftheloggingstatement.

5. Toexitthismode,wecanusetheshortcutCrtl+Cintheterminal.

Howitworks…InMagento1,wecouldusetheMage::log()functionthatwrotemessagestothelogfiles.InMagento2,theMageclassisgoneandalogginginterfaceiscreatedtodotheMagentologging.

TheloggerinterfaceisthePsr\Log\LoggerInterfaceinstance.Withthisinterface,wecancallthefollowingfunctionstowritedatatologfiles:

alert()

critical()

debug()

emergency()

error()

info()

log()

notice()

warning()

Whenyoulogdatatofiles,youcanusethefunctionthatfitstoyourlogmessage.Inthisrecipe,weusedthedebug()functiontologsomedebugdata.Ifyouwanttologthemessageofanerror,youcanusetheerror()function.

Theloggingmethodonlyacceptsstringvariables,soitisnotpossibletopassarraysorobjectstothismethod.Tofixthisissue,weusedtheprint_r()functiontoconvertthecontentofthearraytoastring.

Weloggedthedataofacategory,butthecategoryisanobject.Ifyouapplyaprint_r()methodtoanobject,youwillgetahugeoutput,whichisnoteasytoread.Tosolvethis,wecanusethedebug()methodonMagentoentities.Thismethodwillconvertthedataoftheobjecttoareadablearray.

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

InPHP,thereistheXdebugextensionthatenablesyoutouseadebuggerincombinationwithanIDE.Inthisrecipe,wewillseehowtoinstallXdebugandintegrateitwiththeIDENetBeans.

GettingreadyInthisrecipe,wewillstartanXdebugsessionwiththeNetBeansIDE.OpenNetBeansandsettheMagentoProjectasMainProject.EnsurethatalltheURLsareconfiguredcorrectlyinthePropertysettingsoftheproject.

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

sudoapt-getinstallphp5-dev

sudoapt-getinstallphp-pear

Howtodoit…ThefollowingstepsdescribehowyoucaninstallXdebugonyourdevelopmentserver:

1. First,wewillinstallthexdebuglibrary.Youcandothisusingthefollowingcommand:

sudopeclinstallxdebug

Thiscommandwillgivethefollowingoutput:

2. Asyoucanreadinthescreenshot,wehavetolocatethexdebug.sofileinthephp.inifile.Tofindthepathofthexdebug.sofile,wecanusethefollowingcommand:

find/-name"xdebug.so"

3. Whenweknowthepath,wehavetoaddthefollowinglineinthephp.inifiles.OnanUbuntuserver,wehavetoaddthesameconfigurationtothefollowingfiles:

/etc/php5/apache2/php.ini

/etc/php5/cli/php.ini

4. Inthesefiles,addthefollowinglineattheendofthefile.Ensurethatthepathtothexdebug.sofilematchestheoneonyourserver:

zend_extension="/usr/lib/php5/20121212/xdebug.so"

5. Restarttheapacheserverusingthefollowingcommand:

sudoserviceapache2restart

6. WhenwewanttotestthatXdebugiscorrectlyinstalled,wecancheckthisintwoways:

ThefirstmethodistocreateaPHPscriptandcallthephpinfo()function.Whenyouopenthisscriptinthebrowser,youwillseeallthePHPsettingsthatareactiveinthatsession.Thesecondmethodistousethecommandphp-i.Thisgivesthesameinformationasphpinfo()butnowforphpovercli.

7. Whenwerunthephp-i|grepxdebugcommand,wewillseethefollowingoutput.ThesearetheXdebugsettings:

xdebug

xdebugsupport=>enabled

xdebug.auto_trace=>Off=>Off

xdebug.cli_color=>0=>0

xdebug.collect_assignments=>Off=>Off

xdebug.collect_includes=>On=>On

xdebug.collect_params=>0=>0

xdebug.collect_return=>Off=>Off

xdebug.collect_vars=>Off=>Off

xdebug.coverage_enable=>On=>On

xdebug.default_enable=>On=>On

xdebug.dump.COOKIE=>novalue=>novalue

xdebug.dump.ENV=>novalue=>novalue

xdebug.dump.FILES=>novalue=>novalue

xdebug.dump.GET=>novalue=>novalue

xdebug.dump.POST=>novalue=>novalue

xdebug.dump.REQUEST=>novalue=>novalue

xdebug.dump.SERVER=>novalue=>novalue

xdebug.dump.SESSION=>novalue=>novalue

xdebug.dump_globals=>On=>On

xdebug.dump_once=>On=>On

xdebug.dump_undefined=>Off=>Off

xdebug.extended_info=>On=>On

xdebug.file_link_format=>novalue=>novalue

xdebug.force_display_errors=>Off=>Off

xdebug.force_error_reporting=>0=>0

xdebug.halt_level=>0=>0

xdebug.idekey=>novalue=>novalue

xdebug.max_nesting_level=>256=>256

xdebug.max_stack_frames=>-1=>-1

xdebug.overload_var_dump=>On=>On

xdebug.profiler_aggregate=>Off=>Off

xdebug.profiler_append=>Off=>Off

xdebug.profiler_enable=>Off=>Off

xdebug.profiler_enable_trigger=>Off=>Off

xdebug.profiler_enable_trigger_value=>novalue=>novalue

xdebug.profiler_output_dir=>/tmp=>/tmp

xdebug.profiler_output_name=>cachegrind.out.%p=>cachegrind.out.%p

xdebug.remote_autostart=>Off=>Off

xdebug.remote_connect_back=>Off=>Off

xdebug.remote_cookie_expire_time=>3600=>3600

xdebug.remote_enable=>Off=>Off

xdebug.remote_handler=>dbgp=>dbgp

xdebug.remote_host=>localhost=>localhost

xdebug.remote_log=>novalue=>novalue

xdebug.remote_mode=>req=>req

xdebug.remote_port=>9000=>9000

xdebug.scream=>Off=>Off

xdebug.show_exception_trace=>Off=>Off

xdebug.show_local_vars=>Off=>Off

xdebug.show_mem_delta=>Off=>Off

xdebug.trace_enable_trigger=>Off=>Off

xdebug.trace_enable_trigger_value=>novalue=>novalue

xdebug.trace_format=>0=>0

xdebug.trace_options=>0=>0

xdebug.trace_output_dir=>/tmp=>/tmp

xdebug.trace_output_name=>trace.%c=>trace.%c

xdebug.var_display_max_children=>128=>128

xdebug.var_display_max_data=>512=>512

xdebug.var_display_max_depth=>3=>3

8. ThenextstepistoconfiguretheintegrationbetweenNetBeansandXdebug.Toconfiguretheintegration,wehavetoaddthefollowingconfigurationattheendofthephp.inifiles:

xdebug.remote_enable=1

xdebug.remote_handler=dbgp

xdebug.remote_mode=req

xdebug.remote_host=localhost

xdebug.remote_port=9000

xdebug.idekey="netbeans-xdebug"

9. RestartyourApacheserverandlookatthephpinfo()pagetocheckwhethertheXdebugsettingshavebeenapplied.

10. Next,wehavetoconfigureNetBeansforXdebug.InNetBeans,navigatetoTools|Optionsandconfigureitasshowninthefollowingscreenshot:

11. Inthepreviousstep,weconfiguredtheglobalNetBeanssettingsforXdebug.Inthenextstep,wewillconfiguretheproject-specificsettings.OpentheProjectPropertiesoption(byright-clickingonproject)andopentheRunConfigurationtab.EnsurethattheProjectURLfieldhasthecorrectvalue,asshowninthefollowingscreenshot:

12. Wearenowreadytostartthefirstdebugsession.Tostartit,wehavetoclickonthedebugbutton,whichisneartheRunbutton.WecanalsousetheshortcutCrtl+F5.

13. Whenstartingthedebugsession,yourbrowserwillbeopenedwithapagethathasthefollowingURL:

http://magento2.local/index.php?XDEBUG_SESSION_START=netbeans-xdebug.

14. Thewebpagedoesn’tloadbecausethedebuggerisinterruptingtheprocess.Tocontinue,wehavetousethedebuggercontrolsinNetBeans.

15. Addabreakpointintheindex.phpfileonthefollowingline:

$bootstrap=\Magento\Framework\App\Bootstrap::create(BP,$_SERVER);

16. Oncontinuingwiththedebugger,youwillseetheactualvaluesofthevariablesasshowninthefollowingscreenshot:

17. Whenyoucontinuethedebuggeratthebreakpoint,youwillseethatthepagewillbeloaded.

18. ThedebugsessionsstaysaliveuntilyouhitthestopbuttoninNetBeans.Whenyoubrowsetootherpagesonyourwebsite,thedebuggerwillcontinueaslongasthesessionisalive.

19. Tostopthedebugsession,clickontheStopbuttoninNetBeans.

Howitworks…WeneedtoinstallXdebugontheserverwherewewanttodebugasiteon.Inthisrecipe,itwillbetheserveronwhichourMagentoinstancewillrun.

TheinstallationoftheXdebugextensionisdonebyPEAR.PEARisanapplicationrepositoryforthePHPplugins.WithPEAR,wedownloadedandinstalledthexdebuglibrary.

WhenXdebugwasinstalledontheserver,weconfiguredthephp.inifiletousethexdebuglibrary.WeaddedsomesettingstomaketheXdebugconfigurationcompatiblewithNetBeans.

TipWhenusingXdebugonaremoteserver,ensurethatyoucanconnecttotheservertroughport9000.ThisismostlydisabledonthefirewalloftheserverandyourlocalPC.

Whentheserverwascorrectlyconfigured,wecheckedtheconfigurationsinNetBeansandwestartedthedebugsession.Whenthissessionwasstarted,wewereabletodebugtheMagentoapplicationlikeadebuggerdoesit.

Thedebuggerenablesustousethefollowingadvanceddebuggerfeatures,amongothers:

SettingbreakpointsExecutingthecodestatementbystatementBrowsingandchangingvaluesofvariables

RunningautomatedtestsfromMagentoWhenyoubuildsomefunctionality,youhavetotestthatthefunctionalityworkslikeyouwouldexpectit.Testingisusuallydoneattheendofyourprojectandthiscanbeautomated.

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

AnewadditiontoMagento2isthatunittestsareautomaticallyincludedinthecore.WhenyouwanttocontributetotheMagentocorewithGitHub,itisrequiredthatyourchangespassthroughtheunitandintegrationtests.

GettingreadyForrunningtheunittests,weneedthecommand-linetoolofMagento.Ensurethatyouhaveaccesstoit.

Howtodoit…Inthefollowingsteps,wedescribehowwecanrunautomatedtestsfromMagento:

1. First,weensurethattheMagento_Developermoduleisenabled.Wecancheckthiswiththephpbin/magentomodule:statuscommand.Thiswilloutputalist,andtheMagento_Developermoduleneedstobeinthislistofenabledmodules.

2. Whenthemoduleisdisabled,wecanenableitusingthefollowingcommand:

phpbin/magentomodule:enableMagento_Developer

3. WiththeMagentocommand-linetool,wecanstarttheexecutionofalltestsintheinstallation.Withthefollowingcommand,wecanseetheavailableoptions:

phpbin/magentodev:tests:run--help

4. Ifwelookattheoutputofthatcommand,weknowthatthecommandwillexecutealltestswithoutaparameter.Ifwewanttorunonlytheunittests,wecanusethefollowingcommand:

phpbin/magentodev:tests:rununit

Thiscommandwillgivethefollowingoutputwhenitisrunning:

ThisistheresultoftheexecutionofalltheunitteststhatareavailableinMagentoandthistakesquitealotoftime.Whenwe’redevelopingafunction,weonlywanttorunoneparticulartest.Forthis,weneedsomeextraconfiguration.

5. Torunspecifictests,wehavetocreatethefollowingfile:dev/tests/unit/phpunit.xml.

6. Inthatfile,addthefollowingcontent:

<?xmlversion="1.0"encoding="UTF-8"?>

<phpunitxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd

"

colors="true"

bootstrap="./framework/bootstrap.php"

>

<testsuitename="SpecificMagento2unittests">

<directory

suffix="Test.php">../../../app/code/Magento/Catalog/Test/Unit</director

y>

<directory

suffix="Test.php">../../../app/code/Magento/Cms/Test/Unit</directory>

</testsuite>

<php>

<ininame="date.timezone"value="America/Los_Angeles"/>

<ininame="xdebug.max_nesting_level"value="200"/>

</php>

<logging>

<logtype="testdox-html"target="./test-reports/testdox.html"/>

<logtype="testdox-text"target="./test-reports/testdox.txt"/>

</logging>

</phpunit>

NoteIfyouinstalledMagentowiththecomposer,youhavetoensurethatthepathsinthedirectoryaregoingtotherightpath.Withcomposer,youhavetolookfortheMagentomodulesinthevendor/magento/folder.

7. Withthepreviousconfiguration,wewillruntheteststhatareavailableinthesefolders:

app/code/Magento/Catalog/Test/Unit

app/code/Magento/Cms/Test/Unit

8. Torunthetests,wehavetochangethecommand-lineprompttothedev/tests/unitfolder.WecandothisbyrunningthefollowingcommandinourMagentoroot:

cddev/tests/unit/

9. Whenweareinthatfolder,wecanrunthefollowingcommandtostarttheunittests:

../../../vendor/bin/phpunit

10. Whenthiscommandhasfinishedexecuting,weseethatsometestsareskippedbutthefullnamesofthosetestsarenotdisplayed.Togetsomemoreinformationaboutunsuccessfultests,wecanusethefollowingcommand,whichshowsusmoreoutput:

../../../vendor/bin/phpunit--verbose

11. Whenrunningthiscommand,wegetthefollowingoutput:

12. Whenwelookinthetest-reportsfolder,weseethatthefollowinglogfilesaregenerated:

testdox.html

testdox.txt

Howitworks…InMagento2,wecanfindtheunittestsinthecodefilesofMagento.WhenwelookintheTestdirectoryofeachmodule,wewillfindthefilesthatwillbeusedwhenrunningautomatedtests.

Thedev:tests:runcommandintheMagentoconsoleisusedtorunallthetestsintheMagentoapplication.Normally,ifyouhavechangedsomething,youhavetotestthewholeapplicationbecauseasmallchangecanbreaktestsinplacesthatyoudonotexpect.

TheconsoletoolusesPHPUnittoruntheunittests.ThistoolisaPHPexecutableandisdeliveredwithMagento.Thephpunitexecutableisavailableinthevendor/binfolder.

Whenrunningthephpunitexecutable,thiswillrununitteststhatareconfiguredinaphpunit.xmlfile.Wecreatedthisfileinthedev/tests/unitfolder.Aphpunit.xml.distfileisavailableinthisfolder,whichcontainsanexampleconfiguration.

Inthisconfigurationfile,wespecifiedthefollowingthings:

ThepathofthebootstrapparameterThefoldersoftheteststhatneedstobeexecutedSomephp.inisettingsThepathoflogfiles

Thebootstrapparameterreferstotheframework/bootstrap.phpfile.ThisfileinitializestheMagentoapplicationbycallingthenecessaryfilesandfunctions.

Whenrunningthephpunittests,wegetanoutputwithdots.AdotmeansthatthetestresultisOK.Sometimes,adotisreplacedwithanSorI.AnSmeansthatatestisskippedandanImeansthatatestisinvalid.Withthe--verboseoption,wecangetmoreinformationonwhyitisinvalidorskipped.

CreatingaMagentotestcaseAsthisisthelastchapterofthisbook,wewillwriteatestthatwecanexecutewiththeMagentoTestingFramework.ThisframeworkusesPHPUnittoexecutethetests.

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

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

Ifyoudon’thavethecompletecode,youcaninstallthestarterfilesforthisrecipe.

Howtodoit…Usingthefollowingsteps,wewillcreateasimpleunittestforMagento:

1. Foraunittest,wehavetocreatethefollowingfolders:

app/code/Packt/HelloWorld/Test/

app/code/Packt/HelloWorld/Test/Unit/

app/code/Packt/HelloWorld/Test/Unit/Block/

app/code/Packt/HelloWorld/Test/Unit/Block/Adminhtml/

app/code/Packt/HelloWorld/Test/Unit/Block/Adminhtml/Subscription/

2. Inthelastfolder,createafilecalledGridTest.php.3. Inthisfile,addthefollowingcontent:

<?php

namespacePackt\HelloWorld\Test\Unit\Block\Adminhtml\Subscription;

classGridTestextends\PHPUnit_Framework_TestCase{

/**

*@var\Packt\HelloWorld\Block\Adminhtml\Subscription\Grid

*/

protected$block;

protectedfunctionsetUp(){

}

protectedfunctiontearDown(){

}

publicfunctiontestDecorateStatus(){

}

}

4. WehavenowcreatedatestclassthatisresponsibletotestthePackt\HelloWorld\Block\Adminhtml\Subscription\Gridclass.Torunthistests,wehavetocreateaphpunit.xmlfileinthedev/tests/unitfolderwiththefollowingcontent:

<?xmlversion="1.0"encoding="UTF-8"?>

<phpunitxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd

"

colors="true"

bootstrap="./framework/bootstrap.php"

>

<testsuitename="PacktHelloWorldmoduletest">

<directory

suffix="Test.php">../../../app/code/Packt/HelloWorld/Test/Unit</directo

ry>

</testsuite>

<php>

<ininame="date.timezone"value="America/Los_Angeles"/>

<ininame="xdebug.max_nesting_level"value="200"/>

</php>

<logging>

<logtype="testdox-html"target="./test-reports/testdox.html"/>

<logtype="testdox-text"target="./test-reports/testdox.txt"/>

</logging>

</phpunit>

5. Torunthetests,wehavetoopentheterminalandopenthedev/tests/unitfolder.WhenyouareintheMagentoroot,youcandothisbyrunningthefollowingcommand:

cddev/tests/unit

6. Inthisfolder,runthefollowingcommandtorunthetests:

../../../vendor/bin/phpunit--verbose

Thetestswillpassasyoucanseeinthefollowingscreenshot:

7. Thetestswillpassbecausethetestclassisempty.IfwewanttotestthedecorateStatus()method,wehavetoinitializetheblockinthesetUp()methodoftheGridTestclass.AddthehighlightedcodetothesetUp()method:

protectedfunctionsetUp(){

$objectManager=new

\Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);

$this->block=$objectManager->getObject(

'Packt\HelloWorld\Block\Adminhtml\Subscription\Grid'

);

}

8. Wealsohavetodestructtheblockwhenthetestsarefinished.WecandothisbyaddingthehighlightedcodetothetearDown()method:

protectedfunctiontearDown(){

$this->block=null;

}

9. Wecannowstartwritingourtest.Forthis,wehavetoknowwhattotest.IfwelookatthedecorateStatus()methodofthePackt\HelloWorld\Block\Adminhtml\Subscription\Gridclass,weseethataspecificHTMLoutputisreturnedbasedontheinputvariable.Totesttheoutputoftheinputvalues,wehavetoaddthehighlightedcodeinthetestDecorateStatus()method:

publicfunctiontestDecorateStatus(){

$this->assertContains('grid-severity-minor',$this->block-

>decorateStatus('pending'));

$this->assertContains('grid-severity-notice',$this->block-

>decorateStatus('approved'));

$this->assertContains('grid-severity-critical',$this->block-

>decorateStatus('declined'));

$this->assertContains('grid-severity-critical',$this->block-

>decorateStatus(6));

$this->assertContains('grid-severity-critical',$this->block-

>decorateStatus(null));

}

10. Runthetestsagainusingthephpunit--verbosecommand.Youwillseethatthetestswillpass(1test,5assertions).

11. WhenyouaddthefollowingassertsinthetestDecorateStatus()methodandrunthetestagain,youwillseethatitfails:

$this->assertContains('grid-severity-minor',$this->block-

>decorateStatus('approved'));

$this->assertNull($this->block->decorateStatus(null));

Howitworks…Tocreateunittests,wehavetousePHPUnit.Wecreatedaphpunit.xmlfilewhereweconfiguredthattheapp/code/Packt/HelloWorld/Test/Unitfolderisthefolderthatcontainstheunittests.

InMagento2,everyclasshasitsowntestclass.WecreatedatestclassforthePackt\HelloWorld\Block\Adminhtml\Subscription\Gridclass.Inthisclass,thereisadecorateStatus()methodthatwewilltest.

WecreatedaGridTestclassinthesamefolderstructureasitisintheoriginalmodule.ThefolderstructurewillcomebackintheTests/Unitfolder.EveryclassissuffixedwiththewordTest.

Everytestmethodinaclassstartswithtestfollowedbythenameofthemethodthatwillbetested.ForthedecorateStatus()function,thiswillbetestDecorateStatus().

AtestclassextendsfromthePHPUnit_Framework_TestCaseclass.ThisclasscontainstheframeworkthatwillexecutethetestsusingPHPUnit.

ThesetUp()methodiscalledbeforethetestsareexecuted.Itislikeaconstructorinanormalclass.Inthisclass,weinitializetheoriginalBlockclassthatwewilluseinthetestmethod.

ThetearDown()methodiscalledaftertheexecutionofthetests.Itworkslikeadestructor,andnormally,wesetalltheclassvariablestonullinthismethod.

Aunittestwillalwayspasswhenthecodeinatestfunctionisempty.WecreatedthetestDecorateStatus()methodwhereweaddedsomeassertionmethods.Inthisrecipe,weusedtheassertContains()method.Whenthismethodisused,thetestwillpassifthevalueofthesecondparameterwillcontainthevariableofthefirstparameter.

WeusedthismethodtotestdifferentinputvariablesofthedecorateStatus()method.Inthelaststep,weusedaassertNull()method.ThetestsfailedbecausetheoutputofthedecorateStatus()methodwasnotnullforthegiveninput.

Inlargeprojectswhereunittestsareused,mostlythetestsarewritteninthearchitecturalphaseofaproject.Aunittestcanbeusedasaspecificationofwhatamethodneedtodowhenitiscalled.Howwehavetohandleinvalidinputvariablesandothersuchthingsarespecifiedinaunittest.

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

AfulllistofassertionmethodscanbefoundatthefollowingURL,whichreferstotheoriginaldocumentationofPHPUnit:

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

IndexA

AccessControlList(ACL)adding/AddinganACL,Howtodoit…,Howitworks…

adaptermodelwriting/Writinganadaptermodel,Howtodoit…,Howitworks…

Apacheabout/OptimizingtheApachewebserver

ApacheBenchabout/Benchmarkingawebsite,Gettingready

Apachewebserveroptimizing/OptimizingtheApachewebserver,Howtodoit…,Howitworks…

app.telemetryabout/GettingreadyURL/Gettingready

app.telemetrypluginRedirectparameter/Howitworks…AppCacheparameter/Howitworks…DNSLookupparameter/Howitworks…TCPConnectionparameter/Howitworks…TCPRequestparameter/Howitworks…TCPResponseparameter/Howitworks…Processingparameter/Howitworks…Onloadeventparameter/Howitworks…

attributesetsworkingwith/Workingwithattributesets,Howtodoit,Howitworksabout/Howitworks

automatedtestsrunning,fromMagento/RunningautomatedtestsfromMagento,Howtodoit…,Howitworks…

Bbackendcomponents

workingwith/Workingwithbackendcomponents,Howtodoit…,Howitworks…

backendcontrollerregistering/Registeringabackendcontroller,Gettingready,Howtodoit…,Howitworks…

Blankthemeabout/ExploringthedefaultMagento2themes

blockfilecreating/Creatingtheblockandtemplatefiles,Howtodoit…,Howitworks…

bundleproductabout/Abundleproduct

Ccatalogdefaults

configuring/Configuringthecatalogdefaults,Howtodoit,Howitworkscollections

about/WorkingwithMagentocollectionsworkingwith/WorkingwithMagentocollections,Howtodoit…,Howitworks…

CommandLineInterface(CLI)about/Howitworks…

configurableproductabout/Aconfigurableproduct

configurationparametersadding/Addingconfigurationparameters,Howtodoit…,Howitworks…

consolecommandadding/Addingaconsolecommand,Howtodoit…

controllercreating/Creatingacontroller,Gettingready,Howtodoit…,Howitworks…,There’smore…

cronjobabout/Introduction,Introducingcronjobsconfiguring/Howtodoit…,Howitworks…creating/Creatingandtestinganewcronjob,Howtodoit…,Howitworks…testing/Creatingandtestinganewcronjob,Howtodoit…,Howitworks…

customconfigurationparametercreating/Creatingacustomconfigurationparameter,Howtodoit…,Howitworks…

customerattributesadding/Addingcustomerattributes,Howtodoit…,Howitworks…

customeventcreating/Creatingyourownevent,Howtodoit…,Howitworks…

customthemecreating/CreatingaMagento2theme,Howtodoit…,There’smore…

Ddatabase

upgrading/Upgradingthedatabase,Howtodoit…,Howitworks…,There’smore…repairing/Repairingthedatabase,Howtodoit…,Howitworks…optimizing/OptimizingthedatabaseandMySQLconfigurations,Howtodoit…,Howitworks…

databasetablecreating,withmodels/Creatingaflattablewithmodels,Howtodoit…,Howitworks…grid,creating/Creatingagridofadatabasetable,Howtodoit…,Howitworks…

dependencyinjectionabout/AddinganinterceptorURL/Seealso

downloadableproductabout/Adownloadableproduct

Eemailtemplates

customizing/Customizingemailtemplates,Howtodoit…emptymodule

creating/Creatinganemptymodule,Howtodoit…,Howitworks…EntityAttributeValuesystem(EAV)

about/Howitworks…eventobserver

adding/Addinganeventobserver,Howtodoit…,Howitworks…events

URL/Seealsoeventtypes

about/Understandingeventtypes,Howtodoit…,Howitworks…

FFacebook

URL/Gettingreadyfallbackmechanism

about/Howitworks…filepermissions

URL/Howtodoit…FullPageCaching

about/Howtodoit…

GGitHub

about/RunningautomatedtestsfromMagentoGooglePlus

URL/Gettingreadygrid

creating,ofdatabasetable/Creatingagridofadatabasetable,Howtodoit…,Howitworks…

groupedproductabout/Agroupedproduct

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

GTmetrixserverabout/There’smore…

HHTMLobject

embedding/EmbeddinganHTMLobject,Howtodoit,HowitworksHTMLoutput

customizing/CustomizingtheHTMLoutput,Howtodoit…,Howitworks…

IIDENetBeans

about/GettingstartedwithXdebuginstallscript

creating/Creatinganinstallandupgradescript,Howtodoit…,Howitworks…

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

interceptoradding/Addinganinterceptor,Howtodoit…,Howitworks…

JjQuerysliderscript

about/Introduction

Llayoutupdates

adding/Addinglayoutupdates,Howtodoit…,Howitworks…LESS

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

Lumathemeabout/ExploringthedefaultMagento2themes

MMagento

about/IntroductionURL/GettingreadyURL,forrulesets/Howitworks…performanceleaks,discovering/FindingperformanceleaksinMagento,Howtodoit…,Howitworks…automatedtests,running/RunningautomatedtestsfromMagento,Howtodoit…,Howitworks…

Magento1upgrade,preparing/PreparinganupgradefromMagento1,Howtodoit…,Howitworks…

Magento1websitecreating,withsampledata/CreatingaMagento1websitewithsampledata,Howtodoit…,Howitworks…

Magento2about/Introductiondefaultthemes,exploring/ExploringthedefaultMagento2themes,Howtodoit…,Howitworks…theme,creating/CreatingaMagento2theme,Howtodoit…,There’smore…loggingin/LoggingintoMagento2,Howtodoit…,Howitworks…

Magento2websitecreating/CreatingaMagento2website,Howtodoit…,Howitworks…,There’smore…

MagentoMigrationWhitepaperURL/Seealso

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

menuextending/Extendingthemenu,Howtodoit…,Howitworks…

MigrationtoolURL/Gettingready

modelsdatabasetable,creatingwith/Creatingaflattablewithmodels,Howtodoit…,Howitworks…

moduleadding,infrontend/Addingthemoduleinthefrontend,Howtodoit…,Howitworks…

moduleconfigurationsinitializing/Initializingmoduleconfigurations,Howtodoit…,Howitworks…

modulefiles

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

about/Howtodoit…MyISAMtables

about/Howtodoit…MySQL

configurations,optimizing/OptimizingthedatabaseandMySQLconfigurations,Howtodoit…,Howitworks…

NNetBeans

about/UsinganIDEURL/Gettingready

Nginxabout/OptimizingtheApachewebserver

OOPcache

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

PPageSpeed

about/There’smore…pagetitle

modifying/Changingapagetitle,Howtodoit…PHP,settings

max_execution_time/Howtodoit…max_input_time/Howtodoit…memory_limit/Howtodoit…output_buffering/Howtodoit…

PHPconfigurationsoptimizing/OptimizingthePHPconfigurations,Howitworks…

phpcsmdpluginabout/There’smore…URL/There’smore…

PHPMessDetector(PHPMD)code,writingwith/WritingcleancodewithPHPMDandPHPCS,Howtodoit…,Howitworks…,There’smore…

phpMyAdminabout/Gettingready

PHPStormabout/There’smore…URL/There’smore…

PHPUnitabout/Howitworks…assertionmethods,URL/There’smore…

PHP_CodeSniffer(PHPCS)code,writingwith/Gettingready,Howtodoit…,Howitworks…,There’smore…URL/Howtodoit…

productattributesabout/Howitworksadding,programmatically/Programmaticallyaddingproductattributes,Howtodoit…,Howitworks…

productpageURL,modifying/ChangingtheURLofaproductpage,Howitworks,There’smore

productsadding/Addingablockofnewproducts,Howtodoit…,Howitworks…

producttemplatesabout/Workingwithattributesets

producttypesworkingwith/Workingwithproducttypes,Howtodoit,Howitworks…

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

RRedis

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

RequireJSlibraryabout/Howtodoit…

routingabout/Howitworks

Sshippingmethod

additionalfeatures,adding/Extendingtheshippingmethodfeatures,Howtodoit…,Howitworks…

Siegeabout/Benchmarkingawebsite,Gettingready

simpleproductabout/Asimpleproduct

slickURL/Gettingready,Howtodoit…

socialmediabuttonsadding/Addingsocialmediabuttons,Howtodoit,Howitworks

sourcemodelsworkingwith/Workingwithsourcemodels,Howtodoit…,Howitworks…

Symfonyconsoleabout/Seealso…URL/Seealso…

TTabSeparatedValue(TSV)file

about/Howtodoit…templatefile

creating/Creatingtheblockandtemplatefiles,Howtodoit…,Howitworks…testcase

creating/CreatingaMagentotestcase,Howtodoit…,Howitworks…,There’smore…

themesexploring,inMagento2/ExploringthedefaultMagento2themes,Howtodoit…,Howitworks…extrafiles,adding/Addingextrafilestothetheme,Howtodoit…,Howitworks…,There’smore…

themingfinalizing/Finalizingthetheming,Howtodoit…,Howitworks…

translationfileadding/Addingatranslationfile,Gettingready,Howitworks…

translationsworkingwith/Workingwithtranslations,Howtodoit…,Howitworks…

TwitterURL/Gettingready

Uupgradescript

creating/Creatinganinstallandupgradescript,Howtodoit…,Howitworks…

URLmodifying,ofproductpage/ChangingtheURLofaproductpage,Howitworks,There’smore

Vvirtualproduct

about/Avirtualproduct

Wwebserver,performancefactors

application/Howitworks…hardware/Howitworks…operatingsystem/Howitworks…network/Howitworks…

websitebenchmarking/Benchmarkingawebsite,Gettingready,Howtodoit…,Howitworks…frontend,optimizing/Optimizingthefrontendofthewebsite,Howitworks…,Howitworks…,There’smore…

widgetconfigurationfilecreating/Creatingawidgetconfigurationfile,Howtodoit…,Howitworks…

widgetsadding,tolayout/Addingwidgetstothelayout,Howtodoit…

XXdebug

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

XMLStyleDefinition(XSD)filesabout/Howtodoit…

YYSlow

about/Gettingready,There’smore…URL/Gettingready

ZZendOPcache

about/ConfiguringOPcache,Redis,andMemcached

top related