0% found this document useful (0 votes)
316 views251 pages

Professional Developer Final

Uploaded by

minhtruong.hvct
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
316 views251 pages

Professional Developer Final

Uploaded by

minhtruong.hvct
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 251

INTRODUCTION

You made a great decision by purchasing the most comprehensive study guide
for the Magento 2 Certified Professional Developer Plus certification (testing
your knowledge of Magento Commerce 2.2)!

Our goal is that this guide serves two purposes:


1. For those who are new to Magento development, this guide will give
concrete information on getting started. This is not a “read and you will pass”
guide but rather a launching point for obtaining the experience necessary to
pass. The paid version contains nine chapters and many illustrations on how
to complete tasks. Each illustration has been stripped back to the minimum
allowing you to easily build and extend.

2. For those who are experienced and are ready to go for this certification,
this will be both a refresher on subjects you haven’t worked on lately and
will point out some new areas that you haven’t utilized yet (message
queueing anyone?).

The study guide answers the questions presented in Magento’s study guide
for the Professional Developer Plus test. Just reading this study guide is part of
solid preparation for passing the test but does not guarantee a passing grade.
That is why we have included a "Practical Experience" section on most points.
This will guide you to specific launching points where you can dive in and learn.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 2


Introduction

Requirements
Please understand that the code examples are designed to work with Magento
Commerce 2.2. If you do not have access to Magento Commerce code, you
will run into parts of the code that do not function properly. In addition, you
might pass the test but will have a very difficult time with the staging and
Commerce questions.

Acknowledgments
I would like to extend heartfelt gratitude to the following individuals who
contributed by edits or comments to the development of the study guide.
These people graciously donated their time to help others out. If you see them
at a conference or on Twitter, please thank them.

• Andreas von Studnitz (Integer Net)

• Joshua Romero (MedioType)

• Navarr Barnier (MedioType)

• Artem Klimov

• Itonomy Commerce

• Richard Feraro (Acid Green)

• Yuchen Liu (Acid Green)


• Edward Sackett (Acid Green)

• Edgar Dela Cruz (Acid Green)

• Jan Ramos (Acid Green)

• Roman Vovnenko (Acid Green)

• Tony Trinh (Acid Green)

• Ferdinand Lipa (Acid Green)

• Harold Bataller (Acid Green)

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 3


Introduction 

• Armin A (Acid Green)

• Jack S (Web Solutions NYC)

• Kevin Bodwell

• James Cowie (SheroCommerce)

• Eren Karayigit (Improove)

• Gregor Pollak (Rocket Web)

• Евген Швец

• Nadezhda Glonyagina

All the best!

Joseph Maxwell

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 4




ADDITIONAL RESOURCES

Throughout this guide, I will reference many external websites and resources.
Here are a few of my favorites:

• Mage2.tv: by Vinai Kopp. I highly recommend taking advantage of his


training. It is well-thought-out material covering advanced subjects. To get
2 free weeks of access, utilize the promo code: STUDY-WELL. You will not
regret subscribing! Besides, you are helping a fellow community member
create more amazing materials.

• IDE: PHPStorm (by JetBrains). I use this every day that I am writing
Magento code.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 5




INTRODUCTION AND SETUP

This study guide is one of the most (or the most) concentrated source of
information for advanced backend Magento development. If you hope to
achieve this certification or just want to learn, you won’t be disappointed.

Our goal is not to just tell you how to write code, but also point you to where
you can dive in yourself AND show you how to do it. With this three-pronged
approach, your willingness to learn is the only thing stopping you from hitting
your goals.

Setup of your development website


We utilize the domain lc.commerce.site for all code examples in this book. Feel
free to place the website at whatever development site domain you wish. Note
there are some examples that you will need to change out the URL. Please
download the practice code from your account area on SwiftOtter.com.

Setup of your development environment


I sincerely hope that you have a quality IDE (like PHPStorm) and your
environment configured with Xdebug. The latter tool allows you to set
breakpoints and step through the code, line-by-line, read variables and execute
commands at run-time. Consider this a requirement to effectively completing
this book.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 6




Magento code
As stated above, you need to have license keys for Magento Commerce
(formerly, Enterprise). If you work for a partner or are a Community Insider, you
will have these keys available to you.

The test
This Professional Developer Plus test consists of 60 questions, requires a 62%
score to pass, and has a time limit of 90 minutes. The questions are scenario-
based, which ensures you have proper knowledge of the subject (instead of
simply memorizing many subjects).

The test ensures you are extremely knowledgeable about Magento 2.2.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 7




CONTENTS

1 MAGENTO ARCHITECTURE........................................... 11
1.1 Determine advanced uses of the Magento
configuration system..................................................... 12
1.2 Demonstrate an ability to design
complex customizations using plugins and di.xml........... 17
1.3 Demonstrate understanding of
Magento events processing........................................... 33
1.4 Demonstrate an ability to
use the Magento command-line interface....................... 35

2 MAGENTO UI................................................................ 39
2.1 Demonstrate understanding UiComponents architecture...... 40
2.2 Demonstrate advanced use of Magento layouts.............. 53
2.3 Demonstrate an ability to
operate with Magento blocks and templates................... 57

3 WORKING WITH DATABASES........................................ 67


3.1 Demonstrate understanding of the architectural
layers of the database access classes,
including models, repositories, and data mappers........... 68
3.3 Demonstrate an ability to use different
types of setup scripts in Magento................................... 100

4 USING THE ENTITY-ATTRIBUTE-VALUE (EAV) MODEL.... 105


4.1 Describe the EAV data access process in Magento.......... 106
4.2 Describe the database tables for EAV entities and
how to create them....................................................... 109

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 8




4.3 Demonstrate an ability to operate with


attribute options........................................................... 112
4.4 Demonstrate an ability to use
non-catalog EAV entities................................................ 114

5 DEVELOPING WITH ADMINHTML.................................. 118


5.1 Demonstrate ability to use ACL...................................... 119
5.2 Demonstrate understanding of the
admin login process and admin actions processing......... 125
5.3 Demonstrate an ability to create
complex forms and grids................................................ 128

6 CUSTOMIZING THE CATALOG....................................... 135


6.1 Demonstrate an ability to understand and
customize Magento products......................................... 136
6.2 Demonstrate an ability to perform complex
operations with the Magento pricing framework............. 144
6.3 Customize catalog price rules ........................................ 155
6.4 Determine how to use Magento categories..................... 156
6.5 Demonstrate an understanding of catalog indexers......... 159
6.6 Demonstrate understanding of catalog staging
and its impact on the system......................................... 167
6.7 Demonstrate an understanding of the
product search framework............................................. 168
6.8 Demonstrate understanding of importing
products and categories in Magento............................... 169

7 CUSTOMIZING THE CHECKOUT PROCESS..................... 174


7.1 Understand the Magento quote architecture and
customizing quote-related functionality......................... 175

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 9




7.2 Demonstrate an ability to customize and extend


the checkout process.................................................... 187
7.3 Create and debug shipping and payment methods
in Magento.................................................................... 198

8 MAGENTO COMMERCE FEATURES................................ 208


8.1 Demonstrate an ability to use message queues.............. 209
8.2 Demonstrate understanding of
customer segmentation................................................ 212
8.3 Demonstrate understanding of advanced
capabilities in Magento Commerce................................. 218
8.4 Demonstrate understanding of target rules.................... 224

9 UNDERSTANDING MAGENTO SECURITY....................... 230


9.1 Demonstrate understanding of frontend security
with Magento................................................................ 231
9.2 Demonstrate understanding Adminhtml security
with Magento................................................................ 235
9.3 Demonstrate understanding of different types of
attacks and preventing them with Magento.................... 239

APPENDIX.................................................................... 247
Discussion of the Entity Manager................................... 248

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 10


1
MAGENTO
ARCHITECTURE
4 questions

Professional Developer Plus


Magento Architecture

1.1 DETERMINE ADVANCED USES OF THE


MAGENTO CONFIGURATION SYSTEM

Understand how to create a custom config file.


Demonstrate an understanding of the Magento
Configuration files framework
A core feature of Magento is its customizability (configuration). Magento’s
configuration is divided into areas: global (base), frontend, adminhtml, webapi_
rest, webapi_soap and crontab. Configuration is loaded depending on the area
chosen. By default, the global configuration is loaded and then the specific area
is merged (or loaded) on top of the global configuration.

Magento includes a number of XML files that cover the majority of use cases
in normal development. Module configuration is located in the app/code/
MyCompany/MyModule/etc/ directory.

Here are a few:


• module.xml: the initialization / configuration for a module. This file contains
the sequence element that determines the module load order.

• events.xml: where event observers are declared (for example, executing


code when the `sales_order_load_before` event is triggered).

• di.xml: where dependency injection is configured. This is where virtual


types, plugins, and argument modifications are set.

• config.xml: where default configuration values are specified.

• acl.xml: where ACL nodes are configured.

• [AREA]/routes.xml: where the controller routes are initialized.

• adminhtml/system.xml: where new Store configuration fields are set up.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 12


Magento Architecture

A core feature of Magento is the ability to override literally every


configuration file.

Example:
• app/code/Chapter1/ConfigLoadOrder and app/code/Chapter1/
ConfigLoadOrder2

• https://lc.commerce.site/chapter11/

Notice app/etc/config.php:

<?php

return [

'modules' => [

'Magento_Store' => 1,

'Magento_Directory' => 1,

'Magento_Eav' => 1,

'Magento_Customer' => 1,

'Chapter1_ConfigLoadOrder2' => 1,

'Chapter1_ConfigLoadOrder' => 1,

ConfigLoadOrder contains a sequence node for ConfigLoadOrder2. This


means that ConfigLoadOrder will be loaded after ConfigLoadOrder2.

Navigate to https://lc.commerce.site/chapter11/. Observe the difference


as specified. Later modules override earlier modules regardless of the
area specified.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 13


Magento Architecture

Information:
• Create or extend configuration types

• Module configuration files

Understand how to create a custom config file with


validation and a unique node that is overridden on
merging
Magento also provides the capability to create custom configuration files with
relative ease. This requires a number of pieces:

• A custom XML configuration file.

• A custom XSD schema file.

• A class for exposing the configuration data. While this isn’t 100% necessary,
it makes easier maintainability in retrieving the data.

• The above utilizes a class that implements \Magento\Framework\Config\


DataInterface or extends \Magento\Framework\Config\Data. This
provides the caching wrapper for the configuration data. (You can use a
virtualType in di.xml.)

• A class that implements \Magento\Framework\Config\ReaderInterface


or extends Magento\Framework\Config\Reader\Filesystem. (You can
use a virtualType in di.xml.)
• A class that implements \Magento\Framework\Config\
ConverterInterface. This class converts XML nodes into a PHP array.

Using an attribute as the unique identifier enables specifying the idAttribute


in the ReaderInterface. The idAttribute is used to identify similar nodes
for merging.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 14


Magento Architecture

Example:
• app/code/Chapter1/CustomConfig

• https://lc.commerce.site/chapter11custom

We create a custom discounts.xml file in which we can create new


<discount/> entities. Note the name attribute (this is specified in the
idAttributes parameter in di.xml). This allows us to merge discounts for the
same name.

Note that for the sake of simplicity, we are using virtualTypes as defined
in di.xml. Also note that virtualTypes do not generate any classes in the
generated/ directory.

Practice:
• Create a new configuration type. Do not copy any code examples in this
section but rather hand type. Pay special attention to the relationship
between classes.

Examples:
• vendor/magento/module-email/Model/Template/Config.php

• vendor/magento/module-email/Model/Template/Config/Data.php

• vendor/magento/module-email/Model/Template/Config/Reader.php
• vendor/magento/module-email/Model/Template/Config/Converter.php

• vendor/magento/module-email/Model/Template/Config/FileResolver.php

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 15


Magento Architecture

Information:
• Create or extend configuration types

• Working with custom configuration files in Magento 2

• Magento 2 create custom xml config reader php class

Understand how to create a config file with a remote


schema
Believe it or not, you can create a configuration file with a remote schema. Here
is an example:

<?xml version="1.0"?>

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

xsi:noNamespaceSchemaLocation="http://raw.githubusercontent.com/

magento/magento2/2.3-develop/lib/internal/Magento/Framework/App/

etc/routes.xsd">

<router id="standard">

<route id="chapter11remote" frontName="chapter11remote">

<module name="Chapter1_RemoteXsd" />

</route>

</router>

</config>

Note that Magento may throw an error if an https URL is utilized because it
can’t properly negotiate SSL.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 16


Magento Architecture

Example:
• app/code/Chapter1/RemoteXsd

• https://lc.commerce.site/chapter11remote

Practice:
• What happens when you change the noNamespaceSchemaLocation in app/
code/Chapter1/RemoteXsd/frontend/routes.xml to be an invalid URL?

1.2 DEMONSTRATE AN ABILITY TO DESIGN


COMPLEX CUSTOMIZATIONS USING
PLUGINS AND DI.XML

Plugins sort order, plugin on plugin scenario, plugin


debugging techniques.
Plugins are part of the foundational bedrock of customizing Magento. Plugins
attach to public methods and can alter the input parameters, determine
whether or not the method is executed or alter the output of a method.
For classes that are built correctly and expose a reasonable number of
public methods, this enables other developers (or even yourself) to build
customizations in a modular format.

Note: on a complex application we recently built, we created a model that


needed some complex validation before and after a calculation was run. The
calculation was run in a class, and then, in the same module, we utilized before
and after plugins to validate the input and output from the calculations.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 17


Magento Architecture

There are three types of plugins:

before changes input parameters

These are returned as an array, in the order specified in the original method.
Example:

// original:

namespace MyCompany\MyModule\Model;

class MyRepository

public function save(MyInterface $model, bool $isFlatTable)

// ...

// plugin:

public function beforeSave(

\MyCompany\MyModule\Model\MyRepository $subject,

$model,

$isFlatTable

) {

return [$model, $isFlatTable];

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 18


Magento Architecture

around determines whether or not the original method is


executed.

Magento documentation warns that around plugins can cause unintended


consequences if the original method has more functionality than the developer
anticipated, or if other plugins are required to successfully complete the task, or
if the functionality breaks as Magento is upgraded in the future.

// around plugin for above MyRepository

public function aroundSave(

\MyCompany\MyModule\Model\MyRepository $subject,

callable $proceed,

$model,

$isFlatTable

) {

return $proceed($model, $isFlatTable);

after alters the output of a method.

after also provides the parameters that were sent to the method itself,
allowing for differing alterations based on these parameters.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 19


Magento Architecture

// after plugin for above MyRepository

public function afterSave(

\MyCompany\MyModule\Model\MyRepository $subject,

$result,

$model,

$isFlatTable

) {

// do something to $result

return $result;

Sort order

Magento provides the capability to specify a sortOrder for each plugin. For
most plugins it is unnecessary or even discouraged to set the sortOrder.
However, if you, for example, need to change the input sent to another plugin
(customizing a module from the Magento Marketplace), then sortOrder would
have a valid use-case.

Typically, plugins are executed in a lowest-to-highest sort order. The


complicating factor is when there are multiple plugins for the same method in
a class.

When there are multiple plugins for the same method in a class, here is how
it works: before plugins are executed lowest sort order, first, to highest sort
order, last. after plugins are executed high sort order, first, to lowest sort
order, last. Please read and study the example in DevDocs below and our
example for this section.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 20


Magento Architecture

Example:
• app/code/Chapter1/Plugins

• https://lc.commerce.site/chapter12plugins/example1

Further Reading:
• Prioritizing plugins

Plugin on Plugin

It is possible to attach a plugin to another plugin. This is done with normal


configuration. One thing to note is that the modifying plugin has a different
method signature than the original plugin.

Debugging

Here is a sample interceptor:

public function format(string $name)

$pluginInfo = $this->pluginList->getNext($this->subjectType,'format');

if (!$pluginInfo) {

return parent::format($name);

} else {

return $this->___callPlugins('format', func_get_args(), $pluginInfo);

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 21


Magento Architecture

To debug a plugin, the easiest place to start is by setting a breakpoint on the


method that you are adding a plugin to. Check the call stack to see if an
interceptor is there. If there is no interceptor, ensure you have properly
configured the plugin in di.xml (remember type then plugin). If the
interceptor is present, set a breakpoint in the interceptor to ensure that your
plugin is being located. Step through the ___callPlugins method in
Magento\Framework\Interception\Interceptor to follow the chain
\‌
of commands.

Further Reading:
• Note: you can use the n98-magerun2.phar
config:data:di tool.

• Plugins (Interceptors)

Example #1: app/code/Chapter1/Plugins

https://lc.commerce.site/chapter12plugins/example1
(remember, this is a development machine URL: please copy the URL and paste
it into your URL bar and change the domain to whatever your development
machine runs)

Study the example. Notice how the before in Chapter1\Plugins\Plugins\


Example1Both1 is executed after the \Chapter1\Plugins\Plugins\
Example1Around1. This is because of the sort order. What happens when you
change the execution order around?

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 22


Magento Architecture

Example #2: app/code/Chapter1/Plugins

https://lc.commerce.site/chapter12plugins/example2
This example proves how you can use a plugin to modify another plugin. The
best use case for this is a third party module that contains a plugin that you
need to modify. Attach your plugin to the other plugin (confusing, I know).

File: app/code/Chapter1/Plugins/Model/Plugins/Example2Plugin2.php

Practical experience:
• Locate the interceptor for MyModel class and set a breakpoint. Follow the
execution through.

Demonstrate an understanding of virtual types, shared


objects, object instantiation process, proxies, factories

Virtual types:

These are classes that are instantiated by the ObjectManager but have no
concrete class located in app/code or in generated. Confusingly, a type is
different in that the type adjusts existing classes whereas a virtualType
is "creating" a new type of class. A virtualType must extend a concrete
class type.

More concretely:

• If you wish to adjust the constructor parameters for an existing class, use the
type declaration. These changes take effect for all class instantiations for
that object in that area.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 23


Magento Architecture

• If you wish to create an entirely new type of object that can be injected
elsewhere, use the virtualType declaration. You need to then inject or
utilize this class in XML configuration (in di.xml or as a block).

These are useful if the only necessary modifications to a class are the classes
that are injected. Additionally, virtual types do generate code (in generated).
As such, don’t use them anywhere that does not use the Object Manager (for
example, async bulk consumers).

Note that you cannot reference the specific virtualType in a class’


constructor. Rather, your constructor should utilize the concrete class type
that the virtualType extends. You also cannot write a class that inherits a
virtual type.

Example for how to inject a virtual type:

<!-- app/code/MyCompany/MyModule/etc/di.xml -->

<virtualType name="MyVirtualType" type="Magento\Sales\

OrderRepository">

<!-- … -->

</virtualType>

<type name="MyCompany\MyModule\DoSomething">

<arguments>

<argument name="repository" xsi:type="object">

MyVirtualType</argument>

</arguments>

</type>

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 24


Magento Architecture

/** app/code/MyCompany/MyModule/DoSomething.php **/

public function __construct(\Magento\Sales\OrderRepository

$repository)

/** … **./

Example:
• see app/code/Chapter1/CustomConfig.

Shared Objects:

The ObjectManager has two methods for getting an object: create and get.

create: loads a new version of the object every time. This is the method that
factories call. Note that arguments can be passed to the object’s constructor
with this method.

get: loads a "cached" version of the object (you cannot pass arguments to the
constructor because the object might have been already instantiated). The
cache is a private variable in the ObjectManager. If the object is not saved, it
is created and then saved. This is the method that is used for injecting objects
into the constructor. Arguments cannot be passed to the object’s constructor,
as Magento has no way of knowing, beforehand, whether or not the object
exists in the ObjectManager repository.

See: generated/code/Magento/Catalog/Model/ProductFactory.php

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 25


Magento Architecture

Practical Experience:
• Set a breakpoint on both the create and get methods. How is the actual
object type determined?

Object Instantiation Process:

The interface that controls the instantiation of objects is: \Magento\


Framework\ObjectManager\FactoryInterface. Inspecting this shows that
there are several classes that extend FactoryInterface:

• \Magento\Framework\ObjectManager\Factory\Dynamic\Production

• \Magento\Framework\ObjectManager\Factory\Dynamic\Developer

Practical Experience:
• What are the differences between the production and developer factories?
How does the ObjectManager get created in the first place (hint: vendor/
magento/framework/App/ObjectManagerFactory.php)?

Proxies:

A proxy allows a class to be loaded at a later point in time. This is useful in the
case of needing to import a class with a resource-intensive function, but it is not
executed until it is needed.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 26


Magento Architecture

/** generated/code/Magento/Customer/Model/Session/Proxy.php **/

public function loginById($customerId)

return $this->_getSubject()->loginById($customerId);

protected function _getSubject()

if (!$this->_subject) {

$this->_subject = true === $this->_isShared

? $this->_objectManager->get($this->_instanceName)

: $this->_objectManager->create($this->_instanceName);

return $this->_subject;

Additionally, proxies can be used to break a circular dependency loop. Note that
you should never directly reference the proxy class in your constructor. Rather,
use di.xml. With di.xml, you set the argument to be the \Proxy of the class
to inject. \Proxy is automatically generated and extends the base class. The
reason you should use di.xml is because PHP will throw errors if the class is
not found (hasn’t been generated yet).

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 27


Magento Architecture

Example:

// --- MyCompany\MyModule\Model\ThisClass --- //

public function __construct(AnotherClass $anotherClass) {}

// --- MyCompany/MyModule/etc/di.xml

<type name="MyCompany\MyModule\Model\ThisClass">

<arguments>

<argument name="anotherClass" xsi:type="object">MyCompany\

MyModule\Model\AnotherClass\Proxy</argument>

<arguments>

</type>

Further Reading:
• Proxies

Example:
• app/code/Chapter1/Proxies

• https://lc.commerce.site/chapter12proxies

The example is a contrived demonstration of how to resolve a circular


dependency using proxies.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 28


Magento Architecture

Practical experience:
• What error message do you get when you remove the proxy from di.xml?

• What class triggers that exception?

Factories:

Because all injected objects are shared, by default, there has to be a way to
create a new object. As you know, the new keyword is pretty much forbidden in
Magento development (except for throwing exceptions). If we need to create a
new Product model, importing the Product model means that any other files
that likewise import the Product model will be shared: updates made in one
class Product model will be seen in another class’ Product model—not good.

Here is an example of the Product factory:

public function create(array $data = array())

return $this->_objectManager->create($this->_instanceName,

$data);

Note that you can send an array of arguments in the $data parameter. These
arguments are sent to the constructor of the class to create:

$this->productFactory->create([

‘categoryRepository’ => $this->getCategoryRepository()

]);

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 29


Magento Architecture

In the above example, the $categoryRepository parameter is specified by


the input array instead of loaded through dependency injection.

Practical experience:
• How do parameters passed in through the $data parameter affect
parameters specified in di.xml?

• Let’s say you have a calculator class. You need to instantiate this calculator
class from several locations. The calculator’s constructor contains several
scalar parameters. Create a factory to make instantiation of this class easier
and typed.

How does an around plugin modify the plugin


execution order?
This can be pretty tricky. If the sortOrder is specified, the around plugin is
executed according to its assigned sort order. This is easy to understand for the
"before" part of the around (inside the around, you should call the proceed
callable—your code can be executed on either side).

If there are plugins with a higher sort order than the around plugin’s sort order,
these plugins are run before the "after" part of the around is executed. If you
are not able to follow this, please reference the additional instruction available
from SwiftOtter.com.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 30


Magento Architecture

Practical experience:
• See example #1 from 1.2. Change the sort order of the around plugin. How
does this affect the output in https://lc.commerce.site/chapter12plugins/
example1?

• vendor/magento/module-customer/etc/di.xml has a plugin named


transactionWrapper. Create a plugin to run some code after the
target method’s code is run but before the transaction is committed to
the database.

How do you debug a plugin that is not executed?


• Ensure that the plugin exists di.xml and:

• That it is specified for the correct area.

• That it is not disabled by another module.

• That the method fits within the requirements as specified in the


documentation.

• Set a breakpoint in the targeted method and see if an Interceptor class


has been generated.

Practical experience:
• Module: app/code/Chapter1/Challenge12PluginDebug
• Url: https://lc.commerce.site/chapter12pluginchallenge/

• Get the value to match the expected result.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 31


Magento Architecture

Demonstrate Plugin on Plugin examples


See the SwiftOtter additional training for this study guide for demonstrations on
how this works.

Which classes are instantiated outside of the


ObjectManager so they cannot be customizing using
di.xml?
• Any classes for Composer dependencies.
• Bootstrapping classes, for example Magento\Framework\App\Http

Further Reading:
• Magento application initialization and bootstrap

Demonstrate a use case for a virtual type (different


instances of a class with a different set of arguments)
See above for examples. The companion pack for this study guide also has
additional information.

Further Reading:
• \Magento\Framework\Event\Manager::dispatch

• \Magento\Framework\Event\Invoker\
InvokerDefault::dispatch

• \Magento\Framework\Event\Invoker\
InvokerDefault::_callObserverMethod

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 32


Magento Architecture

1.3 DEMONSTRATE UNDERSTANDING OF


MAGENTO EVENTS PROCESSING

Demonstrate an understanding of the events


processing flow.
• \Magento\Framework\Event\Manager is injected.

• dispatch is called.

• Observers are iterated

• A new event and observer are created for each event triggered.

• If you set a new value on the event’s data property, that value will
be discarded. My understanding is that the goal is to discourage
leveraging events for what plugins are intended to do.

• An invoker is called (\Magento\Framework\Event\Invoker\


InvokerDefault) which ensures that the observer is not disabled, and
determines whether or not to use a shared instance.

• Observers are called

• Observers must implement \Magento\Framework\Event\


ObserverInterface (developer mode triggers an exception, production
mode skips execution).

<event name="catalog_product_save_after">

<observer name="RefreshCaches" instance="MyCompany\MyModule\

Observers\RefreshCaches"/>

</event>

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 33


Magento Architecture

Practical experience:
• Create a new observer for the catalog_controller_category_init_
after event.

• If an event is triggered with a string, are you able to modify it?

• Now, disable the event from another module.

Influence of Staging on the event processing. What


is a modification of the event processing mechanism
introduced by the staging module? (Commerce)
The Staging module changes the default implementation for Magento\
Framework\Event\ManagerInterface to Magento\Staging\Model\
Event\Manager (actually, this module’s proxy). The Staging event manager
introduces new functionality to prevent events or observers from firing when
the page that is being viewed is in preview mode.

Example:
• vendor/magento/module-catalog-url-rewrite-staging/etc/
adminhtml/di.xml

At this time, there are no banned observers or events for the frontend.

Practical experience:
• Ban an event and an observer on the frontend.

• Review the VersionManager in vendor/magento/module-staging/


Model/VersionManager.php.

• What happens if there is no database connected (in a build environment)?

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 34


Magento Architecture

1.4 DEMONSTRATE AN ABILITY TO USE THE


MAGENTO COMMAND-LINE INTERFACE

Create a new CLI command, emulate different areas


within it. Create a new CLI-command, configure it in
di.xml, add optional/required options/keys.
Creating a new CLI command is quite simple:

• Add CLI command to Magento\Framework\Console\


CommandListInterface via di.xml. (example)

• Create a class (generally in your modules’ Console directory).

• Make the class extend Symfony\Component\Console\Command\Command.

The \Magento\Framework\App\State controls the state for the application. By


default, the app state is not set and your application will throw an error if it accesses
any functionality that requires a state to be set. It is up to the developer to change this
to whatever area is applicable for the actions that need to be undertaken.

Options vs Arguments:

Remember this:

• Options have a flag (bin/magento do:something --admin-


email="[email protected]")

• Arguments have no flag (bin/magento do:something [email protected])

Most console commands accept options. A few offer arguments. In the example
listed below, the argument is an array (separated by spaces) of languages:

bin/magento setup:static-content:deploy --content-version=31 en_US de_DE

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 35


Magento Architecture

Example:
• app/code/Chapter1/CLI

• bin/magento customer:create

Run:

• bin/magento customer:create [email protected]

• bin/magento customer:create "Joseph Maxwell"

Practical Experience:
• Create a new CLI command to execute:

• bin/magento mycommand:run --setup=1 --area=frontend


(or adminhtml)

• Command must automatically switch areas.

Environment specification using Area class


• Inject \Magento\Framework\App\State.

• Call $this->state->setAreaCode().

Environment emulation for a section of code


If you are in an area, say the adminhtml area, and you need to switch to
the frontend area, as we see above, you cannot just "switch" the area. A
time when this is used is in the product alerts (vendor/magento/module-
product-alert/Model/Email.php).

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 36


Magento Architecture

Important Notes
• Calling getAreaCode() when no area code is set throws an
exception.

• Calling setAreaCode() when an area code is already set


throws an exception.

• Example: app/code/Chapter1/CLI

• bin/magento trigger:event

The "magic" method is emulateAreaCode(). This method accepts two


parameters, the new area and a callback that contains the code to execute while
in emulation mode. Note that emulation does not change all aspects of the
system: the Magento scope remains the same. When calling setAreaCode the
scope is initially set, but emulation does not change it.

The other way to emulate is through the \Magento\Store\Model\


App\Emulation class. This class does not use a callback but rather the
startEnvironmentEmulation() and stopEnvironmentEmulation().

Example:
• app/code/Chapter1/CLI

• bin/magento trigger:event:area

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 37


Magento Architecture

Practical experience:
• Determine what features do change when emulating an environment.

Further Reading:
• Emulating Areas in Magento 2

• Magento2 Store Emulation

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 38


2
MAGENTO
UI
4 questions

Professional Developer Plus


Magento UI

2.1 DEMONSTRATE UNDERSTANDING


UICOMPONENTS ARCHITECTURE

Warning:
I am barely able to scratch the surface of uiComponents. It
is imperative that you study this section, work through the
practical examples, and understand the sample code before
attempting the test.

Note:
Note: with uiComponents, everything is a component. That
means every uiComponent extends uiElement (vendor/
magento/module-ui/view/base/web/js/lib/core/
element/element.js). Every uiComponent is a child of
another uiComponent. The custom uiComponents you create
are children of uiComponents. This is fundamental knowledge.

UiComponent workflow, initialization, execution,


configuration structure, data loading process

Workflow:

UiComponents represent a semi-intuitive means of creating admin interfaces.


While they appear complex on the surface, one can create an extensible
interface with only a few files.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 40


Magento UI

Beyond the standard requirements to serve an HTML page, these files are
required for a UiComponent:

• UiComponent XML definition: view/adminhtml/ui_component/ui_


component_name.xml

• (nothing else)

As you can see, there are very few files required. While some of the
uiComponent XML structure makes little sense, drawing up even basic
uiComponents is quite fast.

Beyond the basics, here are some helpful ancillary files that you will
probably create:

• Actions column which will provide the actions drop down menu column.
Extend \Magento\Ui\Component\Listing\Columns\Column.

• Data provider to format, customize, or retrieve data. Extend \Magento\


Framework\View\Element\UiComponent\DataProvider\
DataProvider.

Note that you also must tell the DataProvider collection factory that there are
more collections available. This usually happens in di.xml, like:

<type name="Magento\Framework\View\Element\UiComponent\

DataProvider\CollectionFactory">

<arguments>

<argument name="collections" xsi:type="array">

<item name="custom_product_grid_data_source"

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

</argument>

</arguments>

</type>

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 41


Magento UI

The name above is originally specified in the UiComponent


data.‌
js_‌ provider and data.js_config.deps nodes.
config.‌

To load a UiComponent, add the <uiComponent name="sample_ui_


component"/> into your layout XML. Note that this must be placed within
another block or container, like:

<referenceContainer name="content">

<uiComponent name="custom_product_form"/>

</referenceContainer>

Often, the host layout XML files are quite empty, as they just contain the
uiComponent instruction.

Execution:

The uiComponent reader model (vendor/magento/framework/View/


Layout/Reader/UiComponent.php) is responsible for instantiating PHP
classes for the layout XML instructions. Specifically, the interpret method:

• Schedules the addition into the layout structure.

• Formulates a path (ex. root/backend.page/page.wrapper/page.


content/page.main.container/admin.scope.col.wrap/main.
col/content/custom_product_grid) for the new component.

• Standardizes visibility conditions for:

• ifconfig and aclResource attributes.

• visibilityCondition child node.

• Up until this point, no uiComponent configuration has been loaded.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 42


Magento UI

• $this->uiConfigFactory loads the uiComponent.

Next, the uiComponent is converted into a block. This happens in vendor/


magento/framework/View/Layout/Generator/UiComponent.php.
It is important to note that the interface for a uiComponent is \Magento\
Framework\View\Element\UiComponent\ContainerInterface.
The concrete implementation is \Magento\Ui\Component\Wrapper\
UiComponent.

• All child blocks are merged into the uiComponent to be rendered.

• The base .xhtml template that is rendered is vendor/magento/module-


ui/view/base/ui_component/templates/[component type]/
default.xhtml.

• The template engine is \Magento\Framework\View\TemplateEngine\


Xhtml.

• \Magento\Ui\TemplateEngine\Xhtml\Result::__toString() is
called with the express purpose of rendering the uiComponent.

• The compiler builds the representation of the .xhtml file.

• appendLayoutConfiguration() is called which converts the uiComponent


into a JSON representation (see wrapContent()).

The original uiComponent rendering contains data to be displayed. Yet, that


data is not displayed until a web request is triggered. Keep reading...

Data loading:

Once the Javascript files on the page have loaded, the uiComponent triggers a
request to: vendor/magento/module-ui/Controller/Adminhtml/Index/
Render.php. This controller is specified with the listing/dataSource/
dataProvider/data/config/update_url path (note that the latter items are
in argument and item nodes).

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 43


Magento UI

So, how does the render class know where the data is? Easy: the request
must specify a namespace parameter. The namespace is really the name of
the uiComponent. Add .xml to it and search through your codebase for a
file with that name (ex. custom_product_grid.xml), and you will find the
uiComponent.

In the render controller:

• The ACL resource is validated.

• All child components are prepared (\Magento\Ui\Component\


AbstractComponent::prepare, a public method).

• If componentJson is a parameter in the URL, the entire component will


be returned. Otherwise, just the data. Note that the entire component is
still loaded.

Forms data:

The default data wrapper name is general. Inside the general array, you will
find the data for your form.

In the example, our form has multiple tabs. Please note that the form will not
save as this is simply a demonstration of uiComponent forms. Pay special
attention to how the data is mapped.

To better understand how this works, run these commands in your browser’s
developer tools:

require('uiRegistry').get('index = name');

Notice the name field’s links:

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 44


Magento UI

links:

value: "custom_product_form.custom_product_form_data

source:data.general.name"

You can then call:

require('uiRegistry').get('custom_product_form.custom_product_

form_data_source').data

to see the data object that your component is hydrated with.

It is important to note that you can insert regular layout instructions into your
uiComponent. For example, in the Customer Form uiComponent (vendor/
magento/module-customer/view/base/ui_component/customer_form.
xml), you will find the following code. Notice that the htmlContent is wrapping
the regular layout XML:

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 45


Magento UI

<?xml version="1.0" encoding="UTF-8"?>

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

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

ui_configuration.xsd">

<!-- … -->

<htmlContent name="customer_edit_tab_view_content">

<block class="Magento\Customer\Block\Adminhtml\Edit\

Tab\View" name="customer_edit_tab_view"

template="Magento_Customer::tab/view.phtml">

<arguments>

<argument name="sort_order" xsi:type="number">10

</argument>

<argument name="tab_label" xsi:type="string"

translate="true">Customer View</argument>

</arguments>

<block class="Magento\Customer\Block\Adminhtml\Edit\

Tab\View\PersonalInfo" name="personal_info"

template="Magento_Customer::tab/view/personal_info.

phtml"/>

</block>

</htmlContent>

<!-- … -->

</form>

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 46


Magento UI

Grid examples:
• vendor/magento/module-customer/view/adminhtml/ui_component/
customer_listing.xml

• vendor/magento/module-catalog/view/adminhtml/ui_component/
product_listing.xml

Form examples:
• vendor/magento/module-customer/view/base/ui_component/
customer_form.xml

• vendor/magento/module-catalog/view/adminhtml/ui_component/
product_form.xml

Note:
Note that an example and more details of this are available in
the SwiftOtter learning kit.

Practical experience:
• Set a breakpoint in __construct in vendor/magento/framework/View/
Element/UiComponent/DataProvider/DataProvider.php (or your
custom DataProvider) and observe how the dataProvider node in the
uiComponent configuration matches the parameters. Doesn’t this open
further configuration possibilities?

• In a DataProvider, what public methods are available for plugging into?

• Set a breakpoint in vendor/magento/framework/View/Layout/Reader/


UiComponent.php, and look at the call stack.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 47


Magento UI

• Navigate to the catalog product page and set a breakpoint in vendor/


magento/framework/View/Layout.php:renderNonCachedElement.
Follow the chain of instructions.

• Prevent the prefix attribute from appearing in the customer_form.xml


uiComponent.

• Now, prevent the disable_auto_group_change in the


customer_‌form.‌xml uiComponent.

• You will need to create a plugin in \Magento\Customer\Model\


Customer\DataProvider. The reason for this is that the uiComponent
configuration is overridden by the metadata coming from the database.

• One important note is that the uiComponent configuration is cached in


the Configuration XML. However, the overrides are not cached and are
loaded through the above DataProvider.

• Should you run into issues with changing a uiComponent, feel free to
modify the DataProvider.

• Become familiar with the classes in module-ui.

• What is the purpose of the classes in Component/?

• Where are some places that the \Magento\Ui\DataProvider\


Modifier\ModifierInterface is used?

• Set a breakpoint in vendor/magento/module-ui/Controller/


Adminhtml/Index/Render.php:execute and navigate either to our
custom uiComponent or to any page that has a uiComponent (product
listing, customer listing).

Further Reading:
• https://www.mage2.tv/ has a large amount of detailed
instruction on uiComponents.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 48


Magento UI

Retrieving a UiComponent's instance from the


uiRegistry
To access a uiComponent’s Javascript implementation, navigate to Catalog >
Products (custom), open your browser’s developer tools and run:

require('uiRegistry').get('custom_product_grid.custom_product_grid')

You can access the base component by first calling the name of the
uiComponent (custom_product_grid) and then the name of the columns
specified (custom_product_grid), separated by a ..

The dataSource is also a uiComponent. This is accessed in the same way. In


our example grid uiComponent (app/code/Chapter2/UiDemonstration/
view/adminhtml/ui_component/custom_product_grid.xml), you can
access the dataSource by calling:

require('uiRegistry').get('custom_product_grid.custom_product_grid_data_source')

You can also get an item with the index command, instead of using the full
component name:

require('uiRegistry').get('index = name');

Further Reading:
• uiRegistry

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 49


Magento UI

Understand the difference between executing a data


provider component and loading data
The big question is how does data get hydrated into our components?
The two most-used containers are forms and grids, so we will talk about
those separately.

By default, data providers are responsible for storing and managing the data
saving process. Confusingly, a data storage component loads the data from
the server.

Forms:

Provider: vendor/magento/module-ui/view/base/web/js/form/
provider.js. The data is specified in the initialization JSON. It is then
accessible in the data source component.

Grids:

Provider: vendor/magento/module-ui/view/base/web/js/grid/
provider.js

Data Storage: vendor/magento/module-ui/view/base/web/js/grid/


data-storage.js

The data storage component is responsible for loading data. Note that the
initialization JSON does have data specified, but that data is quickly discarded.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 50


Magento UI

Practical experience:
• Set a breakpoint in vendor/magento/module-ui/view/base/web/js/
grid/data-storage.js in Admin > Catalog > Products (custom) to see
how the data is loaded in.

• Set a breakpoint in \Magento\Ui\Controller\Index\Render::execute


to understand how data is loaded in the renderer path.

Describe the role of UiComponent PHP classes


The purpose of the UiComponent PHP classes is to interface between XML
configuration files and the JSON output. These classes are why you can utilize a
fieldset tag in your uiComponent.

The prepare method in (vendor/magento/module-ui/Component/


AbstractComponent.php) contains the majority of global customizations.

Further Reading:
• vendor/magento/module-ui

Practical Experience:
• Set a breakpoint in \Magento\Ui\Component\AbstractComponent::__
construct. Understand how the values that come into the $data argument
originate in the XML uiComponent definition.

• With this understanding, examine \Magento\Ui\Component\


AbstractComponent to determine how you can extend a uiComponent.

• Determine what is the purpose of observers (as seen in \Magento\Ui\


Component\AbstractComponent).

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 51


Magento UI

Understand the uiClass instance, extending


uiComponent
First, uiClass is not an actual Javascript file in the Magento codebase. Instead,
it is an alias with the association being found in vendor/magento/module-ui/
view/base/requirejs-config.js.

uiClass: vendor/magento/module-ui/view/base/web/js/lib/core/
class.js

\/

uiElement: vendor/magento/module-ui/view/base/web/js/lib/core/
element/element.js

\/

uiComponent: vendor/magento/module-ui/view/base/web/js/lib/
core/collection.js

uiClass:

This is the basic building block for any uiComponent or related element. This
provides the basic functions necessary to propagate and extend.

uiElement:

This represents a basic element—one that does not need the ability to
manage children.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 52


Magento UI

Example:
• vendor/magento/module-ui/view/base/web/js/grid/resize.js

uiComponent:

Most uiComponents extend this class. This has the capability of storing and
rendering multiple children (similar to blocks in Magento layout).

Example:
• vendor/magento/module-ui/view/base/web/js/grid/toolbar.js

2.2 DEMONSTRATE ADVANCED USE OF


MAGENTO LAYOUTS

Non-standard layouts, custom handles, debugging


layouts
Magento layout is powerful, and we will delve into the more subtle intricacies of
this part of the system.

A layout must be defined in two places in a module:

• view/frontend/layouts.xml: this specifies the id and the human-


readable name of the layout. The example in our sample module is
my-‌custom-layout.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 53


Magento UI

• view/frontend/page_layout/[layout id].xml: this sets up the initial


layout instructions. The minimum is likely to be
<update handle="1column"/> or something similar.

Further Reading:
• Layout File Types

• vendor/magento/magento-theme

Custom handles are usually easy to add (an exception being a category page).
Our controller (app/code/Chapter2/Layout/Controller/Index/Index‌
.‌
php)
contains an example of how to do this.

There are a few techniques to debugging problems with layouts.

• Are you sure that your module is enabled?

• Set a breakpoint in vendor/magento/framework/View/Model/Layout/


Merge.php:_loadFileLayoutUpdatesXml to see if your module is loaded.

• Are you sure that you have the correct layout XML file? It is easy to confuse
the router id and the router front name.

• Set a breakpoint on your block or view model’s class line. This will tell you
when the autoloader includes your file, and you can continue until you locate
where Magento skips over your block.

Example:
• https://lc.commerce.site/index.php/chapter22layout/ (handles)

• app/code/Chapter2/Layout

• https://lc.commerce.site/index.php/chapter22layout/index/index/xml/1
(displaying XML layout)

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 54


Magento UI

Further Reading:
• Adding Custom Layout Handles in Magento 2

• How to add custom layout handles programatically for


Category View in Magento 2

Add a custom handle, obtain a list of handles loaded for


a page
See above example.

Obtain the layout XML for a page


See above example.

Containers elements with a wrapping DIV tag


In your layout XML file, add:

<container name="my.container" htmlTag="div">

<!-- … -->

</container>

Note that you can also specify several other attributes:

• htmlId

• htmlClass

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 55


Magento UI

Further Reading:
• Container Structure

Dynamically modify the layout tree


The \Magento\Framework\View\LayoutInterface class contains the
majority of methods to modify the layout tree. With this, you can create,
access, and delete elements.

Because LayoutInterface extends \Magento\Framework\Simplexml\


Config, its core is literally an XML node storage system. In the Config class, you
will find methods like getXmlString(), loadFile(), extend, etc.

One thing to note is that many methods in \Magento\Framework\View\


Layout (the implementation for LayoutInterface) call the protected
build() method, which ultimately calls \Magento\Framework\View\
Layout\Builder. This method loads layout XML files and uses that
information to spawn the PHP classes that power the rendering of the frontend.

A side effect of this is that once build() has been called, any additional layout
handles added will have no effect on the Layout XML that is loaded. As such, it
is imperative to add layout handles as early as possible.

See the example for one way to do this.

Example:
• https://lc.commerce.site/index.php/chapter22layout/index/addblock

• app/code/Chapter2/Layout/Controller/Index/AddBlock.php

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 56


Magento UI

2.3 DEMONSTRATE AN ABILITY TO


OPERATE WITH MAGENTO BLOCKS AND
TEMPLATES

Block caching, fallback debugging, email templates,


translations

Block caching

By default, blocks are not cached (unless a block is included in a parent block’s
cache). To cache a block, you must specify the cache_lifetime key in the
block’s _data variable and have the block_html cache enabled. Because
Magento 2 has integrated full page caching so well, I believe that block caching
is less relevant than it used to be.

Note that you can disable the entire page’s caching by specifying
cacheable="false" in a block, like:

<block name="my.test.block" template="MyCompany_MyModule::test-

block.phtml" cacheable="false">

<arguments>

<argument name="customer_info" xsi:type="object">MyCompany\

MyModule\ViewModel\CustomerInfo</argument>

</arguments>

</block>

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 57


Magento UI

There are few use cases where this makes sense. Unfortunately, there are
modules in circulation out there that misuse this directive and end up excluding
half or more of a website from being full-page cached.

You can use it to prevent customer data from being cached. However, in these
cases, it is usually better to use AJAX requests (and the API) to load the
respective data. You can also utilize Magento’s ESI implementation called Private
Content. You can also search the Magento codebase to find the cases where
this is used.

Further Reading:
• Page Caching

This directive is used in \Magento\Framework\View\Layout::isCacheable.


This method searches the XML layout tree to find any instances of
cacheable="false". If any are found, the method returns false.

If you are instantiating a LayoutInterface, you can pass ['cacheable' =>


false] array as a constructor argument to prevent caching.

Example:
• \Magento\Email\Model\Template\Filter::emulateAreaCallback

Practical experience:
• Step through \Magento\Framework\View\Element\AbstractBlock::_loadCache

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 58


Magento UI

Further Reading:
• Private Content

Fallback debugging
The core class that renders templates is: \Magento\Framework\View\
Element\Template. The getTemplateFile() method is responsible for
locating the specific file to be used in this situation.

Remember that as of Magento 2.2.1, the aforementioned class is the default


class for a block. Specifying a block’s class is no longer required as Magento
assumes this default.

Further Reading:
• Layout Instructions

To debug what is happening with the template override:

• Check to ensure that the path of the template override is correct:

app/design/frontend/MyCompany/MyTheme/Module_Name/templates/path/

to/template.phtml

• Make sure your theme is enabled.

• Set a breakpoint in \Magento\Framework\View\Element\


Template::getTemplateFile and step into $this->resolver.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 59


Magento UI

Example:
• https://lc.commerce.site/index.php/chapter23blocks

• app/code/Chapter2/Blocks

Practical experience:
• Set a breakpoint in \Chapter2\Blocks\Block\ExampleBlock. Step
through line-by-line to understand how the template resolver works.

• Create a module to change the template for the Luma theme’s menu.

• Examine the \Magento\Framework\View\Design\Fallback\


RulePool::getRule method (and associated methods).

• Step through \Magento\Framework\View\Design\Fallback\Rule\


Theme::getPatternDirs.

Email templates

Email template fallbacks are supported in the same way that regular block
templates are supported. Instead of email templates being located in the view/
[area]/templates/ directory, they are found in view/‌
[area]/‌
email.

Further Reading:
• Customize Email Templates

Translations

A fundamental and core feature of Magento is the ability to provide translations


for every string that is rendered on the website.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 60


Magento UI

Translations are stored in several places:


• In the database: translation table.

• In CSV files in your module’s /i18n directory.

• In CSV files in your theme’s /i18n directory.

• In CSV files inside the app/i18n directory.

Magento can generate a language package for your module:

bin/magento i18n:collect-phrases app/code/Chapter2/Layout

The above command will generate a translation file for your module that you
can then send to someone to translate.

Further Reading:
• Translations Overview

• Generate a translation dictionary

Cache all instances of the block, specific instance


This is accomplished by setting the cache_lifetime argument for a block. You
can specify this for each block in an argument. However, to make all instances
of a block cached, you will have to extend \Magento\Framework\View\
Element\Template and utilize this block.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 61


Magento UI

Keep in mind that a virtualType would be a perfect fit here: define the virtual
type and set the cache_lifetime. Then, utilize that virtual type in layout XML.
Note that you cannot implement plugins on a virtual type, but instances, where
this is necessary, are very rare.

Example:
• https://lc.commerce.site/index.php/chapter23blocks

• app/code/Chapter2/Blocks

• Ensure full_page cache is off, but block_html cache is on. Then, refresh
the page several times and note how the upper time does not change, but
the lower time does.

Assign an object to the email template. Render


different images depending on a locale in the email
template

$transport = $this->transportBuilder->setTemplateIdentifier($templateId)

->setTemplateOptions(['area' => 'frontend', 'store' => $storeId])

->setTemplateVars(['parameters' => 'for', 'the' => 'template'])

->setFrom($from)

->addTo($email, ‘'Joseph Maxwell')

->getTransport();

$transport->sendMessage();

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 62


Magento UI

The setTemplateVars method applies objects to the email template.


In the above example, calling getTransport generates the email
message body (see \Magento\Framework\Mail\Template\
TransportBuilder::prepareMessage).

If you want to intercept the variables before they are applied to the
email message, the easiest place is likely: \Magento\Email\Model\
AbstractTemplate::getProcessedTemplate.

Render different images depending on a locale in the email template

See \Magento\Newsletter\Model\TemplateTest::testGetProcessedTemplateArea
for an example of how this works.

{{view url="Magento_Theme::favicon.ico"}}

Further Reading:
• \Magento\Customer\Model\
EmailNotification::passwordReset

• \Magento\Email\Model\Template\
Filter::viewDirective

• \Magento\Framework\View\Asset\
Repository::getUrlWithParams

Identify the location of block instantiation

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 63


Magento UI

Practical experience:
• Hopefully, you easily know how to find this by now: if you don’t, here
is the answer. Set a breakpoint on the class Template line in \
Magento\Framework\View\Element\Template and reload a Magento
frontend page.

• Look back through the call stack to determine where the block is
instantiated. Note that everything down to the ObjectManager is standard
Magento instantiation. The next class or two would be considered
the answer.

Answer:

\Magento\Framework\View\Element\BlockFactory::createBlock

\Magento\Framework\View\Layout\Generator\Block::getBlockInstance

\Magento\Framework\View\Layout\Generator\Block::createBlock

Print out all places where Magento looks for a template

Example for template Chapter2_Blocks::override.phtml:


• app/design/frontend/SwiftOtter/Flex/Chapter2_Blocks/
templates

• vendor/magento/theme-frontend-blank/Chapter2_Blocks/
templates

• app/code/Chapter2/Blocks/view/frontend/templates

• app/code/Chapter2/Blocks/view/base/templates

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 64


Magento UI

Source: \Magento\Framework\View\Design\FileResolution\Fallback\
Resolver\Simple::resolveFile

Demonstrate an understanding of different types


of translations working together (inline, phrase in
JavaScript code, CSV file)

Inline

This happens with the now-famous __() method. This method is available
anywhere (from vendor/magento/framework/Phrase/__.php).

__('My %1 phrase (of %2 phrases) to translate.', 'first', 'many');

The above method returns a \Magento\Framework\Phrase object which


translates the input phrase when the class is cast to a string.

By default, there are three translation renders. Each one receives the output
from the previous renderer:

• \Magento\Framework\Phrase\Renderer\Translate: this
class does what we would expect from a translation renderer. It
translates the input text. Ultimately, the translation is loaded from
\‌Magento\‌ Framework\‌ Translate.

• \Magento\Framework\Phrase\Renderer\Placeholder This one parses


the placeholders (above, %1) and replaces them in the translation.

• \Magento\Framework\Phrase\Renderer\Inline If inline translation


is enabled, this returns output such that this phrase is capable of being
translated on the frontend.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 65


Magento UI

Phrase in JavaScript code

The mage/translate class is responsible for providing translations. Step


through lib/web/mage/translate.js to observe how this works.

Example:
• app/code/Chapter2/Blocks/view/frontend/templates/translation.phtml

CSV file

These are the heart of developer-supplied translations—CSV files that are easily
transferred from translator to developer allow for quick translation updates.

Practical experience:
• Create a new translation file in your module. Using en_US.csv as the default
works fine.

• Manually add a new translation into the translation table.

• Create a template that utilizes the __() translation method and set a
breakpoint there to understand how translation works.
• Set breakpoints in app/code/Chapter2/Blocks/view/frontend/
templates/translation.phtml to understand how this logic works.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 66


3
WORKING WITH
DATABASES
8 questions

Professional Developer Plus


Working with Databases

3.1 DEMONSTRATE UNDERSTANDING OF


THE ARCHITECTURAL LAYERS OF THE
DATABASE ACCESS CLASSES, INCLUDING
MODELS, REPOSITORIES, AND DATA
MAPPERS

Models, resource models, and collections in


Magento, their impact on performance. Repositories,
SearchCriteria, WebAPI, WebAPI access, extension
attributes

Example website:
• https://lc.commerce.site/index.php/chapter3database

Models

Models represent a row of data from the database. Each row, whether it is
loaded through a collection or a resource model, is represented by the model.

Custom models typically extend \Magento\Framework\Model\AbstractModel.

Models that need extension attributes (most Magento modules,


3rd-party extensions) extend \Magento\Framework\Model\
AbstractExtensibleModel.

Few, if any, true Magento data models will utilize the constructor for
dependency injection. Best practice is to have data models as a storage
container for storing data so that it is easily utilized.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 68


Working with Databases

Note:
When creating an API that returns a model, you do not have
to return “the” data model that was loaded from the database.
Feel free to create a new model and inject “the” data model
that was loaded. The new model acts as a proxy and modifies
output values. Another technique is to utilize two interfaces:
one for the internal application and one for the external
application. We will discuss this more later.

Example:
• app/code/Chapter3/Database/Model/Discount.php

Resource models

This is where stuff gets done. The resource model is what communicates with
the database. This is where you should put most custom queries. The resource
model is hooked up to the model, collection, and repository.

Resource models extend:


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

Example:
• app/code/Chapter3/Database/Model/ResourceModel/Discount.php

• Notice the method to load all the discount amounts.

Collections

A collection loads multiple entities from the database. Their use is greatly
diminished with the advent of repositories (although both share very similar

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 69


Working with Databases

purposes). One advantage of a collection over a repository for EAV entities


(products, categories, customers) is that you can specify what data is loaded in
a collection. You have little to no control over this in a repository—all attributes
are loaded. As such, for EAV entities, a collection represents a much faster
way to load data. For the API, this really doesn’t matter, and a repository is
better suited.

Collections extend:
\Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection

Example:
• app/code/Chapter3/Database/Model/ResourceModel/Discount/
Collection.php

• Notice how the getAllDiscountsOver10() method filters a custom set


of models.

Repositories

At a basic level, repositories are a combination of resource models and


collections. They save individual models but also have a defined way to load
a list of models (SearchCriteriaInterface). Their original purpose, in my
understanding, was to facilitate API interaction, but they have grown to be
useful in every part of Magento.

There are no abstract repository classes. To be available on the frontend,


repositories must implement a service contract (stored in the /Api directory).
While it would be worthless, you could build a repository that exposes no public
methods. The repository only has the number of methods and the amount
of functionality that you give to it. While many Magento repositories are full-

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 70


Working with Databases

featured, you do not have to write a repository that uses all these features. The
repository we are creating for our discount model is based on the \Magento\
Cms\Model\BlockRepository.

Example:
• app/code/Chapter3/Database/Model/DiscountRepository.php

To create a repository, you must:

• Build a service contract interface for the repository.

• Create a repository model that implements this service contract.

• Create an interface to make the \Magento\Framework\Api\


SearchResultsInterface type-friendly to your class (if you wish to build a
getList method).

• Inject:

• Resource model (to perform save / delete operations)

• Model factory (initialize before the resource model hydrates)

• Collection factory (for returning a list of results)

• \Magento\Framework\Api\SearchCriteria\
CollectionProcessorInterface to convert a \Magento\
Framework\Api\SearchCriteriaInterface into filters that a
collection understands.

• Write it up. See the example for more details.

To enable the repository to be accessed via an API, you must:

• Create etc/webapi.xml

• Create etc/acl.xml (if it is necessary to restrict access to the API calls)

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 71


Working with Databases

SearchCriteria

This is an ingenuitive and new way to locate records. You attach a long url
parameter to a request (that accepts SearchCriteriaInterface, such as a
getList method).

In our sample application, note that to get all items, simply pass a null value
for the criteria parameter.

const url = '/rest/V1/discount/?' +

'searchCriteria[filter_groups][0][filters][0][field]=' + field + '&' +

'searchCriteria[filter_groups][0][filters][0][value]=' + value + '&' +

'searchCriteria[filter_groups][0][filters][0][condition_type]=' + conditionType;

return fetch(url, {

credentials: 'include',

headers: {

'Accept': 'application/json, text/plain, */*',

'Content-Type': 'application/json'

}).then(function(response) { return response.json() });

Example:
• app/code/Chapter3/Database/view/frontend/web/js/api.js

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 72


Working with Databases

Practical experience:
• Create an API request (curl, fetch, or PHP) to request /rest/V1/products
(in the admin panel, so keep sessions in mind). Ensure you are familiar with
creating filter groups and filters.

Further Reading:
• Search using REST endpoints

WebAPI, WebAPI access


The web API is a powerful system for allowing machine interaction with
the website.

Here are a couple of concepts:

• API routes and endpoints are defined in etc/webapi.xml.

• APIs rely on service contracts. The types must be clearly specified in


the docblock.

• API configuration is saved in the cache: any updates to etc/webapi.xml


require a refresh of the config_webservice cache.

• APIs do not allow you to set cookies. If this is required (like logging a user in),
you must use a controller, which can be accompanied by a JSON response.

• You can restrict access with the <resources/> node:

• <resource ref="anonymous"/>: anyone can access.

• <resource ref="self"/>: authenticated by the session. At this point,


only the frontend session works with this.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 73


Working with Databases

• <resource ref="Chapter3_Database::discounts"/>:
authenticated with the admin ACL.

Examples:
• app/code/Chapter3/Database/etc/webapi.xml

• app/code/Chapter3/Database/etc/acl.xml

Extension attributes

Magento is built on a foundation of flexibility. Yet, in this age of strong-typing


everything, how is it possible to extend even interfaces?

Meet the mighty extension attribute. It is a brilliant solution to an age-old


problem such as this.

Extension attributes are configured in a module’s etc/extension_


attributes.xml file. Extension attributes can be scalar values or objects.
These attributes are compiled into an Extension class and interface.

Here is an example of extension attribute configuration:

<extension_attributes for="Magento\Directory\Api\Data\

CountryInformationInterface">

<attribute code="currency_information" type="Chapter3\

Database\Api\Data\CurrencyInformationInterface" />

</extension_attributes>

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 74


Working with Databases

Further Reading:
• Configure services as web API

By adding the above, Magento will add getCurrencyInformation and


setCurrencyInformation to three generated files (found in generated/
code/Magento/Directory/Api/Data/):

• CountryInformationExtension.php: the concrete class providing getters


and setters for each declared extension attribute entry.

• CountryInformationExtensionInterface.php: the contract defining


the getters and setters.

• CountryInformationExtensionInterfaceFactory.php the factory


for generating CountryInformationExtension. Because the extension
attribute classes are not generated automatically, you will utilize this class to
create an instance if necessary.

For the above extension attribute declaration, let’s make some observations:

• The generated files are in the same namespace as the for class or interface.

• The generated files replace Interface (if it exists) and add Extension,
ExtensionInterface and ExtensionInterfaceFactory to the class or
interface name.

If you are adding a new extension attribute to a class whose extension files
have already been generated, you will need to delete the generated files for
that class.

Our example will inject a dummy class into a list of directory countries.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 75


Working with Databases

Practical experience:
• Set a breakpoint in \Chapter3\Database\Plugin\
SetCurrencyInformation::afterGetCountriesInfo.

• Navigate to https://lc.commerce.site/rest/V1/directory/countries and


notice the currency_information coming through in the extension_
attributes.

• Based on our example, create another extension attribute.

How to create an entity that supports extension


attributes
Extension attributes depend on one method being specified in a class:
getExtensionAttributes. This method is automatically specified in
Magento\Framework\Api\AbstractExtensibleObject and \Magento\
\‌
Framework\Model\AbstractExtensibleModel (for EAV models). However,
you are required to override this method in your implementation to set the
correct return type in the PHP doc block.

Further Reading:
• An Introduction to Extension Attributes

• EAV and extension attributes

Here is an example from the order model:

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 76


Working with Databases

// vendor/magento/module-sales/Model/Order.php

/**

* @return \Magento\Sales\Api\Data\OrderExtensionInterface|null

*/

public function getExtensionAttributes()

return $this->_getExtensionAttributes();

I suggest you review the ExtensionAttributesFactory below to observe


how this generates the applicable files.

Practical experience:
• Set a breakpoint in \Magento\Framework\Api\Code\Generator\
ExtensionAttributesGenerator::_generateCode.

• Delete the files in generated/code/Magento/Directory/Api/Data.

• Navigate to: https://lc.commerce.site/rest/V1/directory/countries

How to implement SearchCriteria processing in the


repository::getList method
See the example in app/code/Chapter3/Database/DiscountRepository.php.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 77


Working with Databases

Further Reading:
• vendor/magento/framework/Api/
ExtensionAttributesFactory.php

How to perform bulk save operations in Magento

Note:
That this is a Commerce-only feature until Magento 2.3, when
it is available for Open Source, too.

Bulk save operations are an extension of message queues (discussed later).


This type of operation is generally bolted onto a cron job or, worse, just done
in the admin, or even worse, when an order is placed. Bulk operations defer
operations, making a better Admin user experience.

This is a tremendous feature and our example should make the learning curve of
this much easier.

To build a bulk save operation, two components are required:

• A publisher, to format the data such that it can be stored and transported to
the queueing system.

• A consumer, to load the data and perform the necessary actions. This
operation is manually started (think supervisor) to receive the messages
coming from RabbitMQ.

In reality, there are more components that are required:

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 78


Working with Databases

• Scheduler (\Queueing\BulkSave\Operations\
BulkDiscountScheduler::execute). This publishes the operation data to
the message queue.

• Merger (\Queueing\BulkSave\Operations\BulkDiscountMerger). This


merges messages together before being published.

• Consumer (\Queueing\BulkSave\Operations\BulkDiscountConsumer). This


receives messages from the queue system and takes action on them.

As you review the above code examples, you will see that a large portion of the
code is gracefully handling error cases.

Example:
• See \Queueing\BulkSave\Controller\Index\Index (this is in a
different namespace as the message queue system and does not use the
Magento Object Manager, thus no virtual types, and cannot have numbers in
the class path).

• Install RabbitMQ on your development machine.

• Note that you can utilize MySQL, but I wrote this in RabbitMQ so you get
the full picture.

• Configure your app/etc/env.php file to connect to RabbitMQ


(example below)

• Run bin/magento setup:upgrade.

• Navigate to: https://lc.commerce.site/index.php/chapter3bulksave

• Then, go to the console and run bin/magento queue:consumers:start


save_discount_price_consumer. This will begin the consumer process.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 79


Working with Databases

• Check the values in swiftotter_discounts.

• Refresh https://lc.commerce.site/index.php/chapter3bulksave and recheck


swiftotter_discounts. The values should update almost immediately.

RabbitMQ configuration in app/etc/env.php:

'queue' => array (

'amqp' =>

array (

'host' => 'localhost',

'port' => '5672',

'user' => 'guest',

'password' => 'guest',

'virtualhost' => '/'

),

I also had to enable permissions for the guest user:

sudo rabbitmqctl set_permissions -p / guest ".*" ".*" ".*"

How to extend the Magento data object (Data


API class) with an attribute that has values in a
remote system
Extension attributes. Ideally, the values for the extension attributes will be
cached (in the Magento cache or in an intermediary API). Loading values from a

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 80


Working with Databases

Further Reading:
• Bulk Operations

• How to Use RabbitMQ Queue in Extensions For Magento


2.0 EE

• Configure message queues

• Magento 2 tutorial: message queues

• How to integrate RabbitMQ with Magento 2.2.* EE

remote system could be time intensive and make an API request come to a
grinding halt.

Further Reading:
• How to Use Data-related Classes, Repositories and Data API
in Magento 2

How to extend existing WebAPI calls with a new


parameter
Adding a parameter is fairly simple but can have some consequences. If this is
a widely-used REST URL (such as saving a product), ensure that you have the
proper safeguards to prevent unintended side effects.

If you wish to add a parameter to the URL, from:

https://lc.commerce.site/rest/V1/testOverride/joseph/

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 81


Working with Databases

to

https://lc.commerce.site/rest/V1/testOverride/joseph/maxwell/

This is essentially a new API definition and should be completed accordingly.


Magento sees these as two separate URLs. If you can simply add a parameter to
the URL, this is backward compatible and should be preferred.

Practical experience:
• In your browser, visit: https://lc.commerce.site/rest/V1/testOverride/
Joseph%20Maxwell

• What is the response? What class is handling this response?

• Now, visit: https://lc.commerce.site/rest/V1/testOverride/Joseph/Maxwell

• What is the response for this?

You can also override the existing API definition by simply specifying the same
URL and method but with a different interface. You must also ensure that your
module’s sequence is after the module you are overriding.

Practical experience:

In your web browser’s developer tools, first run:

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 82


Working with Databases

fetch('https://lc.commerce.site/rest/V1/postOverride', {

method: 'post',

headers: {

"Content-Type": "application/json"

},

body: JSON.stringify({name: 'Joseph Maxwell'}),

}).then(response => response.json()).then(response => console

log(response));

What is the response? Now, run:

fetch('https://lc.commerce.site/rest/V1/postOverride', {

method: 'post',

headers: {

"Content-Type": "application/json"

},

body: JSON.stringify({firstName: 'Joseph', lastName:

'Maxwell'}),

}).then(response => response.json()).then(response => console.

log(response));

How to create a dynamic WebAPI ACL


Create a plugin for: \Magento\Framework\Webapi\Authorization::isAllowed

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 83


Working with Databases

Example:

<!-- vendor/magento/module-webapi/etc/webapi_rest/di.xml -->

<type name="Magento\Framework\Authorization">

<plugin name="guestAuthorization" type="Magento\Webapi\Model\

Plugin\GuestAuthorization" />

</type>

PHP: \Magento\Webapi\Model\Plugin\GuestAuthorization::aroundIsAllowed

The difference between extension attributes and


custom attributes
Custom attributes are loaded from the model’s EAV entity tables (ex: catalog_
product_entity_varchar). Extension attributes could be this, but rather,
they are other information structures loaded from elsewhere (database, cache,
filesystem, even remote APIs).

3.2 Demonstrate understanding of the staging


workflow

Staging modification to the Magento database


operations (row_id, entity managers)
Commerce has a feature called content staging. This allows content managers
to test and then schedule updates in the future.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 84


Working with Databases

One of the fundamental changes that arise as a result is how Magento uses
row_id as an entity table’s primary key (instead of entity_id).

entity_id is present in Open Source and represents an item (ex. product).


row_id is the version identifier that defines the version of the current row and
is the unique, primary key. created_in and updated_in are Unix timestamps
(bigint) that correspond with the id column in staging_update.

Once the *-staging modules are installed (Commerce-only), a significant


change takes place in the attribute value tables. Instead of these tables
referencing the entity_id (or having an entity_id column) they reference
the row_id column.

catalog_product_entity table:

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 85


Working with Databases

catalog_product_entity_varchar table:

staging_update table:

Staging updates are based on timestamps (bigint type). In the example


below, "1547914545" converts to Sat, 19 Jan 2019 16:15:45 +0000. By default,
created_in = 1 and updated_in = 2147483647 (\Magento\Staging\
Model\VersionManager::MAX_VERSION). These columns will never have the
same value.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 86


Working with Databases

Important points to remember:


• staging_update is the master table for new scheduled updates.

• row_id becomes the primary key.

• All attribute value tables reference the entity table’s row_id and not the
entity_id.

• There can be multiple of the same entity_id in an EAV + staging


enabled entity.

• created_in is always !== to updated_in.

• created_in and updated_in are timestamps.

Practical experience:

Locate the table that is keyed to the row_id for the products
(hint: sequence_…).

Further Reading:
• vendor/magento/module-catalog-staging

• Magento 2: Schema Changes for EE Catalog Staging

• Magento 2.1 ee Content Staging

Entity Manager

The entity manager is a framework that Magento built to provide database


interaction by configuration. They achieved the goal—but have abandoned it
due to too much configuration.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 87


Working with Databases

Information:
Study vendor/magento/module-staging/Model/
VersionInfoProvider.php.

If you wish to learn more about the entity manager, see the appendix at the end
of the book.

How does data versioning work?


Note: if you want to see what edition and version of Magento a site is running,
go to /magento_version, like: https://swiftotter.com/magento_version/

This is the deepest dive that I have taken into this mechanism in Magento.
However, I wasn’t seeing any code related to this in Magento. Doing some
research, I found that Magento released CMS version control in Magento 2.0 but
then removed in Magento 2.0.1 (vendor/magento/module-versions-cms/
Setup/UpgradeSchema.php).

As such, we will explore Scheduled Changes.

Important points:
• The created_in and updated_in columns reference the id column in
staging_update. All are Unix timestamps.

• The core entity model for Scheduled Changes is \Magento\Staging\


Model\Update.

• The \Magento\Staging\Model\VersionManager stores information


about the active version (whether with saving, or preview or active).

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 88


Working with Databases

• staging_update is the primary location for versioning information.

• \Magento\Staging\Model\EntityStaging is the central point for


scheduling or unscheduling changes.

• vendor/magento/module-versions-cms is actually the module for a


CMS hierarchy.

How does it work when creating a new scheduled update?


• \Magento\Staging\Model\Entity\Update\Save::execute is the entry
point for creating or updating a scheduled update.

• The specific action class is loaded from Magento\Staging\Model\Entity\


Update\Action\Pool. An example of this configuration is in vendor/
magento/module-cms-staging/etc/adminhtml/di.xml.

• The save action model (inheriting Magento\Staging\Model\Entity\


Update\Action\Save\SaveAction) is created.

• The executor action model (\Magento\Staging\Model\Entity\Update\


Action\TransactionExecutor) is loaded.

• A transaction is created.

• The action is executed:

• Update is either created or loaded.

• The version manager has the new scheduled update version set.

• The entity is scheduled (i.e. saved).

• See: \Magento\CmsStaging\Model\PageStaging::schedule

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 89


Working with Databases

Practical experience:
• Set a breakpoint in \Magento\Staging\Model\Entity\Update\
Save::execute.

• Open a CMS page, and create a Scheduled Update for it.

• Save the scheduled update and step through the process.

• Then, change the scheduled update and save it again.

Different possibilities of data versioning (row/table/


database level) and how this is implemented in
Magento
At the row level, data versioning uses the row_id (multiple row_id’s per
entity_id). These rows are associated with the table sequence_[entity
type] to ensure a unique value. In addition, the created_in and updated_‌in
columns for a table that is managed in content staging, references the
staging_update table.

To load a product from the database, the Magento Open Source query would
look something like:

SELECT * FROM catalog_product_entity WHERE entity_id = 1;

However, this changes significantly with Magento Commerce. Consider this


dataset with the above query. This is the result:

row_id entity_id created_in updated_in attribute_set_id type_idsku

1 1 1 1549763040 41 simple 24-MB01

2055 1 1 549763040 2147483647 41 simple 24-MB01

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 90


Working with Databases

As such, you need to load a specific row for an entity_id. Here is an example
SQL query:

SELECT `catalog_product_entity`.* FROM `catalog_product_entity`

WHERE (entity_id =1) AND (catalog_product_entity.created_in

<= '1549763040') AND (catalog_product_entity.updated_in >

'1549763040')

How do you load a product that is the current version?


You could write something yourself, or you can take the better road and let
Magento do it for you—automatically! Simply load a product from a staging-
powered repository or resource model, and Magento will add the where clause
for determining the correct staging version. Actually, you can even create
a custom Select, with the from being a staging-enabled entity table, and
Magento will add the conditions for you.

Here’s how the SQL statement renderers work:

• One of the renderers is supplied by the staging module


(\Magento\Staging\Model\Select\FromRenderer).

• This class has a stagedTables property that contains a list of all


staging-enabled tables. To add a table to this list, add your table name to
the Magento\Staging\Model\StagingList’s entities argument.

• Then, this class will load metadata from the metadata pool for this table.

• When rendering the table name, the version (vendor/magento/module-


staging/Model/VersionManager.php::getVersion) is loaded.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 91


Working with Databases

• This takes into account the __version request parameter, to specify


a version.

• \Magento\Staging\Model\ResourceModel\Update::getMaxIdByTime is
called. This method converts the requested timestamp into a \DateTime
object. (SELECT * FROM staging_update WHERE start_time <=
[TIMESTAMP] ORDER BY `id` DESC LIMIT 1). The current version or
the next most recent (but not in the future) will be selected.

• If no __version request parameter is specified, then Magento looks up


staging in the flag table. The flag_data column contains the current
staging version.

Note:
The flag table stores information that isn’t configuration or
cacheable but needs to be updated or used on a regular basis.

Practical experience:
• Schedule a product modification.

• Preview it.

• Set a breakpoint in \Magento\Catalog\Helper\‌


Product::initProduct.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 92


Working with Databases

• Follow the path through the stack trace to find where the specific version is
set. Observe how this is set and where the versions are pulled from.

Further Reading:
• vendor/magento/module-staging/Model/Select/
FromRenderer.php

• vendor/magento/module-staging/Model/Update/
VersionHistory.php

How do you load a product or entity that is in a


specified version?
First, read the above as it will provide additional context for this example.

/** @var $versionManager \Magento\Staging\Model\VersionManager **/

$versionManager->setCurrentVersionId($versionId);

$this->productRepository->getById($productId);

If you wish to disable the additions for staging, and you have access to the
Select object, add a part to the Select:

$select->setPart(‘disable_staging_preview', true);

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 93


Working with Databases

Practical example:
• Log into your Magento admin and create some staging versions for product
ID #1 (24-MB01)

• Navigate to Catalog > Example 1 (select products)

• Code found in: app/code/Chapter3/Staging

How do you create a join for a staging entity?


You join on the entity_id ($productResource->getEntityId()) column.
Here is an example of SQL:

SELECT `main_table`.*, `product`.`sku`, `product`.`created_in`,

`product`.`updated_in`

FROM `sales_order_item` AS `main_table`

INNER JOIN `catalog_product_entity` AS `product` ON main_table.

product_id = product.entity_id AND (product.created_in <=

'1547914645' AND product.updated_in > '1547914645')

Notice that joining to a staging entity involves using the


$resource‌ getEntityId(). Joining from a staging entity to staging entity’s
‑‌‌‌>‌
attribute values requires using the $resource->getLinkField().

Practical example:
• Log into your Magento admin and create some staging versions for product
ID #1429 (WS03-M-Red)

• Navigate to Catalog > Example 2 (join products)

• Code found in: app/code/Chapter3/Staging

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 94


Working with Databases

Further Reading:
• vendor/magento/module-catalog-staging/Model/
Product/Locator/StagingLocator.php

What happens when a staging update "goes live"?

Every minute the Magento cron executes the \Magento\Staging\Model\


StagingApplier::execute. This class:

• Determines the current timestamp.

• Iterates through each staging-enabled entity type.

• Finds all rows that have the created_in between the last-enabled version
and the current version (timestamp).

• Runs the applicable applier (if available) for each row.

• Then, loads up the repository and saves that item (save).

The performance implications are that each item is being saved. If there are
many items affected by the scheduled update, this could have a major impact
on the website, and possibly make the frontend unresponsive, unless varnish
has been properly configured.

The role of the entity manager


As described above, the entity manager is (was) a replacement for the abstract
resource model. It handles all CRUD operations for the more complex of
Magento’s entities. It provides tremendous flexibility and capability over
how these operations are handled. However, it was a bit too complex for
most usages.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 95


Working with Databases

High level staging implementation overview


In the PHP code, Staging-enabled entities are not modified from the Open
Source not-enabled entities. However, staging brings additional configuration
and classes to support the extra functionality.

Staging is enabled through the entity manager.

Practical investigation:

• Compare vendor/magento/module-cms and


vendor/‌ module‑cms-staging. The CMS module is the most
magento/‌
simple staging-enabled module.

• vendor/magento/module-cms-staging/etc/di.xml

Comparison of metadata pool.

Original (vendor/magento/module-cms/etc/di.xml):

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 96


Working with Databases

<!-- … -->

<type name="Magento\Framework\EntityManager\MetadataPool">

<arguments>

<argument name="metadata" xsi:type="array">

<item name="Magento\Cms\Api\Data\PageInterface" xsi:type="array">

<item name="entityTableName" xsi:type="string">cms_page</item>

<item name="identifierField" xsi:type="string">page_id</item>

</item>

<!-- … -->

</argument>

</arguments>

</type>

<!-- … -->

Modified (vendor/magento/module-cms-staging/etc/di.xml):

<type name="Magento\Framework\EntityManager\MetadataPool">

<arguments>

<argument name="metadata" xsi:type="array">

<item name="Magento\Cms\Api\Data\PageInterface" xsi:type="array">

<item name="entityTableName" xsi:type="string">cms_page</item>

<item name="sequenceTable" xsi:type="string">sequence_cms_page</item>

<item name="identifierField" xsi:type="string">page_id</item>

</item>

<!-- … -->

</argument>

</arguments>

</type>

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 97


Working with Databases

Also, note the update in the OperationPool.

Original (app/etc/di.xml):

<type name="Magento\Framework\EntityManager\OperationPool">

<arguments>

<argument name="operations" xsi:type="array">

<item name="default" xsi:type="array">

<item name="checkIfExists" xsi:type="string">

Magento\Framework\EntityManager\Operation\

CheckIfExists</item>

<item name="read" xsi:type="string">Magento\

Framework\EntityManager\Operation\Read</item>

<item name="create" xsi:type="string">Magento\

Framework\EntityManager\Operation\Create</item>

<item name="update" xsi:type="string">Magento\

Framework\EntityManager\Operation\Update</item>

<item name="delete" xsi:type="string">Magento\

Framework\EntityManager\Operation\Delete</item>

</item>

</argument>

</arguments>

</type>

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 98


Working with Databases

Modified:

<type name="Magento\Framework\EntityManager\OperationPool">

<arguments>

<argument name="operations" xsi:type="array">

<item name="Magento\Cms\Api\Data\PageInterface"

xsi:type="array">

<item name="create" xsi:type="string">Magento\

Staging\Model\Operation\Create</item>

<item name="update" xsi:type="string">Magento\

Staging\Model\Operation\Update</item>

<item name="delete" xsi:type="string">Magento\

Staging\Model\Operation\Delete</item>

</item>

<!-- ... -->

</argument>

</arguments>

</type>

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 99


Working with Databases

3.3 DEMONSTRATE AN ABILITY TO USE


DIFFERENT TYPES OF SETUP SCRIPTS IN
MAGENTO

Schema and data setup scripts, uninstall scripts,


recurring scripts, uninstall schema vs. uninstall data
Setup scripts are Magento 2.2’s way of configuring the database. While they are
primitive, in functionality, they give fairly unfettered access to the database and
can pose a danger if not treated with respect.

Setup scripts are quite similar but categorized for specific purposes (data,
schema, etc.).

Practical examples:
• Run bin/magento module:enable Chapter3_SetupScripts

• app/code/Chapter3/SetupScripts

• In PHPStorm, create a new PHP Script configuration in


Run > Run/Debug Configurations.

• Set the file to be the path to your bin/magento file.

• Set the arguments to be: setup:upgrade --keep-generated

Install / upgrade scripts

Install scripts run when the module is not found in setup_module table, and
upgrade scripts run when the module is out of date.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 100


Working with Databases

You might notice the availability of


ModuleDataSetupInterface::startSetup() and
ModuleDataSetupInterface::endSetup(). These are not necessary
to include in your setup scripts. If you step into the code (\Chapter3\
SetupScripts\Setup\UpgradeData::upgrade), you will see that the
execution for this command happens in \Magento\Framework\DB\Adapter\
Pdo\Mysql::startSetup. This method only runs the following SQL queries:

SET SQL_MODE=''"

SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0

SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO'

As you can see, calling startSetup disables foreign key checks and allows 0
not to trigger, replacing itself with the next auto increment ID.

Further Reading:
• Setup scripts in Magento 2

Recurring scripts

Recurring scripts run anytime setup:upgrade is run. These scripts run after the
schema or data scripts are executed.

Recurring schema: MyCompany\MyModule\Setup\Recurring implements


\‌
Magento\Framework\Setup\InstallSchemaInterface

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 101


Working with Databases

Recurring data: MyCompany\MyModule\Setup\RecurringData implements \


Magento\Framework\Setup\InstallDataInterface

Practical examples:
• \Chapter3\SetupScripts\Setup\Recurring

• \Chapter3\SetupScripts\Setup\RecurringData

Uninstall scripts

There is one uninstall script (combined for data and schema). The only
example in the Magento code is: \Temando\Shipping\Setup\Uninstall.
If you wish to include an Uninstall script, place it in your module’s
Setup/‌ php file.
Uninstall.‌

These scripts are executed whenever


bin/‌ [MODULE NAME] is run.
magento module:uninstall ‌

What happens when an uninstall script is executed:


data version change, deleted tables, etc.
What happens is whatever the developer writes in the code. There is no magic
to running these methods.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 102


Working with Databases

Recurring scripts and their order in the


setup:upgrade process
• Schema install / upgrade

• Schema recurring

• Data install / upgrade

• Data recurring

Practical experience:
• Create a breakpoint in:
\Magento\Setup\Model\Installer::handleDBSchemaData

Accessing areas and system configuration values in


setup scripts
Areas: \Magento\Framework\App\State (Make sure to catch exceptions as
the area code is not set by default in setup scripts.)

Store configuration values:


\Magento\Framework\App\Config\ScopeConfigInterface

Further Reading:
• Setup scripts

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 103


Working with Databases

Practical experience:
• Set a breakpoint in:
\Chapter3\SetupScripts\Setup\Recurring::install

• When running bin/magento setup:upgrade --keep-generated

• Observe how getValue behaves.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 104


4
USING THE ENTITY-
ATTRIBUTE-VALUE
(EAV) MODEL
6 questions

Professional Developer Plus


Using the Entity-Attribute-Value (EAV) Model

4.1 DESCRIBE THE EAV DATA ACCESS


PROCESS IN MAGENTO

Getting an attribute instance


Inject an attribute repository instance (\Magento\Eav\Model\
AttributeRepository) into your class and call get.

Practical example:
• \Chapter3\Staging\ViewModel\JoinProducts::getOrderItems

Impact of attribute sets. What is the impact of 10,000


attribute sets?
This will slow down response times for loading product pages (edit and listing)
in the admin panel. Each attribute set is written into the edit product response
(uiComponent). There is no limit for how many attribute sets are available
for selection.

Additionally, the user interface will also slow down as KnockoutJS is not
optimized for handling large amounts of data.

Further Reading:
• \Magento\Catalog\Ui\DataProvider\Product\Form\
Modifier\AttributeSet

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 106


Using the Entity-Attribute-Value (EAV) Model

Large number of attributes and attribute sets. 1,000


attributes in a set?
This can have a serious impact on the rendering of products on the frontend.
Remember that Magento loads the attribute metadata and then the value for
that attribute. Entities will have to store this information in memory. However,
because attribute metadata is stored in the Magento cache, the worst effects
would be seen on the first page load.

This can also cause problems with indexing in the flat entity tables. Flat entity
tables are not per attribute set and thus are an aggregation of all attributes.
You could run into MySQL maximum column limits. As of MySQL 5.6.9, the
maximum number of columns is 1017. It should be quite simple to pare back the
number of attributes that are marked as “Used in Product Listing.”

Finally, so many attributes could bring the admin product edit page to an
unusable crawl. Again, KnockoutJS is not designed for high-performance.

Further Reading:
• Error Code 1117 Too many columns; MySQL column-limit
on table

• 14.6.1.6 Limits on InnoDB Tables

How to get information about an attribute


The safest is to utilize methods in \Magento\Eav\Api\Data\
AttributeInterface to obtain information. However, the type returned
(\‌
Magento\Eav\Model\Entity\Attribute) from the attribute repository

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 107


Using the Entity-Attribute-Value (EAV) Model

contains many more methods. One helpful method is the getBackendTable()


which provides access to the table that stores this attribute’s values.

Note:
Note: \Magento\Catalog\Api\Data\
ProductAttributeInterface has many default product
attribute codes already defined in an @api. Anytime you can
use methods or constants specified in the API, your code has
less chances of breakage.

How to perform attribute operations programmatically:


assign it to a set/group, update properties, etc.
Creating attributes is quite simple: inject the \Magento\Eav\Setup\
EavSetupFactory class and call addAttribute. We will not go into all of the
options that are available.

Here are a few key points:

• If you specify a group key in the attribute settings, Magento will either create
a group or add this attribute to an existing group. Remember, an attribute set
has attribute groups which have attributes. Additionally, if the group key is
specified, the attribute will be added into all attribute sets. Do not specify the
group if you want to control which attribute sets the attribute is added to.

• Reference: \Magento\Eav\Setup\EavSetup::addAttribute

• To update an attribute, call the updateAttribute method on the


EavSetupFactory.

• EAV entities can have an addition_attribute_table specified (see

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 108


Using the Entity-Attribute-Value (EAV) Model

eav_‌entity_type). These tables (catalog_eav_attribute and


customer_eav_attribute) contain extra information about an attribute. To
specify values for these tables, simply include them in the attribute definition
when creating or updating.

Customer attributes require adding the attribute to forms, after the attribute is
created. See the practical example for how to do this.

Practical example:
• \Chapter4\EAV\Setup\InstallData::install Feel free to execute this
as a PHP script in PHPStorm. Set a breakpoint on the addAttribute line.

• \Chapter4\EAV\Setup\UpgradeData::updateProductAttribute

• \Chapter4\EAV\Setup\UpgradeData::createCustomerAttribute

Further Reading:
• How to Add a New Product Attribute

• Magento 2 Add Customer Attribute Programmatically

4.2 DESCRIBE THE DATABASE TABLES FOR


EAV ENTITIES AND HOW TO CREATE THEM
The EAV database structure, performance considerations, entity-level attribute
properties (catalog_eav_attribute)

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 109


Using the Entity-Attribute-Value (EAV) Model

There are quite a number of tables for the EAV configuration:

• eav_attribute: where the attributes are configured and stored.

• eav_attribute_label: provides translations from the admin panel for


attribute labels.

• eav_attribute_option: definition for admin-created attribute options.


Specifies sort order and attribute ID.

• eav_attribute_option_swatch: specifies swatch information for an


option value.

• eav_attribute_option_value: sets the name per store for an option.

• eav_attribute_group: groups attributes together (the collapsible items


on the product edit page).

• eav_attribute_set: defines sets or groupings or lists of attributes.

• eav_entity_attribute: associates an attribute with a group and a set.

• eav_entity_type: defines the type of entity (product, category,


customer, etc.)

Then, for each entity type, there are several tables that store values (using
catalog_product as the example):

• catalog_product_entity: defines information for the product. Note that


this table contains the fastest lookups. It is quite simple to add columns to
this table (so long as the attribute’s scope is Global, more later).

• catalog_product_entity_[datetime, decimal, int, text,


varchar]: stores values for the entity.

Note that the entity-level attribute properties are discussed above in 4.1.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 110


Using the Entity-Attribute-Value (EAV) Model

Where are catalog-specific attribute properties stored


and what are they used for?
They are stored in catalog_eav_attribute. They provide supplemental
information about each attribute. For example, the apply_to column defines
which product types that this attribute works for. Note that if a row’s value for
this column is blank, then the attribute applies to everything. However, you
can limit back an attribute’s application by setting a comma-separated list in
this column.

How does Magento store the attribute to attribute


group association?
This is stored in eav_entity_attribute. Here are the columns in the table:

• entity_attribute_id: primary key

• entity_type_id: the type of attribute. Technically, this is a repeat from


what is found in the definition in eav_attribute.

• attribute_set_id: the attribute set

• attribute_group_id: the attribute group (inside the attribute set). Note


that troubleshooting a missing attribute can lead you to an attribute group
that is assigned to the wrong attribute set.

• attribute_id: the attribute


• sort_order: in what order this attribute should appear.

What backend types are available? How do you add a


new backend type?

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 111


Using the Entity-Attribute-Value (EAV) Model

By default:
• datetime

• decimal

• int

• text

• varchar

To create a new backend type, create an attribute with the custom backend
type specified. You will need to either specify the value for the attribute’s
backend_table or you need to create a new table for that entity with
the type. For example, if you wanted to create a new JSON backend type
for the catalog_‌
product entity type, you would create a new table:
product_entity_json to host the data.
catalog_‌

Specifics around static attributes


Static attributes are an easy way to store data where it is quickly retrievable (i.e.
minimal processing overhead). The disadvantage with static attributes is that
they can only be utilized for the global scope.

You must manually create the column for the static attribute.

4.3 DEMONSTRATE AN ABILITY TO


OPERATE WITH ATTRIBUTE OPTIONS
Different ways to store attribute options. Using eav_attribute_option_* tables

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 112


Using the Entity-Attribute-Value (EAV) Model

Attribute options are loaded via the attribute’s source class. The source
class must extend \Magento\Eav\Model\Entity\Attribute\Source\
AbstractSource. Note that if you have a custom source class and you are
utilizing flat tables and your attribute is not added to the flat tables, you need to
specify some additional information. Look at the getFlatColumns() method
in the aforementioned abstract class.

If you wish to initially populate options for an attribute (and let the admin
control the values henceforth), make sure to set the input (create attribute)
or frontend_input (update attribute) to be multiselect or select. Then,
when no custom source_model is specified \Magento\Eav\Model\Entity\
Attribute\Source\Table is automatically assumed.

You can call addAttributeOption on the EavSetup class for each option you
want to add.

Further Reading:
• \Magento\Customer\Setup\
UpgradeData::upgradeVersionTwoZeroTwo

The eav_attribute_option_ table[sic]: tables that contain shared


options between different entities, pros and cons of using the table

I am unable to see how the options as specified in eav_attribute_option are


able to be shared with other attributes. Each row is associated with one specific
attribute_id.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 113


Using the Entity-Attribute-Value (EAV) Model

If you need to create shared options between entities, you must create a
custom source_model for your attribute. This will load rows from the pertinent
table and make those available in the getAllOptions method.

Why would you, as a Magento developer, add items to this table? You would do
so if you need to give the store’s administrator an easy place to manage these
values or to allow easy customization per store. However, it is very inflexible
for future updates to this information. As such, we do not use the eav_
attribute_option tables.

4.4 DEMONSTRATE AN ABILITY TO USE


NON-CATALOG EAV ENTITIES
Adding an attribute to Customer, Customer Address and Sales entities.
Making an attribute visible in the Admin or the storefront. Pitfalls in attributes
operations in non-catalog EAV attributes

See the examples for how to create these attribute types.

To make a customer or customer address attribute save, you must add the
attribute to the correct form. Also, you must specify the group = General for
non-catalog entities.

To make the inputs appear on the frontend, you must update the customer
form or customer address form templates to add the appropriate inputs.

To simplify creating an order attribute, Magento has provided a setup helper


class (Magento\Sales\Setup\SalesSetup) to streamline the process of
adding order attributes. Calling the standard addAttribute method on

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 114


Using the Entity-Attribute-Value (EAV) Model

the SalesSetup class adds a column to the sales_order table and the
sales_order_grid table (actually, for any table referenced in that class’
flatEntityTables property).
$_‌

Practical examples:
• \Chapter4\EAV\Setup\UpgradeData::createCustomerAttribute

• \Chapter4\EAV\Setup\UpgradeData::createCustomerAddressAttribute

• \Chapter4\EAV\Setup\UpgradeData::createOrderAttribute

Further Reading:
\Magento\Eav\Model\ResourceModel\Attribute
::_afterSave is where the attributes are added to the forms.
In my tests, saving an attribute via the AttributeRepository did
not add the attribute to the forms. Rather, I had to call save
directly on the attribute.

Adding an attribute to customers, saving and loading


the attribute, problems related to the save process.
What is the role of attribute sets and groups for
customer attributes?
See examples above. If you do not add the attribute to the
customer_‌form_‌attributes table, the value will not save on the frontend.

There is one attribute set for customers.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 115


Using the Entity-Attribute-Value (EAV) Model

Adding an attribute to customer addresses, the role of


the is_system property and why it only works for the
Customer Address entity
The is_system switch tells Magento whether or not an attribute’s value should
go into the custom attributes list (\Magento\Customer\Model\Metadata\
AddressMetadata::getCustomAttributesMetadata).

This switch also tells the Customer Attribute management module whether or
not the attribute can be deleted (see \Magento\CustomerCustomAttributes\
Block\Adminhtml\Customer\Formtype\Edit::_construct).

How to make a customer or customer address attribute


visible in the My Account, Checkout, and Admin pages

My Account

Override Magento_Customer::form/edit.phtml and add the HTML to make


your new input appear.

Checkout

Customer addresses are not directly visible in the checkout. You must configure
the frontend uiComponent.

Further Reading:
• Add a new field in address form

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 116


Using the Entity-Attribute-Value (EAV) Model

Admin pages

As discussed above, you must ensure that the attribute is present in the
customer_eav_attributes table (and, of course, clear the cache).

What is the purpose of the SalesSetup class and why


do you use the addAttribute method for sales entities?
See above under “Adding an attribute to Customer, Customer Address and
Sales entities.”

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 117


5
DEVELOPING WITH
ADMINHTML
3 questions

Professional Developer Plus


Developing with Adminhtml

5.1 DEMONSTRATE ABILITY TO USE ACL

Complex cases of ACL setup


Magento ACL lookups are comprised of several components:

• The admin user’s role.

• The resource needing to be accessed.

• (there is a $privileges parameter, but this is never used in Magento)

Further Reading:
• \Magento\Authorization\Model\Acl\Loader\
Rule::populateAcl

Configuration for ACL is stored in each module’s etc/acl.xml file. Here is


a sample:

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 119


Developing with Adminhtml

<?xml version="1.0"?>

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

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

<acl>

<resources>

<resource id="Magento_Backend::admin" title="Magento

Admin" translate="title" sortOrder="20">

<resource id="Chapter5_ACL::all_actions"

title="All Actions" translate="title" sortOrder="0" />

</resource>

</resources>

</acl>

</config>

Note that the admin ACL entries are always found in the
Backend::admin resource (otherwise, you will not be able to
Magento_‌
administer these entries). Below this node, you can create as complex of a
structure as necessary.

WebAPI ACL
The API is tightly integrated with the ACL. Every API route declaration can be
associated with a ACL resource (notice how the ref matches):

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 120


Developing with Adminhtml

<route url="/V1/acl" method="GET">

<service class="Chapter5\ACL\Api\AclInterface" method="getById"/>

<resources>

<resource ref="Chapter5_ACL::all_actions"/>

</resources>

</route>

Note that the entry point for the WebAPI ACL is: \Magento\Framework\
Webapi\Authorization::isAllowed. This allows a separation between the
Magento Admin ACL and the API ACL. The API authorization mechanism also
allows for anonymous and self (customer session). It does not work for the
admin session as the API always initializes the frontend session.

Note that you must use a token to access admin resources.

\Magento\Authorization\Model\Acl\AclRetriever::_canRoleBeCreatedForUserType

Further Reading:
• Authentication

• Order Admin Token

Practical experience:
• Enable Chapter5_ACL.

• First, go to: https://lc.commerce.site/rest/V1/acl. Step through to the code


where this is rejected.

• \Magento\Webapi\Model\WebapiRoleLocator::getAclRoleId

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 121


Developing with Adminhtml

• Now, modify app/code/Chapter5/api-test.php to match your


username and password.

• Then run the file (PHPStorm’s run PHP scripts works well), or:

• php -f app/code/Chapter5/api-test.php

• You will need to change the username and password to ones that work in
your test environment.

• Note that if you do not use the testing URL (https://lc.commerce.site),


you will need to update this file.

ACL process customization


Customization is done relatively easy through plugins. See below for
more examples.

Admin and API common point: \Magento\Framework\Authorization::isAllowed

API only: \Magento\Framework\Webapi\Authorization::isAllowed

How to debug an ACL record


First, ensure that your record is in Magento_Backend::admin (for admin and
API entries) and Magento_Backend::admin/Magento_Backend::stores/
Magento_Backend::stores_settings for store configuration entries (so
that it matches the configuration tree in role configuration).

• Set a breakpoint in \Magento\Framework\Authorization::isAllowed.


This is common to both the admin and API paths.

• Ensure that a user is set and that a role is specified.

• Step through \Zend_Acl::isAllowed to determine what resources are


allowed for this user.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 122


Developing with Adminhtml

Further Reading:
• Relation between acl.xml and webapi.xml

How does a row-based ACL or IP-based ACL work in


Magento?
Because you can create a plugin for the isAllowed method, you have complete
flexibility over what is allowed.

For example, to authenticate a user’s IP address, inject Magento\Framework\


HTTP\PhpEnvironment\RemoteAddress into your plugin’s __construct()
method. Then, create afterIsAllowed and check that the IP address matches.

Row-based ACL is authorizing access by row. In other words, the concept is that
specific rows in the database are authorized or denied. For example, this would
be specific products are allowed and others are denied.

Magento provides a glimpse into how this works in the AdminGws module.
Instead of using the isAllowed method, they interact with each touchpoint
throughout the Magento admin (and there are a lot). This allows more fine-
grained control to prevent users from even seeing objects that they are not
allowed to see.

Further Reading:
Study the approach that Magento takes in the Magento\
AdminGws module.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 123


Developing with Adminhtml

The connection between admin ACL and WebAPI


ACL
The connection is found in the webapi.xml file, as discussed above. ACL
resources are setup in acl.xml and then associated with specific routes in
webapi.xml.

Different ways to access WebAPI resources including


admin access

To access protected API endpoints, you must be authenticated. One way to


do this is through the /rest/V1/integration/admin/token endpoint
(token authentication). This is good for machine-to-machine interaction.

If no authentication is provided, Magento assumes guest access. If a valid


session is passed, Magento initializes the frontend session and attempts
to authenticate as a customer (note that this does not work for an admin
session).

Finally, Magento allows Oauth 1.0 (important that this is NOT Oauth 2.0).
This allows a user to configure access.

Further Reading:
• Authentication

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 124


Developing with Adminhtml

5.2 DEMONSTRATE UNDERSTANDING OF


THE ADMIN LOGIN PROCESS AND ADMIN
ACTIONS PROCESSING

Admin login, customizing and debugging issues


related to admins logging into Magento, the Admin
Action class
Magento uses an around plugin for the Magento\Backend\App\
AbstractAction::dispatch method to either log in an admin user or ensure
they are logged in.

Here is an overview of what happens:

• aroundDispatch checks:

• If the user is logged in, the ACL permissions are refreshed for the session.

• If the user is NOT logged in:

• If the form key matches and there is a login post parameter:


\‌Magento\Backend\Model\Auth::login is called with the
username and password specified in login.

• \Magento\User\Model\User::login(), then
\‌ User\Model\User::authenticate() is called.
Magento\‌

• Inside this class, the public verifyIdentity() is called. This


ensures that the password matches. It also ensures that this user
is active and is assigned to a role.

• You can use either events (admin_user_authenticate_after)


or plugins to modify the functionality of this method.

• The login is recorded to the database.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 125


Developing with Adminhtml

As you can see, there are many access points to customizing the admin
login process.

All adminhtml controllers should extend the venerable \Magento\Backend\


App\AbstractAction class. This class ensures that users are properly logged
in and the formKey or URL secretKey is validated.

Practical experience:
• Set a breakpoint in:
\Magento\Backend\App\Action\Plugin\Authentication::aroundDispatch

• Log into the admin panel.

Other examples:
• Google Backend Login

• MSP TwoFactorAuth

Debugging the login process


See above for information on how to do this.

Logging in an admin user programmatically


The process is as follows:

• Obtain a \Magento\User\Model\User class with a valid user.

• Call \Magento\Backend\Model\Auth\StorageInterface::setUser($user) to
assign the user with the current admin session.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 126


Developing with Adminhtml

• Call \Magento\Backend\Model\Auth\StorageInterface::processLogin() to
setup the session and refresh the ACL.

As long as proper security measures are followed, a developer can completely


bypass the default Magento authentication system and easily provide login
access through a different mechanism. One example would be using Google
Oauth to provide single sign-on functionality.

Customizing the login process: for example, adding


2-factor authentication
The login process can easily be customized. Above I detail the numerous places
where you can attach functionality to the login process.

In the example of 2-factor authentication, Riccardo Tempesta has created an


outstanding module that adds 2FA for any Magento adminhtml area. I
recommend using this module on every Magento 2 website. However, at least,
review the code as it is a solid implementation of this concept.

Further Reading:
• MSP TwoFactorAuth

• Specifically: ControllerActionPredispatch.php

Operations performed by the Magento\Backend\App\


Action class, for example, the secret key

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 127


Developing with Adminhtml

If you look at this class, you will see it is blank. This class extends \Magento\
Backend\App\AbstractAction.

Here are some of the adminhtml-only functionalities available:

• dispatch(): ensures that the user is logged in and that they have access
to the const ADMIN_RESOURCE method. Note that descendant controller
actions can override this const instead of specifying the _isAllowed
method. Attaching a plugin to this method ensures that your functionality
will be executed on every admin page.

• _validateSecretKey: every URL in the Magento admin has a secret key.


This prevents cross-site request forgery where a malicious script takes
advantage of the fact that Magento trusts you.

• There are quite a few view helper methods (_addContent, _addLeft,


addJs, etc.) that give the controller better access to the view layer.
_‌

Further Reading:
• \Magento\Backend\App\AbstractAction

5.3 DEMONSTRATE AN ABILITY TO CREATE


COMPLEX FORMS AND GRIDS
In this section, we will focus on a high-level overview on how to complete each
action with some demonstration and further reference points.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 128


Developing with Adminhtml

Complex forms with custom elements and with tabs.


This will be discussed later in this section.

Complex grids with custom columns and inline editing


customizations
Custom columns are created:

• Use a column tag in the uiComponent xml file.

• If you want to customize the data that is going to the column, you can
specify a class attribute that extends the \Magento\Ui\Component\
Listing\Columns\Column column.

• If you wish to utilize a custom renderer, specify the component attribute.


Your component will likely extend Magento_Ui/js/grid/columns/
column.

• You can also set a settings node, or use <argument name="data" .../>
for a custom settings tree.

Inline editing is enabled:

• app/code/Chapter5/BackendCustomization/view/adminhtml/ui_
component/custom_customer_grid.xml

• Create the columns/settings/editorConfig area.

• Add the editor/editorType configuration to each column that is editable.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 129


Developing with Adminhtml

Practical example:
• In app/code/Chapter5/BackendCustomization

• Run bin/magento module:enable Chapter5_BackendCustomization

• Go to the Magento admin > Customers > Chapter 5 Backend Customization

Further Reading:
• How inline edit works in admin ui-components grid
Magento2?

Create custom elements for forms


To create a custom form element:

• Inside your field declaration, add a formElements/component tag.

• If you want to update functionality, create a Javascript file to extend the


original uiComponent (the default is vendor/magento/module-ui/view/
base/web/js/form/element/abstract.js).

• If you want to change the HTML template, create a new HTML template and
reference that in the template property.

You can also add a button to any existing uiComponent like:

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 130


Developing with Adminhtml

<listing>

<settings>

<!-- adding a new button is as easy as: -->

<buttons>

<button name="add">

<url path="*/*/edit"/>

<class>primary</class>

<label translate="true">Edit Customer</label>

</button>

</buttons>

</settings>

</listing>

Practical example:
• app/code/Chapter5/BackendCustomization/view/adminhtml/ui_
component/custom_customer_form.xml

• See firstname field.

Further Reading:
• Declare a custom UI Component

Create a form with tabs


This is created with the fieldset component.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 131


Developing with Adminhtml

Note that, by default, Magento puts fields in different fieldsets into an array
with that fieldset’s name. For example, general is the default fieldset
name. All fields inside general will be POSTed in the $_POST['general']
array, like $_POST['general']['my_field_name']. If you create another
fieldset named customer, all fields in that fieldset would be accessed in
POST['customer']['customer_name']. The use of $_POST in the
$_‌
example is for the sake of simplicity.

If you wish to keep all POST data in the same array, specify
settings/dataScope, like:
fieldset/‌

<fieldset name="customer">

<settings>

<dataScope>data.general</dataScope>

</settings>

</fieldset>

Further Reading:
• How to add Tab in Form Ui Component

• Fieldset Component

Create a form with a grid inside of a tab


To add a grid inside of a form’s tab:

Add a htmlContent element. This element will somewhat switch the "mode"
from uiComponents to layout XML.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 132


Developing with Adminhtml

Add a block element with a class that implements \Magento\Ui\Component\


Layout\Tabs\TabInterface and extends \Magento\Backend\Block\
Widget\Grid\Extended. This is the "old" style grid (think Magento 1) but is
often used for embedding grids.

Further Reading:
• vendor/magento/module-rma/view/base/ui_
component/customer_form.xml

Forms for editing related/nested data


This is the same concept as above. Unfortunately uiComponents operate on a
fairly linear level and are not extensible beyond that.

Customization of inline editing in a grid; for example, a


file uploader
The field-type to editing field type is defined in the file below in further reading.
You can use a mixin to create a new mapping in defaults/templates/
fields/[FIELD TYPE].

Further Reading:
• vendor/magento/module-ui/view/base/web/js/
grid/editing/record.js

Bookmark filters selection for a grid

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 133


Developing with Adminhtml

To include bookmark functionality, add this to your grid uiComponent:

<bookmark name="bookmarks"/>

You can also specify the namespace in which to store the bookmark data:

<bookmark name="bookmarks">

<settings>

<storageConfig>

<namespace>my_namespace</namespace>

</storageConfig>

</settings>

</bookmark>

Further Reading:
• How to use Magento 2 UI Components

Grid meta information: adding a new column that requires a join to


another table

This happens in the DataSource. See the practical example below for a sample
on how to complete this.

Practical example:
• app/code/Chapter2/UiDemonstration/Plugin/AddProductDetailsToDataProvider.php

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 134


6
CUSTOMIZING
THE CATALOG
14 questions

Professional Developer Plus


Customizing the Catalog

6.1 DEMONSTRATE AN ABILITY TO


UNDERSTAND AND CUSTOMIZE MAGENTO
PRODUCTS

Selecting the right product type for a given


requirement (configurable with custom options, bundle
with grouped).
Here are the product types which are included with Magento:

• Simple (basic unit of inventory)

• Configurable

• Group

• Virtual (base building block for non-inventory items)

• Bundle

• Downloadable

• Gift Card (Commerce)

I find the major delimiting factors are these:

• Does the item require inventory? Simple product (yes), virtual or


downloadable (no).

• Is this item a container for other items? Group, configurable or bundle.

You can create a configurable product that has custom options. Note that you
are unable to specify a percent price type for a custom option.

However, a grouped product only allows simple and virtual product types as
participants in its selection choice (see vendor/magento/module-bundle/
etc/product_types.xml).

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 136


Customizing the Catalog

Practical experience:
• Create a configurable product and assign some custom options to it. How
does it appear on the frontend?

• While we assume we are experts in the Magento admin panel, are we? Ensure
you have tested combinations and somewhat memorized the admin panel.

To create a new product type (overview):


1. Configure the new product type in your module’s etc/product_types.
xml.

2. If the product will be saleable, add the product type to your module’s
etc/‌sales.xml in the order/available_product_type node (see
vendor‌
/‌magento/module-bundle/etc/sales.xml).

3. For any attributes that you wish to have applied to this new product type,
you may need to update the catalog_eav_attribute table. By default,
apply_to is blank and a blank value means that this attribute applies to all
product types. However, if apply_to is not blank, you need to append the
new product type to this list.

4. Configure your price renderer in the catalog_product_prices layout


handle (see vendor/magento/module-bundle/view/base/layout/
catalog_product_prices.xml).

5. Add your product type to the checkout_cart_item_renders layout


handle to configure how your item will be rendered in the cart.

6. Add your product type to the admin and frontend renderers (see the view/
[area]/layout directory in vendor/magento/module-bundle).

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 137


Customizing the Catalog

Example product declaration from the bundle product:

<!-- vendor/magento/module-bundle/etc/product_types.xml -->

<type name="bundle" label="Bundle Product" modelInstance="Magento\

Bundle\Model\Product\Type" composite='true' indexPriority="40"

sortOrder="50">

<priceModel instance="Magento\Bundle\Model\Product\Price" />

<indexerModel instance="Magento\Bundle\Model\ResourceModel\

Indexer\Price" />

<stockIndexerModel instance="Magento\Bundle\Model\

ResourceModel\Indexer\Stock" />

<allowedSelectionTypes>

<type name="simple" />

<type name="virtual" />

</allowedSelectionTypes>

<customAttributes>

<attribute name="refundable" value="true"/>

</customAttributes>

</type>

Important notes:

• Note the separate price and indexer models. Price calculates the product
price on the product page and thereafter. The indexer calculates the price
before the product page (search and category).

• The allowedSelectionTypes node determines which products can be


included in this product (see that the composite attribute is true).

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 138


Customizing the Catalog

• The customAttributes node allows you to configure specifications about


a product that can be used in filtering product types. As far as I can see, this
has nothing to do with a product model’s custom attributes.

Further study:
• vendor/magento/module-bundle/

• vendor/magento/module-grouped/

Deciding to use non-standard products: for example,


for licenses, subscriptions, courseware, or glasses.
Product relations (related, upsells).
In deciding which product type to utilize, the first thing to determine is whether
or not you can use something that already exists.

Magento product types solve for the majority of use cases but not everything.

We solved for this on SwiftOtter.com by creating a new product type: Test. This
allows us to build custom functionality surrounding the handling and layout of
this product type.

Select a product type for a subscription/subscription


bundled with a physical product
Unfortunately, the intent of this objective is unclear. Ultimately, we have two
products in use: a subscription and a physical product. The subscription will
likely be an extension of a virtual product. The grouping of the two will likely

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 139


Customizing the Catalog

be either a grouped product or a customized version (kit) where users cannot


select how many of each product they want.

Select a product type for courseware


This objective is also unclear. If the courseware will be built into Magento, you
will begin building with a virtual product (remember, no inventory, so a simple
product is not necessary). If the courseware is downloadable, then use the
product type with that same name.

Select a product type for glasses with a prescription


This would be a simple product. You can upload the prescription with a
custom option.

Compare custom options with configurable products


Custom options are a great way to customize a specific product (unit of
inventory). This would include engraving a name on a coffee mug or powder
coating a steel fitting for a surcharge.

Configurable products are a way to choose a specific unit of inventory (simple


product). The product is not being customized but rather mapped (1-1) to the
unit of inventory.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 140


Customizing the Catalog

Configurable product with custom options for the


associated simple products
Simple products with associated options are not allowed in a configurable
product. If custom options are needed (by default), they must be applied to the
configurable product and not the child.

Bundled product with custom options for its associated


simple products
Bundled products do not allow simple products with custom options.

What is the related products database structure?


Understand the performance impact of having many
related products
Related products are stored in catalog_product_link. The types of product
relations are stored in catalog_product_link_type. The definition for
relationship attributes is stored in catalog_product_link_attribute. The
values for these attributes are stored in catalog_product_link_attribute_
[decimal, int, varchar].

Having a large number of product relationships will slow down both editing the
product as well as the product view page (possibly the shopping cart page, too).

Compare related with upsells


Upsells are a bigger and better product that (hopefully) the merchant will make
more money selling.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 141


Customizing the Catalog

Related products are an add-on product that complements the product being
viewed. These products are easily added to the cart with a checkbox.

Further Reading:
• Related Products

• Up-sells

Programmatically access related products, custom


options, configurable parameters

Related products:

The most performant way to get the list of related products is to go to the
Link resource model: Magento\Catalog\Model\ResourceModel\Product\
Link::getChildrenIds($parentId, $typeId). You can also load specific
relation types through the Magento\Catalog\Model\Product class (for
example, getUpSellProductCollection()).

Custom options:

The Magento\Catalog\Model\Product class offers the getCustomOptions


and getOptions methods. Additionally, you can use \Magento\Catalog\
Model\Product\Option::getProductOptionCollection() to obtain the
custom options for a specific product.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 142


Customizing the Catalog

Configurable parameters:

A very performant way to retrieve a configurable product’s children is with the


getChildrenIds() method in \Magento\ConfigurableProduct\Model\
ResourceModel\Product\Type\Configurable.

The \Magento\ConfigurableProduct\Model\Product\Type\Configurable
class contains easy ways to obtain information about the attributes and
product that are associated with a configurable product. Note that this class
can be accessed from a product’s $product->getTypeInstance() method.
However, blindly assuming that you are working with a Configurable class may
yield some PHP fatal errors, so ensure you do proper type checking.

Here are some of the most important methods:

• getUsedProductAttributes: this method returns the associated


attributes for a configurable product.

• getUsedProducts: This returns a list of all associated products. The


intersection of products and selected attributes is what determines the final
select product.

Dynamic related products


To make a related product list truly dynamic, the best solution is to use plugins
for the applicable method in the Magento\Catalog\Model\Product class
(getCrossSellProductCollection, getUpSellProductCollection and
getRelatedProductCollection). With these methods, you can entirely
replace the collection that is being used.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 143


Customizing the Catalog

6.2 DEMONSTRATE AN ABILITY TO


PERFORM COMPLEX OPERATIONS WITH THE
MAGENTO PRICING FRAMEWORK

Understand the pricing calculation and rendering


framework. Which classes are involved in rendering/
calculation?
Prices are figured in the \Magento\Catalog\Model\Product\Type\Price
class, or a derivative thereof. There is no interface to define a standard structure
for these classes.

There are several types of prices (simple product):

• Price: the basic value that is loaded from the database.

• Tiered price: a discount that is available based on the quantity ordered,


website, and customer group.

• Special price: a price reduction that is in effect for a specified period of time.

• Base price (accepts qty parameter): minimum of the tiered price and
special price.

• Final price: base price plus options amount.

Practical example:
• Set a breakpoint on line ~33 (renderAmount), and step through the chain for
each product type.
• vendor/magento/module-configurable-product/view/base/
templates/product/price/final_price.phtml

• Product price types:


• \Magento\Catalog\Model\Product\Type\Price

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 144


Customizing the Catalog

• \Magento\ConfigurableProduct\Model\Product\Type\Configurable\Price

• \Magento\Bundle\Model\Product\Price

• \Magento\Downloadable\Model\Product\Price

• \Magento\GiftCard\Model\Catalog\Product\Price\Giftcard

• \Magento\GroupedProduct\Model\Product\Type\Grouped\Price

Configurable product

Calling getPrice ("Price" above) checks to see if a child product has been
set. If it has, the price is calculated based on the child product. Otherwise, 0
is returned.

Bundle product

Overrides the Final Price calculation to add the total bundle items price to the
base price. Remember that bundle products still have a base price.

Downloadable product

Overrides the Final Price calculation to allow downloadable item links to be


purchased separately.

Giftcard

Overrides Price and Final Price to specify the custom option value for the
amount that was specified.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 145


Customizing the Catalog

Grouped

Overrides the Final Price calculation to add the totals for the associated
(child) products found in the associated_product_[child product id]
custom option.

Special price

Special pricing has two extra fields: start and stop dates for the new price. You
might notice that these fields are missing in Commerce. The reason is that
Commerce uses the staging start and stop dates. As such, the special_‌from_
date and the special_to_date fields are loaded the same as
normal attributes.

Further Reading:
• Magento\CatalogStaging\Observer\
UpdateProductDateAttributes::execute

Price rendering

To render a price, Magento has created the catalog_product_prices


layout handle (vendor/magento/module-catalog/view/base/layout/
catalog_product_prices.xml). A good example of how to take advantage
of this functionality is found in the \Magento\Catalog\Block\Product\
ListProduct::getProductPrice method:

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 146


Customizing the Catalog

$this->getLayout()->getBlock('product.price.render.default')->render(

\Magento\Catalog\Pricing\Price\FinalPrice::PRICE_CODE,

$product,

'include_container' => true,

'display_minimal_price' => true

);

Through a roundabout way (vendor/magento/module-catalog/view/base/


layout/empty.xml) this handle is included on every page in the frontend.

Magento then allows you to specify specific renderers and templates for each
product type.

Default price renderers:

• Default: Magento\Catalog\Pricing\Render\PriceBox

• Amount: Magento\Framework\Pricing\Render\Amount Used to render


an amount. For example, on the product list page (category), this renders the
"As low as: $12" for configurable products.

• Final Price: Magento\Catalog\Pricing\Render\FinalPriceBox As


guessed, this ensures a product is salable, then it prints the final price for
a product.

• Configured Price: Magento\Catalog\Pricing\Render\ConfiguredPriceBox

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 147


Customizing the Catalog

There is a specific order for loading these renderers (simple product type as
an example):

• simple/prices/final_price/render_class

• simple/default_render_class

• default/prices/final_price/render_class

• default/default_render_class

What is the role of indexing? How do different price


modifiers work together?
Indexing calculates the prices, and range of prices, for products. The goal of this
is to eliminate redundant pricing calculations and, instead, store these values in
the database.

The product indexer inserts prices (originating in \Magento\Catalog\


Model\ResourceModel\Product\Indexer\Price\Query\
BaseFinalPrice::getQuery) into a temporary table. Then, the pricing
modifiers take over and apply their logic and adjustments to the values in this
table. Once the pricing modifiers are complete, all items are moved from the
temporary table, ultimately, to the final price index table.

Pricing modifiers:

These modify and adjust prices that are visible (see \Magento\
Catalog\Model\ResourceModel\Product\Indexer\Price\
BasePriceModifier::modifyPrice). For example, if products are to be
hidden when they are out of stock, the CatalogInventory adjuster deletes
indexed prices for all out of stock items.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 148


Customizing the Catalog

• Magento\CatalogInventory\Model\Indexer\
ProductPriceIndexFilter

• Magento\CatalogRule\Model\Indexer\ProductPriceIndexModifier

• Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\
CustomOptionPriceModifier

Practical experience:

Create a PHP Script runner in PHPStorm to execute bin/magento


indexer:reindex catalog_product_price.

Set a breakpoint in Magento\Catalog\Model\ResourceModel\Product\


Indexer\Price\SimpleProductPrice::executeByDimensions.

You can also set breakpoints in each class that implements Magento\
Framework\Indexer\DimensionalIndexerInterface.

How is a price calculated on the product detail page,


the product listing page? Price calculation classes.
Relation to the price indexer
Pricing on the product detail page (and all pages thereafter in the customer
journey) is calculated on-the-fly.

Pricing on the product listing page is loaded from the index.

As far as I can see, there is no connection of price calculation classes to price


indexing. The reason for this is they approach the price calculation problem
from two angles and are used to solve two different problems.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 149


Customizing the Catalog

Price calculation classes operate on additional information. For example, they


utilize the quantity to determine the product’s final price. They use custom
options which detail the child products included to figure how much the
children cost.

Price indexing is used to display an initial price, very fast, on lists of products.
There, the extra information does not matter.

Exception: when previewing category pages in Commerce staging, note that


the indexers are bypassed.

The Magento\Framework\Price[sic]\* component and


its extension in the Catalog module
All Magento pricing entities extend the \Magento\Framework\Pricing\
Price\AbstractPrice (which implements \Magento\Framework\Pricing\
Price\PriceInterface).

In the Catalog module, TierPrice, CustomOptionPrice, SpecialPrice,


RegularPrice, FinalPrice and ConfiguredPrice all extend this class.

Practical experience:
• Set breakpoints in the following, and browse the frontend:

• Magento\Framework\Pricing\Render::render

• Magento\Framework\Pricing\Render::renderAmount

• Magento\Framework\Pricing\Render\Amount::_toHtml

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 150


Customizing the Catalog

How to render a product price: which class to use,


which data needs to be provided
See above discussion on "Price rendering" that details how to accomplish
rendering a price.

Indexed price vs. prices calculated on the fly


See above discussion for the differences that these offer.

Price configuration: tier prices, special price, custom


option, configurable adjustment, catalog rules,
cart rules

Tier prices

Tier prices are a way to provide discounts at quantity. Prices are configured for
combination websites and customer groups (or neither).

Special prices

Special prices is another way to say "sale." In Open Source, there are three
fields: Special Price, Special Price From (start date), and Special Price To (end
date). With Commerce, there is only Special Price as Scheduled Updates are
used to configure the start and end dates.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 151


Customizing the Catalog

Custom options

These allow a visitor to change information about a product. For example,


customers can specify initials to be engraved on a widget using a custom
option. These can appear similar to a configurable product in that they both
offer a drop-down menu selection (in addition to text and image upload).
The general rule is that custom options make modifications to a specific unit
of inventory.

Configurable adjustment

Configurable products are a way to aggregate simple (child) products each


of which represents a specific unit of inventory. Once the options on a
configurable product are selected, the configurable product is simply a wrapper
for the chosen child product. These are different from custom options because
the configurable product determines the unit of inventory to ship. Custom
options modify the unit of inventory to be shipped.

Catalog rules

Catalog rules adjust pricing before items are added to the cart. User-defined
attributes, categories and attribute sets are available for selection as a
condition. Action is the discount.

Cart rules

Cart rules adjust pricing after items are added to the cart. These are applied by
website and customer group. You can offer a coupon code.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 152


Customizing the Catalog

If you wish to apply a cart rule by customer segment, you can specify this
as a condition. In addition to product attributes—total, weight, and shipping
destination are available as conditions.

One confusing point is that both conditions and actions have conditions.
The conditions tab determines whether or not to activate this price rule. The
actions tab defines which products to activate the rule for. A good example
is free shipping for specific products. Maybe some products are too heavy to
economically ship. You want to calculate live rates for the heavy products, but
exclude the tally for the light products.

Practical experience:
• Create a product with a custom option.

• Create a configurable product: can you add custom options to a


configurable product?

• Create a catalog rule. What conditions are present? How can these
conditions be modified?

• Create a cart price rule. Offer free shipping on just women’s jackets. Shipping
should calculate for all other products added to the cart.

How to change the price rendering process for a given


product type/product instance
Because Magento utilizes a layout handle for product pricing, changing the
base block to render prices is easy. Additionally, you can use plugins and
override templates.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 153


Customizing the Catalog

Further Reading:
• Default price renderer: Magento\Catalog\Pricing\
Render\PriceBox

• Default final price renderer: Magento\Catalog\Pricing\


Render\FinalPriceBox

• Default configurable price renderer: Magento\Catalog\


Pricing\Render\ConfiguredPriceBox

• vendor/magento/module-bundle/view/base/layout/
catalog_product_prices.xml

• Magento\Bundle\Pricing\Render\FinalPriceBox

How to add a custom price adjustment to modify the


calculation process. What happens if the price index is
not aligned with this change?
Plugins. They are one of the best inventions since the invention of sliced bread
(at least in Magento-land). Or, if you need access to protected methods, you
can create a preference to entirely replace the calculation.

Remember, calculations are used for the product page and beyond in the
customer journey. If these changes are not synchronized, customers will see
one price on the category page and another on the product page.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 154


Customizing the Catalog

6.3 CUSTOMIZE CATALOG PRICE RULES

Programmatically create a catalog price rule, the


impact of catalog price rules on performance,
extending catalog price rule conditions with
custom entities

Programmatically create a catalog price rule

To create a catalog price rule, you follow these steps:

• Replicate the data structure from the $_POST for creating a price rule in
admin (see practice example).

• Feed this information into an empty price rule model and call loadPost.

• Save it.

Practical example:

Create a catalog price rule programmatically. The basic Magento example


is found in: \Magento\CatalogRule\Controller\Adminhtml\Promo\
Catalog\Save::execute. Set a breakpoint here, and create a rule in the
admin panel. This will give you the data structure to pattern in creating your
own rule.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 155


Customizing the Catalog

Impact of catalog price rules on performance

Catalog rules are indexed. They are also staging enabled. As such, there can be
a very large number of rows in the catalogrule* tables. The indexers combine
the results in the catalogrule_product_price with the other indexed prices.

The greatest performance impact would be when calculating product prices on


the product page. If, for some reason, thousands of price rules are used, this
could cause all uncached product pages and shopping cart / checkout pages to
load slow.

Further Reading:
• \Magento\CatalogRule\Model\Rule::calcProductPriceRule

Extending catalog price rule conditions with custom entities

Conditions are found in \Magento\CatalogRule\Model\Rule\


Condition namespace. To add or adjust price rule conditions, you
would tap into \Magento\CatalogRule\Model\Rule\Condition\
Combine::getNewChildSelectOptions.

6.4 DETERMINE HOW TO USE MAGENTO


CATEGORIES

Advanced category features: hierarchy, custom


attributes, impact on performance

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 156


Customizing the Catalog

Hierarchy

Categories are able to be ordered in a parent / child hierarchy (you are


probably surprised to hear this). The hierarchy information is stored in
the catalog_‌
category_entity table. The pertinent columns are path,
position and level. The base category is always ID #1 and is not visible in the
admin panel: all categories inherit from ID #1. The path is the path of the entity
IDs (not row IDs).

Custom attributes

Categories like other EAV entities use an attribute set and have attributes
assigned to them. When creating most attributes, category attributes included,
the attribute is automatically added to every applicable attribute set.

The big difference for category attributes is you need to create the
uiComponent XML to make the attribute visible on the category edit page.

Further Reading:
• Add a Category Attribute

Practical example:
• app/code/Chapter4/EAV/view/adminhtml/ui_component/category_form.xml

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 157


Customizing the Catalog

What happens if a project has many categories?


Having many categories will affect the performance of the edit category page
in the admin. On the frontend, navigating from one to another category should
have little performance impacts unless a category has a very large number of
sub-categories and filtered navigation is active.

One of the best ways to improve performance in categories is to enable the


flat tables (Store > Configuration > Catalog > Catalog). Unlike products, flat
category tables contain all attributes. Additionally, the source model does
not define the column structure. This is determined in \Magento\Catalog\
Model\Indexer\Category\Flat\AbstractAction::getEavColumns.
Also note that having a massive number of attributes can bump into MySQL
column limits, causing unintended errors or functionality (Magento has no
limiting mechanism).

Dynamic rules for the order of products in category


Dynamic rules for the order of product in a category are found in the
Magento\VisualMerchandiser\Model\Sorting namespace and inherit
\‌
\‌
Magento\VisualMerchandiser\Model\Sorting\SortInterface.

Unfortunately, the list of dynamic sorting attributes is hardcoded so you will


need to use plugins to adjust the items in the list of sorting attributes.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 158


Customizing the Catalog

The category is_anchor attribute and its effect on


performance
is_anchor does two things:

• Turns on layered navigation. This helps customers find products easier.

• Displays all products in child categories in this category. The benefit of this is
that product lists can be "drilled-down" by category or attribute. The
downside is that effectively managing the products displayed can become
unwieldy. This can contribute to poor user experience as products are not
curated and organized.

Further Reading:
• Anchor categories and position sorting explained

• What is 'best practice' when setting anchor on multi-level


categories?

6.5 DEMONSTRATE AN UNDERSTANDING


OF CATALOG INDEXERS

The indexing framework, the price indexer, the


inventory indexer, the EAV indexer. How does Magento
use indexed data?
Indexers are declared in the etc/indexer.xml file. Here is an
example declaration:

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 159


Customizing the Catalog

<indexer id="catalog_product_price" view_id="catalog_product_price"

class="Magento\Catalog\Model\Indexer\Product\Price">

<title translate="true">Product Price</title>

<description translate="true">Index product prices</description>

</indexer>

You can configure an indexer to depend on another indexer using the


dependencies node (see vendor/magento/module-catalog-rule/etc/
indexer.xml). For example, the catalogrule index is reindexed before the
catalog_product_price index.

The list of indexers is available in the indexer collection \Magento\Indexer\


Model\Indexer\Collection. If you need to add an indexer but only enable
it in specific conditions (like enabling a module), you can put a plugin on the
Magento\Indexer\Model\Indexer\Collection::getItems method.
\‌

Practical experience:
• Create a PHPStorm PHP Script debug configuration to execute this
command: bin/magento indexer:reindex catalog_product_price.

• Set a breakpoint on the first line of \Magento\Indexer\Console\


Command\IndexerReindexCommand::execute.

The big picture is that the indexers are represented by the \Magento\
Indexer\Model\Indexer. There are three methods relating to updating index
rows: reindexAll, reindexRow and reindexList.

The action instance (what does the work) must implement


\‌
Magento\‌
Framework\Indexer\ActionInterface.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 160


Customizing the Catalog

Important Note:
Running the indexer too often can impact the user experience.
Prices might be $0, for example, if the page is loaded while the
new price indexes are being inserted into the price index table.

Price Indexer

The price indexer determines the lowest price from all of the pricing sources
(price, special price, tier price, catalog price rules). This data is stored in
catalog_product_index_price.

The price indexer iterates through each product type’s indexerModel entry.
These instances should implement \Magento\Framework\Indexer\
DimensionalIndexerInterface (although there is backward compatibility
not to use this method).

What are dimensions? Dimensions are a way to execute the indexer in multiple
threads (processes) so indexing is run in parallel for each product type. Since
Magento 2.2.6, indexers can be executed in multiple threads separated by
Website and Customer Group (see \Magento\Catalog\Model\Indexer\
Product\Price\DimensionModeConfiguration). The PHP process is forked
into as many child processes as necessary per indexer.

To run a reindex using parallel dimensions, set the MAGE_INDEXER_THREADS_


COUNT=3 before running the reindex process.

This is especially helpful to ensure that large catalogs with many tiered prices
are indexed efficiently without taking down the entire site.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 161


Customizing the Catalog

Indexers are then run in batches (see \Magento\Framework\Indexer\


BatchProvider::getBatches) to prevent memory errors.

Pricing data is utilized in the category view.

Further Reading:
• View indexer status

Practical experience:
• Set a breakpoint in each method in \Magento\Catalog\Model\Indexer\
Product\Price. How can you trigger each method (reindexAll,
reindexRow and reindexList)?

• Step through the creation of the price indexes.

• How does setting the MAGE_INDEXER_THREADS_COUNT environment


variable make a difference in the reindex process?

• Set a breakpoint at the end of \Magento\Catalog\Block\Product\


ListProduct::initializeProductCollection.
• Then, run $collection->getSelect()->__toString(). You can
observe the SQL joins that include the indexed tables.

Inventory indexer

The inventory indexer determines the number of items in stock and whether or
not the item is available to be ordered.

The primary indexer class is \Magento\CatalogInventory\Model\Indexer\Stock.


Each type of index operation (full, list, ID) has their own indexing class. Each

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 162


Customizing the Catalog

product can specify its own stock indexer in the stockIndexerModel class.
This is utilized in bundle, configurable, and grouped products because their
stock status is dependent on the status of child products.

The EAV indexer

This is used to aggregate attributes for search. With the introduction of


ElasticSearch for Open Source in Magento 2.3 and the deprecation of MySQL
search, this indexer will become irrelevant.

Estimate indexing customization efforts when making


architectural decisions. Estimate indexing process
complexity and time for different given conditions. The
impact of indexing for frequent catalog updates.
Experience is necessary to achieve this objective as there is more information
than can be written here.

Indexing customizations are among the more difficult to make in Magento 2 and
to do it right. The majority of indexing operations aggregate data with complex
SQL queries. See \Magento\Catalog\Model\ResourceModel\Product\
Indexer\Price\DefaultPrice::getSelect for an example of such a query.

If indexing is occurring frequently, browsing the category pages can become


unusable. That is because some indexing tables are truncated and data is copied
back in. If a customer browses to the category page while the tables are empty,
the INNER JOIN queries will eliminate results, and the empty category listing
will be cached by the full-page caching.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 163


Customizing the Catalog

What steps does Magento perform when indexing?


What is the role of the indexer_state table
The steps that Magento takes are outlined in detail above.

The indexer_state table stores the state of the indexers (aptly named). There
are three states: working, valid and invalid.

How to register a new indexer


Declare the indexer in the etc/indexer.xml file. Your indexer must implement
\Magento\Framework\Indexer\ActionInterface.

How does the price indexer work? Which events


trigger it? How do different product types declare
their indexers?
Most of this is covered above, except for: "which events trigger a price indexer?"

• Command line indexer:reindex

• Cron, for rows that have been updated.

• Saving a product.

• Mass updating products.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 164


Customizing the Catalog

Practical experience:
• Set a breakpoint in \Magento\Catalog\Model\Indexer\Product\Price::executeRow.

• Save a product.

• Evaluate the call stack—how does the indexer get triggered?

How important is an order of indexers in price


indexing?
The order is very important. Price indexers are dependent on the catalogrule
indexer successfully completing. Otherwise, the pricing will not take into
account these rules.

Inventory indexer overview, inventory indexing for


different product types
This is covered above.

What is the scope of a price in the price index? How


difficult is it to extend the scope of an index?
Magento’s default is that prices are in the global (default) or website scope.
However, all of the pricing in the catalog_product_index_price table is for
the website scope.

Changing the scope to, say, the store view would involve changing quite a
number of touchpoints. In regards to indexing, you would need to update all
pricing index tables (and the SQL queries to populate the data). This would likely
involve adding an extra join or query parameters.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 165


Customizing the Catalog

Keep in mind that price modifiers are available to adjust prices


once they are in the temporary price index table (see \Magento\
Catalog\Model\ResourceModel\Product\Indexer\Price\
SimpleProductPrice::executeByDimensions).

This is an example of a SQL query: \Magento\Catalog\


Model\ResourceModel\Product\Indexer\Price\Query\
BaseFinalPrice::getQuery

Impact of many stores/websites on price indexing


Every website (not store) that is present will increase the number of rows in the
catalog_product_price_index table.

There are four conditional columns: entity_id (product ID),


group_id, website_id and tax_class_id. To figure the number
customer_‌
of rows in the table, multiply the number of valid entries for each of these
columns (except tax class). If you have 2,000 products, 4 customer groups, and
2 websites: 16,000 rows. If you have 3 websites: 24,000 rows.

As you can see, the number of rows can grow exponentially.

Custom price modifiers: Pros and cons of customizing


the index versus adjusting the native features like price
rules or custom options
Anytime you can avoid modifying price indexes, you are saving yourself
maintenance. While it is possible to do so, utilizing or repurposing native
features is often the best way.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 166


Customizing the Catalog

The upside to customizing the index is that price lookups will be faster. The
downside is it takes more work to accomplish this.

Remember that indexing is useful only for pricing and information shown on
category and search pages (also in related/cross-sell/up-sell).

6.6 DEMONSTRATE UNDERSTANDING OF


CATALOG STAGING AND ITS IMPACT ON
THE SYSTEM

Flow modifications introduced by staging (triggers,


row_id, data versions). Staging-related modifications
of the indexing process
I am unable to find any triggers related to staging (they are present when
setting the indexer to reindex on save).

The row_id primarily affects the flat tables (see \Magento\CatalogStaging\


Plugin\Helper\Product\Flat\FlatColumnsDefinition and \Magento\
CatalogStaging\Plugin\Helper\Product\Flat\FlatIndexes).

However, the question you might ask is: "How does the indexer only
index the active product?" The answer was discussed a few chapters
back. There is a Select renderer (\Magento\Staging\Model\Select\
FromRenderer::render) that adds the filters for the currently-active
product version.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 167


Customizing the Catalog

Further Reading:
• Magento 2 Partial Index

This was also discussed previously. When joining in the catalog_product_


entity table, Magento will automatically add the appropriate filter. However,
when joining a product to attribute values, you must join on the row_id and not
entity_id.

Catalog triggers
See above.

6.7 DEMONSTRATE AN UNDERSTANDING OF


THE PRODUCT SEARCH FRAMEWORK

How to customize Elastic search


ElasticSearch is found in:

• vendor/magento/module-elasticsearch

• This module depends on the official ElasticSearch PHP Composer package.

While you can customize the Magento portion of the ElasticSearch


implementation, the only official ElasticSearch customization is configuring stop
words. Stop words are a language’s most-used words but are the least relevant
for searches (example: a, and, the, their).

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 168


Customizing the Catalog

Read the DevDocs link below to understand how to update stopwords for a
given locale.

Further Reading:
• Install and configure Elasticsearch

• Configure Elasticsearch stopwords

6.8 DEMONSTRATE UNDERSTANDING OF


IMPORTING PRODUCTS AND CATEGORIES
IN MAGENTO

Frequent imports, massive imports, import with many


attributes

Background:

Before we can understand the effects of imports at scale, we must look at what
happens in an import.

• Product imports happen in: \Magento\CatalogImportExport\Model\


Import\Product::_saveProducts
• When importing products, this is what happens for all products:

• Categories are created (example: Default Category/Women/Bottoms/


Shorts,Default Category/Collections/Erin Recommends,Default
Category would create each category path that is separated by a comma).
These IDs are assigned to a cache for later use.

• Images are copied from pub/media/import to pub/media/catalog.

• Tax class is created if it doesn’t already exist.

• The row data is converted to attribute values (via the source model, see

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 169


Customizing the Catalog

\‌
Magento\CatalogImportExport\Model\Import\Product\Type\
AbstractType::prepareAttributesWithDefaultValueForSave). Note
that commas in unintended places can break the import.

• The row data is iterated:


• Attribute is loaded.

• Attribute value is converted to a database-friendly value.

• \Magento\CatalogImportExport\Model\Import\
Product::saveProductEntity
• In before plugin, Staging sets the created_in and updated_in fields
to defaults.

• Data is inserted into the catalog_product_entity table.


• \Magento\CatalogImportExport\Model\Import\Product::_
saveProductWebsites

• Product IDs are associated with websites in the


catalog_‌ product_‌ website table.

• \Magento\CatalogImportExport\Model\Import\Product::_
saveProductCategories
• Products IDs are associated with categories in the
catalog_‌ category_‌ product table.

• \Magento\CatalogImportExport\Model\Import\Product::_
saveProductTierPrices
• Tier pricing is inserted into the database.

• \Magento\CatalogImportExport\Model\Import\Product\
MediaGalleryProcessor::saveMediaGallery
• Media gallery is inserted or updated.

• \Magento\CatalogImportExport\Model\Import\
Product::_‌
saveProductAttributes
• Attributes are inserted or updated en masse (not line-by-line).

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 170


Customizing the Catalog

• catalog_product_import_bunch_save_after event is triggered

• Generates URL rewrites.

After the above process happens, each product type can run custom actions
(for example, to automatically associate parent configurable products with
children). Running these actions after the initial import ensures that all SKUs are
present for their association.

Links are then saved.

Inventory is saved. This is when the catalog_product reindex process happens.

Product option options are saved.

There are several after plugins for the primary import method (\Magento\
ImportExport\Model\Import::importSource). This is an example where
the sort order for plugins is critical.

• All product-related indexes are invalidated (twice).

• Catalog permissions are imported.

The reindex that runs with the every-one-minute cron will pick up these
changes and reindex them.

Frequent imports

The risk with frequent imports is largely dependent on their size and how
much data is being inserted. Frequent imports with few products would have
little impact on website performance. But, if there are many attributes, site
performance could suffer due to all of the attributes needing to be loaded.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 171


Customizing the Catalog

Massive imports

Magento has streamlined this as best as possible to use mass-inserts and


updates. However, these new imports will need to be re-indexed and that is
likely the biggest sticking point.

Import with many attributes

This could be problematic no matter where the attributes are used in Magento.
For the import, Magento loads these attributes in from the database and stores
them in memory. There is the potential that you could run into memory limit
errors if there are too many attributes.

Issues related to a frequent import, for example every


minute
See discussion above.

Compare importing products using the native import,


save by model, or custom SQL
Native import should be the most reliable as the attribute’s data is validated,
non-existent categories are created, and media files are properly moved around.
Additionally, Magento provides access points to ensure that all systems are
properly adjusted (options, configurable associations, related products).

Save by model is the slowest approach. But, it is quite easy to do. However, the
indexing is called for each product that is created.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 172


Customizing the Catalog

Custom SQL would be slightly faster than native import. The challenge is
ensuring that all data is present and properly inserted into the database. Also,
one must be careful that exceptions (for example, missing categories) are
handled appropriately.

Specifics of importing a catalog with many attributes


and attribute sets
If there are a large number of attributes, spread out over many attribute
sets, you might consider breaking up the imports by attribute set. This will
reduce the number of attributes loaded in memory and might resolve out of
memory issues.

Importing categories and product relations


Categories are imported by populating the categories column. Place the path
of category names, each separated by a "/", with category paths separated by a
",". Magento will create categories with these names.

Keep a couple of things in mind:

• Trailing spaces are not trimmed.


• Magento will try to create a category even for a blank name (ex. accidentally
adding in two slashes "//").

• Putting commas into category names will break the category paths.

Product relations are imported in the respectively-named columns. These are


comma-separated.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 173


7
CUSTOMIZING
THE CHECKOUT
PROCESS
10 questions

Professional Developer Plus


Customizing the Checkout Process

7.1 UNDERSTAND THE MAGENTO QUOTE


ARCHITECTURE AND CUSTOMIZING QUOTE-
RELATED FUNCTIONALITY

Quote-related objects, total models and the price


calculation process, the add to cart process, custom
add to cart operations, customizations of the price
calculation, taxes and discount, various display settings

Quote-related objects

Quote functionality is in the Magento\Quote namespace. Here are the most


notable classes:

• Quote entity: \Magento\Quote\Model\Quote


• Product (item): \Magento\Quote\Model\Quote\Item

• Address: \Magento\Quote\Model\Quote\Address

• Shipping Rate: \Magento\Quote\Model\Quote\Address\Rate

• Item (items that are assigned to an address): \Magento\Quote\


Model\Quote\Address\Item

• Totals: \Magento\Quote\Model\Quote\Address\Total\Grand (for


example)

• Payment: \Magento\Quote\Model\Quote\Payment

The above outlines how quotes are structured.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 175


Customizing the Checkout Process

Total models

Totals come together to ultimately generate the grand total for an order.

Note: totals in Magento are comprised of two numbers, the total and the base
total (for example, grand total and base grand total). The base total is the
total in the base currency of the website (Store > Configuration > General >
Currency). The total is in the currency of the store view.

Totals are calculated per address. The amounts are tallied and assigned to the
quote object. Unless a store is using multi-address checkout or the customer
is checking out with a virtual cart, the total calculators are run twice: once for
billing and once for shipping.

Total calculators usually inherit \Magento\Quote\Model\Quote\Address\


Total\AbstractTotal. They must implement \Magento\Quote\Model\
Quote\Address\Total\CollectorInterface.

Practical experience:
• Set a breakpoint in: \Magento\Quote\Model\Quote\
TotalsCollector::collect and add an item to the cart. Step through
each segment of this process.

• \Magento\Quote\Model\Quote\Address\Total\Grand::collect is
always called for each calculation.

Price calculation process

The Subtotal (\Magento\Quote\Model\Quote\Address\Total\Subtotal)


calculator determines a product’s price in the cart:

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 176


Customizing the Checkout Process

• First, the final price is retrieved. Remember, the final price can take into
account a quantity (it does, in this instance). However, the price that the final
price returns is per-item.

• The quantity is multiplied by the final price. Quote Items can have a
custom_price set. If this is set, the final price is discarded, and the
custom price is used instead (\Magento\Quote\Model\Quote\Item\
AbstractItem::getCalculationPriceOriginal).

• The item totals are added together to form the subtotal.

Practical experience:

Set a breakpoint in \Magento\Quote\Model\Quote\Address\Total\


Subtotal::_initItem and add an item through the cart.

Add to cart process

The most common point for adding products to the cart is: \Magento\Quote\
Model\Quote::addProduct. This method takes the following actions:

• Configures options, configurable options (\Magento\Catalog\Model\


Product\Type\AbstractType::_prepareProduct)

• Determines the number of items to add to the cart.

• These products are represented as candidates to add to the cart.


• If this product is already in the cart:
• \Magento\Quote\Model\Quote\Item::representProduct checks if
the product’s ID matches and if the options match.

• If there is a match, this item is updated, instead of adding a new item to


the cart.

• If the product is not in the cart:


• The item is initialized (\Magento\Quote\Model\Quote\Item\Processor::init)

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 177


Customizing the Checkout Process

• Details are set on the item.

• The item is added to the quote.

Entry points for adding to the cart:

\Magento\Wishlist\Controller\Index\Cart::execute

\Magento\Checkout\Controller\Cart\Add::execute

\Magento\Quote\Model\Quote\Item\Repository::save (this one calls


setItems instead of addProduct or addItem).

\Magento\Sales\Model\AdminOrder\Create::addProduct

Practical experience:

Set a breakpoint in \Magento\Quote\Model\Quote::addProduct and add an


item to the cart.

Custom add to cart operations

The Magento add controller provides a good example (although the wrapper for
the functionality is deprecated).

$quote->addProduct(/** … **/);

$quote->getShippingAddress()->setCollectShippingRates(true);

$quote->collectTotals();

$this->quoteRepository->save($this->getQuote());

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 178


Customizing the Checkout Process

It is important to call collectTotals to ensure that the quote has the latest
information regarding the items.

Customizations of the price calculation

To override the price in the cart, use the custom_price data key. This overrides
the standard price calculation (using getFinalPrice).

Practical experience:
• \Chapter7\FreeGift\Plugin\AddFreeGiftToCart

• Utilizes custom_price to adjust the price of an item in the cart.

• Note that this example is very basic and does not cover all scenarios.

Taxes and discount


• Magento provides broad support for many types of taxes:

• VAT

• Sales tax

• Fixed product tax (Magento\Weee). This allows you to configure flat product
taxes. In Europe, WEEE is a flat tax for each electronic sold, to cover recycling
it. In Store configuration, this is called Fixed Product Taxes. To enable for a
product, you must create a product attribute with the Fixed Product Tax
attribute type.

Further Reading:
• Magento_Weee module

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 179


Customizing the Checkout Process

Practical experience:
• Review Store Configuration > Sales > Tax, Checkout

• Configure a fixed sales tax rate for a store.

• Configure VAT rates for a store.

The quote merge functionality


The method is found in \Magento\Quote\Model\Quote::merge.

Each item is compared. The method that compares the quote items is:
Magento\Quote\Model\Quote\Item\Compare::compare. If the
\‌
comparison returns false, a new item will be added. If the comparison returns
true, the quote item’s quantity is increased.

So, what’s in the compare method?

• The item’s options are retrieved.

• The option codes (keys) are compared using the array_diff_key method.
If there is any variation, a merge is not possible.

• The values are also compared. Note that these comparisons are using non-
strict equality.

Practical experience:
• Set a breakpoint in \Magento\Quote\Model\Quote::merge.
• Add an item to your cart.

• Log into your customer account.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 180


Customizing the Checkout Process

Programmatically add a free gift when a certain


condition is met
The most simple use-case is demonstrated with a plugin. There are a couple
of other plugins you will need to add in order to create robust functionality:
handling for when other cart items are updated or deleted.

Practical experience:
• Set a breakpoint in
\Chapter7\FreeGift\Plugin\AddFreeGiftToCart::afterAddProduct
• Add an item to your cart.

• Extend the example to cover product updating and deleting.

Programmatically set a price for a product


This is done with the setCustomPrice() and setOriginalCustomPrice()
methods. Once these are set on an item, they override the product’s final price
and serve as the price for the subtotal calculation.

Taxes with discounts calculation


This is an area of tax law compliance. Some jurisdictions require taxing after
the discount is applied and others (it seems quite rare) require taxing before
the discount is applied. The default is the former: tax is calculated on the
discounted price. This is configured in Store > Configuration > Sales > Tax >
Calculation Settings > Apply Discount on Prices.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 181


Customizing the Checkout Process

The impact of many shopping cart price rules on


performance
The performance impact comes for when there are many rules with no coupon
code and they are automatically applied. The conditions are first filtered by
Website, Customer Group, and Coupon code (see \Magento\SalesRule\
Model\Validator::_getRules).

Every time the cart’s totals are collected, these rules are re-applied for each
item (see \Magento\SalesRule\Model\RulesApplier::applyRules). The
conditions are filtered, and the actions are run. This can become very expensive.

Practical experience:
• Set a breakpoint in \Magento\SalesRule\Model\Quote\Discount::collect and
navigate to the cart page.

Programmatically create shopping cart price rules


The $_POST data is filtered and applied to the \Magento\SalesRule\Model\Rule
model using the loadPost method. It is then saved.

Practical experience:

Set a breakpoint in \Magento\SalesRule\Controller\Adminhtml\Promo\


Quote\Save::execute and save a new Cart Price Rule.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 182


Customizing the Checkout Process

Importing coupon codes


There is no way in the admin to import coupon codes.

Coupon codes are stored in the salesrule_coupon table. Create a CSV and
import this file directly into the table, and the coupon codes will be available.

Further Reading:
• How to Import Coupons Codes in Magento 2

Extend the shopping cart price rules with custom


entities
Good news! There are multiple ways to add new conditions to Sales Rules
(unlike Catalog Price Rules). You can use plugins (\Magento\SalesRule\
Model\Rule\Condition\Combine::getNewChildSelectOptions) or the
salesrule_rule_condition_combine event.

Rules are added with the following notation:

$rule[] = [

'value' => \MyCompany\MyModule\RuleCondition\CustomRule::class,

'label' => __('My Custom Rule')

];

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 183


Customizing the Checkout Process

Programmatically separate line items in the shopping


cart so there are two line items instead of one
with qty=2
There are two methods that handle this function.

First, is when you add another product to the cart (that has the same product
ID as already in the cart). \Magento\Quote\Model\Quote\Item::representProduct
handles this comparison. The product IDs are checked, then whether or not the
product is a child versus a parent, and finally the item’s options.

Second, is when carts are merged. There is very similar functionality (with the
exception of the parent/child item check) in \Magento\Quote\Model\Quote\
Item\Compare::compare.

Practical experience:
• Set a breakpoint in \Magento\Quote\Model\Quote\Item::representProduct and
add a similar item to the cart.

Adding a new total model and evaluating its impact on


taxes and discounts
Totals are added in etc/sales.xml:

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 184


Customizing the Checkout Process

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

xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_

Sales:etc/sales.xsd">

<section name="quote">

<group name="totals">

<item name="subtotal" instance="MyCompany\MyModule\

Total\Fee" sort_order="150"/>

</group>

</section>

</config>

The impact on taxes and discounts is determined by the sortOrder attribute.


The higher the sort order the more totals will already have been calculated.
Grand total’s sort order is 550.

Discounts and taxes are calculated on each item and not on the order’s subtotal
or other totals. As such, new totals do not affect these items.

Further Reading:
• How to Use Total Models in Magento 2

Shipping discounts behavior and customizations


This is configured through Cart Price Rules. The actions tab contain the updates
that are made to the cart.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 185


Customizing the Checkout Process

One option is to apply the discount to the shipping amount. Or, if you are like
most merchants, you will just give the customer free shipping.

WebAPI for quote operations. Create a quote, add an


item, create a coupon, add a discount
The WebAPI is a powerful option for managing the frontend customer experience.

Note that there are two types of endpoints for accessing the cart: /rest/V1/
carts/mine/items and /rest/V1/guest-carts/{cartId}. The difference
is that with a guest cart, you have to store and manage the cart ID. Initializing
a guest cart requires calling POST /rest/V1/guest-carts/ and storing the
quote_id. The challenge, then, is integrating the guest cart from the API with
the real checkout. The API does not set cookies, and you will likely need to
build a custom controller (which can set cookies) to assign the current session’s
quote ID to be the API’s quote ID.

Note that the checkout loads the quote ID from


window.checkoutConfig.quoteData.entity_id
(vendor/magento/module-checkout/view/frontend/web/js/model/quote.js).

We opted for a Javascript demonstration for this as many backend developers


touch Javascript at least once in their lifetime (possibly more?).

Practical experience:
• Log into a Magento customer account.

• https://lc.commerce.site/chapter7quoteapi

• app/code/Chapter7/QuoteApi/view/frontend/web/js/items.js

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 186


Customizing the Checkout Process

Further Reading:
• Rest API's

7.2 DEMONSTRATE AN ABILITY TO


CUSTOMIZE AND EXTEND THE CHECKOUT
PROCESS

Checkout steps, the REST API, customizations of the


checkout API, the order placement process. The impact
of many concurrent order placements

Checkout steps

#1 Shipping: enter the shipping address and choose shipping method.

#2 Payment: choose payment method, set billing address, apply discount code,
gift card, and store credit.

Practical experience:
• Run through the checkout process yourself. It’s pretty simple, add items to
the cart and click the Proceed to Checkout button.

The REST API

The default Magento checkout utilizes the REST API for persisting checkout
details. This enables easy extension points for customizations to add details.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 187


Customizing the Checkout Process

However, many times it is safer to create a new REST API route than to modify
an existing route. With the ease of utilizing Javascript mixins, this is quite simple
to do.

Practical experience:
• Open Chrome Developer Tools and set a breakpoint in vendor/magento/
module-checkout/view/frontend/web/js/model/shipping-save-
processor/default.js (or /static/versionMYVERSION/frontend/
Magento/luma/en_US/Magento_Checkout/js/model/shipping-save-
processor.js), the saveShippingInformation. Select a shipping
method, and click Next.

Further Reading:
• Familiarize yourself with the REST API calls in the quote
namespace: Rest API's

Customizations of the checkout API

This is discussed in more detail forthcoming.

Further Reading:
• Customize Checkout

The order placement process

Javascript implementation details:

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 188


Customizing the Checkout Process

• The submit button is located inside of the payment method form (example:
vendor/magento/module-offline-payments/view/frontend/web/
template/payment/checkmo.html).

• Payment method renderers inherit (vendor/magento/module-checkout/


view/frontend/web/js/view/payment/default.js).

• Clicking the Place Order button normally calls placeOrder in the above
Javascript class.

• This calls the Place Order action (vendor/magento/module-checkout/


view/frontend/web/js/action/place-order.js).

• Endpoints:
• Customer logged in: /rest/V1/carts/mine/payment-information:
Magento\Checkout\Api\PaymentInformationManagementInterface::
savePaymentInformationAndPlaceOrder

• Not logged in: /rest/V1/guest-carts/:quoteId/payment-information:

• Magento\Checkout\Api\GuestPaymentInformationManagementInterface::
savePaymentInformationAndPlaceOrder

PHP implementation details:


• Guest: \Magento\Checkout\Model\GuestPaymentInformationManagement::
savePaymentInformationAndPlaceOrder

• Logged in: \Magento\Checkout\Model\PaymentInformationManagement::


savePaymentInformationAndPlaceOrder
• Both end up calling \Magento\Quote\Model\QuoteManagement::placeOrder.

Actions:
• Magento first checks to see if this is a B2B customer and if they have
permission to place orders.

• checkout_submit_before is called.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 189


Customizing the Checkout Process

• \Magento\Quote\Model\QuoteManagement::submitQuote

• The quote is validated (see implementations of


\Magento\Quote\Model\ValidationRules\QuoteValidationRuleInterface).

• If a customer checksout, the quote is updated with the customer


address information (actually, a customer can be created if the $quote-
>getPasswordHash() has a value).

• The order ID is reserved.

• The order and order address objects are created from the quote.

• The payment is converted from the quote to the order.

• The items are converted.

• Order data is set.

• sales_model_service_quote_submit_before is triggered.

• Place order is called:


• B2B sets order extension attributes (if applicable).

• \Magento\Sales\Model\Order::place

• sales_order_place_before

• Payment is authorized or captured.


(\Magento\Sales\Model\Order\Payment::place)

• sales_order_place_after

• Coupon usages are updated.


(\Magento\SalesRule\Plugin\CouponUsagesIncrement::afterPlace)

• Order is saved.

• sales_model_service_quote_submit_success is called
• This reindexes the catalog inventory index.
(\Magento\CatalogInventory\Observer\
ReindexQuoteInventoryObserver::execute)

• Order email is sent, if there is no redirect flag.


(\Magento\Sales\Model\Order\Email\Sender\OrderSender::send)

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 190


Customizing the Checkout Process

• checkout_submit_all_after

• The checkout session contains the latest order and quote information.

Practical experience:
• Set a breakpoint in
\Magento\Checkout\Model\GuestPaymentInformationManagement::
savePaymentInformationAndPlaceOrder and
\Magento\Checkout\Model\PaymentInformationManagement::
savePaymentInformationAndPlaceOrder.

• Step through the checkout process for both guest and customer.

The impact of many concurrent order placements

As you can see in the checkout action outline above, checkouts touch a majority
of the Magento system’s major features. This means that checkout is likely
the most difficult place to scale. One impact is the single point of failure: the
inventory catalog. This is improved in Magento 2.3.

Adding a step to the checkout after the payment step,


or between the payment and shipping steps
Adding a step after the payment step involves a number of touchpoints. First,
it is probable you will need to modify each payment type as the Place Order
button is found in the payment template. You can likely use a mixin to modify
existing functionality, but there will be a number of permutations to check.

Adding a step between the shipping and payment steps involves registering the
step with the correct sort order (see example in further reading).

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 191


Customizing the Checkout Process

Further Reading:
• Customize Checkout

• vendor/magento/module-checkout/view/frontend/
web/js/model/step-navigator.js

• vendor/magento/module-checkout/view/frontend/
web/js/view/shipping.js, initialize method

Implementing a "one-click" checkout, evaluate possible


pitfalls such as discounts being applied or canceled
when the order is placed
I am interpreting this as instant checkout.

This is possible with:

• A logged-in customer

• A payment method that is enabled for vault and instant purchase (Braintree
Credit Card, Braintree PayPal, and PayPal Payflow Pro)

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 192


Customizing the Checkout Process

Product page:

Confirmation window:

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 193


Customizing the Checkout Process

Success:

The biggest danger is that the customer is charged more than they are
expecting. Ensuring good customer communication is the most important part
of these considerations.

Practical experience:
• Sign up for a Braintree sandbox.

• Configure Braintree in Magento.

• Set a breakpoint in \Magento\InstantPurchase\Controller\Button\


PlaceOrder::execute and click Instant Purchase.

The checkout REST API: Modifying the native flow


(separate calls to save payment and to place the order)
One thing to keep in mind here is utilizing payment types that send the credit
card through the server. Unless the merchant is using an offline payment
method, it is likely a poor idea to save payment, if even possible.

That is why the save payment information and place order are the
same method.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 194


Customizing the Checkout Process

Extending the checkout REST API. Adding new


parameters to different API endpoints. Using extension
attributes
This is covered above under their respective sections.

Issues related to simultaneous order placement,


inventory locking
The problem is usually related to table locking on the inventory table. As
described below, every time orders are placed, the order item quantity
is decremented.

In these methods (\Magento\CatalogInventory\Model\


StockManagement::registerProductsSale and \Magento\
CatalogInventory\Model\ResourceModel\Stock::correctItemsQty),
the stock is loaded and then saved. In some cases, the entire stock item row is
saved. Other times, just the qty is updated. Either way, this is a single point that
multiple processes can access at the same moment.

Determining the exact moment when the stock is


decremented during an order placement
It’s done during the sales_model_service_quote_submit_before and
checkout_submit_all_after events. The latter is never (if ever) triggered as
the former always runs first.

The sales_model_service_quote_submit_before event is triggered


before the try/catch block surrounding the place order process

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 195


Customizing the Checkout Process

(see \‌
Magento\‌
Quote\Model\QuoteManagement::submitQuote).
This means that an error could cause inventory to be out of sync. The
good news is Magento thought of that and attached an observer to
the sales_‌model_‌service_quote_submit_failure event (see
\‌
Magento\‌
CatalogInventory\Observer\RevertQuoteInventoryObserver).

See:

• \Magento\CatalogInventory\Observer\
SubtractQuoteInventoryObserver::execute

• \Magento\CatalogInventory\Observer\
CheckoutAllSubmitAfterObserver::execute

Practical experience:
• Set a breakpoint in the above observers.

• Evaluate the call stack and step through the code.

Customizing the order placement such that it uses


message queues
Instant checkout provides the inspiration on how message queues can be used
to place orders. In instant checkout, all payment details have been stored in a
vault and other order details can be saved to the quote. As such, this can be
securely passed through the message queueing system.

Note that you will likely want to reserve the order increment ID before sending
the quote to the queue. This is so that you can tell the user their order number.

You will also need to build notification mechanisms for if the payment method
is declined and a way for the customer to change the payment details.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 196


Customizing the Checkout Process

Horizontal sharding of orders tables to improve order


placement capacity. What challenges need to be
resolved?
Horizontal sharding of order tables is when you split the table out by rows.
For example, orders placed with a last name that starts with A-M goes into
one order table. Orders placed with a last name that starts with N-Z goes into
another order table. The result is that there is less activity on each table.

The challenge is then knowing where to locate the orders. Looking up an order
is no longer selecting by ID or increment ID. Instead, you need to know more
information about the order.

Further Reading:
• Super-Scaling Magento

• Split database performance solution

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 197


Customizing the Checkout Process

7.3 CREATE AND DEBUG SHIPPING AND


PAYMENT METHODS IN MAGENTO

Different types of payment methods: gateway, offline,


hosted. The gateway payment methods framework.
The payment method availability logic. The shipping
rates calculation process, debugging shipping rates and
table rates customizations

Gateway, offline, hosted payment methods

Payment methods pretty much fit into one of these categories. Gateway is
when the payment details (for example, credit card) is sent to Magento and then
on to the merchant. This can also apply to when the credit card is tokenized
(Braintree or Stripe) on the frontend and the authorization/capture happens
from Magento.

Offline is for payment types that are not connected to an external service.
Examples of this are Check/Money Order, Bank Transfer, Purchase Order and
Cash on Delivery (found in \Magento\OfflinePayments). These payment types
offer a Credit Memo but only for the entire order and not per invoice.

Hosted payment methods involve redirecting the customer to a third-party


page. This used to be the old PayPal Express in Magento 1. Now, the only two
native types that are hosted in Magento are Payflow Express and Payflow Bill
Me Later.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 198


Customizing the Checkout Process

When a customer selects a hosted payment method in the checkout, they


click a button (often blue, but the color may vary depending on the merchant’s
preferences) which takes them to the payment provider’s website. Here, the
payment details are entered and the provider communicates the relevant details
back to Magento. PayPal communicates back with IPN.

Gateway payment methods framework

The majority of information online about creating online payment methods


relies on the wonderfully simple extension of \Magento\Payment\Model\Method\Cc.
This class (and the abstract payment method) is deprecated as of Magento 2.1.
As such, I hope this brief overview will provide good insight.

The payment method class for the gateway method framework is


\‌ Payment\Model\Method\Adapter. This is the class on to which
Magento\‌
the configuration is bolted.

Instead of a central class that you create containing all information necessary
to complete a payment transaction, the central class is fixed and relies on
configuration to customize necessary functions (adapter pattern). To see this in
action, enable either our example payment method or a sandbox for true online
payment method and set a breakpoint in \Magento\Payment\Model\Method\
Adapter::getConfigData.

Each payment method in the gateway payment method framework must have:

• A value handler pool (see SuperPaymentsValueHandlerPool). For


the most part, this is a configuration loader. However, you can set
custom handlers for any value. See the can_void handler. Observe the
two virtual types configured to provide system configuration values

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 199


Customizing the Checkout Process

(SuperPaymentsConfigValueHandler and SuperPaymentsConfigValues


in app/code/Chapter7/PaymentMethod/etc/di.xml). This allows the
proper system configuration node to be referenced.

• A validator pool (see SuperPaymentsValidatorPool). No validators


are necessary, but the class needs to be specified. This, combined with
the virtual country validator (SuperPaymentsCountryValidator),
demonstrates how to create custom validators. You can utilize the global
validator to be always run.

• Command pool. This is a list of configured commands. See the next section.

Payment commands

Payment commands are specific actions to be performed on a payment. Here


is a list of commands found in \Magento\Payment\Model\Method\Adapter
(they match the method name):

• fetch_transaction_information

• order

• authorize

• capture

• refund
• cancel

• void

• acceptPayment

• denyPayment

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 200


Customizing the Checkout Process

To add a new command:

• Locate the command pool type (or virtual type) for the payment method you
are modifying.

• Add the command to the commands argument.

• Create configuration for the command to extend


Magento\‌
Payment\‌
Gateway\Command\GatewayCommand.

• The command must specify a requestBuilder (generating the request for


the gateway), transferFactory (generating a transfer), client (sending
the request), and the handler (receiving the data).

Further Reading:
• Adapter pattern

Practical experience:
• Run bin/magento Chapter7_PaymentMethods

• Run bin/magento setup:upgrade

• The amount of customization is far more than we have space to discuss


here. Understand that the possibilities are limitless through configuration
(and certainly with plugins, if necessary).

• Read through each class specified in


app/code/Chapter7/PaymentMethod/etc/di.xml

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 201


Customizing the Checkout Process

Payment method availability logic


• Magento calls the isActive method on the payment method adapter.

• This calls getConfiguredValue on the adapter.

• This checks the value handler pool to see if handler is registered for this
value. See SuperPaymentsValueHandlerPool for an example of how to
register a new value handler. You would create one for the active type.

• If no handler is specified, and if the payment method is properly configured,


\Magento\Payment\Gateway\Config\Config::getValue loads the value
from store configuration.

Practical experience:
• Set a breakpoint in \Magento\Payment\Model\PaymentMethodList::getList and
go to the checkout page.

Shipping rates calculation process

Above, we discussed address totals. The shipping rates calculation process


follow this model. The calculator (\Magento\Quote\Model\Quote\Address\
Total\Shipping::collect) extends \Magento\Quote\Model\Quote\
Address\Total\AbstractTotal.

\Magento\Quote\Model\Quote\Address::requestShippingRates
assembles a shipping rate request. This object should contain all information
necessary to obtain shipping rates. Note the FreeMethodWeight is how much
weight should NOT be calculated in the rate request. For example, if a package
weight 2.5kgs, and the free method weight is 0.5kgs, rates will be retrieved for
shipping 2.0kgs.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 202


Customizing the Checkout Process

A request can also limit the carrier. This is based on the limit_carrier data
key in a quote address object. This gives you an easy way to restrict options to
one choice.

Other study points:


• \Magento\Shipping\Model\Shipping::collectRates iterates through
all shipping providers. This is one place to understand why your carrier is
skipped over.

• \Magento\Shipping\Model\Shipping::collectCarrierRates loads
shipping rates from a specific carrier (if the carrier is active).

Shipping method calculators are similar to the old way of payment methods.
There is one file that (usually) extends an abstract class and adds the lookup
functionality.

Calculators return a \Magento\Shipping\Model\Rate\Result class. This


is a container for storing rates. Rates are of type \Magento\Quote\Model\
Quote\Address\RateResult\Method.

In our example, we have configured the FedEx carrier. An online carrier is one
that is connected to a third-party service or live rating. One potential impact to
understand is that when these services go down, it can bring a merchant’s site
down, too (we have had this happen).

By default, online shipping carriers offer a free shipping method and


a free shipping up to option. This is seen in \Magento\Shipping\
Model\Carrier\AbstractCarrierOnline::getMethodPrice.
If free shipping does not apply to this method, the next step is
to calculate the handling fee (\Magento\Shipping\Model\Carrier\
AbstractCarrier::getFinalPriceWithHandlingFee). Handling fees can be

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 203


Customizing the Checkout Process

per package or per order. The number of packages is determined by maximum


weight, and nothing smarter.

Practical experience:
• Set a breakpoint in Magento\Quote\Model\Quote\Address\Total\
Shipping::collect and navigate to the checkout, enter your address, and
step through the collection process.

• You can also configure an online shipping method (in this example, FedEx),
and set a breakpoint in \Magento\Fedex\Model\Carrier::collectRates.
Evaluate the call stack and step through the rate collection process. Note
that the products ordered must have a weight.

Debugging shipping rates and table rates customizations

Above gives a good overview for debugging shipping rates. Most often, the
shipping rate has been disabled (by a country exclusion, invalid credentials for
online carriers, or errors returned from online carriers).

Table rates provide one carrier name for a list of rates (you cannot specify both,
say, "Expedited Shipping" and "Standard Shipping"). Rates are uploaded per
website and cannot apply to the global scope. The available columns for table
rates are:

• Country

• Region/State

• Zip/Postal Code

• Order Subtotal (and above)

• Shipping Price

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 204


Customizing the Checkout Process

\Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate::getRate
locates the rates for the shipping request.

The gateway framework vs. AbstractMethod


The abstract method for payments has been deprecated. However, all offline
payment methods still use it, so I am unsure the true status (it makes no sense
to use the gateway framework for offline payment methods).

The structure of offline payment methods. What makes


them "offline"?
Offline payments have no connection to a third-party gateway. Examples are
payment by check, purchase order and manual bank transfer.

Partial invoices/refunds for payment methods


Payment methods must be enabled for partial invoices. The base payment
method provider exposes two protected variables: $_canCapturePartial and
$_canRefundInvoicePartial.

Add a hosted-type payment method (redirects, custom


order review page)
\Magento\Quote\Model\Quote\Payment::getOrderPlaceRedirectUrl
calls getConfigData on the payment adapter class. This means that we can use
configuration to specify a redirect url, if pertinent.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 205


Customizing the Checkout Process

The Payflow Express payment method provides an example of a custom order


review page.

Add a new shipping method. Customize the logic for


getting a list of available shipping methods
Perhaps the most simple example of a shipping method is:
\‌
Magento\‌
OfflineShipping\Model\Carrier\Freeshipping.

Steps to create a new shipping method:

• Create default configuration in etc/config.xml for


default/‌carriers/‌[carrier name].

• Create store configuration options in etc/adminhtml/system.xml.

• Create a shipping carrier model that extends \Magento\Shipping\Model\


Carrier\AbstractCarrier (for your sanity) and \Magento\Shipping\
Model\Carrier\CarrierInterface (for Magento’s sanity).

• If desired, add validation rules for your shipping type (see vendor/magento/
module-offline-shipping/view/frontend/layout/checkout_cart_index.xml).

The shipping rate calculation flow. Debug shipping


rates, identify plugins that that influence the shipping
rate calculation process
This is discussed above.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 206


Customizing the Checkout Process

Customizing table rates. Add a new "column" to the


"table", change the logic of selecting the rate.
Table rates are stored in shipping_tablerate. Create a plugin for
\‌
Magento\‌
OfflineShipping\Model\ResourceModel\Carrier\
Tablerate\RateQuery::prepareSelect and add additional query
parameters. The request is available in \Magento\OfflineShipping\Model\
ResourceModel\Carrier\Tablerate\RateQuery::getRequest.

Gateway shipping methods. Remote call flow. How to


avoid the remote call if it is unnecessary
AbstractCarrierOnline offers an easy way to cache quotes:
getCachedQuotes($requestString); It is your responsibility to generate
_‌
the request cache key, but after that, the abstract carrier will handle the cache
storage mechanism.

Practical experience:
• Enable and configure FedEx shipping in sandbox mode.

• Set a breakpoint in \Magento\Fedex\Model\Carrier::collectRates.

• Navigate to the checkout and enter your address information.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 207


8
MAGENTO
COMMERCE
FEATURES
8 questions

Professional Developer Plus


Magento Commerce Features

8.1 DEMONSTRATE AN ABILITY TO USE


MESSAGE QUEUES

Create a listener/publisher. Use cases for using


message queues
Creating a message queue with a publisher and a consumer is quite simple. This
will cover how to create one of these yourself.

There are several pieces necessary to realize a functioning message queue.

First, there are a couple of concepts to understand.

• A publisher (etc/queue_publisher.xml) is configured to send messages


to a topic.

• A topic (configured in etc/communication.xml) is a way to categorize


messages to consumers.

• A consumer is configured to listen for messages with specific topics.

• Queues route topics to consumers (this happens in etc/queue_topology.xml).

• Consumers accept messages and perform actions on them


(etc/‌queue_‌consumer.xml).

Files to create:

• A schedule (publisher): creates an operation


(\‌ AsynchronousOperations\Api\Data\OperationInterface) and sends it
Magento\‌
to the publisher for the specific topic.

• A consumer: accepts the operation (\‌


Magento\‌
AsynchronousOperations\Api\
Data\OperationInterface) from the queue and performs an action on it.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 209


Magento Commerce Features

The DevDocs links below have more information. The goal of this section is to
provide a basic working knowledge so you can get into the code and execute it
yourself, giving a deeper understanding.

Practical example:

• See app/code/Queueing/BasicMessageQueue

• Run bin/magento module:enable Queueing_BasicMessageQueue

• Run bin/magento setup:upgrade (this installs the consumer)

• Run bin/magento queue:consumers:start example_consumer

• Then visit https://lc.commerce.site/index.php/chapter8queue/

Further Reading:
• AMQP 0-9-1 Model Explained

• RabbitMQ

• Message Queues

• Configure message queues

• How to Configure and use RabbitMQ in Magento 2.3

Use cases

Message queues are a powerful way to offload immediate processing needs


to another process. One thing to understand is that queues are one-way: you
send information to them, but it defeats the purpose (or is impossible) for the
publisher to also receive the result.

Another way to look at message queues is similar to the Magento event system.
You dispatch (or, publish) a message to the event system. This event contains

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 210


Magento Commerce Features

data (or, operation). Magento routes the message to the appropriate observer
(or, consumer).

The Magento technical guidelines dictate that no changes are made to event
object data (there are a couple of exceptions). Because of this, most event
observers could become message queue consumers.

Examples:
• GDPR compliance in downloading customer data, then sending an email.

• Sending order email.

• Decrementing stock item quantities when products are ordered.

• Saving product data.

• All reindex operations.

I believe that Magento’s admin interface will become more performant as the
message queues are adopted.

How to register a listener/publisher for a queue


See above.

How to configure a queue


See above.

What is a use case to use message queues?


See above.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 211


Magento Commerce Features

8.2 DEMONSTRATE UNDERSTANDING OF


CUSTOMER SEGMENTATION

Describe the customer segments functionality, its use


cases, impact on performance, and programmatical
operations with customer segments

Describe the customer segments functionality

Customer segments allow a merchant to locate all customers that match a


criteria. This is different than customer groups in that customers are assigned to
a customer group, but segments pick which customers match the criteria.

Customer segments are found in Customer > Customer Segments.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 212


Magento Commerce Features

Here is the default list of customer segment filters:

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 213


Magento Commerce Features

Practical experience:
• Create a new customer segment.

• Specify a criteria.

• Locate the customers.

• Try to change customer segment Apply To: does it work?

Customer segments are stored in:

• magento_customersegment_segment: stores base customer segment


details. Note that both the serialized conditions and the condition SQL
statement to fetch the customers are stored in this table.

• magento_customersegment_website: assignment of the customer


segment to the website.

• magento_customersegment_event: events on which to refresh each


customer segment.

• magento_customersegment_customer: customers that fit within


the segment.

How it works:

Customer / segment associations are "indexed" and stored in


customersegment_customer. Events trigger updates to the
magento_‌
associations stored in this table (see vendor/magento/module-customer-
segment/etc/events.xml and vendor/magento/module-customer-
segment/etc/frontend/events.xml). Most of the events are triggered
when a customer takes a specific action.

Magento loads determine if the current event matches an event to be processed


for a customer segment (see the magento_customersegment_event table).

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 214


Magento Commerce Features

For each segment that is valid for this customer, the customer is added
to the matched segments (\Magento\CustomerSegment\Model\
Customer::addCustomerToWebsiteSegments). This includes updating the
database table (magento_customersegment_customer) and adding the
segments to the session and the HTTP context.

Effect of Apply To:

If a customer segment’s Apply To is set to Visitors, no customers will be


matched. Instead, this rule will apply to the frontend.

Whenever a segment’s rules are changed, all associated customers are


deleted from magento_customersegment_customer and then re-
added through \Magento\CustomerSegment\Model\ResourceModel\
Segment::aggregateMatchedCustomers.

Use cases for customer segments


• Apply catalog and cart price rules (you need to use a condition for this).

• Display banners (dynamic blocks in 2.3).

• Reporting for marketing campaigns.

Impact on performance

Because customer segments are re-calculated each time a customer (or visitor)
takes almost any cart or catalog-impacting action on the frontend, having many
segments can take a toll on performance.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 215


Magento Commerce Features

Practical experience:
• Set a breakpoint in:
\Magento\CustomerSegment\Model\Customer::processEvent.

• Create a new customer segment and a customer that matches.

• Log in with that customer.

• Observe the actions taking place.

Programmatic operations with customer segments


• Fetch all applied segments for a customer:
\Magento\CustomerSegment\Model\Customer::getCustomerSegmentIdsForWebsite

• Trigger customer segment refresh for a customer:


\Magento\CustomerSegment\Model\Customer::processEvent

• Rebuild an entire segment’s customer associations:


\Magento\CustomerSegment\Model\Segment::matchCustomers

Programmatically create a customer segment


Similar to creating catalog and cart price rules, the easiest way to observe the
process is to set a breakpoint in \Magento\CustomerSegment\Controller\
Adminhtml\Index\Save::execute. Then, save a customer segment.
Observe the $model->loadPost() and the data structure being pulled into
the model.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 216


Magento Commerce Features

Extend customer segments with new entities or


attributes

New entity:

Create a class that extends \Magento\Rule\Model\Condition\Combine.


Rules implement _prepareConditionsSql to add conditions to the SQL
expression to locate customers.

Add a new condition with an after plugin on \Magento\CustomerSegment\


Model\Segment\Condition\Combine::getNewChildSelectOptions:

public function afterGetNewChildSelectOptions($subject, $result)

$result[] = [

'value' => \MyCompany\MyModule\Model\Segment\Condition\

MyCondition::class,

'label' => 'My Condition',

'available_in_guest_mode' => true

];

return $result;

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 217


Magento Commerce Features

New attribute:

Customer segment customer attributes must: 1) have a label, 2) not be a file or


image, 3) and be marked to use in a customer segment.

Understand the database structure of customer


segments
Discussed above.

8.3 DEMONSTRATE UNDERSTANDING OF


ADVANCED CAPABILITIES IN MAGENTO
COMMERCE

Store credits, reward points, RMA, gift cards

Store credits

Store credits reside in the Magento\CustomerBalance module. They are


found in Customers > [choose customers] > Store credit tab.

You can make all credit memos be automatically returned through Store Credit
with Store Configuration > Customers > Store Credit > Refund Store Credit
Automatically.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 218


Magento Commerce Features

Practical experience:
• Create a customer.

• Add store credit to their account.

• Purchase something using the Store Credit payment method.

• Purchase something using a normal payment method.

• Refund latter order to Store Credit.

Reward points

Reward points create a loyalty program for customers. When customers take
specific actions, they get X number of points. They can then use these points to
purchase merchandise.

Reward points are configured in Store Configuration > Customers > Reward
Points. The exchange rate (points conversion to dollar value) is in Stores >
Reward Exchange Rates. Here, you can specify a website, Customer Group,
direction (Points to Currency and Currency to Points), and the exchange rate.

Additionally, you can use a cart price rule to add a fixed number of reward points
for a qualifying order.

The other important point to note is the configuration for Reward Points
Expiry Calculation. This default to static, which means that when the balance
increases, the expiration date is reset to the number of days specified. However,
setting to dynamic means that you can change the days to expiration, and it will
be figured into all future calculations.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 219


Magento Commerce Features

Practical experience:
• Create a customer.

• Ensure reward points are enabled and configured to add points for specific
actions.

• Place an order.

• Add a reward exchange rate.

• Make a purchase with reward points.

Further Reading:
• Reward Points System on Magento 2 Enterprise Edition

RMA

The RMA module provides a streamlined process for approving and accepting
returns. This is found in Magento\Rma. Configuration is found in Store
Configuration > Sales > RMA Settings.

Orders must be invoiced before they are eligible for a return.

Returns have their own attribute type (Stores > Attributes > Returns).
If you need to add a new type of refund, you can easily add it to the
Resolution attribute.

Practical experience:
• Enable RMA in store configuration.

• Place an order and invoice it.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 220


Magento Commerce Features

• In the customer account, locate the order and create a return.


• Set a breakpoint in \Magento\Rma\Model\ResourceModel\
Item::getOrderItems to observe the selection criteria for determining
eligible order items.

• Process the return in the admin panel.

• Create a credit memo. This is not done automatically and requires the admin
to complete and submit a credit memo.

In the admin panel, RMA items follow a workflow of authorizing items, receiving
them (qty in the Returned field), and approving them. Note that the person
receiving the return must also change the status each time a QTY field
is updated.

If you need to make changes to the workflow, you can enjoy updating the old-
style Magento forms (not uiComponents). Depending on your experience, this
may or may not be a plus.

Further Reading:
• Configuring Returns

• Returns Attribute

• Magento 2 Product Return RMA

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 221


Magento Commerce Features

Gift Cards

Gift cards are a product type. They are found in the \Magento\GiftCard
namespace. Gift cards are physical (shipped, thus shipping calculations are
applied) or virtual (via email). Configuration is found in Store Configuration >
Sales > Gift Cards.

Examples:

• https://lc.commerce.site/luma-mailed-gift-card

• https://lc.commerce.site/luma-virtual-gift-card

Purchased gift cards are visible under Marketing > Gift Card Accounts. By
default, the gift card account is created when the order is invoiced. You can
change this to create the gift card account when the order is placed (although,
the problem is that the purchasing credit card might authorize but fail to capture
and the customer now has a free gift card).

Each gift card has its own code that comes from a pool of generated codes
(magento_giftcardaccount_pool). These codes are configured in store
configuration. Once a card is associated with a gift card code, the status
column for the code (in the pool table) is switched to 1.

Further Reading:
• Creating a Gift Card

• Gift Card Workflow

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 222


Magento Commerce Features

Compare RMA with refunds


The RMA process provides a structured way to receive products. However, there
is no connection with credit memos. An admin must track what products are
returned and then create a credit memo for those items.

Customizing the RMA process


Because Commerce provides the ability to edit RMA attributes, this solves many
customization needs for the RMA process.

However, here are a couple of other locations that are helpful to tap into
RMA functionality:

• RMA item tab: \Magento\Rma\Block\Adminhtml\Rma\Edit\Tab\Items\Grid

• List items available to be returned: \Magento\Rma\Model\ResourceModel\


Item::getOrderItems

• Don’t forget that RMAs have extension attributes. This is a great way to
export additional RMA information to an ERP.

Customizing store credits/reward points accrual/


spending
Store configuration (Stores > Configuration > Customers > Reward Points)
contains the majority of customizations necessary:

• Rewards Points Balance Redemption Threshold: prevents customers from


using points until they have achieved a certain number of points.

• Cap Reward Points Balance At: prevents customer from obtaining too many
points.

• Reward Points Expire in (days): have reward points fall off after this number of

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 223


Magento Commerce Features

days. See \Magento\Reward\Model\ResourceModel\Reward\History\


Collection::addExpirationDate and magento_reward_history.

• Reward Points Expiry Calculation:


• Dynamic: if this is set, when you change the Expire In (above) value, the
expiration for rewards is also updated.

• Static: if this is set, changing Expire In (above) value only changes new
reward values.

Points for customizing reward points / store credits:

• \Magento\Reward\Model\ResourceModel\Reward\History

• \Magento\Reward\Cron\ScheduledPointsExpiration::execute
(entry point for expiration customization)

• \Magento\Reward\Cron\ScheduledBalanceExpireNotification::execute (entry
point for expiration customization)

• \Magento\CustomerBalance\Model\Balance

8.4 DEMONSTRATE UNDERSTANDING OF


TARGET RULES

Customer rules database structure, use cases,


comparison to other product relations, operate with
target rules programmatically
Customer rules (Related Products Rules or Target Rules) are found in Marketing
> Related Products Rules. The goal is to reduce administration time for

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 224


Magento Commerce Features

maintaining upsells, cross-sells, and related products while also providing


intuitive selection for products.

Target Rules have these conditions:

• Apply to (upsell, cross-sell or related product)

• Customer segments

Target rules have two tabs: Products to Match and Products to Display. The
former selects products that these rules apply to. The latter selects products to
show on these products (in the list that is selected).

Further Reading:
• Creating a Related Product Rule

Customer rules database structure


• magento_targetrule: master table for storing target records. Note the
action_select column which provides additional filtering based on the
current product. For example, if you look at this column using sample data,
many rules are filtering to ensure that a product is in a category (see the
Crosssell Women Top Jackets rule).
• magento_targetrule_customersegment: table that specifies which
customer segments the rule applies to.

• magento_targetrule_product: stores associations of rules that are


associated with a product.

Note that there are other Target Rule tables (magento_targetrule_index,


magento_targetrule_index_crosssell, magento_targetrule_
crosssell_product, magento_targetrule_index_related,

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 225


Magento Commerce Features

magento_‌
targetrule_index_related_product, magento_targetrule_
index_upsell, magento_targetrule_index_upsell_product). I am
unable to find where or how these tables are utilized, which leads me to believe
they may be abandoned.

Here is an example of the filter stored in the action_select column (the


embedded SELECT):

SELECT e.entity_id FROM catalog_product_flat_1 AS e

WHERE (( (SELECT COUNT(*) FROM catalog_category_product WHERE


(product_id=e.entity_id) AND (category_id IN('5'))) > 0)) AND (e.entity_id NOT
IN('1404')) LIMIT 4

Use cases

The primary use case to ease product associations. This is valuable because
one way to improve a website’s conversion rate and average order value is by
showing additional products that are pertinent or interesting.

Comparison to other product relations

Target rule products can operate in addition to or replace products specified


on a product level. Because target rules operate on a more global level,
some product suggestions may not be overly relevant. In these cases, an
administrator can add the required products to the applicable product list.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 226


Magento Commerce Features

Operate target rules programmatically

To load a list of related products for a particular product (source is


\‌
Magento\TargetRule\Block\Catalog\Product\ProductList\
AbstractProductList::_getTargetRuleProductIds):

/** @var $indexModel \Magento\TargetRule\Model\Index **/

$indexModel = $this->_getTargetRuleIndex()->setType(

$this->getProductListType()

// 1 = related, 2 = upsell, 3 = cross-sell

// see \Magento\TargetRule\Model\Rule

)->setLimit(

$limit

)->setProduct(

$this->getProduct()

)->setExcludeProductIds(

$excludeProductIds

);

if ($limit !== null) {

$indexModel->setLimit($limit);

return $indexModel->getProductIds();

The above first configures a filter then it:

• Iterates through all applicable customer segments and loads products


for each (see \Magento\TargetRule\Model\ResourceModel\
Index::getProductIds). Before going to the database, the cache is consulted.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 227


Magento Commerce Features

• Products are loaded by rule index and customer segment ID (default is 0


for no segment). See \Magento\TargetRule\Model\ResourceModel\
Index::_matchProductIdsBySegmentId.

• This method locates rules that apply to the conditions specified.

• Product IDs are loaded with the \Magento\TargetRule\Model\


ResourceModel\Index::_getProductIdsByRule method.

Practical learning:
• Set breakpoints in the classes mentioned above.

• Navigate to https://lc.commerce.site/nadia-elements-shell (target rule #2


applies to this product by default).

• Step through the product selection criteria.

Target rules vs. related products


See above.

Extending target rules with a custom attribute or a


custom entity
To allow a custom attribute to be available for selection, ensure the
attribute’s type is a common type (i.e. not file, image, or fixed product
tax), and it is visible (see \Magento\Rule\Model\Condition\Product\
AbstractProduct::loadAttributeOptions).

To create your own target rule condition, create an after plugin


for \Magento\TargetRule\Model\Rule\Condition\Product\
Attributes::getNewChildSelectOptions to append another rule.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 228


Magento Commerce Features

Create a rule programmatically, understand the


database structure of a rule
The best way to accomplish this is by creating a rule in the admin panel and
setting a breakpoint in \Magento\TargetRule\Controller\Adminhtml\
Targetrule\Save::execute. Step through the code and replicate the data
structure in your custom code.

What is the performance impact of having many target


rules in the system?
For the initial page load (before the full page cache takes over responding
to a page), having too many target rules will cause a bottleneck around the
processing and application of the rules.

How does editing a product or adding new products


affect existing target rules?
The rules are reindexed.

Practical experience:
• Set a breakpoint in \Magento\TargetRule\Model\Indexer\
TargetRule\AbstractAction::_reindexByProductIds.

• Save a product that is associated with rules.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 229


9
UNDERSTANDING
MAGENTO
SECURITY
3 questions

Professional Developer Plus


Understanding Magento Security

9.1 DEMONSTRATE UNDERSTANDING OF


FRONTEND SECURITY WITH MAGENTO

Filter input and escape output; password and sensitive


data hashing; validate user file uploads; how to prevent
cross site scripting vulnerabilities

Filter input and escape output

Filtering input is important to prevent saving malicious code. Escaping is a


secondary layer of protection to ensure that anything that made it through
filters is rendered benign.

Magento provides a couple of form filters. These implement the


\‌ Framework\Data\Form\Filter\FilterInterface and provide
Magento\‌
the inputFilter and outputFilter methods.

Both customers and EAV abstract provide filtering when utilizing the
extractValue method.

Practical experience:
• Set a breakpoint in \Magento\Customer\Model\Metadata\Form::extractData

• Create a customer.

Escaping output is available in blocks with the escape* methods in


\‌
Magento\‌
Framework\View\Element\AbstractBlock:

• escapeHtml: essentially runs thehtmlspecialchars method (unless you


provide a list of allowed tags).

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 231


Understanding Magento Security

• escapeJs: escapes a string so that it can appear in quotes in Javascript.

• escapeHtmlAttr: escapes code echoed into an HTML attribute.

Further Reading:
• Escape Methods

Password and sensitive data hashing

Password hashing is a one-way "encryption" (hashing). Subsequent validations


are hashed, and the hash is compared. This removes the need to save plain text
or encrypted passwords.

The \Magento\Framework\Encryption\Encryptor class provides a


standard set of tools to hash and encrypt/decrypt.

Practical experience:
• \Magento\Customer\Controller\Account\CreatePost::execute

How to prevent cross site scripting vulnerabilities


Cross-site scripting happens when a malicious user enters code into a website
and the website renders that code in plain text (in the source).

Ensure that all output (whether loaded from the database or direct visitor input)
is escaped. Even if an admin is in control of the output (for example, products),
don’t trust it (admin accounts are cracked).

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 232


Understanding Magento Security

Further Reading:
• Cross site scripting

How to validate field values before inserting them into


the database
If possible, utilize and enforce types if the input value is an integer or float (and
casting to that type). This eliminates many attack vectors. However, accepting
integer and float values is not entirely possible.

The Magento\Framework\Data\Form\Filter\FilterInterface provides a


standardized way to run specific filters against input:

• \Magento\Framework\Data\Form\Filter\Striptags ensures there are


no HTML tags present.

• \Magento\Framework\Data\Form\Filter\Escapehtml renders HTML


tags harmless.

• \Magento\Framework\Data\Form\Filter\Trim eliminates extra


string characters.

How to escape output to remove HTML tags


In a block, call $block->escapeHtml().

Using secure encryption and hashing functions to store


sensitive values in the database
See above.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 233


Understanding Magento Security

Validate file uploads, the file type, the file contents,


and the file metadata
While the Zend library includes a good number of validators, its use is
deprecated and will be eliminated. As such, we will focus on validating images.

The validateUploadFile opens the file to ensure that it is a valid image.


If more careful validation is needed, use the new Zend validator (for example
\‌
Zend\Validator\File\FilesSize).

Practical experience #1 (example of Zend validation):


• Add a product option to a product of the file type.

• Navigate to the product on the frontend.

• Set a breakpoint in \Magento\Catalog\Model\Product\Option\Type\


File::validateUserValue

• Set an image, and add the product to the cart.

• Pay careful attention to: \Magento\Catalog\Model\Product\Option\


Type\File\ValidatorFile::validate

Practical experience #2:


• Set a breakpoint in \Magento\Catalog\Controller\Adminhtml\
Product\Gallery\Upload::execute

• Set a breakpoint in \Magento\Framework\Image\Adapter\


AbstractAdapter::validateUploadFile

• Open a product, and upload an image.

• Step through the above method.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 234


Understanding Magento Security

9.2 DEMONSTRATE UNDERSTANDING


ADMINHTML SECURITY WITH MAGENTO

CSRF tokens; stored XSS; adminhtml secret key;


security configuration; securing email from injection;
preventing admin privilege escalation

CSRF tokens

CSRF tokens ensure that the page was linked to from another validated page.
The way it does this is use a session token that creates a hash that is utilized on
the next page load. When you navigate to the linked page, the hash is verified
against the session.

There are two Magento features built for this: backend URL keys (secret key) and
form keys. These will be discussed in more detail later.

This effectively eliminates someone emailing you a link to your website that you
click, and it does something malicious (if you are logged in).

Stored XSS

This is when a user saves malicious details into the database (using even
legitimate means like creating a customer account or placing an order). While it
is not good to store malicious code in the database, the double-trouble comes
when rendering that code. In the case of the problem that SUPEE-7405 fixed
(Magento 1), people could create an email address with Javascript code.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 235


Understanding Magento Security

Further Reading:
• Magento CSFR Protection

Adminhtml secret key

This is that really long string of characters that is appended to each admin URL:

https://lc.commerce.site/index.php/admin_dev/admin/index/index/

key/5010facb5b81eba2e80c2daf41c5b7c3e7b94b5eba9314fd5265e176d7cd51cc/

The good news is we all know to never disable it.

Security configuration

Merchant-configurable options are found in Store > Configuration > System


> Admin. These include setting a custom Admin Base URL, enforcing account
security (like preventing account sharing, ensuring secret key is enabled, admin
session length, forced password change, etc.).

In addition, it is important to leverage Role Resources (and Role Scopes in


Commerce) to ensure that admins have limited access to data.

Practical experience:
• Familiarize yourself with Magento security configurations.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 236


Understanding Magento Security

Securing email from injection

Ensure that reCaptcha (or, Magento’s unfortunate implementation of a captcha


idea) is enabled. This greatly reduces bots from creating Magento accounts for
most everyone in the world.

Preventing admin privilege escalation

Privilege escalation is when a user is able to access something that they are not
allowed to do but those with more permissions are.

Preventing this involves carefully writing code and keeping the Magento
application up to date.

Example:
• https://magento.com/security/patches/magento-2.2.7-and-2.1.16-security-
update, see PRODSECBUG-2153, PRODSECBUG-2063, PRODSECBUG-2126,
PRODSECBUG-2152, and MAGETWO-94370

How to prevent CSRF attacks; the importance of the


form_key field in forms
Magento’s backend controllers automatically append the secret key (thanks to
preferences specifying \Magento\Backend\Model\Url), so that part is easy
unless someone disabled it.

The form_key is easy to add to forms, so it is a sign of a very poor quality


developer (in my opinion) not to use this everywhere.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 237


Understanding Magento Security

<form method="POST">

<?= $block->getBlockHtml('form_key') ?>

</form>

It’s that easy.

All Magento adminhtml controllers should extend \Magento\Backend\App\


AbstractAction. Extending this class automatically checks form keys for
POST and secret key for all other requests.

How is the admin secret key generated in URLs?


Anytime the adminhtml (backend) url model is used to create a URL the
route name, controller, action, and form key are appended and then hashed
(see above).

Practical learning:
• Set a breakpoint in \Magento\Backend\Model\Url::getSecretKey.

• Navigate to an admin page.

Security related admin configurations


See above.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 238


Understanding Magento Security

Security by obscurity, the custom admin path


While security by obscurity is not ideal as the ONLY means of security, it does
provide an additional layer of security for a web application.

There are two places to specify the admin path:

• in app/etc/env.php in the backend/frontName array.

• in Store Configuration > Advanced > Admin.

Further Reading:
• Using a Custom Admin URL

9.3 DEMONSTRATE UNDERSTANDING


OF DIFFERENT TYPES OF ATTACKS AND
PREVENTING THEM WITH MAGENTO

Remote code execution; local and remote file


inclusions; session hijacking; SQL injection; insecure
functions; directory traversal attacks

Note:
I strongly recommend reading each link in this section. This
information should be reviewed periodically to ensure it is
at the forefront of your mind while customizing Magento
applications.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 239


Understanding Magento Security

Remote code execution

This happens when an attacker is able to get PHP executed. This could give the
attacker access to system secrets, allow them to install skimmer technology or
the ability to steal lists of customers.

Further Reading:
• Code Injection

Local and remote file inclusions

This happens when an attacker is able to get a local (on the file system) or a
remote file executed in the production environment. Again, this allows them to
access almost anything they want.

One way this can happen is if a code or class path is stored in the database. For
example, most rules (catalog price rule, cart price rule, customer segments,
target rules) store class paths in the database.

What helps to mitigate this is that Magento validates classes once they have
been instantiated to ensure they implement the correct values. See
\‌
Magento\‌
CustomerSegment\Model\ConditionFactory::create.

Further Reading:
• Testing for Local File Inclusion

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 240


Understanding Magento Security

Session hijacking

This happens when an attacker gets the session ID from the frontend,
adminhtml, or PHPSESSID cookie.

This is mitigated by enabling session validation options in Store Configuration >


General > Web. However, this can lead to dropped sessions (i.e. customers
losing cart contents).

Further Reading:
• Session Hijacking

SQL injection

This happens when an attacker finds an input that is not sanitized before an
operation happens on the database. Good developers escape input and use
parameters wherever possible.

// BAD:

$this->getConnection()->query("SELECT * FROM catalog_product_

entity WHERE entity_id = " . $productId);

// GOOD

$this->select()

->from('catalog_product_entity')

->where('entity_id = ?', (int)$value);

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 241


Understanding Magento Security

Not only is the latter easier to read, it is protected against SQL injections.
Unfortunately, I have seen modules with the former code.

Further Reading:
• SQL Injection

Insecure functions

I strongly recommend to NEVER write insecure functions. All kidding aside,


being a conscientious developer is the best antidote.

The context here would be not using dangerous PHP functions. There is little to
no reason to ever use eval. Be careful when using system commands like exec
as this can give unfettered access to the production environment. If possible,
use the Magento wrappers for any file system commands as they not only have
error handling built-in but there is also some security enhancements.

Further Reading:
• Dangerous PHP Functions

Directory traversal attacks

If there is a way to specify a file path, like ../var/import/file.csv, a


malicious attacker could specify ../../../etc/passwd.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 242


Understanding Magento Security

Further Reading:
• Path Traversal

PHP functions that should never be used, per Magento


recommendations (eval, serialize, etc.)
Magento recommends against using:

• eval: I can’t think of a reason why you would need to do this.

• serialize and unserialize: utilize json_encode and json_decode and


inject data into the appropriate model instead of letting PHP automatically
unserialize into a class.

• md5: use hash

• srand: use \Magento\Framework\Math\Random::getRandomString

• mt_srand: use \Magento\Framework\Math\Random::getRandomString

• The ArrayObject class.

Further Reading:
• Writing Secure Code

How to prevent directory traversals attacks when


uploading a file
Utilize the \Magento\Framework\App\Filesystem\
DirectoryResolver::validatePath method. This ensures that the
specified path matches the root directory to Magento.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 243


Understanding Magento Security

Importance of the HttpOnly property when setting


a cookie
HttpOnly cookies are cookies that Javascript cannot read in document.cookie. If
malicious code is installed onto your site, this ensures they cannot read the
session cookie, thus allowing unwanted access to the website.

Further Reading:
• HttpOnly

• Secure and HttpOnly Cookies

Serving Magento from the base vs. the pub/ directory


It’s amazing that we keep coming across sites that are served from the base
directory.

Why serve from pub/? This limits exposure to possibly sensitive information
should the server be misconfigured. I have come across a number of websites
where their var/ directory is publicly accessible. One of those websites had
multiple human-readable SQL data dumps present—not good.

Serving from the pub/ directory prevents this from even happening.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 244


Understanding Magento Security

Retrieving the user IP from the REMOTE_ADDR header


vs. the X-Forwarded-For header
You can normally fetch the user’s IP address using the Magento\Framework\
HTTP\PhpEnvironment\RemoteAddress::getRemoteAddress().

However, in cases of load balancing or a web application firewall (like


CloudFlare), the IP address can be CloudFlare’s IP address, which is quite
useless. Proxies utilize the X-Forwarded-For header to provide the original
IP address.

Further Reading:
• How to add alternative Http headers to Magento 2

• How does Cloudflare handle HTTP Request headers?

File operations influenced by user-submitted request


parameters, how to stop directory traversal attacks and
restrict access to dirs/files
This is discussed above. Ideally, you want to limit any input that a user (frontend
or backend) has directly on the file system. But, if it is a must that they have
access, carefully ensure that the path is properly validated.

Further Reading:
• Secure cron.php to run in a browser

• X-Frame-Options header

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 245


Understanding Magento Security

How to write secure SQL queries


Ensure that you are using parameters. Use type casting wherever the value
should be an integer or a float. Don’t write direct SQL queries.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 246


APPENDIX

Professional Developer Plus


Appendix

DISCUSSION OF THE ENTITY MANAGER


This isn’t particularly relevant to the overall discussion of staging (where this is
mentioned). Entity Manager is the framework that staging is bolted on to. As
such, it warrants a mention, but not in the flow of the book.

Magento recommends against using an entity manager for any new work:
vendor/magento/framework/EntityManager/EntityManager.php. As
such, we will not discuss this too deeply other than to provide pointers as to
where you can learn more.

The idea behind EntityManager is that you use configuration (in di.xml) to
assemble most of the functionality that is needed—and that this would simplify
the process of storing and managing data. Ultimately, EntityManager proved too
complex as the simple abstract resource model does the job quite nicely.

The Magento\Framework\EntityManager\MetadataPool stores the


schema information associated with updating the database. Here is a snippet
from vendor/magento/module-catalog/etc/di.xml:

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 248


Appendix

<type name="Magento\Framework\EntityManager\MetadataPool">

<arguments>

   <argument name="metadata" xsi:type="array">

     <item name="Magento\Catalog\Api\Data\ProductInterface" xsi:type="array">

       <item name="entityTableName" xsi:type="string">catalog_product_entity</item>

       <item name="eavEntityType" xsi:type="string">catalog_product</item>

       <item name="identifierField" xsi:type="string">entity_id</item>

       <item name="entityContext" xsi:type="array">

         <item name="store" xsi:type="string">Magento\Store\Model\StoreScopeProvider</item>

       </item>

     </item>

   </argument>

</arguments>

</type>

Note that there are a number of events that are available for the EntityManager.

Load/Create/Delete/Update:
• entity_manager_[action]_before

• $entityType_[action]_before (for the entity type of


Magento\‌ Api\Data\PageInterface the result will be
Cms\‌
magento_‌
cms_‌
api_data_pageinterface_load_before)

• $entityType_[action]_after

• entity_manager_[action]_after

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 249


Appendix

Attributes and Extensions (EntityManager):


The only examples I find are extensions: vendor/magento/module-sales-
rule/etc/di.xml

Loading:

First, the metadata is loaded. This object contains information about the entity
to be saved, including references to additional tables (for example in the CMS
page entity, sequence_cms_page). Second, the hydrator is loaded. This takes
raw data that has been loaded from the database and returns a hydrated entity
model. The data is loaded from the database, then hydrated (\Magento\
Framework\EntityManager\Operation\Read\ReadMain::execute). All
linked fields are then loaded, and the data is merged. This happens for both
extensions (\Magento\Framework\EntityManager\Operation\Read\
ReadExtensions) and attributes (\Magento\Framework\EntityManager\
Operation\Read\ReadAttributes).

Saving:

Similar to loading, the same classes are loaded. The before events are run,
then the data is saved: first, the row data, next the attribute data, finally, the
extension data.

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 250


Appendix

Practical experience:
• Navigate to a CMS page in adminhtml.

• Set a breakpoint in \Magento\Framework\EntityManager\Operation\


Read::execute.

• Refresh.

Further Reading:
• Entity Manager

• Recipe for a good Data Model on Magento 2

Copyright © 2019, SwiftOtter, Inc. and Joseph Maxwell 251

You might also like