Test Quiz

Please DO NOT renumber any of the existing questions and don’t swap answers between themselves, the cheat sheet that is kept separately must refer properly to the numbers and letters here.

 

    1. Which of the following statements are true?
      1. Maintenance mode and standard mode can be active at the same time
      2. Production mode makes it impossible to check errors
      3. Production mode uses pub directory for web-accessible files
      4. There must be error_reporting(E_ALL) present in developer mode
    2. In which Magento modes static files materialization is enabled?
      1. developer
      2. default
      3. production
      4. maintenance
    3. Administrator can control which IP addresses will still have access to the instance during maintenance mode by…
      1. editing .htaccess
      2. modifying System Configuration
      3. using php bin/magento maintenance:allow-ips
      4. editing var/.maintenance.ip file
    4. In order to specify that your module depends on other modules, you need…
      1. mention them in <sequence> node in the <moduleDir>/etc/module.xml comma-separated
      2. mention them in <dependency> node in the <moduleDir>/etc/module.xml as a list of module subnodes
      3. mention them in <sequence> node in the <moduleDir>/etc/module.xml as a list of dependency subnodes
      4. mention them in <sequence> node in the <moduleDir>/etc/module.xml as a list of module subnodes and add dependencies to module’s composer.json
      5. mention them in <sequence> node in the etc/config.php as array elements
    5. Can there be several modules responsible for the same feature in your Magento application?
      1. Yes, there is no limitation
      2. Absolutely not
      3. Only if you declare all modules in the same namespace
    6. What dependencies should be declared as module dependencies?
      1. Only modules current module depends on
      2. Themes
      3. Language packs
      4. All of the above
    7. What are possible locations for the module directory for a module Vaimo_FancyModule?
      1. app/code/Vaimo/FancyModule
      2. app/code/Vaimo_FancyModule
      3. vendor/module-vaimo-fancymodule/Vaimo/FancyModule
      4. vendor/module-vaimo-fancymodule
    8. What is the minimal required content of a module directory?
      1. Model, Controller, etc/module.xml, config.xml
      2. etc/module.xml, registration.php
      3. etc/model.xml, Model
      4. registration.php
    9. Where Magento stores credentials for the database, cache, session and encryption key?
      1. app/etc/local.xml
      2. app/etc/config.xml
      3. app/etc/config.php
      4. app/etc/env.php
    10. What’s the sequence of merging configuration files across the system?
      1. Primary application config, global config files in every module, area-specific config files in every module, config values from database
      2. Primary application config, config values from database, for every module: { global config files, area-specific config files }
      3. Primary application config, for every module: { global config files, area-specific config files }, config values from database
      4. Config values from database, application config, global config files in every module, area-specific config files in every module
    11. You need to instantiate a product. What do you do?
      1. Configure \Magento\Catalog\Model\Product object to be injected into your class
      2. Access Object Manager and perform $om->get(‘Magento\Catalog\Model\Product‘)
      3. Inject the \Magento\Catalog\Api\ProductRepositoryInterface product repository, then perform $productRepository->get($sku)
      4. Access Object Manager and perform $om->create(‘Magento\Catalog\Model\Product‘)
    12. The third party module contains the following code: 
  • 1
    2
    3
    4
    5
    6
  • protected function getPriceInfo()
    {
    $priceModel = new \MageSuper\Price\Model\PriceModel($this);
     
    return $priceModel->calculate(true);
    }

  • You are told to override \MageSuper\Price\Model\PriceModel in order to change the calculation formula. Your action:

      1. You’re extending \MyCompany\Price\Model\PriceModel from \MageSuper\Price\Model\PriceModel and overwriting calculate() method
      2. You’re extending \MyCompany\Price\Model\PriceModel from \MageSuper\Price\Model\PriceModel and creating a plugin aroundCalculate() to modify the behavior
      3. You’re extending \MyCompany\Price\Model\PriceModel from \MageSuper\Price\Model\PriceModel and declaring preference to prefer your class instead of the original one in di.xml, then you override calculate() in your class
      4. You refuse the job, because you cannot do what you are told
    1. An action controller you’re working on requires a product collection. In order to get an instance of a product collection, you
      1. Configure your controller to be injected with a product collection with shared=”false”
      2. Fetch an instance of Object Manager, then do $om->get(‘\Magento\Catalog\Api\ProductRepositoryInterface’)->getList()
      3. Configure your controller to be injected with a product collection
      4. Fetch an instance of product collection from Registry

Practice Tasks

  1. Create a MINIMAL module. Describe what is the minimal code and declarations you need to add to accomplish that?
  2. Create a module and make it dependent on the module created in the previous task. then disable the first module and describe that will happen.
  3. For the class \Magento\Catalog\Model\Product, it’s getPrice() method:
    – create a plugin that will modify price
    – create a preference that will substitute the core class with your own
    – make sure that your plugin still works. 
  4. Create a custom module and do the following:
    – create an observer to the event “controller_action_predispatch”
    – get a URL from the request object and log it.
  5. Find a place in the code where output is flushed to the browser ($response->send()). Create an extension that captures the output, and logs the URL, the controller action name, and the content of the tag <title> in the rendered page.
  6. Create an extension that logs a list of all available routers to a log file. Modify it to render a page that shows that list.
  7. Create an extension with a custom router that understands URLs like /frontname-actionpath-action and converts them into /frontname/actionpath/action.
  8. Modify Magento so that “not found” state would forward to the home page.
  9. Modify the frontend catalog product view controller action so that each product has “EXCLUSIVE!” text as a prefix for the product name.
  10. Create an admin panel controller that allows access only if a GET parameter “secret” is equal to “yes”
  11. Create a block extended from \Magento\Framework\View\Element\AbstractBlock. Render it in a new controller action.
  12. Create and render a text block in a new controller.
  13. Customize the product view description block in order to modify the product description and have “Description:” being output before each description.
  14. Define which template is used in \Magento\Catalog\Block\Product\View\Attributes and override it in your extension.
  15. Create an additional content in the top of the content of every page (block content.top) with a custom template. Pass a background color as a block argument in the layout XML, and use that value in the template for rendering.
    Change the background color to a different one only for product detail pages.
    On category pages, move the block to the bottom of the left column.
  16. Create a custom controller action. For that page, set a one column layout, and set a title of the page using layout XML. For this page, remove the block created in the previous task. Add the link to this page to the top links.
  17. Create a custom page on the frontend that lists all store views and associated with them root categories.
    Use a Store collection to fetch all store views, Category collection filtered by specific root categories fetched from the stores.
  18. Create a custom module that adds logging of every product save, including the product ID, SKU and the data that has been changed
  19. Create a DB table with your custom module using install schema logic:
    – create a DB table using DDL
    – execute setup using console tools
    – check the module version within setup_module DB table
  20. Upgrade the DB table created in the previous task:
    – create an upgrade schema class
    – add an additional column to the table using DDL adapter methods
    – increase the module’s version
    – run the appropriate console command
    – verify that the field was added and the module version has increased in the setup_module DB table
  21. Add a data fixture to the data table created and upgraded in the two previous tasks:
  22. – make sure you have a model/resource model in your module that corresponds the table data structure
  23. – create an upgrade data class
  24. – increase the module’s version
  25. – run the appropriate console command
  26. – verify that the fixture data was added on the module upgrade and the module version has increased in the setup_module DB table
  27. Create a custom module that adds a new attribute to the product called “subtitle
  28. – add it to the default attribute set
  29. – give it a varchar type
  30. – make sure that it appears on the product edit page in admin panel and saves successfully
  31. – make it visible on the product view frontend page.
  32. Write an upgrade class for the module created in the previous task that adds another multiselect attribute to the product
  33. – create the attribute called  “alternatives
  34. – set a proper backend model that allows to handle an array of options in the admin panel
  35. – add several predefined options to the attribute
  36. – make it visible on the product view frontend page.
  37. Customize the frontend rendering for the attribute created in the previous task:
  38. – create a custom frontend model
  39. – make it that the attribute is being rendered as an HTML list rather than a comma-separated list
  40. – randomize the output.
  41. Create a custom select attribute for the customer:
  42. – use a data upgrade class for creating a custom attribute
  43. – name the attribute “priority” and make it of “select” frontend input type
  44. – assign a custom source model
  45. – implement a custom source model that allows to select a number from 1 to 10
  46. – test that the attribute works as expected

Module 4. Working with Database. EAV Explained

  • Database Overview
  • Detailed Workflow
    • $model->load()
    • $model->_beforeLoad()
    • $resourceModel->load()
    • $model->_afterLoad()
    • $model->afterLoad()
    • $model->save()
    • $resourceModel->save()
    • $model->isDeleted([$flag])
    • $model->validateBeforeSave()
    • $model->beforeSave()
    • $model->isSaveAllowed()
    • $model->afterSave()
    • $model->afterCommitCallback()
    • $model->delete()
    • $model->beforeDelete()
    • $model->afterDelete()
    • $model->afterDeleteCommit()
  • Setup Scripts and Resources
  • EAV Concepts
  • EAV Specifics in Load and Save
  • Attribute Management
    • $eavSetup->addAttribute($entityTypeId, $code, $attr)
    • $eavSetup->updateAttribute($entityTypeId, $id, $field, $value, $sortOrder)

Database Overview

Database layer in Magento 2 is very similar to one Magento 1.x had.

As model is just an object representation of a data entity, it’s not necessarily a part of the data layer. To make it storable, a developer should extend a model from \Magento\Framework\Model\AbstractModel or one of its descendants. That will provide basic functionality to handle data model fields, primary key field, as well as a mechanism to declare a resource model. There is no need anymore to define resource models in config, an appropriate resource model is simply initiated in the model’s _construct() protected method. Thus, developers should write something like this:

 /**

 * Initialize resource

 *

 * @return void

 */

protected function _construct()

{

    $this->_init(‘Magento\Catalog\Model\ResourceModel\Product\Link’);

}

Similarly as in Magento 1.x, \Magento\Framework\Model\AbstractModel provides $this->_eventPrefix / $this->_eventObject mechanism that is used to fire corresponding CRUD-related events. It also provides a domain-level validation (as opposed to the data structure validation, performed in resource model.

 

Below there is a diagram showing relationship between different elements of the data model layer, resource model and resource collection.

In Magento 2, there is exactly one resource model and one collection defined for every model.

It’s not a data model responsibility to know how to persist itself, it only goes as far as declare a resource model that is responsible for performing CRUD operations over the data stored in the model. Even though there are load(), save() and delete() methods, they are only functional through the declared resource model. As in Magento 1.x, when any of CRUD methods is called, the data model passes itself as a data container and delegates CRUD operations to the resource model along, which is using low level commands of resource adapter to perform them.

 

Therefore, the business logic is decoupled from the storage layer logic, and the storage schema is decoupled from the database driver implementation. 

All resource models that work with the DB storage layer are extended from \Magento\Framework\Model\ResourceModel\Db\AbstractDb. Resource models are responsible for the following:

  • processing CRUD operations (load/save/delete)
  • implementing all necessary additional logic that is needed to manipulate the storage data for the model
  • mapping object properties to the database fields and vice versa.

Unlike Magento 1.x, Magento 2 resource models provide a single entry point to access the storage adapter, getConnection(). Magento 2 Enterprise provides a module ResourceConnection that implements features that allow to utilize DB master/slave mode for better load balancing, performance and security.

Resource collection represents a list of model instances of specific type. All resource collections are extended from \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection and also can perform their internal logic through the resource adapter. Resource collections provide the following:

  • implementing EncapsulatedCollection pattern
  • implementing IterationAggregate, that allows to iterate the collection
  • representing entity groups
  • containing logic for group operations, such as filtering, sorting, paging.

Resource adapter used in all DB resource models and DB collections implements \Magento\Framework\DB\Adapter\AdapterInterface. By default, it’s \Magento\Framework\DB\Adapter\Pdo\Mysql (it also extends Zend_Db_Adapter_Pdo_Mysql).

 

There are a number of Model Type interfaces declared across Magento. Model Type interfaces make it easier for developers to work with the application without knowing low level details. Type interfaces define specific setters and getters, as well as allow using “magic” getters and setters implemented in \Magento\Framework\DataObject.

Detailed Workflow

$model->load()

  • requires a first argument as an ID to retrieve a record and map it to the model instance
  • a loaded instance is always returned, if loading fails, an exception is being thrown
  • an optional second attribute allows to specify the name of the field that should be used as fetching ID when loading

Categories and products, as EAV entities, can be loaded by an alternative attribute using loadByAttribute() method.

Anatomy of the load() method:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

/**

 * Load object data

 *

 * @param integer $modelId

 * @param null|string $field

 * @return $this

 */

public function load($modelId, $field = null)

{

    $this->_beforeLoad($modelId, $field);

    $this->_getResource()->load($this, $modelId, $field);

    $this->_afterLoad();

    $this->setOrigData();

    $this->_hasDataChanges = false;

    $this->updateStoredData();

    return $this;

}

$model->_beforeLoad()

  • dispatches model_load_before
  • dispatches $model->_eventPrefix . ‘_load_before’
  • possible use case: checking ACL to decide if reading from DB is appropriate
  • this WILL not be dispatched when loading models in a collection

$resourceModel->load()

  • dispatches load() request to the data mapper layer
  • $resourceModel->_getLoadSelect() uses ID and selection criterion

$model->_afterLoad()

  • dispatches model_load_after
  • dispatches $model->_eventPrefix . ‘_load_after’
  • possible use case: joining two domain items; for example, products and inventory objects
  • this WILL not be dispatched when loading models in a collection

$model->afterLoad()

  • this public method is provided for using in collections that load data outside of the $model->load() workflow. This is done for performance optimization.

$model->save()

  • Inserts or updates a database record mapped with the model properties
  • Determines the presence of a record by checking on an existence of a primary key
  • Composite key entities are best handled by adding an auto increment primary key

$resourceModel->save()

  • dispatches save() request to the data mapper layer

$model->isDeleted([$flag])

  • checks if an instance is marked for deletion, sets a deletion flag if specified

$model->validateBeforeSave()

  • initiates a validator factory to validate all validation rules that the model contains
  • is used in resource model before save

$model->beforeSave()

  • dispatches model_save_before
  • dispatches $model->_eventPrefix . ‘_save_before’
  • possible use case: can be used for business logic check of ACL check

$model->isSaveAllowed()

  • checks $model->_dataSaveAllowed flag
  • it can be helpful to control the saving process in case, for example, data validation failed

$model->afterSave()

  • dispatches model_save_after
  • dispatches $model->_eventPrefix . ‘_save_after’
  • dispatches clean_cache_by_tags
  • possible use case: can be used for post-processing logic

$model->afterCommitCallback()

  • dispatches model_save_commit_after
  • dispatches $model->_eventPrefix . ‘_save_commit_after’
  • is used for further operations after data has been committed
  • is called via call_user_func() in the resource model 
  • possible use case: can be used for post-processing logic

$model->delete()

  • is used for deleting database records that represent model entities
  • deletes a record based on the value of the primary key
  • dispatches delete() request to the data mapper layer

$model->beforeDelete()

  • dispatches model_delete_before
  • dispatches $model->_eventPrefix . ‘_delete_before’
  • existing events handlers are used to check if deletion is allowed for the current area, and to invalidate the object-related cache
  • possible use cases: can be used for various business logic

$model->afterDelete()

  • dispatches model_delete_after
  • dispatches $model->_eventPrefix . ‘_delete_after’
  • dispatches clean_cache_by_tags
  • possible use cases: can be used for various business logic

$model->afterDeleteCommit()

  • dispatches model_delete_commit_after
  • dispatches $model->_eventPrefix . ‘_delete_commit_after’
  • is used for further operations after delete has been committed
  • possible use case: can be used for various business logic, including post-delete cleanup

Setup Scripts and Resources

Setup scripts is a way to install or upgrade schema or predefined stored data when a module is installed afresh, or a new version of the module is updated.

Module version is set in the module’s module.xml file:

1

2

3

4

5

6

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

    <sequence>

        <module name=“Magento_Store”/>

        <module name=“Magento_Theme”/>

    </sequence>

</module>

Processed module versions are registered in the setup_module table:

In order to install (or upgrade) a module, the following commands must be ran:

  • php bin/magento cache:flush
  • php bin/magento setup:upgrade
  • php bin/magento module:enable <Your_ModuleName>

Then check if the module is present in the System Configuration under Advanced/Advanced.

Note that schema_version and data_version are always same unless an error occurred during running the setup script.

In order to install a new module, there should be install schema class created:

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

namespace Magento\Cms\Setup;

 

use Magento\Framework\Setup\InstallSchemaInterface;

use Magento\Framework\Setup\ModuleContextInterface;

use Magento\Framework\Setup\SchemaSetupInterface;

use Magento\Framework\DB\Adapter\AdapterInterface;

 

/**

 * @codeCoverageIgnore

 */

class InstallSchema implements InstallSchemaInterface

{

    /**

     * {@inheritdoc}

     * @SuppressWarnings(PHPMD.ExcessiveMethodLength)

     */

    public function install(SchemaSetupInterface $setup, ModuleContextInterface $context)

    {

        $installer = $setup;

        $installer->startSetup();

        $connection = $installer->getConnection();

 

        // …

 

        $installer->endSetup();

    }

}

The syntax for DDL commands used for manipulating DB schema are the same as in Magento 1.x.

Once the module is successfully installed, and the both versions are updated in the setup_module table, the InstallSchema::install() method will never be run again (short of deleting the module’s record in the setup_module table). Instead, an upgrade class should be created. Upgrade schema class is very similar to the install schema class with a few key differences.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

namespace Magento\Catalog\Setup;

 

use Magento\Framework\Setup\UpgradeSchemaInterface;

use Magento\Framework\Setup\ModuleContextInterface;

use Magento\Framework\Setup\SchemaSetupInterface;

use Magento\Catalog\Model\ResourceModel\Product\Attribute\Backend\Media;

use Magento\Catalog\Model\Product\Attribute\Backend\Media\ImageEntryConverter;

 

/**

 * Upgrade the Catalog module DB scheme

 */

class UpgradeSchema implements UpgradeSchemaInterface

{

    public function upgrade(SchemaSetupInterface $setup, ModuleContextInterface $context)

    {

        $setup->startSetup();

 

        / …

 

        $setup->endSetup();

    }

}

Install/upgrade data classes are for preparing, presetting and persisting model-level data at the moment the module is installed or upgraded. 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

namespace Magento\Catalog\Setup;

 

use Magento\Framework\Setup\InstallDataInterface;

use Magento\Framework\Setup\ModuleContextInterface;

use Magento\Framework\Setup\ModuleDataSetupInterface;

 

/**

 * @codeCoverageIgnore

 */

class InstallData implements InstallDataInterface

{

    public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context)

    {

        $setup->startSetup();

 

        // …

 

        $setup->endSetup();

    }

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

namespace Magento\Catalog\Setup;

 

use Magento\Framework\Setup\UpgradeDataInterface;

use Magento\Framework\Setup\ModuleContextInterface;

use Magento\Framework\Setup\ModuleDataSetupInterface;

 

/**

 * Upgrade Data script

 * @codeCoverageIgnore

 */

class UpgradeData implements UpgradeDataInterface

{

    public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface $context)

    {

        $setup->startSetup();

 

 

        // …

 

        $setup->endSetup();

    }

}

Setup process provides a setup context to every install or upgrade script. The context object contains information necessary for the setup process.

It’s mandatory for schema setup classes and is considered a best practice with data setup classes to start your install/upgrade logic with $setup->startSetup() and end it with $setup->endSetup().

Even though it’s still possible to command setup logic to run SQL clauses directly, it’s highly recommended to avoid this in favor using DDL syntax to make the code unified and be in RDBMS-independent way.

The workflow Magento 2 uses for setting installs/upgrades, includes the following commands:

  • php bin/magento setup:db:status
  • php bin/magento setup:upgrade
  • php bin/magento setup:db-schema:upgrade
  • php bin/magento setup:db-data:upgrade

EAV Concepts

EAV in Magento is used to provide flexibility in storing variable amount of attributes to different entities, maintain different types of attributes and store attribute values across different stores. It also allows developers to encapsulate attribute-related business logic and manage attributes by adding new and removing unused ones.

EAV storage is separated into meta information and content parts.

Meta Information contains:

  • Entity types
  • Attributes per entity type
  • Attribute sets and groups

Content part contains:

  • Entity records
  • Attribute values

Major difference between a classic EAV and Magento EAV implementation:

  • In Magento, core EAV tables are prefixed with “eav_
  • A backend type is assigned to each attribute (examples: varchar, int, decimal, text…)
  • EAV tables store entity data in separate tables. There entity-specific tables (catalog_category_entity, catalog_product_entity, customer_entity, etc.) and common eav_entity table that contains meta data
  • Special unique attributes with global scope are integrated into the entity-specific tables and are called static attributes, they have a static backend type
  • Attribute value tables are also split by entity_type and backend type
  • Certain information is duplicated between tables for performance reason (for example, entity_type_id is included into three different tables)

In Magento, it’s also possible to group attributes into groups and sets. While there can be only one attribute set assigned to a specific entity type, the groups are used only for clarity, so that developers could group attributes for better end user perception.

There can be different groups in each attribute set, but there is always a default group with a code “general“.

As it was mentioned before, the storage of attribute values is distributed across several DB tables. For example, for the entity type catalog_product:

Table name

Description

catalog_product_entity

The main entity table, among other things contains values for static attributes

catalog_product_entity_int

Stores integer values for attributes

catalog_product_entity_decimal

Stores decimal value types for attributes

catalog_product_entity_datetime

Stores attribute values of timestamp type

catalog_product_entity_text

Stores values for attributes with big string values

catalog_product_entity_varchar

Stores values for one-line text fields

References to the models related to each attribute are stored in the eav_attribute table and correspond the following:

Field

Description

backend_model

A name of PHP class used to process the attribute data before and after CRUD operations

source_model

A name of PHP class used for providing options for select and multiselect input type attributes

frontend_model

A name of PHP class used for formatting attribute value before display

Comparing to a flat table model structure processing, EAV makes it more complex. Both backend and source models are playing significant role in pre- and post-CRUD operations in EAV models. When displaying attributes for EAV models like catalog_product, catalog_category and customer, the attribute set model and group model are used. The attribute set model is also used when loading all the attributes associated to a specific product for a frontend detail view.

EAV Specifics in Load and Save

There are significant differences in how EAV model CRUD mechanisms work. As the EAV model is still extended from \Magento\Framework\Model\AbstractModel, the resource collection it uses should be extended from \Magento\Eav\Model\Entity\AbstractEntity (let the class name not fool you, it’s a special case of resource model, not data model).

In addition to the standard arsenal of resource model methods, EAV resource model implements additional methods that makes working with attributes easier:

  • getAttribute() — allows to get an attribute instance by a code
  • saveAttribute() — allows to save an attribute value to the EAV entity without going through the whole model saving process. You have to set the attribute value to the entity instance first.
  • unsetAttributes() — allows to unset specified attributes from the entity model
  • getEntityTable() — implemented in contrast to getMainTable() of flat table resource models

EAV collection also has additional distinct methods which influence the load process  that are worth mentioning:

  • addAttributeToSelect()
  • addAttributeToFilter()
  • joinAttribute()

They work same way as in Magento 1.x.

Unlike Magento 1.x, where attribute values were fetched from EAV tables using complex JOINs, in Magento 2 UNION construct is used that is much faster. It applies for both the attributes loaded for the collection, and for the process of loading a single entity.

Attribute Management

Below are the most important field within an attribute record with description. Note that some of those fields are stored in the main attribute table, eav_attribute, some will be stored in additional attribute table.

Field

Description

attribute_id

Unique attribute ID

entity_type_id

Entity type ID to associate the attribute with a specific entity type

attribute_code

Unique human-readable attribute code, must be unique within same entity type

attribute_model

Optional alternative model to use. It defaults to \Magento\Eav\Model\Entity\Attribute if not specified.

backend_model

Optional alternative backend model. It defaults to \Magento\Eav\Model\Entity\Attribute\Backend\DefaultBackend if not specified.

backend_type

Data type of the attribute. It’s either “static“, or corresponds to the postfilx of an attribute value table specific for each entity type

backend_table

Optional name of the attribute value table. If not specified, the name is formed as <entity_type_code>_<backend_type>.

frontend_model

Optional alternative frontend model. It defaults to \Magento\Eav\Model\Entity\Attribute\Frontend\DefaultFrontend if not specified.

frontend_input

Input type for the attribute in case the admin HTML form is being rendered automatically

frontend_label

Default label in case the admin HTML form is being rendered automatically

frontend_class

Optional CSS class name which will be added to the admin HTML input element for the attribute in case the admin HTML form is being rendered automatically. Useful for JS validation.

source_model

Optional alternative source model. Should only be specified for the attributes with select and multiselect frontend input type. Each source model must be extended from \Magento\Eav\Model\Entity\Attribute\Source\AbstractSource and implement \Magento\Eav\Model\Entity\Attribute\Source\SourceInterface.

is_required

Enabled JS validation in case the admin HTML form is being rendered automatically. It is also evaluated during the DataFlow and ImportExport processes.

default_value

Optional default value, it is being displayed by the frontend model if the attribute doesn’t have a value.

When an access to the meta info of the EAV system is accessible through \Magento\Eav\Model\Config. It aggregates the meta information about entity types and attributes.

As it was mentioned before, there are a number of standard backend types for attributes. Starting with “static“, which defines an attribute that is stored in the main entity data table, and completing the list with “varchar“, “text“, “int“, “decimal“, “datetime“. However, custom backend types are possible, too.

The EAV setup model \Magento\Eav\Setup\EavSetup must be used to work with attributes and entity types in the module setup classes. It’s a very important class for this reason.

$eavSetup->addAttribute($entityTypeId, $code, $attr)

  • adds an attribute with a specific code to a specific entity type
  • fields in the $attr argument are being mapped before applied to the attribute table fields
  • if the default values are sufficient, there is no need to specify a field in the array of parameters

$eavSetup->updateAttribute($entityTypeId, $id, $field, $value, $sortOrder)

  • updates a specific field of an attribute
  • the $value goes directly to a database table column that corresponds to $field

The fields mapper is different for different EAV entity implementations, for example this is how it looks for \Magento\Catalog\Model\ResourceModel\Setup\PropertyMapper:

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

namespace Magento\Catalog\Model\ResourceModel\Setup;

 

use Magento\Eav\Model\Entity\Setup\PropertyMapperAbstract;

 

class PropertyMapper extends PropertyMapperAbstract

{

    /**

     * Map input attribute properties to storage representation

     *

     * @param array $input

     * @param int $entityTypeId

     * @return array

     * @SuppressWarnings(PHPMD.UnusedFormalParameter)

     */

    public function map(array $input, $entityTypeId)

    {

        return [

            ‘frontend_input_renderer’ => $this->_getValue($input, ‘input_renderer’),

            ‘is_global’ => $this->_getValue(

                $input,

                ‘global’,

                \Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface::SCOPE_GLOBAL

            ),

            ‘is_visible’ => $this->_getValue($input, ‘visible’, 1),

            ‘is_searchable’ => $this->_getValue($input, ‘searchable’, 0),

            ‘is_filterable’ => $this->_getValue($input, ‘filterable’, 0),

            ‘is_comparable’ => $this->_getValue($input, ‘comparable’, 0),

            ‘is_visible_on_front’ => $this->_getValue($input, ‘visible_on_front’, 0),

            ‘is_wysiwyg_enabled’ => $this->_getValue($input, ‘wysiwyg_enabled’, 0),

            ‘is_html_allowed_on_front’ => $this->_getValue($input, ‘is_html_allowed_on_front’, 0),

            ‘is_visible_in_advanced_search’ => $this->_getValue($input, ‘visible_in_advanced_search’, 0),

            ‘is_filterable_in_search’ => $this->_getValue($input, ‘filterable_in_search’, 0),

            ‘used_in_product_listing’ => $this->_getValue($input, ‘used_in_product_listing’, 0),

            ‘used_for_sort_by’ => $this->_getValue($input, ‘used_for_sort_by’, 0),

            ‘apply_to’ => $this->_getValue($input, ‘apply_to’),

            ‘position’ => $this->_getValue($input, ‘position’, 0),

            ‘is_used_for_promo_rules’ => $this->_getValue($input, ‘used_for_promo_rules’, 0),

            ‘is_used_in_grid’ => $this->_getValue($input, ‘is_used_in_grid’, 0),

            ‘is_visible_in_grid’ => $this->_getValue($input, ‘is_visible_in_grid’, 0),

            ‘is_filterable_in_grid’ => $this->_getValue($input, ‘is_filterable_in_grid’, 0),

        ];

    }

}

Module 3. View Layer. Rendering

  • Overview
    • Templates
    • Blocks
    • Design Layout XML Schema
  • Rendering flow
    • Rendering Flow Aspects: View
    • Rendering Flow Aspects: Pages and Result Object
  • View elements
  • Block Architecture and Lifecycle
    • Block Inside
    • Lifecycle
  • Templates
    • Templates Initiation
    • Conventional Templates Location
    • Overriding Templates
    • Root template
    • Argument Values from Layout
  • Layout XML structure
    • <block>
    • <container>
    • The before and after attributes
    • <action>
    • <referenceBlock> and <referenceContainer>
    • <move>
    • <update>
    • <argument>

Overview

Templates

Magento 2 still uses PHTML files for templates. Unlike Magento 1.x, using PHTML is no longer the only option, any template system can be used, but for we will stick with PHTML for now as they come by default and cover all available themes.

Blocks

Blocks are classes to encapsulate reusable functionality responsible for rendering. Blocks usually instantiate models to trigger some rendering-related business logic, or to fetch necessary data for rendering.

Design Layout XML Schema

Layout XML files pull together the entire set of blocks template files to be rendered in browser. It also declares block hierarchy that allows to build a layout tree before rendering. Layout XML format evolved since Magento 1.x.

Rendering flow

The chart below shows a general process flow for rendering pages within a website. 

Rendering Flow Aspects: View

The \Magento\Framework\App\ViewInterface contains the following main methods:

  • loadLayoutUpdates()
  • renderLayout()
  • loadLayout()
  • getPage()
  • getLayout()

This interface now encapsulates logic that belonged to the action controller class in Magento 1.x. You have an access to the View object in the action controller by using $this->_view.

The \Magento\Framework\View\Layout class is an analog of the Mage_Core_Model_Layout class in Magento 1.x. This class together with a Processor class (\Magento\Framework\View\Layout\ProcessorInterface) implements the whole logic of loading, parsing and filtering layout XML files, generating layout XML for the current page, generating block instances, and setting the right parameters into them.

This mechanism is somewhat deprecated, Magento 2 has another rendering mechanism based on a Page object, that is going to substitute View infrastructure in the future releases.

There are two phases of rendering.

Phase One, loading layout, makes Magento load all XML layout, render them to build a list of instructions for block creation. Then, Magento creates a layout structure and instantiates blocks in it

Phase Two, when it’s time to render the loaded layout, Magento executes toHtml() methods of the root blocks and concatenates the result.

Rendering Flow Aspects: Pages and Result Object

A concept of result object is to use page object returned to the Application inside Application::launch() as a result of the rest of the request flow. The idea is to create a \Magento\Framework\View\Result\Page object in the action controller using \Magento\Framework\View\Result\PageFactory. If the Page object is returned in Application, it then is rendered using renderPage() method. 

This part of training is a bit inconsistent and incomplete at the moment, there was a contradictory information in the Fundamentals training, no info in the Developers documentation or any other resource. Use with care, and check the view rendering flow yourself on the first opportunity. Sorry for inconvenience!

View elements

There are three types of view elements in Magento 2:

  • Containers are logical wrappers very similar to \Magento\Framework\View\Element\Text\ListText, except there can be an instance of container, they are abstract concept
  • Blocks — almost same as in Magento 1.x
  • UiComponents — responsible for representing and rendering form elements. Implementation is in \Magento\Framework\View\Element\UiComponentInterface. Note that \Magento\Framework\View\Element\UiComponentInterface extends \Magento\Framework\View\Element\BlockInterface, which means UI components are blocks, too.

A containers:

  • doesn’t have any classes related to it
  • renders all its children
  • allows configuration of some attributes (e.g. wrapping tag, CSS class)

Block Architecture and Lifecycle

Every page consists of hierarchical structure of blocks. Layout does not define a location and shape of the blocks on a page, it just defines their sequence and hierarchy. How the blocks actually look on the page, depends on how they are rendered.

The role of the blocks in layout is adding content within the layout structure. Blocks are put to the layout as children to containers and to other blocks.

Block Inside

Every block in Magento 2 implements \Magento\Framework\View\Element\BlockInterface and is extends \Magento\Framework\View\Element\AbstractBlock.

The interface commands a block to implement public toHtml() method which is always called if the block needs to be rendered.

The AbstractBlock also introduces a number of important methods:

 

 

_prepareLayout()

A specific method that is executed when block is created. It is often redeclared in a specific block and contains init operations that happen at the moment layout is being built. It’s important to know it is called before toHtml()

addChild()

Adds a child block. Since block structure is hierarchical, there needs to be methods to add, remove, find and sort children.

_toHtml(), _beforeToHtml(), _afterToHtml()

These are methods that are executed right before, at the moment and after the rendering, they are called in toHtml() and are redeclared quite often in concrete blocks implementation.

toHtml()

This method is an implementation of rendering initially declared in BlockInterface.

This is how toHtml() method implemented in the AbstractBlock:

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

/**

 * Produce and return block’s html output

 *

 * This method should not be overridden. You can override _toHtml() method in descendants if needed.

 *

 * @return string

 */

public function toHtml()

{

    $this->_eventManager->dispatch(‘view_block_abstract_to_html_before’, [‘block’ => $this]);

    if ($this->_scopeConfig->getValue(

        ‘advanced/modules_disable_output/’ . $this->getModuleName(),

        \Magento\Store\Model\ScopeInterface::SCOPE_STORE

    )) {

        return ;

    }

 

    $html = $this->_loadCache();

    if ($html === false) {

        if ($this->hasData(‘translate_inline’)) {

            $this->inlineTranslation->suspend($this->getData(‘translate_inline’));

        }

 

        $this->_beforeToHtml();

        $html = $this->_toHtml();

        $this->_saveCache($html);

 

        if ($this->hasData(‘translate_inline’)) {

            $this->inlineTranslation->resume();

        }

    }

    $html = $this->_afterToHtml($html);

 

    return $html;

}

While toHtml() method is declared as final in Magento 1.x, in Magento 2 it is not (in order to be able to write plugins, most probably). However, you must avoid redeclaring toHtml() directly.

The most important block type a developer should be aware of are as follows:

Block class

Description

\Magento\Framework\View\Element\Text

Text block, it allows you to add various portions of text using addText() and then render that text concatenated

\Magento\Framework\View\Element\Text\ListText

Text list block. All it does when being rendered is render its own children. It’s quite similar to the behavior of containers

\Magento\Framework\View\Element\Messages

Messages block. It provides an interface to set a collection of messages, or add messages one by one, and then it renders them with special wrapping based on a specific template

\Magento\Framework\View\Element\Redirect

Redirect block, it handles various browser-side redirections

\Magento\Framework\View\Element\Template

A very basic template block. Sometimes it’s just enough to have a block that can render itself based on a template you specify.

There are many ways to pass template to a block that is extended from \Magento\Framework\View\Element\Template. You can do it by

  • passing [‘template’ => ‘your-template.phtml’] as a second argument in the block constructor
  • calling $block->setTemplate(‘your-template.phtml’)
  • specifying a template as an attribute within <block> construct in the layout XML file. 

Lifecycle

It was already mentioned that block has two separate stages of its life. 

Block generation

Block rendering

  • Instances of all blocks declared in the layout are created
  • Structure is built and block children are set
  • Block’s _prepareLayout() is called for every block
  • Nothing is rendered at this point

As containers are not blocks and don’t have own instance, all that happens to a container at this point is assigning attributes from layout XML declaration

  • starts with calling Layout::getOutput()
  • a loop through every root block and container defined in the empty.xml layout XML file
  • every block’s toHtml() method is called
  • in case of containers, the process loops through all container’s children and concatenates their resulting rendering output
  • result of rendering is being concatenated into one string

Templates

Templates are snippets of HTML code provided as PHTML files that contain PHP instructions, local variable declarations and calls of class methods.

Templates Initiation

Templates are usually initiated in layout files. Each layout block has an associated template. The template is specified in the templateattribute of the layout instruction. For example, from <Magento_Catalog_module_dir>/view/frontend/layout/catalog_category_view.xml:

Example

<block class=“Magento\Catalog\Block\Category\View” name=“category.image” template=“Magento_Catalog::category/image.phtml”/> 

This means that the category.image block is rendered by the image.phtml template, which is located in the category subdirectory of the Magento_Catalog module templates directory.

The templates directory of Magento_Catalog is <Magento_Catalog_module_dir>/view/frontend/templates.

Conventional Templates Location

Templates are stored in the following locations:

  • Module templates: <module_dir>/view/frontend/templates/<path_to_templates>
  • Theme templates: <theme_dir>/<Namespace>_<Module>/templates/<path_to_templates>

Here <path_to_templates> might have several levels of directory nesting, or might be empty.

Examples:

  • <Magento_Catalog_module_dir>/view/frontend/templates/product/widget/new/content/new_grid.phtml
  • <Magento_Checkout_module_dir>/view/frontend/templates/cart.phtml

Overriding Templates

For template files with the same name, the following is true:

  • Theme templates override module templates
  • Child theme templates override parent theme templates.

This mechanism is the basis of the template customization concept in Magento application, to change the output defined by a certain default template, you need to overriding one in your custom theme.

Root template

Magento 2 has a special template which serves as root template for all pages in the application: <Magento_Theme_module_dir>/view/base/templates/root.phtml. Unlike other templates, root.phtml contains the doctype specification and contributes to <head> and <body> sections of all pages rendered by Magento application.

Similarly to other templates, root.phtml can be overridden in a theme.

Argument Values from Layout

Arguments values set in a layout file can be accessed in templates using the get{ArgumentName}() and has{ArgumentName}() methods.

Layout XML structure

<block>

Defines a block.

Details: A block is a unit of page output that renders some distinctive content – a piece of information, a user interface element – anything visually tangible for the end-user. Blocks employ templates to generate HTML. Examples of blocks include a category list, a mini cart, product tags, and product listing.

Attribute

Description

Values

Required?

class

Name of a class that implements rendering of a particular block. An object of this class is responsible for actual rendering of block output.

class name

yes

name

Name that can be used to address the block to which this attribute is assigned. The name must be unique per generated page. If not specified, an automatic name will be assigned in the format ANONYMOUS_n

0-9, A-Z, a-z, underscore (_), period (.), dash (-). Should start with a letter. Case-sensitive.

no

before

Used to position the block

before an element under the same parent. The element name or alias name is specified in the value. Use dash (-) to position the block before all other elements of its level of nesting.

Possible values: element name or dash (-)

no

after

Used to position the block after an element under the same parent. The element name or alias name is specified in the value. Use dash (-) to position the block after all other elements of its level of nesting.

Possible values: element name or dash (-)

no

template

A template that represents the functionality of the block to which this attribute is assigned.

template file name

no

as

An alias name that serves as identifier in the scope of the parent element.

0-9, A-Z, a-z, underscore (_), period (.), dash (-). Case-sensitive.

no

cacheable

Defines whether a block element is cacheable. This can be used for development purposes and to make needed elements of the page dynamic.

true or false

no

To pass parameters use the <argument> instruction.

<container>

A structure without content that holds other layout elements such as blocks and containers.

Details: A container renders child elements during view output generation. It can be empty or it can contain an arbitrary set of <container>and <block> elements.

Attribute

Description

Values

Required?

name

A name that can be used to address the container in which this attribute is assigned. The name must be unique per generated page.

A-Z, a-z, 0-9, underscore (_), period (.), dash (-). Should start with a letter. Case-sensitive.

yes

label

An arbitrary name to display in the web browser.

any

no

before

Used to position the container before an element under the same parent. The element name or alias name is specified in the value. Use dash (-) to position the block before all other elements of its level of nesting.

Possible values: element name or dash (-).

no

after

Used to position the container after an element under the same parent. The element name or alias name is specified in the value. Use dash (-) to position the block after all other elements of its level of nesting.

Possible values: element name or dash (-).

no

as

An alias name that serves as identifier in the scope of the parent element.

0-9, A-Z, a-z, underscore (_), period (.), dash (-). Case-sensitive.

no

output

Defines whether to output the root element. If specified, the element will be added to output list. (If not specified, the parent element is responsible for rendering its children.)

Any value except the obsoletetoHtml. Recommended value is1.

no

htmlTag

Output parameter. If specified, the output is wrapped into specified HTML tag.

Any valid HTML 5 tag.

no

htmlId

Output parameter. If specified, the value is added to the wrapper element. If there is no wrapper element, this attribute has no effect.

Any valid HTML 5 <id> value.

no

htmlClass

Output parameter. If specified, the value is added to the wrapper element. If there is no wrapper element, this attribute has no effect.

Any valid HTML 5 <class> value.

no

Sample of usage in layout:

1

2

3

4

5

<!– … –>

<container name=“div.sidebar.additional” htmlTag=“div” htmlClass=“sidebar sidebar-additional” after=“div.sidebar.main”>

    <container name=“sidebar.additional” as=“sidebar_additional” label=“Sidebar Additional”/>

</container>

<!– … –>

This would add a new column to the page layout.

The before and after attributes

To help you to position elements in a specific order suitable for design, SEO, usability, or other requirements, Magento software provides the before and after layout attributes.

These optional attributes can be used in layout XML files to control the order of elements in their common parent. The following tables give a detailed description of the results you can get using the before and after attributes. The first table uses a block a as positioned element.

Attribute

Value

Description

before

Dash (-)

The block displays before all other elements in its parent node.

before

[element name]

The block displays before the named element.

before

empty value or [element name] is absent

Use the value of after. If that value is empty or absent as well, the element is considered as non-positioned.

after

Dash (-)

The block displays after all other elements in its parent node.

after

[element name]

The block displays after the named element.

after

empty value or [element name] is absent

Use the value of before. If that value is empty or absent as well, the block is considered as non-positioned.

Examples:

Situation

Result

Both before and after attributes are present

after takes precedence.

Both before and after attributes are absent or empty

The element is considered as non-positioned. All other elements are positioned at their specified locations. The missing element displays at a random position that doesn’t violate requirements for the positioned elements.

Several elements have before or after set to dash (-)

All elements display at the top (or bottom, in case of the after attribute), but the ordering of group of these elements is undefined.

The before or after attribute’s value refers to an element that is not located in the parent node of the element being defined.

The element displays at a random location that doesn’t violate requirements for the correctly positioned elements.

<action>

The <action> instruction is deprecated. If the method implementation allows, use the <argument> for <block> or <referenceBlock> to access block public API.

Calls public methods on the block API.

Details: Used to set up the execution of a certain method of the block during block generation; the <action> node must be located in the scope of the <block> node.

Example

1

2

3

4

5

6

7

8

<block class=“Magento\Module\Block\Class” name=“block”>

    <action method=“setText”>

        <argument name=“text” translate=“true” xsi:type=“string”>Text</argument>

    </action>

    <action method=“setEnabled”>

        <argument name=“enabled” xsi:type=“boolean”>true</argument>

    </action>

</block>

The <action> child nodes are translated into block method arguments. Child nodes names are arbitrary. If there are two or more nodes with the same name under <action>, they are passed as one array.

In the previous example, the value of <arg1> is passed as the first argument and <arg2> values are passed as array(‘one’, ‘two’). The list of all available methods depends on the block implementation (for example, the public method of the block class).

Attribute

Description

Values

Required?

method

Name of the public method of the block class this tag is located in that is called during block generation.

block method name

yes

To pass parameters, use the <argument> instruction.

<referenceBlock> and <referenceContainer>

Updates in <referenceBlock> and <referenceContainer> are applied to the corresponding <block> or <container>.

For example, if you make a reference by <referenceBlock name=”right”>, you’re targeting the block <block name=”right”>.

To pass parameters to a block use the <argument> instruction.

Attribute

Description

Values

Required?

remove

Allows to remove or cancel the removal of the element. When a container is removed, its child elements are removed as well.

The remove attribute is optional and its default value is false.

This implementation allows you to cancel removal of a block or container in your layout by setting remove attribute value to false. 

Example

1

<referenceBlock name=“block.name” remove=“true” />

true/false

no

display

Allows you to disable rendering of specific block or container with all its children (both set directly and by reference). The block’s/container’s and its children’ respective PHP objects are still generated and available for manipulation.

The display attribute is optional and its default value is true.

You are always able to overwrite this value in your layout. In situation when remove value is true, the display attribute is ignored. 

Example

1

<referenceContainer name=“container.name” display=“false” />

true/false

no

<move>

Sets the declared block or container element as a child of another element in the specified order.

Example

1

<move element=“name.of.an.element” destination=“name.of.destination.element” as=“new_alias” after=“name.of.element.after” before=“name.of.element.before”/>

The <move> is skipped if the element to be moved is not defined.

If the as attribute is not defined, the current value of the element alias is used. If that is not possible, the value of the name attribute is used instead.

During layout generation, the <move> instruction is processed before the <remove> instruction. This means if any elements are moved to the element scheduled for removal, they will be removed as well.

Attribute

Description

Values

Required?

element

Name of the element to move.

element name

yes

destination

Name of the target parent element.

element name

yes

as

Alias name for the element in the new location.

0-9, A-Z, a-z, underscore (_), period (.), dash (-). Case-sensitive.

no

after | before

Specifies the element’s position relative to siblings. Use dash (-) to position the block before or after all other siblings of its level of nesting. If the attribute is omitted, the element is placed after all siblings.

element name

no

<update>

Includes a certain layout file. Used as follows:

Example

1

<update handle=“{name_of_handle_to_include}”/>

The specified handle is “included” and executed recursively.

<argument>

Used to pass an argument.

Attribute

Description

Values

Required?

name

Argument name.

unique

yes

xsi:type

Argument type.

string|boolean|object|number|null|array

yes

translate

 

true|false

no

To pass multiple arguments use the following construction:

<arguments>

   <argument></argument>

   <argument></argument>

   

</arguments>

To pass an argument that is an array use the following construction:

<argument>

   <item></item>

   <item></item>

   

</argument>

Arguments values set in a layout file can be accessed in templates using the get{ArgumentName}() and has{ArgumentName}() methods. The latter returns a boolean defining whether there’s any value set. {ArgumentName} is obtained from the name attribute the following way: for getting the value of <argument name=”some_string”> the method name is getSomeString().

Example: Setting a value of css_class in the <Magento_Theme_module_dir>/view/frontend/layout/default.xml layout file:

Example

1

2

3

4

5

<!– … –>

<arguments>

    <argument name=“css_class” xsi:type=“string”>header links</argument>

</arguments>

<!– … –>

Using the value of css_class in <Magento_Theme_module_dir>/view/frontend/templates/html/title.phtml:

Example

1

2

3

// …

$cssClass = $this->getCssClass() ? ‘ ‘ . $this->getCssClass() : ;

Module 2. Request Flow. Routers, Actions, Rewrites

  • Request flow overview
  • Request routing
    • Frontend Controller
    • Admin Controller
    • Forwarding and Redirection
  • Working with controller actions
  • URL Rewrites

Request flow overview

The Request Flow principle in Magento 2 is similar to one in Magento 1.x, but the code was significantly refactored. As there is no more god-object Mage class, the responsibilities for handling the request were distributed between Bootstrap, App, and FrontController. While dispatching the request, FrontController class tries to match the request against every Router configured in the system, and the first router that is able to match would resolve the Controller class name, and perform Controller::execute() method.

Previously, the functionality that loaded and rendered layout was part of the controller action class. Not anymore, now the View class is responsible for building the layout tree and rendering it.

Once the controller action is handled and supposedly rendered, which the FrontController sends the content as a Response body, to the end user as a result of the whole Request Flow.

Request routing

Step

Description

Everything starts with instantiating the Bootstrap instance, fetching the Application instance using it, then calling Bootstrap::run() method and passing the Application to it. The run() method initializes the Error Handler and the Object Manager, asserts if Magento is in maintenance more or not installed yet, and then…

…launches the Application by calling Application::launch(). There can be different application started at this stage. In case of the application that processes HTTP requests (\Magento\Framework\App\Http), the config that corresponds to the specific area is loaded, then FrontController (\Magento\Framework\App\FrontControllerInterface) is instantiated, and then…

FrontController::dispatch() is called. Inside of that method, a loop is running that will end only if request obtains isDispatched flag, or if amount of iterations exceeds 100.

Inside of the FrontController::dispatch() loop, each of the routers from the router list, is trying to match the request. If it succeeds, the match() method returns an instance of action controller. In case of any uncaught exception, a preconfigured “noroute” action will be dispatched.

Interestingly, the router loop processing is flexible: the action controller instance may either be extended from the \Magento\Framework\App\Action\AbstractAction, or simply contain a public execute() method. In first case, the Controller::dispatch() method is called, that may have different implementation but in general calls the execute() method, or the Controller::execute() method is directly called.

Inside of the Controller::execute() method, models can be instantiated to provide some business logic, request can be validated, then forwarding or redirecting may occur or the action can render a resulting HTML. For that, the instance of View class can be used to load the layout tree and instantiate all blocks inside it using View::loadLayout(), and then, when it’s appropriate, render the resulting HTML by calling View::renderLayout();

Finally, the rendered content needs to be sent to the browser. For that purpose, the Response::sendResponse() is used. It sends the rendered content as a body, and prepends it with all default and additional headers generated during the request flow processing.


Controller architecture

Controller is a class specific to a URL or group of URLs. 

In Magento 2, a controller can only process a single action. Each Controller, or Action class includes:

  • a normal __construct() constructor used for injecting all necessary dependencies (as controller class is injectable)
  • a public execute() method that represents the action and contains necessary logic
  • extra private or protected methods and properties that help implementing controller logic.

Frontend Controller

Usually, frontend-related controllers extend \Magento\Framework\App\Action\Action, which, in its turn, is extended from two other abstract classes that implement \Magento\Framework\App\ActionInterface. It’s very important to extend your frontend controllers from \Magento\Framework\App\Action\Action as it implements dispatch() method and extra important methods used by developers. The Router calls dispatch(), not execute() method when routing happens.

Sometimes, when it’s impossible to extend \Magento\Framework\App\Action\Action, another class implementing \Magento\Framework\App\ActionInterface can be used. In that case the developer must implement the dispatch() method on his/her own.

Admin Controller

The difference of admin controller is that it requires to check a user permissions to decide whether the user is eligible to visit this controller. It also implements some admin panel-specific functionality. For that purpose, they are extended from \Magento\Backend\App\Action which has a modified dispatch() method which checks if the user logged in, and calls $this->_isAllowed() that checks if the ACL role of the current user is linked with the action to allow using it.

The admin action controller also has its own implementation for redirect() and forward() methods, as well as knows how to handle form keys.

There are also some auxiliary methods that can be used in the admin action controllers:

  • _getSession()
  • _addBreadcrumb()
  • _addJs()
  • _addContent()
  • _addLeft()
  • _getUrl()

Forwarding and Redirection

  • _forward() — is delegating execution to another action that correspond to the specified path. This happens inside of the FrontController and routing loop, it simply modifies the request object, tells the FrontController that the action was not dispatched, and route matching process starts anew.
  • _redirect() — is returning a Response object with redirect header set, after returning that object in the action, a real redirection is initiated instead of rendering a page.

Working with controller actions

In order to create a new action controller, three steps must be done:

  1. Create a route.xml config file
  2. Create a correct action class and implement an execute() method
  3. Test the controller

Here are examples for router configurations and action controller classes for Magento_Catalog module:

Frontend

Admin

URL: /catalog/product/view

URL: /<adminPath>/catalog/product/upsell

1

2

3

4

5

6

7

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

    <router id=“standard”>

        <route id=“catalog” frontName=“catalog”>

            <module name=“Magento_Catalog” />

        </route>

    </router>

</config

1

2

3

4

5

6

7

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

    <router id=“admin”>

        <route id=“catalog” frontName=“catalog”>

            <module name=“Magento_Catalog” before=“Magento_Backend” />

        </route>

    </router>

</config>

\Magento\Catalog\Controller\Product\View

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

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

namespace Magento\Catalog\Controller\Product;

 

use Magento\Framework\App\Action\Context;

use Magento\Framework\View\Result\PageFactory;

 

class View extends \Magento\Catalog\Controller\Product

{

    /**

     * @var \Magento\Catalog\Helper\Product\View

     */

    protected $viewHelper;

 

    /**

     * @var \Magento\Framework\Controller\Result\ForwardFactory

     */

    protected $resultForwardFactory;

 

    /**

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

     */

    protected $resultPageFactory;

 

    /**

     * Constructor

     *

     * @param Context $context

     * @param \Magento\Catalog\Helper\Product\View $viewHelper

     * @param \Magento\Framework\Controller\Result\ForwardFactory $resultForwardFactory

     * @param PageFactory $resultPageFactory

     */

    public function __construct(

        Context $context,

        \Magento\Catalog\Helper\Product\View $viewHelper,

        \Magento\Framework\Controller\Result\ForwardFactory $resultForwardFactory,

        PageFactory $resultPageFactory

    ) {

        $this->viewHelper = $viewHelper;

        $this->resultForwardFactory = $resultForwardFactory;

        $this->resultPageFactory = $resultPageFactory;

        parent::__construct($context);

    }

 

    /**

     * Redirect if product failed to load

     *

     * @return \Magento\Framework\Controller\Result\Redirect|\Magento\Framework\Controller\Result\Forward

     */

    protected function noProductRedirect()

    {

        $store = $this->getRequest()->getQuery(‘store’);

        if (isset($store) && !$this->getResponse()->isRedirect()) {

            $resultRedirect = $this->resultRedirectFactory->create();

            return $resultRedirect->setPath();

        } elseif (!$this->getResponse()->isRedirect()) {

            $resultForward = $this->resultForwardFactory->create();

            $resultForward->forward(‘noroute’);

            return $resultForward;

        }

    }

 

    /**

     * Product view action

     *

     * @return \Magento\Framework\Controller\Result\Forward|\Magento\Framework\Controller\Result\Redirect

     */

    public function execute()

    {

        // Get initial data from request

        $categoryId = (int) $this->getRequest()->getParam(‘category’, false);

        $productId = (int) $this->getRequest()->getParam(‘id’);

        $specifyOptions = $this->getRequest()->getParam(‘options’);

 

        if ($this->getRequest()->isPost() && $this->getRequest()->getParam(self::PARAM_NAME_URL_ENCODED)) {

            $product = $this->_initProduct();

            if (!$product) {

                return $this->noProductRedirect();

            }

            if ($specifyOptions) {

                $notice = $product->getTypeInstance()->getSpecifyOptionMessage();

                $this->messageManager->addNotice($notice);

            }

            if ($this->getRequest()->isAjax()) {

                $this->getResponse()->representJson(

                    $this->_objectManager->get(‘Magento\Framework\Json\Helper\Data’)->jsonEncode([

                        ‘backUrl’ => $this->_redirect->getRedirectUrl()

                    ])

                );

                return;

            }

            $resultRedirect = $this->resultRedirectFactory->create();

            $resultRedirect->setRefererOrBaseUrl();

            return $resultRedirect;

        }

 

        // Prepare helper and params

        $params = new \Magento\Framework\DataObject();

        $params->setCategoryId($categoryId);

        $params->setSpecifyOptions($specifyOptions);

 

        // Render page

        try {

            $page = $this->resultPageFactory->create(false, [‘isIsolated’ => true]);

            $this->viewHelper->prepareAndRender($page, $productId, $this, $params);

            return $page;

        } catch (\Magento\Framework\Exception\NoSuchEntityException $e) {

            return $this->noProductRedirect();

        } catch (\Exception $e) {

            $this->_objectManager->get(‘Psr\Log\LoggerInterface’)->critical($e);

            $resultForward = $this->resultForwardFactory->create();

            $resultForward->forward(‘noroute’);

            return $resultForward;

        }

    }

\Magento\Catalog\Controller\Adminhtml\Product\Upsell

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

namespace Magento\Catalog\Controller\Adminhtml\Product;

 

class Upsell extends \Magento\Catalog\Controller\Adminhtml\Product

{

    /**

     * @var \Magento\Framework\View\Result\LayoutFactory

     */

    protected $resultLayoutFactory;

 

    /**

     * @param \Magento\Backend\App\Action\Context $context

     * @param \Magento\Catalog\Controller\Adminhtml\Product\Builder $productBuilder

     * @param \Magento\Framework\View\Result\LayoutFactory $resultLayoutFactory

     */

    public function __construct(

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

        \Magento\Catalog\Controller\Adminhtml\Product\Builder $productBuilder,

        \Magento\Framework\View\Result\LayoutFactory $resultLayoutFactory

    ) {

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

        $this->resultLayoutFactory = $resultLayoutFactory;

    }

 

    /**

     * Get upsell products grid and serializer block

     *

     * @return \Magento\Framework\View\Result\Layout

     */

    public function execute()

    {

        $this->productBuilder->build($this->getRequest());

        $resultLayout = $this->resultLayoutFactory->create();

        $resultLayout->getLayout()->getBlock(‘catalog.product.edit.tab.upsell’)

            ->setProductsUpsell($this->getRequest()->getPost(‘products_upsell’, null));

        return $resultLayout;

    }

The simplest way to test an action controller is to try the URL that corresponds the action. Of course testing requires more preliminaries in case of admin controller.

Controller classes are injectable. This means that to customize a controller, you can create a preference or plugin.

URL Rewrites

The process of URL rewriting is used to make complicated or human unreadable URL addresses more user-friendly by making them shorter, more descriptive and easier to remember.

Magento allows you to specify a so-called URL key on every static, content, product and category page. URL key can become a part of a rewrite URL, if it’s enabled in each case.

How the URL rewriting works? When during routing process no routers respond with a match for a controller action, before letting default router to forward to the “noroute” action, a URL rewrite router takes its chances. The \Magento\UrlRewrite\Controller\Router class tries to match the URL by request paths specified in the url_rewrite data table.

\Magento\UrlRewrite\Controller\Router

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

/**

 * Match corresponding URL Rewrite and modify request

 *

 * @param \Magento\Framework\App\RequestInterface $request

 * @return \Magento\Framework\App\ActionInterface|null

 */

public function match(\Magento\Framework\App\RequestInterface $request)

{

    if ($fromStore = $request->getParam(‘___from_store’)) {

        $oldStoreId = $this->storeManager->getStore($fromStore)->getId();

        $oldRewrite = $this->getRewrite($request->getPathInfo(), $oldStoreId);

        if ($oldRewrite) {

            $rewrite = $this->urlFinder->findOneByData(

                [

                    UrlRewrite::ENTITY_TYPE => $oldRewrite->getEntityType(),

                    UrlRewrite::ENTITY_ID => $oldRewrite->getEntityId(),

                    UrlRewrite::STORE_ID => $this->storeManager->getStore()->getId(),

                    UrlRewrite::IS_AUTOGENERATED => 1,

                ]

            );

            if ($rewrite && $rewrite->getRequestPath() !== $oldRewrite->getRequestPath()) {

                return $this->redirect($request, $rewrite->getRequestPath(), OptionProvider::TEMPORARY);

            }

        }

    }

    $rewrite = $this->getRewrite($request->getPathInfo(), $this->storeManager->getStore()->getId());

    if ($rewrite === null) {

        return null;

    }

 

    if ($rewrite->getRedirectType()) {

        return $this->processRedirect($request, $rewrite);

    }

 

    $request->setAlias(\Magento\Framework\UrlInterface::REWRITE_REQUEST_PATH_ALIAS, $rewrite->getRequestPath());

    $request->setPathInfo(‘/’ . $rewrite->getTargetPath());

    return $this->actionFactory->create(‘Magento\Framework\App\Action\Forward’);

}

When it’s possible to fetch a rewrite data that corresponds the current request path, a field redirect_type is checked. In case it’s “1”, a redirect happens, otherwise a forwarding action is created from action factory, and it gets forwarded with an updated request info.

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.