Module 1. Magento 2 Basics

  • Magento modes

    • Setting Mode
    • Developer Mode
    • Production Mode
    • Default Mode
  • Main Concepts
  • File System Structure
    • File and Directories Permissions
    • Code and Themes
    • Code Location and Structure
    • Directories Structure
  • Configuration system
  • Error Reporting
  • Dependency Injection, Object Manager, and Code Generation
    • Dependency Injection
    • Object Manager
    • Shared vs Non-Shared Objects
    • Virtual Types
    • Preferences
    • Code Generation
  • Plugins and Events
    • Plugins
    • Configuration Inheritance
    • Events
  • Caching in Magento 2

Magento modes

Unlike Magento 1.x, default mode assumed in Magento 2 instance is default, not production. Below there is a chart of all available modes.

 

Mode

Static files caching

Exceptions displayed

Exceptions logged

Performance impacted

developer

 

 

production

 

 

 

default

 

Please note that the standard mode for a freshly imported project is default, a developer should always check the mode and switch the instance in the developer mode.

Only developer mode should be used by developers. It has many benefits although it renders Magento instance noticeably slow.

Setting Mode

You can set certain mode by setting MAGE_MODE environment variable.

First, run 

MAGE_MODE=(developer|default|production)

Then restart Apache

sudo service apache2 restart # Ubuntu

sudo service httpd restart # CentOS/RedHat

A better way is to set mode in Apache configuration file, or in .htaccess file:

SetEnv MAGE_MODE default

It can also be set in php-fpm config file as follows:

env[MAGE_MODE]=developer

The preferable way to manipulate the modes, though, is using the CLI command:

php bin/magento deploy:mode:set <mode>

php bin/magento deploy:mode:show  # to show the currently set mode

Developer Mode

The developer mode makes Magento act as follows:

  • Static files materialization is not enabled
  • Uncaught exceptions are displayed in the browser
  • Exceptions thrown in the error handler, are not logged
  • System exception logging in var/report is highly detailed.

Production Mode

After an instance is deployed to a server, it must be set to production mode. The production mode makes Magento act as follows:

  • Magento is in its highest in terms of performance
  • Exceptions are not being displayed to the user, they are logged instead
  • Static files materialization is disabled
  • The Magento base directory can be set to read only permissions, it’s forbidden to write to the base directory in production mode.

Default Mode

The default mode, which, as it was already said, is implied when no other mode was set directly, switches Magento to act like this:

  • Exceptions are not being displayed to the user, they are logged instead
  • Static files materialization is enabled
  • Not recommended for using on real servers, as caching impacts performance negatively.

There is also an independent maintenance mode, it can be handled by creating var/.maintenance.flag file. As long as it’s there, website responds with 403 HTTP state unless the IP from which visitors try to access the website is not inside var/.maintenance.ip file.

The maintenance mode can also be manipulated through running commands as follows:

php bin/magento maintenance:disable

php bin/magento maintenance:enable

php bin/magento maintenance:status

php bin/magento maintenance:allow-ips

It’s highly recommended to use CLI commands if possible.

Main Concepts

Term or Concept

Definition

Magento Framework

Provides core business logic and functionality, base classes, resource models and data access capabilities. The fundamental concepts and rules for how the base components behave are defined within the Magento Framework. The Framework provides core components with base functionality that can then be inherited by the custom components for a specific website or application. The final behavior, look & feel of the website are determined by how the components are extended and customized. Modules and Theme s are units of customization in Magento. Modules for business features and themes for the user experience and look & feel, both have a life cycle allowing them to get installed, deleted, disabled and so on.

Modules

Units of customization in Magento. Modules are packages of code that encapsulate particular business features or set of features. They have a life cycle allowing them to get installed, deleted, disabled and so on.

  • a module is a logical group, a directory containing blocks, controllers, helpers, models, view layer files and so on, related to specific feature or widget
  • using a modular approach implies that every module encapsulates a feature and has minimum dependencies on other modules

Themes

Units of customization in Magento. Themes are used for implementing visual design, user experience and look & feel. They have a life cycle allowing them to get installed, deleted, disabled and so on.

Language Packages

Magento can present the UI in different languages without modifying the actual application source code. Magento translates system messages, error messages, and labels for display in the UI. By convention, the label and system messages for the UI are expressed in English (en_US) in the source code. To replace these phrases with a different language when the source code is interpreted, Magento has a layer of indirection. Language packages for other languages either are shipped with Magento or can be developed and installed separately.

Areas

Scopes in configuration that allows you to load configuration files and options. Within Magento 2, there are areas six areas, for the frontend, admin, install, crontab, RESTful API and SOAP API. The purpose of areas is to increase efficiency by not requiring to load all the configuration at once.

  • allow to load only required config files
  • only the dependent config options for a specific area are loaded
  • typically have both behavior and view components

Libraries

Provide a reusable logic that can be called upon as building blocks to be used in both Modules and Themes

  • play significant role in configuration and file management
  • the library component is much larger and better than in Magento 1.x
  • don’t provide independent business features; instead, they offer building blocks for modules and themes
  • non-Composer libraries are placed in the lib directory and are PSR-0 compliant
  • Composer allows to maintain libraries in the vendor directory and provides autolading access to their codebase to the rest of the Magento code

File System Structure

The file system in a Magento 2 instance has certain structure and needs specific requirements to be met.

File and Directories Permissions

As Magento 2 is mainly being operated by it’s CLI interface, and some commands modify files and directories, it’s very important to remember the recommendation given in Magento official documentation regarding the file system owner. As you run commands in the terminal under a user, that user will be an owner of all newly generated files and will have to get writing access to files created by PHP.

It refers to the commands ran as a cron job, too.

It’s important to have all three agents: webserver/PHP, terminal user and cron job user be the same user.

Code and Themes

Please note that Magento 2 doesn’t have separate design packages and skins, it operates with united entities, themes. An improved fallback mechanism works on all theme files, including styles and JavaScript files.

For better granularity, Magento 2 is now using a revamped approach for storing layout definitions, template files, as well as styles and browser scripts, related to specific modules. Now they don’t have be part of any theme, they can be stored within the module view directory.

Having all the above in mind, it’s clear that a new mechanism is used to gather all static files that are supposed to be visible to the browser and put them in one place, accessible by direct URLs. This is called “static files deployment”, and happens either on demand or on the fly, depending on the current instance mode.

Part of that files pre-generation happens during flushing the cache, part of it can be triggered by running 

php bin/magento setup:static-content:deploy

Sometimes it’s not enough to give Magento a direct command to regenerate static files, depending on the mode and on user permissions it may be good to physically remove the static files by running

rm -rf pub/static/*

and then flush the cache and run static files deployment process again.

Code Location and Structure

It’s clearly stated that the app/code directory is the place for your code to put if you want it be functional in Magento 2. When having Magento 2 installed through Composer and working with it, you may notice that that directory is empty. It’s the way composer works, the rule is, when want to have a custom module (let’s call it MyCompany_Module), you EITHER create a directory app/code/MyCompany/Module and put the code there and thus follow PSR-0 conventions, OR you create a directory vendor/module-mycompany-module, put the code there and initialize it to work with Composer the usual way. Both ways it works, and the latter is the way to do when you work at Vaimo.

It’s mandatory to follow the naming convention that is same as in Magento 1.x: the module name consists of a capitalized and camelized vendor name, and a capitalized and camelized local module name, separated with an underscore. The goal of this naming convention is to make sure that a module will always have a unique name regardless of who developed it.

A module should be declared directly in two places:

  • <moduleDir>/etc/module.xml
  • app/etc/config.php

1

2

3

4

5

6

7

8

9

<?xml version=“1.0”?>

<config xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance” xsi:noNamespaceSchemaLocation=“urn:magento:framework:Module/etc/module.xsd”>

    <module name=“Magento_Cms” setup_version=“2.0.0”>

        <sequence>

            <module name=“Magento_Store”/>

            <module name=“Magento_Theme”/>

        </sequence>

    </module>

</config>

1

2

3

4

5

6

7

8

9

<?php

return array (

  ‘modules’ =>

  array (

    // …

    ‘Magento_Cms’ => 1,

    // …

  ),

);

You can see that Magento_Cms module has dependencies defined in the node <sequence> in module.xml. It’s important to specify what other modules should be loaded before the module you’re developing. It’s especially crucial to maintain proper dependency sequence when you extend functionality of specific modules, as well as add/overwrite theme files to extend output.

Please note:

  • Multiple modules cannot be responsible for the same feature
  • One module cannot be responsible for multiple features
  • a module declares explicit dependency (if any) on another module
  • Any dependency on other components (e.g. themes) must also be declared
  • Removing or disabling a module does not result in disabling other modules in the sequence

Composer is used as a mechanism of maintaining of all modules used for development. Of course external library or module dependencies can and should be declared in composer.json. So in case of dependencies between Magento modules, they, too, should be declared in composer.json, too.

There are two types of module dependencies in Magento 2:

  • Hard Dependency. Implies that a module CANNOT function without the other modules on which it depends. This dependency is always assumed if a module uses the code from another module, and must be declared.
  • Soft Dependency. Implies that the module CAN function without the other modules on which it depends.

Directories Structure

Let’s look at the project root and describe what we see.

Path

Description

app/code

Contains all modules except those located in the vendor directory.

Each module is located inside under a directory that corresponds to specific vendor and module name.

A path to a module looks like this: app/code/<Vendor>/<ModuleName>

Inside, there are the following elements, some of them are mandatory, some of them are not:

Path

Description

Block

Block classes are stored here

Controller

All action controllers go here. Note that there can be only one action per controller, and controllers follow their own interfaces for code reusability.

etc

Module configuration files. Contains XML files directly or under subdirectories named as areas, for narrowing the scope of the configuration.

Helper

Optional directory in case there is need for helpers in the module. Note, there is no need to have a default helper to process module-specific translations. This directory is the only mandatory for the module.

i18n

Module-specific translation files.

Model

Models hierarchy

Observer

Contains observer classes. Note that they are separated from models, as now they implement their own interface and there can be only one event handler per class.

Setup

Contains install and upgrade classes

Test

All module tests live here

view

Visual representation files that are related to the module, they will be merged with theme files and used by rendering mechanism. Note that inside of this subdirectory, there are areas and there are specific files under each defined area.

Here is the internal structure of an area subdirectory within view directory:

Path

Description

templates

Module-specific templates

layout

Module-specific layout files

web

Optional directory where there can be static files like JavaScript, images and CSS files be located. It’s content will later be merged with pub/static directory content during static file deployment.

composer.json

A composer JSON configuration file for the module

registration.php

A module registration file, must contain the following:

<?php

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

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

    ‘<Vendor>_<ModuleName>’,

    __DIR__

);

app/design

Contains all themes defined in the instance except those located in the vendor directory.

Each theme is located inside under a directory that corresponds to an area name.

A path to a theme looks like this: app/design/<area>/<Vendor>/<name>

Inside, there must be the following elements:

Path

Description

media/preview.png

Theme preview picture

registration.php

A theme registration file, must contain the following:

<?php

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

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

    ‘<area>/<Vendor>/<name>’,

    __DIR__

);

theme.xml

A theme declaration file, must contain the following:

<theme xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance” xsi:noNamespaceSchemaLocation=“urn:magento:framework:Config/etc/theme.xsd”>

    <title>My Custom Theme</title>

    <parent>Magento/luma</parent>

    <media>

        <preview_image>media/preview.jpg</preview_image>

    </media>

</theme>

*

All other files and directories that correspond to the theme.

app/etc

Contains the following mandatory files:

File

Description

config.php

Contains an array of every module installed

di.xml

Contains a definition of all global class dependencies

env.php

Contains the same info that Magento 1.x used local.xml for, but in a PHP format

bin

This directory is a home for the Magento CLI interface

lib

This directory is a home for all libraries, or rather it would be a home for all libraries if most of the libraries weren’t located under vendor directory

pub

This is a directory for all files accessible from the browser by URL in production mode.

setup

Magento installation code is located in this directory

var

A directory to store all temporary files

vendor

 

composer.json

 

composer.lock

 

index.php

 

 

Configuration system

Magento 2 configuration system is much more improved in comparison to Magento 1.x. Now, files are split by function and meaning, so you’ve left with a number of XML files composed of different file types. Each config file type is validated by specific XSD schema definition.

Config file

Scope

Description

env.php

global

Contains database connection information and other settings that correspond

config.php

global

Contains information about every module installed in the system

di.xml

global, module

Dependency injection, class substitute preferences and plugin declarations

config.xml

module

Module-specific system configuration option values

system.xml

module

Module-specific system configuration option field declarations

events.xml

module

Event observer declarations

routes.xml

module

Router declarations

module.xml

module

Module declaration, possibly with module dependencies

acl.xml

module

Admin ACL resource list specific for the module

There can be many other config files. To support them, module config models should be extended and XSD schema files created.

Magento configuration mechanism is build so that specific configuration file is being loaded if an application requires a specific top configuration node. Only global specific area scope configuration files are loaded.

Once the files are loaded into memory, they are being merged into a whole configuration tree. Having files with same name in different areas and different modules doesn’t make only the last loaded file being added to the memory, every file is being consequently merged one upon another instead.

Additionally, once the whole configuration tree is formed in the memory, a content of the data table core_config_data is being loaded, too, and it’s values are being merged upon the configuration tree same way as it was done for the files. 

 

 

Merging of config files works like this:

  • Nodes in configuration files are merged based on their fully qualified XPaths
  • A special attribute is defined in the $idAttributes array and declared as an identifier
  • After two document files are merged, the resulting tree contains nodes from both files
  • The second XML file either supplements or overwrites nodes from in first XML file

Each configuration file is validated against schema specific to its configuration file.

The Magento_Config component is responsible for processing configuration files and validating them against corresponding schemas. It also provides a developer a way to extend the configuration loading as needed, by implementing the module’s interfaces.

Error Reporting

Magento 2 uses a strongest level of error reporting, so that even a PHP notice will cause an exception. As a developer, you’re responsible to avoid notification as well as other levels of error reporting, an Magento helps with it. 

The way errors are reported and look is determined by the instance mode.

Dependency Injection, Object Manager, and Code Generation

Dependency Injection

Dependency Injection (DI) is a way to manage object dependencies by setting objects to be used inside the current object by passing references of the objects it depends upon in its constructor. It may seem redundant, and a constructor that has up to 14 arguments, quote unreadable, but it’s only the first glance.

 All object dependencies are passed (injected) into an object instead of being pulled by the object from the environment on demand.

 A dependency (coupling) implies that one component relies on another components to perform its function.

 A large amount of dependency limits the code reuse and makes moving components to new projects difficult.

Dependency Injection makes every single class you have easily testable, it lets you keep building and modifying your class not caring what changes were made to the objects you depend upon as long as they still implement same interfaces as you rely on.

It also allows to build your architecture by using classes as ready to use services dependent on limited amount of other services by their interfaces, instead of instantiating and configuring specific classes on the fly each time you need them in your code.

The less objects you inject into your class, the better. There should be a reasonable balance in this, and a big amount of dependency you require is a good signal your code smells and you need to refactor it.

Object Manager

It would have been a very sad experience creating a code in Magento with DI mechanism, had it not been supported by Object Manager (OM), a container class that can read dependency declaration across all the modules, maintain references to every possible class interface that may be required by other classes, and instantiate any them on demand being also fully injected with all required dependencies. Magento provides two main Object Managers: one real, for work (\Magento\Framework\App\ObjectManager), an another for testing purposes (\Magento\TestFramework\ObjectManager).

There are many ways to get an access to the ObjectManager. One of them would be a wrong thing to do and can only be tolerated for using in code drafts, to test a concept:

1

$om = \Magento\Framework\App\ObjectManager::getInstance();

A better way to instantiate the ObjectManager is to get an access to the object manager factory (through using an injected reference, of course), and then create an instance:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

/**

 * Constructor

 *

 * @param \Magento\Framework\App\ObjectManagerFactory $objectManagerFactory

 */

public function __construct(ObjectManagerFactory $objectManagerFactory)

{

    $params = $_SERVER;

    $params[StoreManager::PARAM_RUN_CODE] = ‘admin’;

    $params[StoreManager::PARAM_RUN_TYPE] = ‘store’;

    $this->om = $objectManagerFactory->create($params);

    $this->log = $this->om->get(‘Magento\Framework\Logger\Monolog’);

    /** @var State $state */

    $state = $this->om->get(‘Magento\Framework\App\State’);

    $state->setAreaCode(‘adminhtml’);

    parent::__construct();

}

It can be done, for example, in a descendant of \Symfony\Component\Console\Command\Command when you create your own CLI command, because it is a third-party library class and it isn’t aware about Magento configuration in general and di.xml files in particular.

You have two options when you have obtained an instance of OM: 

  • $instance = $om->get(‘Full\Class\Name’) — Retrieves a singleton-like instance of a certain class. First time it’s created, then every time sombody tries to call get() for the same class, same instance will be returned
  • $instance = $om->create(‘Full\Class\Name’) — creates a fresh-new instance of a certain class.

Note that Magento 2 no longer requires a so-called “class path” to refer to a class name as it was required in Magento 1.x, you use a proper full class name with namespaces separated by a backslash.

The best way to use the Object Manager is to not use it directly at all. Instead, configure your class as a service in your module’s di.xml and provide relevant dependencies, and you won’t need to invoke OM directly.

First, you create your class declaration:

Class declaration

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

namespace Magento\Catalog\Helper;

 

use Magento\Catalog\Api\CategoryRepositoryInterface;

use Magento\Catalog\Api\ProductRepositoryInterface;

use Magento\Catalog\Model\Product as ModelProduct;

use Magento\Framework\Exception\NoSuchEntityException;

use Magento\Store\Model\Store;

 

/**

 * Catalog category helper

 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)

 */

class Product extends \Magento\Framework\Url\Helper\Data

{

    // …

 

    /**

     * @param \Magento\Framework\App\Helper\Context $context

     * @param \Magento\Store\Model\StoreManagerInterface $storeManager

     * @param \Magento\Catalog\Model\Session $catalogSession

     * @param \Magento\Framework\View\Asset\Repository $assetRepo

     * @param \Magento\Framework\Registry $coreRegistry

     * @param \Magento\Catalog\Model\Attribute\Config $attributeConfig

     * @param array $reindexPriceIndexerData

     * @param array $reindexProductCategoryIndexerData

     * @param ProductRepositoryInterface $productRepository

     * @param CategoryRepositoryInterface $categoryRepository

     * @SuppressWarnings(PHPMD.ExcessiveParameterList)

     */

    public function __construct(

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

        \Magento\Store\Model\StoreManagerInterface $storeManager,

        \Magento\Catalog\Model\Session $catalogSession,

        \Magento\Framework\View\Asset\Repository $assetRepo,

        \Magento\Framework\Registry $coreRegistry,

        \Magento\Catalog\Model\Attribute\Config $attributeConfig,

        $reindexPriceIndexerData,

        $reindexProductCategoryIndexerData,

        ProductRepositoryInterface $productRepository,

        CategoryRepositoryInterface $categoryRepository

    ) {

        $this->_catalogSession = $catalogSession;

        $this->_attributeConfig = $attributeConfig;

        $this->_coreRegistry = $coreRegistry;

        $this->_assetRepo = $assetRepo;

        $this->_reindexPriceIndexerData = $reindexPriceIndexerData;

        $this->productRepository = $productRepository;

        $this->categoryRepository = $categoryRepository;

        $this->_reindexProductCategoryIndexerData = $reindexProductCategoryIndexerData;

        $this->_storeManager = $storeManager;

        parent::__construct($context);

    }

 

 

    // …

}

Second, you declare what dependencies your class should obtain when instantiated:

Module’s di.xml

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

<?xml version=“1.0”?>

<config xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance” xsi:noNamespaceSchemaLocation=“urn:magento:framework:ObjectManager/etc/config.xsd”>

    <!– … –>

 

    <type name=“Magento\Catalog\Helper\Product”>

        <arguments>

            <argument name=“catalogSession” xsi:type=“object”>Magento\Catalog\Model\Session\Proxy</argument>

            <argument name=“reindexPriceIndexerData” xsi:type=“array”>

                <item name=“byDataResult” xsi:type=“array”>

                    <item name=“tier_price_changed” xsi:type=“string”>tier_price_changed</item>

                </item>

                <item name=“byDataChange” xsi:type=“array”>

                    <item name=“status” xsi:type=“string”>status</item>

                    <item name=“price” xsi:type=“string”>price</item>

                    <item name=“special_price” xsi:type=“string”>special_price</item>

                    <item name=“special_from_date” xsi:type=“string”>special_from_date</item>

                    <item name=“special_to_date” xsi:type=“string”>special_to_date</item>

                    <item name=“website_ids” xsi:type=“string”>website_ids</item>

                    <item name=“gift_wrapping_price” xsi:type=“string”>gift_wrapping_price</item>

                    <item name=“tax_class_id” xsi:type=“string”>tax_class_id</item>

                </item>

            </argument>

            <argument name=“reindexProductCategoryIndexerData” xsi:type=“array”>

                <item name=“byDataChange” xsi:type=“array”>

                    <item name=“category_ids” xsi:type=“string”>category_ids</item>

                    <item name=“entity_id” xsi:type=“string”>entity_id</item>

                    <item name=“store_id” xsi:type=“string”>store_id</item>

                    <item name=“website_ids” xsi:type=“string”>website_ids</item>

                    <item name=“visibility” xsi:type=“string”>visibility</item>

                    <item name=“status” xsi:type=“string”>status</item>

                </item>

            </argument>

            <argument name=“productRepository” xsi:type=“object”>Magento\Catalog\Api\ProductRepositoryInterface\Proxy</argument>

        </arguments>

    </type>

 

    <!– … –>

</config>

Now, when you ask the Object Manager to instantiate an instance of a product helper, or are working in a class which already has this helper injected, you will get an instance of helper that has all requested dependencies injected and ready to work.

You may notice that there are less arguments declared in the di.xml file than there are present. It just means that they were previously declared in some other place, and this declaration just redeclared some of them and extended others.

There are different types of arguments, below you can see a full list of all possible types that can be set in xsi:type attribute:

Type name

Description

array

Associative array. It implies that the tag body will contain items as <item name=”xxx” xsi:type=”yyyy”></item>, so it’s possible to specify multi dimensional arrays or arrays of objects. When config is merged, all array items are merged recursively.

string

A text string

object

An object. It implies that the tag body will contain a full class name (with no leading back slash)

boolean

A boolean value

const

A constant. It implies that the tag body contains a constant name

number

A numeric value. Can be both float and integer

init_parameter

Same as const at the moment

null

A null value

Shared vs Non-Shared Objects

By default, each argument or item of an array that references an object, is “shared”, i.e. it’s assumed that it’s declared with shared=”true”. It means that OM’s create() method will be used when it’s time to instantiate it. If you want a singleton-like dependency, you have to use shared=”true” explicitly. OM’s get() method will be used in this case.

There are objects that are not injectable.

  • Injectable — an object, typically a singleton, that can be instantiated by the Object Manager
  • Non-injectable — an object that cannot be properly instantiated by the Object Manager. Typically, such objects have transient lifetime, or it requires an external input (user response, database) to be properly created.

For instance, a catalog product object: usually it doesn’t make any sense to get ANY object of a particular class, instead you would like to load project from the database and have an instance of SPECIFIC project, instantiated and initialized with required data.

 If the business logic requires to create a non-injectable object, the surrounding class must request a factory that spawn non-injectables of specific class.

 If the method requires to perform an operation on a existing non-injectable, it must get that object as an argument.

Avoid injecting injectable objects to the constructors of non-injectable, because it requires an additional lookup during object unserialization.

Preferably, when you specify classes for dependencies you want to inject, you should use an interface instead of an object of real class that can be instantiated. How does Magento instantiates such dependencies? By using type preferences.

Virtual Types

Virtual types are a way to inject different dependencies into existing classes without affecting other classes. What is the difference between <type> and <virtualType>?

For example the Magento\Framework\Session\Storage class takes a $namespace argument in its constructor, which defaults to the value ‘default’, and you could use the type definition to change the namespace to ‘core’.

<type name=“Magento\Framework\Session\Storage”>

    <arguments>

        <argument name=“namespace” xsi:type=“string”>core</argument>

    </arguments>

</type>

The above config would make it so that all instances of Magento\Framework\Session\Storage have a namespace of ‘core’. Using a virtual type allows for the equivalent of a sub-class to be created, where only the sub-class has the altered argument values.

In the codebase we see the following two configurations:

<virtualType name=“Magento\Core\Model\Session\Storage” type=“Magento\Framework\Session\Storage”>

    <arguments>

        <argument name=“namespace” xsi:type=“string”>core</argument>

    </arguments>

</virtualType>

<type name=“Magento\Framework\Session\Generic”>

    <arguments>

        <argument name=“storage” xsi:type=“object”>Magento\Core\Model\Session\Storage</argument>

    </arguments>

</type>

The first snippet creates a virtual type for Magento\Core\Model\Session\Storage which alters the namespace, and the second injects the virtual type into Magento\Framework\Session\Generic. This allows Magento\Framework\Session\Generic to be customized without affecting other classes that also declare a dependency on Magento\Framework\Session\Storage

Preferences

Type preference is also declared in the di.xml and looks like this:

1

2

3

4

5

6

7

8

<?xml version=“1.0”?>

<config xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance” xsi:noNamespaceSchemaLocation=“urn:magento:framework:ObjectManager/etc/config.xsd”>

    <!– … –>

 

    <preference for=“Magento\Catalog\Api\Data\ProductInterface” type=“Magento\Catalog\Model\Product” />

 

    <!– … –>

</config>

This declaration ensures that once any objects request OM to instantiate a dependency of \Magento\Catalog\Api\Data\ProductInterface type, the \Magento\Catalog\Model\Product will be used instead. Of course the class you choose as preferred for another class must derive of it.

Code Generation

Class definitions are read using reflection. The definition compiler tool to accomplish this than with PHP. The tool:

  • Generates all required factories
  • Generates interceptors for all classes that have plugins declared in every di.xml
  • Automatically compiles definitions for all modules and libraries
  • Compiles class inheritance implementation relations to increase performance of configuration inheritance operations
  • Compiles plugin definitions (the list of declared public methods)
  • Puts all generated classes by Magento and modules into the var/generation directory
  • Puts the rest of the above to the var/di directory

If you don’t run the compiler tool and these files don’t exist, the slower class processing is taking place.

The definition compiler tool doesn’t analyze auto-generated factory classes in files located in the lib/internal/Magento directory. Do not use autogenerated factory classes at the library level, these classes must be created manually.

Use the slower definition compiling mechanism during development, and the pre-compiled code in production. This is much faster for the production because it’s pre-compiled. It doesn’t work well in the development mode, because it is time consuming, having to go to thousands of classes and read every constructor.

Plugins and Events

Plugins

Plugins are used to change the behavior of any method in the class while not touching the class itself. It helps resolving the old problem of Magento 1.x: you can override a class, but another vendor may try and override the same class by extending it, not being aware of your modification. As PHP doesn’t support extending classes from many base classes, the problem seems unresolvable, yet plugins plus code generation can partially resolve it. Plugins don’t conflict with each others, because they are executed one after another.

You cannot use plugins for:

  • Final methods and classes
  • Non-public methods
  • Class methods (such as static methods)
  • Inherited methods
  • __construct()
  • Virtual types
  • Classes created without dependency injection.

In order for plugins to work, Magento automatically generates interceptors — classes that are extended from the original class but additionally implement plugin execution code. The additional code ensures to call every plugin method as well as the original method.

It’s very simple to declare a plugin in any module’s di.xml config file:

1

2

3

<type name=“Magento\Framework\Mview\View\StateInterface”>

    <plugin name=“setStatusForMview” type=“Magento\Catalog\Model\Indexer\Category\Product\Plugin\MviewState” />

</type>

Then, in the plugin class:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

namespace Magento\Catalog\Model\Indexer\Category\Product\Plugin;

 

class MviewState

{

    // …

 

    /**

     * Synchronize status for view

     *

     * @param \Magento\Framework\Mview\View\StateInterface $state

     * @return \Magento\Framework\Mview\View\StateInterface

     */

    public function afterSetStatus(\Magento\Framework\Mview\View\StateInterface $state)

    {

        // …

 

        return $state;

    }

}

The notation used in this particular plugin means “intercept running the method \Magento\Framework\Mview\View\StateInterface::setStatus(\Magento\Framework\Mview\View\StateInterface $state) and run an additional code that accepts $state as a parameter and is expected to return the state AFTER the original method is ran”. 

The <plugin> declaration has following attributes:

XML attribute

Description

name

Mandatory. A name of the plugin unique inside the same <type> node.

type

A class name of the plugin class. Can be omitted if disabled=”true”.

sortOrder

Optional. Sets the sort order of the plugin among other configured for the same base type plugins.

disabled

Optional, by default “false” is assumed. If “true”, a plugin with a particular name will be disabled.

There are possible three types of interception within a plugin:

Interception type

Code example

BEFORE

1

2

3

4

5

6

7

8

9

namespace My\Module\Plugin;

 

class ProductPlugin

{

    public function beforeSetName(\Magento\Catalog\Model\Product $subject, $name)

    {

        return [‘(‘ . $name . ‘)’];

    }

}

AROUND

1

2

3

4

5

6

7

8

9

10

11

12

13

14

namespace My\Module\Plugin;

 

class ProductPlugin

{

    public function aroundSave(\Magento\Catalog\Model\Product $subject, \Closure $proceed)

    {

        $this->doSmthBeforeProductIsSaved();

        $returnValue = $proceed();

        if ($returnValue) {

            $this->postProductToFacebook();

        }

        return $returnValue;

    }

}

AFTER

1

2

3

4

5

6

7

8

9

namespace My\Module\Plugin;

 

class ProductPlugin

{

    public function afterGetName(\Magento\Catalog\Model\Product $subject, $result)

    {

        return ‘|’ . $result . ‘|’;

    }

}

Configuration Inheritance

Because of how Magento configuration is being merged, it’s possible to put additional declarations for injection, preferences, plugins etc. without fear that the previous declarations will be overwritten. If not do it intentionally by using same plugins or injection argument names and maintain their uniqueness across whole configuration tree, each time a new declaration added, it extends the existing configuration rather than overwrite it.

Events

Events mechanism was extended yet unified in Magento 2 in comparison to Magento 1.x. Event observer class now fully comply with the Observer pattern, each observer class holds a single handler method and observer classes are separated from models. Events are used in very specific places of the system where using plugins is not possible because certain events happen inside a logic flow that could not be refactored to happen on the beginning or end of certain method call.

It’s very easy to declare events:

1

2

3

4

5

6

7

8

9

10

11

12

<?xml version=“1.0”?>

<config xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance” xsi:noNamespaceSchemaLocation=“urn:magento:framework:Event/etc/events.xsd”>

    <event name=“customer_login”>

        <observer name=“catalog” instance=“Magento\Catalog\Observer\Compare\BindCustomerLoginObserver” shared=“false” />

    </event>

    <event name=“customer_logout”>

        <observer name=“catalog” instance=“Magento\Catalog\Observer\Compare\BindCustomerLogoutObserver” shared=“false” />

    </event>

    <event name=“page_block_html_topmenu_gethtml_before”>

        <observer name=“catalog_add_topmenu_items” instance=“Magento\Catalog\Observer\AddCatalogToTopmenuItemsObserver” />

    </event>

</config>

Here is what the observer class looks like:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

namespace Magento\Catalog\Observer\Compare;

 

use Magento\Framework\Event\ObserverInterface;

use Magento\Catalog\Model\Product\Compare\Item;

 

/**

 * Catalog Compare Item Model

 */

class BindCustomerLogoutObserver implements ObserverInterface

{

    // ..

 

    /**

     * Customer login bind process

     * @param \Magento\Framework\Event\Observer $observer

     * @return $this

     *

     * @SuppressWarnings(PHPMD.UnusedLocalVariable)

     */

    public function execute(\Magento\Framework\Event\Observer $observer)

    {

        $this->item->bindCustomerLogout();

 

        return $this;

    }

Here is a full list of attributes available for the <observer> declaration.

XML attribute

Description

name

Mandatory. A name of the observer unique inside the same <event> node.

instance

A class name of the observer class. Can be omitted if disabled=”true”.

shared

Optional, by default “true” is assumed. If “false”, the observer should be instantiated each time rather than used as a singleton.

disabled

Optional, by default “false” is assumed. If “true”, an observer with a particular name will be disabled.

Caching in Magento 2

The Magento\Cache library component is responsible for implementation of Magento-specific caching. 

Configuring the cache involves setting up cache frontend and cache backend. The di.xml files are used to configure the cache as well as to configure cache settings for other classes that allow associate specific cached data with classes.

There are many cache types and groups in Magento.

  • Cache group is a way to associate cached information by specific functional role.
  • Cache type facilitates the handling of the cache — for example, to execute operations not on the whole cache, but on parts assigned to same cache type. You can manage cache types in the Cache Management page in the admin panel, or via the Magento CLI command.
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s