├── .gitignore ├── 1. Magento Architecture and Customization Techniques ├── 1. Describe Magento’s module-based architecture.md ├── 2. Describe Magento’s directory structure.md ├── 3. Utilize configuration XML and variables scope.md ├── 4. Demonstrate how to use dependency injection.md ├── 5. Demonstrate ability to use plugins.md ├── 6. Configure event observers and scheduled jobs.md ├── 7. Utilize the CLI.md ├── 8. Demonstrate the ability to manage the cache.md └── Magento Architecture and Customization Techniques.md ├── 10. Customer Management ├── 10.1 Demonstrate ability to customize My Account.md └── 10.2 Demonstrate ability to customize customer functionality.md ├── 2. Request Flow Processing ├── 1. Utilize modes and application initialization.md ├── 2. Demonstrate ability to process URLs in Magento.md ├── 3. Demonstrate ability to customize request routing.md └── 4. Determine the layout initialization process.md ├── 3. Customizing the Magento UI ├── 1. Demonstrate ability to utilize themes and the template structure.md ├── 2. Determine how to use blocks.md ├── 3. Demonstrate ability to use layout and XML schema.md └── 4. Utilize JavaScript in Magento.md ├── 4. Working with Databases in Magento └── 1. Demonstrate ability to use data-related classes.md ├── 5. Using the Entity-Attribute-Value -EAV- Model └── 2. Demonstrate ability to use EAV entity load and save.md ├── 6. Developing with Adminhtml ├── 1. Describe common structure architecture.md ├── 2. Define form and grid widgets.md └── 4. Utilize ACL to set menu items and permissions.md ├── 7. Customizing the Catalog ├── 1. Demonstrate ability to use products and product types.md ├── 2. Describe price functionality.md ├── 3. Demonstrate ability to use and customize categories.md └── 4. Determine and manage catalog rules.md ├── 8. Customizing the Checkout Process ├── 1. Demonstrate ability to use quote, quote item, address, and shopping cart rules in checkout.md ├── 2. Demonstrate ability to use totals models.md ├── 3. Demonstrate ability to customize the shopping cart.md └── 4. Demonstrate ability to customize shipping and pa yment methods.md ├── 9. Sales Operations └── 9.1 Demonstrate ability to customize sales operations.md ├── README.md └── xx. Magento Commerce Features ├── Customer segments.md └── Staging.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ -------------------------------------------------------------------------------- /1. Magento Architecture and Customization Techniques/1. Describe Magento’s module-based architecture.md: -------------------------------------------------------------------------------- 1 | # Magento module overview 2 | >A module is a logical group – that is, a directory containing blocks, controllers, helpers, models – that are related to a specific business feature. In keeping with Magento’s commitment to optimal modularity, a module encapsulates one feature and has minimal dependencies on other modules. - [Magento DevDocs - Module overview](http://devdocs.magento.com/guides/v2.2/architecture/archi_perspectives/components/modules/mod_intro.html) 3 | > 4 | >Modules and themes are the units of customization in Magento. Modules provide business features, with supporting logic, while themes strongly influence user experience and storefront appearance. Both components have a life cycle that allows them to be installed, deleted, and disabled. From the perspective of both merchants and extension developers, modules are the central unit of Magento organization. - [Magento DevDocs - Module overview](http://devdocs.magento.com/guides/v2.2/architecture/archi_perspectives/components/modules/mod_intro.html) 5 | > 6 | >The Magento Framework provides a set of core logic: PHP code, libraries, and the basic functions that are inherited by the modules and other components. - [Magento DevDocs - Module overview](http://devdocs.magento.com/guides/v2.2/architecture/archi_perspectives/components/modules/mod_intro.html) 7 | 8 | 9 | Modules can be installed with Composer allowing their version management. All modules installed in such way are placed in the `vendor/` folder and have next base structure `vendor//-`. In this case `` can be: 10 | 1. `module` - Magento module 11 | 1. `theme` - admin or frontend themes 12 | 1. `language` - language packs 13 | 14 | In a case when you have a very specific functionality or customisation which is related to a specific project and there is no need to share it with other projects it should be created in the `app/code//-` directory and the required directories within it. 15 | 16 | ### Module registration 17 | 18 | >Magento components, including modules, themes, and language packages, must be registered in the Magento system through the Magento ComponentRegistrar class. - [Magento DevDocs - Register your component](http://devdocs.magento.com/guides/v2.2/extension-dev-guide/build/component-registration.html) 19 | 20 | >Each component must have a file called registration.php in its root directory. For example, here is the registration.php file for Magento’s AdminNotification module. Depending on the type of component, registration is performed through registration.php by adding to it as follows: - [Magento DevDocs - Register your component](http://devdocs.magento.com/guides/v2.2/extension-dev-guide/build/component-registration.html) 21 | 22 | - How to register modules - registration.php [1] 23 | ```php 24 | _', __DIR__); 39 | ``` 40 | 41 | - how to register themes - registration.php [3] 42 | ```php 43 | //', __DIR__); 48 | ``` 49 | 50 | 51 | - composer.json autoload/files[] = "registration.php" [4] 52 | ```json 53 | { 54 | "name": "Acme-vendor/bar-component", 55 | "autoload": { 56 | "psr-4": { "AcmeVendor\\BarComponent\\": "" }, 57 | "files": [ "registration.php" ] 58 | } } 59 | ``` 60 | 61 | - at what stage - when including vendor/autoload.php [5] 62 | ```php 63 | 1. Name and declare the module in the module.xml file. 121 | >1. Declare any dependencies that the module has (whether on other modules or on a different component) in the module’s composer.json file. 122 | >1. (Optional) Define the desired load order of config files and .css files in the module.xml file. 123 | > 124 | > -- [Magento DevDocs - Module dependencies](http://devdocs.magento.com/guides/v2.2/architecture/archi_perspectives/components/modules/mod_depend.html) 125 | 126 | > Example: Module A declares a dependency upon Module B. Thus, in Module A’s module.xml file, Module B is listed in the list, so that B’s files are loaded before A’s. Additionally, you must declare a dependency upon Module B in A’s composer.json file. Furthermore, in the deployment configuration, Modules A and B must both be defined as enabled. - [Magento DevDocs - Module dependencies](http://devdocs.magento.com/guides/v2.2/architecture/archi_perspectives/components/modules/mod_depend.html) 127 | 128 | ### Dependency types 129 | 130 | ##### Hard dependency 131 | > Modules with a hard dependency on another module cannot function without the module it depends on. 132 | Specifically: 133 | >1. The module contains code that directly uses logic from another module (for example, the latter module’s instances, class constants, static methods, public class properties, interfaces, and traits). 134 | >1. The module contains strings that include class names, method names, class constants, class properties, interfaces, and traits from another module. 135 | >1. The module deserializes an object declared in another module. 136 | >1. The module uses or modifies the database tables used by another module. 137 | > 138 | > -- [Magento DevDocs - Module dependency types](http://devdocs.magento.com/guides/v2.2/architecture/archi_perspectives/components/modules/mod_depend_types.html) 139 | 140 | ##### Soft dependency 141 | >Modules with a soft dependency on another module can function properly without the other module, even if it has a dependency upon it. Specifically: 142 | >The module directly checks another module’s availability. 143 | >The module extends another module’s configuration. 144 | >The module extends another module’s layout. 145 | > 146 | > -- [Magento DevDocs - Module dependency types](http://devdocs.magento.com/guides/v2.2/architecture/archi_perspectives/components/modules/mod_depend.html) 147 | 148 | Magento module install order flow: 149 | >1. The module serving as a dependency for another module 150 | >2. The module dependent on it 151 | 152 | Following dependencies should not be created: 153 | >1. Circular (both direct and indirect) 154 | >1. Undeclared 155 | >1. Incorrect 156 | > 157 | > -- [Magento DevDocs - Module dependency types](http://devdocs.magento.com/guides/v2.2/architecture/archi_perspectives/components/modules/mod_depend.html) 158 | 159 | 160 | >You can build dependencies between classes in the application layer, but these classes must belong to the same module. Dependencies between the modules of the application layer should be built only by the service contract or the service provider interface (SPI). - [Magento DevDocs - Module dependency types](http://devdocs.magento.com/guides/v2.2/architecture/archi_perspectives/components/modules/mod_depend.html) 161 | 162 | ### Magento areas 163 | A Magento area organizes code for optimized request processing by loading components parts which are related only to the specific area. Areas are registered in the `di.xml` file. 164 | 165 | >Modules define which resources are visible and accessible in an area, as well as an area’s behavior. The same module can influence several areas. For instance, the RMA module is represented partly in the adminhtml area and partly in the frontend area. 166 | If your extension works in several different areas, ensure it has separate behavior and view components for each area. 167 | Each area declares itself within a module. All resources specific for an area are located within the same module as well. 168 | You can enable or disable an area within a module. If this module is enabled, it injects an area’s routers into the general application’s routing process. 169 | If this module is disabled, Magento will not load an area’s routers and, as a result, an area’s resources and specific functionality are not available. 170 | > 171 | > -- [Magento DevDocs - Modules and areas](http://devdocs.magento.com/guides/v2.2/architecture/archi_perspectives/components/modules/mod_and_areas.html) 172 | 173 | Magento has 5 areas types: 174 | > 1. Magento Admin (`adminhtml`): entry point for this area is index.php or pub/index.php. The Admin panel area includes the code needed for store management. The /app/design/adminhtml directory contains all the code for components you’ll see while working in the Admin panel. 175 | > 1. Storefront (`frontend`): entry point for this area is index.php or pub/index.php. The storefront (or frontend) contains template and layout files that define the appearance of your storefront. 176 | > 1. Basic (`base`): used as a fallback for files absent in adminhtml and frontend areas. 177 | > 1. Cron (`crontab`): In cron.php, the [`\Magento\Framework\App\Cron`](https://github.com/magento/magento2/blob/2.2/lib/internal/Magento/Framework/App/Cron.php#L68-L70) class always loads the 'crontab' area. 178 | > 179 | > You can also send requests to Magento using the SOAP and REST APIs. These two areas: 180 | > 1. Web API REST (`webapi_rest`): entry point for this area is index.php or pub/index.php. The REST area has a front controller that understands how to do URL lookups for REST-based URLs. 181 | > 1. Web API SOAP (`webapi_soap`): entry point for this area is index.php or pub/index.php. 182 | > 183 | > -- [Magento DevDocs - Modules and areas](http://devdocs.magento.com/guides/v2.2/architecture/archi_perspectives/components/modules/mod_and_areas.html) 184 | 185 | Not documented but used in code [Magento/Framework/App/Area.php]( 186 | https://github.com/magento/magento2/blob/2.2/lib/internal/Magento/Framework/App/Area.php#L18-L25): 187 | 1. Documentation (doc). Deprecated. 188 | 1. Admin (admin). Deprecated. 189 | 190 | 191 | # What side effects can come from this interaction? 192 | - error when module is missing or disabled 193 | - error when injecting missing class 194 | - (?) null or error when using object manager for missing class 195 | ReflectionException - Class MissingClass does not exist 196 | objectManager->create() = new $type() or new $type(...args) --> PHP Warning: Uncaught Error: Class 'MissingClass' not found 197 | -------------------------------------------------------------------------------- /1. Magento Architecture and Customization Techniques/2. Describe Magento’s directory structure.md: -------------------------------------------------------------------------------- 1 | # Determine how to locate different types of files in Magento. 2 | 3 | >One of the first things you can do to get started with component development is to understand and set up the file system. Each type of component has a different file structure, although all components require certain files. 4 | In addition, you can choose the component root directory to start development. The following sections have more information. - [Magento DevDocs - About component file structure](https://devdocs.magento.com/guides/v2.2/extension-dev-guide/prepare/prepare_file-str.html) 5 | 6 | ### Where are the files containing JavaScript, HTML, and PHP located? 7 | - view/frontend/web/js 8 | - view/frontend/requirejs-config.js 9 | - view/frontend/layout 10 | - view/frontend/templates 11 | 12 | ### Root directory location 13 | 14 | >A component’s root directory is the top-level directory for that component under which its folders and files are located. Depending on how your Magento development environment was installed, your component’s root directory can be located in two places: - [Magento DevDocs - Create your component file structure](https://devdocs.magento.com/guides/v2.2/extension-dev-guide/build/module-file-structure.html) 15 | 16 | ##### `/app:` 17 | * Modules - app/code. 18 | * Storefront themes - app/design/frontend. 19 | * Admin themes - app/design/adminhtml. 20 | * Language packages - use app/i18n. 21 | 22 | ##### `/vendor` 23 | 24 | >This location is found in the alternative setups where the composer create-project command was used to get a Magento 2 metapackage (which downloads the CE or EE code), or a compressed Magento 2 archive was extracted in order to install Magento. 25 | > 26 | >Any third party components (and the Magento application itself) are downloaded and stored under the vendor directory. If you are using Git to manage project, this directory is typically added to the .gitignore file. Therefore, we recommend you do your customization work in app/code, not vendor. 27 | > 28 | > -- [Magento DevDocs - Create your component file structure](https://devdocs.magento.com/guides/v2.2/extension-dev-guide/build/module-file-structure.html) 29 | 30 | ### Required files 31 | 32 | >`registration.php`: Among other things, this file specifies the directory in which the component is installed by vendors in production environments. By default, composer automatically installs components in the /vendor directory. For more information, see Component registration. 33 | > 34 | >`etc/module.xml`: This file specifies basic information about the component such as the components dependencies and its version number. This version number is used to determine schema and data updates when bin/magento setup:upgrade is run. 35 | > 36 | >`composer.json`: Specifies component dependencies and other metadata. For more information, see Composer integration. 37 | > 38 | > -- [Magento DevDocs - About component file structure](https://devdocs.magento.com/guides/v2.2/extension-dev-guide/prepare/prepare_file-str.html) 39 | 40 | Class [Magento\Framework\Module\ModuleList\Loader](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Module/ModuleList/Loader.php#L78) load `etc/module.xml` files and sort modules by sequence. 41 | The sequence use for sorting events, plugins, preferences and layouts. 42 | 43 | 44 | ### Common directories 45 | 46 | - `Api` - Any PHP classes exposed to the API. 47 | - `Block` - PHP view classes as part of Model View Controller(MVC) vertical implementation of module logic. 48 | - `Console` - Console commands 49 | - `Controller` - PHP controller classes as part of MVC vertical implementation of module logic. 50 | - `Controller/Adminhtml` - Admin controllers 51 | - `Cron` - Cron job classes 52 | - `etc` - Configuration files; in particular, module.xml, which is required. 53 | - `Helper` - Helpers 54 | - `i18n` - Localization files in CSV format 55 | - `Model` - PHP model classes as part of MVC vertical implementation of module logic. 56 | - `Model/ResourceModel` - Database interactions 57 | - `Observer` - Event listeners 58 | - `Plugin` - Contains any needed plug-ins. 59 | - `Setup` - Classes for module database structure and data setup which are invoked when installing or upgrading. 60 | - `Test` - Unit tests 61 | - `Ui` - UI component classes 62 | - `view` - View files, including static view files, design templates, email templates, and layout files. 63 | - view/{area}/email 64 | - view/{area}/layout 65 | - view/{area}/templates 66 | - view/{area}/ui_component 67 | - view/{area}/ui_component/templates 68 | - view/{area}/web 69 | - view/{area}/web/template 70 | - view/{area}/requirejs-config.js 71 | 72 | see [Magento DevDocs - Create your component file structure](https://devdocs.magento.com/guides/v2.2/extension-dev-guide/build/module-file-structure.html) 73 | 74 | ### Service contract 75 | 76 | > The service contract of a module is defined by the set of interfaces in the module’s `/Api` directory. 77 | > 78 | > This directory contains: 79 | > 80 | > * Service interfaces in the `Api` namespace of the module ([Catalog API](https://github.com/magento/magento2/tree/2.0/app/code/Magento/Customer/Api)). 81 | > 82 | > * Data (or entity) interfaces in the `Api/Data` directory ([Catalog API/Data](https://github.com/magento/magento2/tree/2.0/app/code/Magento/Customer/Api/Data)). Data entities* are data structures passed to and returned from service interfaces. Files in the data directory contain get() and set() methods for entries in the entity table and extension attributes. 83 | > 84 | > -- [Service contract anatomy](https://devdocs.magento.com/guides/v2.2/architecture/archi_perspectives/service_layer.html#service-contract-anatomy) 85 | 86 | 87 | 88 | ### Theme file structure 89 | Example #1 90 | ``` 91 | ├── composer.json 92 | ├── theme.xml 93 | ├── etc 94 | │ └── view.xml 95 | ├── i18n 96 | │ └── en_US.csv 97 | ├── LICENSE_AFL.txt 98 | ├── LICENSE.txt 99 | ├── media 100 | │ └── preview.jpg 101 | ├── registration.php 102 | └── web 103 | ├── css 104 | │ ├── email.less 105 | │ ├── print.less 106 | │ ├── source 107 | │ │ ├── _actions-toolbar.less 108 | │ │ ├── _breadcrumbs.less 109 | │ │ ├── _buttons.less 110 | │ │ ├── components 111 | │ │ │ └── _modals_extend.less 112 | │ │ ├── _icons.less 113 | │ │ ├── _layout.less 114 | │ │ ├── _theme.less 115 | │ │ ├── _tooltips.less 116 | │ │ ├── _typography.less 117 | │ │ └── _variables.less 118 | │ ├── _styles.less 119 | │ ├── styles-l.less 120 | │ └── styles-m.less 121 | ├── images 122 | │ └── logo.svg 123 | └── js 124 | ├── navigation-menu.js 125 | ├── responsive.js 126 | └── theme.js 127 | ``` 128 | 129 | ### Language package file structure 130 | Example #2 131 | ``` 132 | ├── de_DE 133 | │ ├── composer.json 134 | │ ├── language.xml 135 | │ ├── LICENSE_AFL.txt 136 | │ ├── LICENSE.txt 137 | │ └── registration.php 138 | ├── en_US 139 | │ ├── composer.json 140 | │ ├── language.xml 141 | │ ├── LICENSE_AFL.txt 142 | │ ├── LICENSE.txt 143 | │ └── registration.php 144 | ├── pt_BR 145 | │ ├── composer.json 146 | │ ├── language.xml 147 | │ ├── LICENSE_AFL.txt 148 | │ ├── LICENSE.txt 149 | │ └── registration.php 150 | ``` 151 | 152 | Examples #1, #2 - - [Magento DevDocs - Create your component file structure](https://devdocs.magento.com/guides/v2.2/extension-dev-guide/build/module-file-structure.html) 153 | -------------------------------------------------------------------------------- /1. Magento Architecture and Customization Techniques/3. Utilize configuration XML and variables scope.md: -------------------------------------------------------------------------------- 1 | # Determine how to use configuration files in Magento. Which configuration files correspond to different features and functionality? 2 | 3 | ### List of existed *.xml configs 4 | - `acl.xml` - resource title, sort 5 | - `adminhtml/rules/payment_{country}.xml` - paypal 6 | - `address_formats.xml` 7 | - `address_types.xml` - format code and title only 8 | - `cache.xml` - name, instance - e.g. full_page=Page Cache 9 | - `catalog_attributes.xml` - catalog_category, catalog_product, unassignable, used_in_autogeneration, quote_item [*](https://www.atwix.com/magento-2/how-to-access-custom-catalog-attributes/) 10 | - `communication.xml` 11 | - `config.xml` - defaults 12 | - `crontab.xml` - group[], job instance, method, schedule [*](https://github.com/magento-notes/magento2-exam-notes/blob/master/1.%20Magento%20Architecture%20and%20Customization%20Techniques/6.%20Configure%20event%20observers%20and%20scheduled%20jobs.md#crontabxml) 13 | - `cron_groups.xml` [*](https://github.com/magento-notes/magento2-exam-notes/blob/master/1.%20Magento%20Architecture%20and%20Customization%20Techniques/6.%20Configure%20event%20observers%20and%20scheduled%20jobs.md#cron-groups) 14 | - `di.xml` - preference, plugins, virtual type [*](https://devdocs.magento.com/guides/v2.2/extension-dev-guide/build/di-xml-file.html) 15 | - `eav_attributes.xml` - locked entity attributes (global, unique etc.) 16 | - `email_templates.xml` - id label file type module -- view/frontend/email/name.html 17 | - `events.xml` - observers, shared, disabled [*](https://github.com/magento-notes/magento2-exam-notes/blob/master/1.%20Magento%20Architecture%20and%20Customization%20Techniques/6.%20Configure%20event%20observers%20and%20scheduled%20jobs.md#demonstrate-how-to-configure-observers) 18 | - `export.xml` 19 | - `extension_attributes.xml` - for, attribute code, attribute type 20 | - `fieldset.xml` 21 | - `import.xml` 22 | - `indexer.xml` - class, view_id, title, description 23 | - `integration.xml` 24 | - `integration/api.xml` 25 | - `integration/config.xml` 26 | - `menu.xml` - admin menu 27 | - `module.xml` - version, sequence 28 | - `mview.xml` - scheduled updates, subscribe to table changes, indexer model 29 | - `page_types.xml` 30 | - `payment.xml` - groups, method allow_multiple_address 31 | - `pdf.xml` - renders by type (invoice, shipment, creditmemo) and product type 32 | - `product_types.xml` - label, model instance, index priority, (?) custom attributes, (!) composable types 33 | - `product_options.xml` 34 | - `resources.xml` 35 | - `routes.xml` 36 | - `sales.xml` - collectors (quote, order, invoice, credit memo) 37 | - `search_engine.xml` 38 | - `search_request.xml` - index, dimensions, queries, filters, aggregations, buckets 39 | - `sections.xml` - action route placeholder -> invalidate customer sections 40 | - `system.xml` - adminhtml config 41 | - `validation.xml` - entity, rules, constraints -> class 42 | - `view.xml` - vars by module 43 | - `webapi.xml` - route, method, service class and method, resources 44 | - `widget.xml` - class, email compatible, image, ttl (?), label, description, parameters 45 | - `zip_codes.xml` 46 | 47 | # Interfaces for work with configs 48 | 49 | #### [\Magento\Framework\Config\Reader\Filesystem](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Config/Reader/Filesystem.php) -> [\Magento\Framework\Config\ReaderInterface](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Config/ReaderInterface.php) 50 | Gets .xsd names from schema locator, gets full .xml file list from file resolver, merges all files, validates, runs converter to get resulting array. 51 | - read(scope) 52 | + `fileResolver->get(_filename)` 53 | + merge and validate each file (if mode developer) 54 | + validate merged DOM 55 | + converter->convert(dom) => array 56 | - `_idAttributes`, `_fileName`, `_schemaFile` (from schemaLocator), `_perFileSchema` (from schemaLocator), filename (menu.xml) 57 | - schemaFile from schemaLocator 58 | 59 | #### [\Magento\Framework\Config\ConverterInterface](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Config/ConverterInterface.php) 60 | Convert an array to any format 61 | - `convert(\DOMDocument $source)` 62 | 63 | #### [\Magento\Framework\Config\SchemaLocatorInterface](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Config/SchemaLocatorInterface.php) - full path to .xsd 64 | - `getPerFileSchema` - per file before merge 65 | - `getSchema` - merged file 66 | 67 | #### [\Magento\Framework\Config\ValidationStateInterface](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Config/ValidationStateInterface.php) 68 | 69 | This interface retrieves the validation state. 70 | - `isValidationRequired()` 71 | 72 | [\Magento\Framework\App\Arguments\ValidationState](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/App/Arguments/ValidationState.php) is default implementation, that require validation only in developer mode. 73 | 74 | #### [\Magento\Framework\Config\ScopeListInterface](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Config/ScopeListInterface.php) 75 | 76 | This interface the list of all scopes. 77 | - `getAllScopes()` 78 | 79 | #### [\Magento\Framework\Config\Data](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Config/Data.php) -> [\Magento\Framework\Config\DataInterface](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Config/DataInterface.php) 80 | 81 | Helps to get the configuration data in a specified scope. 82 | - `merge(array $config);` 83 | - `get($key, $default = null)` 84 | 85 | 86 | ###### Links and examples: 87 | - Product types configs model to read data from [product_types.xml](https://github.com/magento/magento2/blob/2.3/app/code/Magento/Catalog/etc/product_types.xml): [\Magento\Catalog\Model\ProductTypes](https://github.com/magento/magento2/tree/2.3/app/code/Magento/Catalog/Model/ProductTypes) 88 | - Implementation via virtual types to read data from layout.xml: [Magento/Theme/etc/di.xml](https://github.com/magento/magento2/blob/2.3/app/code/Magento/Theme/etc/di.xml#L42) 89 | - https://www.atwix.com/magento-2/working-with-custom-configuration-files/ 90 | 91 | 92 | ### Configuration load and merge flow 93 | 94 | #### Loading order 95 | 1. First step is a `app/etc/di.xml` loading 96 | 1. Next step is collecting configuration files from all modules and merging them: `vendor_name/component_name/etc/*.xml` 97 | 1. At the last step Magento will collect all configs from `vendor_name/component_name/etc//*.xml` 98 | 99 | #### Merge flow 100 | 101 | >Nodes in configuration files are merged based on their fully qualified XPaths, which has a special attribute defined in $idAttributes array declared as its identifier. This identifier must be unique for all nodes nested under the same parent node. 102 | > 103 | > * If node identifiers are equal (or if there is no identifier defined), all underlying content in the node (attributes, child nodes, and scalar content) is overridden. 104 | > * If node identifiers are not equal, the node is a new child of the parent node. 105 | > * If the original document has multiple nodes with the same identifier, an error is triggered because the identifiers cannot be distinguished. 106 | > * After configuration files are merged, the resulting document contains all nodes from the original files. 107 | > 108 | > -- [Magento DevDocs - Module configuration files](https://devdocs.magento.com/guides/v2.2/config-guide/config/config-files.html) 109 | 110 | config merger = \Magento\Framework\Config\Dom 111 | - when merging each file 112 | + createConfigMerger|merge 113 | + \Magento\Framework\Config\Dom::_initDom(dom, perFileSchema) 114 | + \Magento\Framework\Config\Dom::validateDomDocument 115 | + $dom->schemaValidate 116 | - after all merged 117 | + \Magento\Framework\Config\Dom::validate(mergedSchema) 118 | + \Magento\Framework\Config\Dom::validateDomDocument 119 | 120 | ## Sensitive and environment settings 121 | 122 | This scope is a very huge part which includes a lot of things and there is a short list of useful links 123 | to the official Magento DevDocs documentation: 124 | 125 | - [Set configuration values](https://devdocs.magento.com/guides/v2.2/config-guide/cli/config-cli-subcommands-config-mgmt-set.html) 126 | - [Sensitive and system-specific](https://devdocs.magento.com/guides/v2.2/config-guide/prod/config-reference-sens.html) 127 | - [Magento Enterprise B2B Extension configuration paths reference](https://devdocs.magento.com/guides/v2.2/config-guide/prod/config-reference-b2b.html) 128 | - [Other configuration paths reference](https://devdocs.magento.com/guides/v2.2/config-guide/prod/config-reference-most.html) 129 | 130 | ### Example of how to set sensitive settings 131 | 132 | - shared config app/etc/config.php 133 | - sensitive or system-specific app/etc/env.php: 134 | 135 | ```xml 136 | 137 | 138 | 139 | 140 | 1 141 | 142 | 143 | 144 | 145 | 1 146 | 147 | 148 | 149 | 150 | ``` 151 | 152 | Sensitive info doesn't get exported with `bin/magento app:config:dump`. use env. params, e.g. 153 | `CONFIG__DEFAULT__PAYMENT__TEST__PASWORD` for `payment/test/password` 154 | 155 | `bin/magento app:config:dump`: 156 | 157 | - system-specific > app/etc/env.php 158 | - shared > app/etc/config.php 159 | - sensitive - skipped 160 | 161 | `bin/magento config:sensitive:set`: 162 | 163 | - writes to app/etc/env.php 164 | -------------------------------------------------------------------------------- /1. Magento Architecture and Customization Techniques/4. Demonstrate how to use dependency injection.md: -------------------------------------------------------------------------------- 1 | # Demonstrate how to use dependency injection 2 | 3 | Magento 2 uses constructor injection, where all injected objects or factories are provided when the object is constructed. 4 | 5 | Magento loads di.xml files and merges them all together from the following stages: 6 | * Initial (app/etc/di.xml) 7 | * Global ({moduleDir}/etc/di.xml) 8 | * Area-specific ({moduleDir}/etc/{area}/di.xml) 9 | 10 | ### Object manager 11 | 12 | Under the hood, the [ObjectManager](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/ObjectManager/ObjectManager.php) 13 | will use PHP reflection features to look at a class’s __construct type hints/parameters, 14 | automatically instantiate the object for us, and then pass it into the constructor as an argument. 15 | 16 | [AbstractFactory](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/ObjectManager/Factory/AbstractFactory.php) > [\Magento\Framework\ObjectManager\FactoryInterface](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/ObjectManager/FactoryInterface.php) 17 | and their implementations use to resolve arguments and create new objects. 18 | 19 | By default, all objects created via automatic constructor dependency injection are singleton objects, 20 | because they created via ObjectManager::get() method. 21 | 22 | ``` 23 | if ($isShared) { 24 | $argument = $this->objectManager->get($argumentType); 25 | } else { 26 | $argument = $this->objectManager->create($argumentType); 27 | } 28 | ``` 29 | [\Magento\Framework\ObjectManager\Factory::resolveArgument()](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/ObjectManager/Factory/AbstractFactory.php#L143-L147) 30 | 31 | ### Arguments 32 | 33 | ``` 34 | {typeName} 35 | {typeName} 36 | 37 | {strValue} 38 | {strValue} 39 | 40 | {boolValue} 41 | {numericValue} 42 | {Constant::NAME} 43 | {Constant::NAME} 44 | 45 | 46 | 47 | someVal 48 | 49 | ``` 50 | 51 | ###### Links 52 | - [Dependency injection](https://devdocs.magento.com/guides/v2.2/extension-dev-guide/depend-inj.html) 53 | - [The di.xml file](https://devdocs.magento.com/guides/v2.2/extension-dev-guide/build/di-xml-file.html) 54 | - [ObjectManager](https://devdocs.magento.com/guides/v2.2/extension-dev-guide/object-manager.html) 55 | - [Proxies](https://devdocs.magento.com/guides/v2.2/extension-dev-guide/proxies.html) 56 | - [Factories](https://devdocs.magento.com/guides/v2.2/extension-dev-guide/factories.html) 57 | - [Alan Storm, The Magento 2 Object System](https://alanstorm.com/category/magento-2/#magento-2-object-system) 58 | - [Alan Kent, Magento 2 Dependency injection](https://alankent.me/2014/06/07/magento-2-dependency-injection-the-m2-way-to-replace-api-implementations/) 59 | -------------------------------------------------------------------------------- /1. Magento Architecture and Customization Techniques/5. Demonstrate ability to use plugins.md: -------------------------------------------------------------------------------- 1 | # Demonstrate ability to use plugins 2 | 3 | There are three types of plugins in magento: around, before and after. 4 | 5 | __Important__: Classes, abstract classes and interfaces that are implementations of or inherit from classes that have plugins will also inherit plugins from the parent class. 6 | For example, if you create a plugin for [\Magento\Catalog\Block\Product\AbstractProduct](https://github.com/magento/magento2/blob/2.3/app/code/Magento/Catalog/Block/Product/AbstractProduct.php), 7 | plugin methods will be called for all child classes, such as \Magento\Catalog\Block\Product\View, \Magento\Catalog\Block\Product\ProductList\Upsell etc. 8 | 9 | ### Limitations 10 | 11 | Plugins only work on __public methods__ for any __classes__ or __interfaces__. 12 | So plugins can not be used on following: 13 | 14 | > 1. Final methods 15 | > 1. Final classes 16 | > 1. Non-public methods 17 | > 1. Class methods (such as static methods) 18 | > 1. __construct 19 | > 1. Virtual types 20 | > 1. Objects that are instantiated before Magento\Framework\Interception is bootstrapped 21 | 22 | @since 2.2 *after* plugin has access to *arguments*! 23 | 24 | ### Prioritizing plugins 25 | 26 | The `sortOrder` property controls how your plugin interacts with other plugins on the same class. 27 | 28 | > The prioritization rules for ordering plugins: 29 | > 30 | > * Before the execution of the observed method, Magento will execute plugins from lowest to greatest `sortOrder`. 31 | > 32 | > * During each plugin execution, Magento executes the current plugin's before method. 33 | > * After the before plugin completes execution, the current plugin's around method will wrap and execute the next plugin or observed method. 34 | > 35 | > * Following the execution of the observed method, Magento will execute plugins from greatest to lowest `sortOrder`. 36 | > 37 | > * During each plugin execution, the current plugin will first finish executing its around method. 38 | > * When the around method completes, the plugin executes its after method before moving on to the next plugin. 39 | > 40 | > -- [Magento DevDocs - Plugins](https://devdocs.magento.com/guides/v2.2/extension-dev-guide/plugins.html#prioritizing-plugins) 41 | 42 | Plugin sortOrder: 43 | 44 | - before sortOrder=10, before sortOrder=20, before sortOrder=30 ... 45 | - before and around (first half) called together for same plugin! 46 | - around (second half) and after called together for same plugin! 47 | 48 | Example: 49 | 50 | - pluginA.beforeMethod, pluginA.aroundMethod first half 51 | - pluginB.beforeMethod, pluginB.aroundMethod first half 52 | - pluginC.beforeMethod, `____________________ 53 | - ____________________, pluginD.aroundMethod first half 54 | - method() 55 | - ____________________, pluginD.aroundMethod second half 56 | - pluginC.afterMethod , ________________________________ 57 | - pluginB.aroundMethod second half, pluginB.afterMethod 58 | - pluginA.aroundMethod second half, pluginA.afterMethod 59 | 60 | ### Identify strengths and weaknesses of plugins. 61 | 62 | > The greatest weakness is exploited in the hands of a developer who is either not 63 | > experienced or not willing to take the time to evaluate the fallout. For example, 64 | > used improperly, an around plugin can prevent the system from functioning. 65 | > They can also make understanding what is going on by reading source code hard 66 | > (spooky action at a distance). 67 | > 68 | > -- [Swiftotter Developer Study Guide](https://swiftotter.com/technical/certifications/magento-2-certified-developer-study-guide) 69 | 70 | Using around plugins: 71 | > Avoid using around method plugins when they are not required because they increase stack traces and affect performance. 72 | > The only use case for around method plugins is when you need to terminate the execution of all further plugins and original methods. 73 | > 74 | > -- [Magento DevDocs - Programming Best Practices](https://devdocs.magento.com/guides/v2.2/ext-best-practices/extension-coding/common-programming-bp.html#using-around-plugins) 75 | 76 | ### In which cases should plugins be avoided? 77 | 78 | > Plugins are useful to modify the input, output, or execution of an existing method. 79 | > Plugins are also best to be avoided in situations where an event observer will work. 80 | > Events work well when the flow of data does not have to be modified. 81 | > 82 | > -- [Swiftotter Developer Study Guide](https://swiftotter.com/technical/certifications/magento-2-certified-developer-study-guide) 83 | 84 | Info from Magento technical guidelines: 85 | > `4. Interception` 86 | > 87 | > 4.1. Around-plugins SHOULD only be used when behavior of an original method is supposed to be substituted in certain scenarios. 88 | > 89 | > 4.2. Plugins SHOULD NOT be used within own module . 90 | > 91 | > 4.3. Plugins SHOULD NOT be added to data objects. 92 | > 93 | > 4.4. Plugins MUST be stateless. 94 | > 95 | > ... 96 | > 97 | > `14. Events` 98 | > 99 | > 14.1. All values (including objects) passed to an event MUST NOT be modified in the event observer. Instead, plugins SHOULD BE used for modifying the input or output of a function. 100 | > 101 | > -- [Magento DevDocs - Technical guidelines](https://devdocs.magento.com/guides/v2.2/coding-standards/technical-guidelines.html) 102 | 103 | 104 | Also check: 105 | - [Yireo - Magent 2 observer or Plugin?](https://www.yireo.com/blog/1875-magent-2-observer-or-plugin) 106 | - [Magento DevDocs - Observers Best Practices](https://devdocs.magento.com/guides/v2.2/ext-best-practices/extension-coding/observers-bp.html) 107 | 108 | 109 | ### how it works? 110 | 111 | Magento automatically generates `Interceptor` class for the plugin target and store it in the `generated\code` directory. 112 | 113 | ``` 114 | namespace \Notes; 115 | 116 | class Interceptor extends \Notes\MyBeautifulClass implements \Magento\Framework\Interception\InterceptorInterface 117 | { 118 | use \Magento\Framework\Interception\Interceptor; 119 | 120 | public function __construct($specificArguments1, $someArg2 = null) 121 | { 122 | $this->___init(); 123 | parent:__construct($specificArguments1, $someArg2); 124 | } 125 | 126 | public function sayHello() 127 | { 128 | pluginInfo = pluginList->getNext('MyBeautifulClass', 'sayHello') 129 | __callPlugins('sayHello', [args], pluginInfo) 130 | } 131 | } 132 | ``` 133 | 134 | [\Magento\Framework\Interception\Interceptor](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Interception/Interceptor.php): 135 | 136 | - $pluginList = [\Magento\Framework\Interception\PluginListInterface](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Interception/PluginListInterface.php) 137 | - $subjectType = 'MyBeautifulClass' 138 | - `___init` - called in in constructor, pluginList = get from object manager, subjectType = class name 139 | - pluginList->getNext 140 | - `___callPlugins` 141 | - `___callParent` 142 | 143 | ### how generated? 144 | 145 | \Magento\Framework\App\Bootstrap::create 146 | \Magento\Framework\App\Bootstrap::__construct 147 | \Magento\Framework\App\ObjectManagerFactory::create 148 | \Magento\Framework\ObjectManager\DefinitionFactory::createClassDefinition 149 | \Magento\Framework\ObjectManager\DefinitionFactory::getCodeGenerator 150 | \Magento\Framework\Code\Generator\Io::__construct 151 | \Magento\Framework\Code\Generator::__construct 152 | spl_autoload_register([new \Magento\Framework\Code\Generator\Autoloader, 'load']); 153 | 154 | \Magento\Framework\App\ObjectManagerFactory::create 155 | \Magento\Framework\Code\Generator::setGeneratedEntities 156 | \Magento\Framework\App\ObjectManager\Environment\Developer::configureObjectManager 157 | 158 | \Magento\Framework\Code\Generator\Autoloader::load 159 | \Magento\Framework\Code\Generator::generateClass 160 | 161 | ### Decide how to generate based on file suffix - generator \Magento\Framework\Code\Generator\EntityAbstract 162 | ``` 163 | array ( 164 | 'extensionInterfaceFactory' => '\\Magento\\Framework\\Api\\Code\\Generator\\ExtensionAttributesInterfaceFactoryGenerator', 165 | 'factory' => '\\Magento\\Framework\\ObjectManager\\Code\\Generator\\Factory', 166 | 'proxy' => '\\Magento\\Framework\\ObjectManager\\Code\\Generator\\Proxy', 167 | 'interceptor' => '\\Magento\\Framework\\Interception\\Code\\Generator\\Interceptor', 168 | 'logger' => '\\Magento\\Framework\\ObjectManager\\Profiler\\Code\\Generator\\Logger', 169 | - logs all public methods call 170 | - Magento\Framework\ObjectManager\Factory\Log -- missing? 171 | 'mapper' => '\\Magento\\Framework\\Api\\Code\\Generator\\Mapper', 172 | - extractDto() = $this->{$name}Builder->populateWithArray()->create 173 | 'persistor' => '\\Magento\\Framework\\ObjectManager\\Code\\Generator\\Persistor', 174 | - getConnection, loadEntity, registerDelete, registerNew, registerFromArray, doPersist, doPersistEntity 175 | 'repository' => '\\Magento\\Framework\\ObjectManager\\Code\\Generator\\Repository', -- deprecated 176 | 'convertor' => '\\Magento\\Framework\\ObjectManager\\Code\\Generator\\Converter', 177 | - Extract data object from model 178 | - getModel(AbstractExtensibleObject $dataObject) = getProductFactory()->create()->setData($dataObject)->__toArray() 179 | 'searchResults' => '\\Magento\\Framework\\Api\\Code\\Generator\\SearchResults', 180 | - extends \Magento\Framework\Api\SearchResults 181 | 'extensionInterface' => '\\Magento\\Framework\\Api\\Code\\Generator\\ExtensionAttributesInterfaceGenerator', 182 | 'extension' => '\\Magento\\Framework\\Api\\Code\\Generator\\ExtensionAttributesGenerator', 183 | - extension_attributes.xml 184 | - extends \Magento\Framework\Api\AbstractSimpleObject 185 | - implements {name}\ExtensionInterface 186 | - for every custom attribute, getters and setters 187 | 'remote' => '\\Magento\\Framework\\MessageQueue\\Code\\Generator\\RemoteServiceGenerator', 188 | ) 189 | ``` 190 | 191 | ``` 192 | Magento\Framework\App\ResourceConnection\Proxy -> type Proxy, name Magento\Framework\App\ResourceConnection 193 | Magento\Framework\Code\Generator::shouldSkipGeneration - type not detected, or class exists 194 | \Magento\Framework\Code\Generator::createGeneratorInstance -- new for every file 195 | ``` 196 | 197 | ### \Magento\Framework\Code\Generator\EntityAbstract - code generation 198 | ``` 199 | \Magento\Framework\Code\Generator\EntityAbstract::generate 200 | \Magento\Framework\Code\Generator\EntityAbstract::_validateData - class not existing etc. 201 | \Magento\Framework\Code\Generator\EntityAbstract::_generateCode 202 | - \Magento\Framework\Code\Generator\ClassGenerator extends \Zend\Code\Generator\ClassGenerator 203 | - \Magento\Framework\Code\Generator\EntityAbstract::_getDefaultConstructorDefinition 204 | - \Magento\Framework\Code\Generator\EntityAbstract::_getClassProperties 205 | - \Magento\Framework\Code\Generator\EntityAbstract::_getClassMethods 206 | ``` 207 | 208 | ###### Links 209 | - [Magento DevDocs - Plugins (Interceptors)](https://devdocs.magento.com/guides/v2.2/extension-dev-guide/plugins.html) 210 | - [Alan Storm - Magento 2 Object Manager Plugin System](https://alanstorm.com/magento_2_object_manager_plugin_system/) -------------------------------------------------------------------------------- /1. Magento Architecture and Customization Techniques/6. Configure event observers and scheduled jobs.md: -------------------------------------------------------------------------------- 1 | # Configure event observers and scheduled jobs 2 | 3 | ## Observers 4 | 5 | Events are dispatched by modules when certain actions are triggered. 6 | In addition to its own events, Magento allows you to create your own events that can be dispatched in your code. 7 | When an event is dispatched, it can pass data to any observers configured to watch that event. 8 | 9 | Best practices: 10 | - Make your observer efficient 11 | - Do not include business logic 12 | - Declare observer in the appropriate scope 13 | - Avoid cyclical event loops 14 | - Do not rely on invocation order 15 | 16 | > 17 | > `14. Events` 18 | > 19 | > 14.1. All values (including objects) passed to an event MUST NOT be modified in the event observer. Instead, plugins SHOULD BE used for modifying the input or output of a function. 20 | > 21 | > -- [Magento DevDocs - Technical guidelines](https://devdocs.magento.com/guides/v2.2/coding-standards/technical-guidelines.html) 22 | 23 | 24 | ### Demonstrate how to configure observers 25 | - can define observer in global area, then disable in specific area 26 | 27 | Observers can be configured in the `events.xml` file. 28 | 29 | Properties: 30 | - name (required) - Name of the observer for the event definition 31 | - instance (required) - Class name of the observer 32 | - disabled - Is observer active or not (Default: false) 33 | - shared - Class lifestyle (Default: false) 34 | 35 | Example: 36 | ```xml 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | ``` 46 | 47 | Observer class should be placed in the /Observer directory and implement 48 | [Magento\Framework\Event\ObserverInterface](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Event/ObserverInterface.php) 49 | interface. 50 | 51 | ```php 52 | getEvent()->getData('order'); // $observer->getEvent()->getOrder(); 59 | // observer code... 60 | } 61 | } 62 | ``` 63 | 64 | ### Dispatching events 65 | 66 | Events dispatch by [Magento\Framework\Event\Manager](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Event/Manager.php) class 67 | that implement [Magento\Framework\Event\ManagerInterface](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Event/ManagerInterface.php) interface: 68 | > dispatch($eventName, array $data = []); 69 | 70 | Example: 71 | > $this->eventManager->dispatch('cms_page_prepare_save', ['page' => $model, 'request' => $this->getRequest()]); 72 | 73 | ###### Links 74 | - [Magento DevDocs - Events and observers](https://devdocs.magento.com/guides/v2.2/extension-dev-guide/events-and-observers.html) 75 | - [Magento DevDocs - Observers Best Practices](https://devdocs.magento.com/guides/v2.2/ext-best-practices/extension-coding/observers-bp.html) 76 | 77 | 78 | ## Scheduled jobs 79 | 80 | ### Demonstrate how to configure a scheduled job 81 | 82 | 83 | ##### Cron groups 84 | 85 | A cron group is a logical group that enables you to easily run cron for more than one process at a time. 86 | Most Magento modules use the default cron group. 87 | 88 | To declare new group and specify settings, create `/etc/cron_groups.xml` file (store view scope): 89 | 90 | ```xml 91 | 92 | 93 | 15 94 | 20 95 | 15 96 | 10 97 | 10080 98 | 10080 99 | 0 100 | 101 | 102 | ``` 103 | 104 | Existing groups: 105 | - default (no separate process) 106 | - index - mview, targetrule 107 | - catalog_event - catalog_event_status_checker - mark event open/closed 108 | - consumers - consumers_runner if configured to run by cron. `bin/magento queue:consumers:start`. PID file var/{$consumer}.pid 109 | - staging - staging_apply_version, staging_remove_updates, staging_synchronize_entities_period 110 | - ddg_automation (dotmailer) 111 | 112 | ##### crontab.xml 113 | 114 | `crontab.xml` file is used to execute an action on schedule. 115 | This file always located in the /etc/ folder (not in /etc/{area}/)! 116 | 117 | ```xml 118 | 119 | 120 | 121 | some/config/path 122 | 123 | 124 | 0 * * * * 125 | 126 | 127 | 128 | ``` 129 | 130 | run: 131 | 132 | - magento cron:run [–group=”"] 133 | - pub/cron.php?[group=] in a web browser, protect with basic auth 134 | 135 | Stack trace: 136 | ``` 137 | \Magento\Cron\Console\Command\CronCommand::execute 138 | \Magento\Framework\App\Cron::launch 139 | `default` event 140 | \Magento\Cron\Observer\ProcessCronQueueObserver 141 | check for specific group 142 | cleanup 143 | generate 144 | check for standalone process 145 | ``` 146 | 147 | [\Magento\Cron\Model\Config\Data](https://github.com/magento/magento2/blob/2.3/app/code/Magento/Cron/Model/Config/Data.php) extends [\Magento\Framework\Config\Data](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Config/Data.php) 148 | - merges [\Magento\Cron\Model\Config\Reader\Db::get](https://github.com/magento/magento2/blob/2.3/app/code/Magento/Cron/Model/Config/Reader/Db.php#L51) from Database 149 | 150 | Sample DB structure: 151 | ``` 152 | default/crontab/GROUP/jobs/JOB/schedule/cron_expr = '* * * * *' 153 | default/crontab/GROUP/jobs/JOB/schedule/config_path = 'some/config/path' -- try to read schedule from this config, store view scope 154 | default/crontab/GROUP/jobs/JOB/run/model = 'class::method' 155 | ``` 156 | DB config usage example: [ProductAlert, system.xml](https://github.com/magento/magento2/blob/2.3/app/code/Magento/ProductAlert/etc/adminhtml/system.xml#L38:L45), [ProductAlert, crontab.xml](https://github.com/magento/magento2/blob/2.3/app/code/Magento/ProductAlert/etc/crontab.xml), backend model: [Magento\Cron\Model\Config\Backend\Product\Alert](https://github.com/magento/magento2/blob/2.3/app/code/Magento/Cron/Model/Config/Backend/Product/Alert.php) 157 | 158 | `bin/magento cron:install` example: 159 | ``` 160 | #~ MAGENTO START 4d557a63fe1eac8a2827a4eca020c6bb 161 | * * * * * /usr/bin/php7.0 /var/www/m22ee/bin/magento cron:run 2>&1 | grep -v "Ran jobs by schedule" >> /var/www/m22ee/var/log/magento.cron.log 162 | * * * * * /usr/bin/php7.0 /var/www/m22ee/update/cron.php >> /var/www/m22ee/var/log/update.cron.log 163 | * * * * * /usr/bin/php7.0 /var/www/m22ee/bin/magento setup:cron:run >> /var/www/m22ee/var/log/setup.cron.log 164 | #~ MAGENTO END 4d557a63fe1eac8a2827a4eca020c6bb 165 | ``` 166 | 167 | how is separate process ran? 168 | `bin/magento cron:run --group=NAME --bootstrap=standaloneProcessStarted=1` 169 | 170 | what is update/cron.php? 171 | TODO: find out 172 | 173 | what is setup:cron:run? 174 | TODO: find out 175 | 176 | ###### Links 177 | - [Magento DevDocs - Configure a custom cron job and cron group (tutorial)](https://devdocs.magento.com/guides/v2.2/config-guide/cron/custom-cron-tut.html) 178 | - [Magento DevDocs - Custom cron job and cron group reference](https://devdocs.magento.com/guides/v2.2/config-guide/cron/custom-cron-ref.html) 179 | 180 | 181 | ### Identify the function and proper use of automatically available events 182 | 183 | Model events [\Magento\Framework\Model\AbstractModel](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Model/AbstractModel.php): 184 | 185 | - `model_load_before`, `{$_eventPrefix}_load_before` 186 | - `model_load_after`, `{$_eventPrefix}_load_after` 187 | - `model_save_commit_after`, `{$_eventPrefix}_save_commit_after` 188 | - `model_save_before`, `{$_eventPrefix}_save_before` 189 | - `model_save_after`, `{$_eventPrefix}_save_after` 190 | - `model_delete_before`, `{$_eventPrefix}_delete_before` 191 | - `model_delete_after`, `{$_eventPrefix}_delete_after` 192 | - `model_delete_commit_after`, `{$_eventPrefix}_delete_commit_after` 193 | 194 | Flat collection events [\Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Model/ResourceModel/Db/Collection/AbstractCollection.php): 195 | 196 | - `core_collection_abstract_load_before`, `{_eventPrefix}_load_before` 197 | - `core_collection_abstract_load_after`, `{_eventPrefix}_load_after` 198 | 199 | only if `_eventPrefix` and `_eventObject` defined: `{prefix}_load_before`, `{prefix}_load_after` 200 | 201 | EAV collection events [\Magento\Eav\Model\Entity\Collection\AbstractCollection](https://github.com/magento/magento2/blob/2.3/app/code/Magento/Eav/Model/Entity/Collection/AbstractCollection.php): 202 | 203 | - eav_collection_abstract_load_before 204 | 205 | [\Magento\Framework\Model\AbstractModel](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Model/AbstractModel.php): 206 | 207 | - _eventObject = 'object' 208 | - _eventPrefix = 'core_abstract', e.g. 'catalog_category' 209 | - _getEventData() - 'data_object' + $_eventObject 210 | - `model_load_before` (object, field=null, value=ID) 211 | - `{_eventPrefix}_load_before`, e.g. `catalog_category_load_before` (object, field, value, data_object, category) 212 | 213 | ###### Links 214 | - [Cyrill Schumacher's Blog - List of all dispatched events](https://cyrillschumacher.com/magento-2.2-list-of-all-dispatched-events/) 215 | -------------------------------------------------------------------------------- /1. Magento Architecture and Customization Techniques/7. Utilize the CLI.md: -------------------------------------------------------------------------------- 1 | # Utilize the CLI 2 | 3 | ### Describe the usage of bin/magento commands in the development cycle. 4 | 5 | ### Demonstrate an ability to create a deployment process. 6 | Modes: *default*, *developer*, *production*. MAGE-MODE env variable 7 | 8 | Commands: 9 | 10 | - `bin/magento deploy:mode:show` 11 | - `magento deploy:mode:set {mode} [-s|--skip-compilation]` -- skip compilation when changing to production 12 | 13 | cannot switch to default mode, only developer or production 14 | 15 | Default: 16 | 17 | - errors logged in var/report, not displayed 18 | - static created dynamically - copied! changes not visible. cached 19 | 20 | Developer: 21 | 22 | - exceptions displayed, not logged 23 | - exception thrown if bad event subscriber. 24 | - var/report detailed 25 | - static created dynamically - symlinked???, changes visible immediately 26 | - error handler - throws exception instead of logging (notice etc.) 27 | 28 | Production - max speed, no errors, no file generation: 29 | 30 | - admin can't enable/disable cache types 31 | - errors logged, not displayed 32 | - static not created dynamically, must be deployed 33 | - not need for www-data to write, pub/static can be read-only 34 | 35 | ### Commands explaination 36 | #### setup:di:compile 37 | 1. [Code compilation includes the following (in no particular order):](https://devdocs.magento.com/guides/v2.2/config-guide/cli/config-cli-subcommands-compiler.html) 38 | * Application code generation (factories, proxies) 39 | * Area configuration aggregation (optimized dependency injection configurations per area) 40 | 41 | see [generated/metadata](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/code-generation.html#codegen-om) 42 | * Interceptor generation (optimized code generation of interceptors) 43 | * Interception cache generation 44 | * Repositories code generation (generated code for APIs) 45 | * Service data attributes generation (generated extension classes for data objects) 46 | * [Running setup:di:compile places the app into a special mode](https://www.cadence-labs.com/2017/07/magento-2-run-setupdicompile/) 47 | 48 | ### How to add CLI commands (off-topic) 49 | 50 | Magento 2 CLI is based on the Symfony Console component. 51 | 52 | To create new CLI command you need: 53 | - Create command class (the recommended location is {module}/Console/Command) 54 | This class must extends from [\Symfony\Component\Console\Command\Command](https://github.com/symfony/console/blob/master/Command/Command.php) and have 2 methods: 55 | 56 | `configure` - to set the name, description, command line arguments etc 57 | 58 | `execute` - is the place where you write your code 59 | 60 | ```php 61 | setName('example:hello') 72 | ->setDescription('Hello world command'); 73 | 74 | // Positional argument 75 | $this->addArgument( 76 | 'myargument', 77 | InputArgument::REQUIRED, 78 | 'Positional required argument example' 79 | ); 80 | 81 | // Not required option 82 | $this->addOption( 83 | 'myoption', 84 | null, 85 | InputOption::VALUE_OPTIONAL, 86 | 'Option example', 87 | ScopeConfigInterface::SCOPE_TYPE_DEFAULT 88 | ); 89 | 90 | parent::configure(); 91 | } 92 | 93 | protected function execute(\Symfony\Component\Console\Input\InputInterface $input, \Symfony\Component\Console\Output\OutputInterface $output) 94 | { 95 | $output->writeln('hello world'); 96 | } 97 | } 98 | ``` 99 | 100 | - Declare your command in [\Magento\Framework\Console\CommandListInterface](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Console/CommandListInterface.php) using dependency injection ({module}/etc/di.xml). 101 | See also CommandListInterface implementation: [\Magento\Framework\Console\CommandList](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Console/CommandList.php) 102 | 103 | {module}/etc/di.xml: 104 | ```xml 105 | 106 | 107 | 108 | Vendor\Module\Console\Command\ExampleCommand 109 | 110 | 111 | 112 | ``` 113 | 114 | - Clean the cache and compiled code directories 115 | 116 | rm -rf cache/* page_cache/* di/* generation/* 117 | 118 | ###### Links 119 | - [Magento DevDocs - How to add CLI commands](https://devdocs.magento.com/guides/v2.2/extension-dev-guide/cli-cmds/cli-howto.html) 120 | - [Magento DevDocs - Command naming guidelines](https://devdocs.magento.com/guides/v2.2/extension-dev-guide/cli-cmds/cli-naming-guidelines.html) 121 | - [Symfony Documentation - The Console Component](https://symfony.com/doc/3.4/components/console.html) 122 | - [Magento - sample-module-command](https://github.com/magento/magento2-samples/tree/master/sample-module-command) 123 | -------------------------------------------------------------------------------- /1. Magento Architecture and Customization Techniques/8. Demonstrate the ability to manage the cache.md: -------------------------------------------------------------------------------- 1 | # Demonstrate the ability to manage the cache 2 | 3 | ### Describe cache types and the tools used to manage caches. 4 | 5 | - config 6 | - layout 7 | - block_html 8 | - collections 9 | - db_ddl 10 | - eav 11 | - full_page 12 | - reflection 13 | - translate 14 | - config_integration 15 | - config_integration_api 16 | - config_webservice 17 | 18 | Description for most cache type can be found on the [Magento DevDocs - Manage the cache](https://devdocs.magento.com/guides/v2.2/config-guide/cli/config-cli-subcommands-cache.html#config-cli-subcommands-cache-clean-over) 19 | 20 | Commands: 21 | 22 | - `magento setup:db-schema:upgrade` 23 | - `magento cache:status`, `magento cache:enable`, `magento cache:disable` 24 | - `magento cache:clean`, `magento cache:flush` 25 | 26 | ### Init: 27 | 28 | frontend: 29 | ``` 30 | \Magento\Framework\App\ObjectManager\ConfigLoader::load 31 | cacheType = config, frontend = default 32 | \Magento\Framework\App\Cache\Frontend\Pool::_initialize 33 | \Magento\Framework\App\Cache\Frontend\Factory::create 34 | \Zend_Cache::_makeFrontend 35 | \Zend_Cache_Core::__construct 36 | ``` 37 | 38 | backend: 39 | 40 | - \Zend_Cache_Backend 41 | - \Zend_Cache_Backend_File 42 | - \Magento\Framework\Cache\Backend\Database 43 | 44 | How do you add dynamic content to pages served from the full page cache? 45 | 46 | 1. Mark any block `cacheable="false"` in layout xml - whole page is uncacheable. Example -checkout 47 | 1. Disable caching in controller using *headers*: 48 | `$page->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0', true);` 49 | 1. Marking block property isScopePrivate - will be loaded via AJAX - deprecated 50 | 1. ESI when Varnish enabled, set TTL - Example - menu block 51 | 1. Configure page variations - extend *http context*, more cached versions of same page - store view, customer group, language, currency, is logged in 52 | `\Magento\Framework\App\Http\Context::getVaryString` 53 | 54 | Only _GET_ and _HEAD_ are cached 55 | 56 | Clear cache 57 | \Magento\Framework\DataObject\IdentityInterface 58 | 59 | ### when giving product page, must somehow send varnish tags 60 | Any block can implement IdentityInterface. After rendering layout and before sending output, 61 | all blocks are examined for implementing this interface. Cache tags are collected as merge of all blocks 62 | getIdentities() tags. 63 | 64 | ``` 65 | \Magento\PageCache\Model\Layout\LayoutPlugin::afterGetOutput 66 | X-Magento-Tags = merge(all blocks.getIdentities) 67 | ``` 68 | 69 | block ListProduct: 70 | 71 | - every product[].getIdentities 72 | - cat_p_{productId} 73 | - *if changed categories* - cat_p_c_{categoryId} 74 | - *if changed status* - every category[] cat_p_c_{categoryId} 75 | - *if frontend* - 'cat_p' 76 | - cat_c_p_{categoryId} 77 | 78 | block product/view: 79 | 80 | - \Magento\Catalog\Model\Product::getIdentities: 81 | - cat_p_{productId} 82 | - *if changed categories* - cat_p_c_{categoryId} 83 | - *if changed status* - every category[] cat_p_c_{categoryId} 84 | - *if frontend* - 'cat_p' 85 | - *if current_category* - cat_c_{categoryId} 86 | 87 | ### after reindex, must somehow clean cache 88 | 89 | - any indexer.execute -- by MView 90 | - any indexer.executeFull 91 | - \Magento\Framework\Indexer\CacheContext::registerTags 92 | 93 | plugin \Magento\Indexer\Model\Processor: 94 | 95 | \Magento\Indexer\Model\Processor\CleanCache::afterUpdateMview 96 | 97 | - event `clean_cache_after_reindex` 98 | - clean cache cacheContext->getIdentities() 99 | 100 | \Magento\Indexer\Model\Processor\CleanCache::afterReindexAllInvalid 101 | 102 | - event `clean_cache_by_tags` 103 | - clean cache cacheContext->getIdentities() 104 | 105 | module-cache-invalidate observer `clean_cache_after_reindex` 106 | \Magento\CacheInvalidate\Observer\InvalidateVarnishObserver::execute 107 | \Magento\CacheInvalidate\Model\PurgeCache::sendPurgeRequest 108 | 109 | ### Describe how to operate with cache clearing. 110 | 111 | How would you clean the cache? In which case would you refresh cache/flash cache storage? 112 | 113 | > To purge out-of-date items from the cache, you can clean or flush cache types: 114 | > 115 | > - Cleaning a cache type deletes all items from enabled Magento cache types only. In other words, this option does not affect other processes or applications because it cleans only the cache that Magento uses. 116 | > 117 | > Disabled cache types are not cleaned. 118 | > 119 | > - Flushing a cache type purges the cache storage, which might affect other processes applications that are using the same storage. 120 | > 121 | > Flush cache types if you’ve already tried cleaning the cache and you’re still having issues that you cannot isolate. 122 | > 123 | > -- [Magento DevDocs - Manage the cache](https://devdocs.magento.com/guides/v2.2/config-guide/cli/config-cli-subcommands-cache.html#config-cli-subcommands-cache-clean-over) 124 | 125 | Sessions and caching data should never be stored in one database in Redis. In that case you'll not have problems with flushing the cache. 126 | 127 | ### Describe how to clear the cache programmatically. 128 | 129 | To clear the cache programmatically you neeed to call next the methods: 130 | - [\Magento\Framework\App\CacheInterface::remove($identifier)](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/App/CacheInterface.php#L48) - remove cached data by identifier 131 | - [\Magento\Framework\App\CacheInterface::clean($tags = [])](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/App/CacheInterface.php#L56) - clean cached data by specific tag 132 | 133 | ##### What mechanisms are available for clearing all or part of the cache? 134 | 135 | Dispatch a `clean_cache_by_tags` event with parameter of the object you want to clear from the cache. 136 | 137 | Example: [\Magento\Framework\Model\AbstractModel](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Model/AbstractModel.php#L817) (afterSave, afterDelete methods) 138 | 139 | ```php 140 | cleanModelCache(); 144 | $this->_eventManager->dispatch('model_save_after', ['object' => $this]); 145 | $this->_eventManager->dispatch('clean_cache_by_tags', ['object' => $this]); 146 | $this->_eventManager->dispatch($this->_eventPrefix . '_save_after', $this->_getEventData()); 147 | $this->updateStoredData(); 148 | return $this; 149 | } 150 | 151 | public function cleanModelCache() 152 | { 153 | $tags = $this->getCacheTags(); 154 | if ($tags !== false) { 155 | $this->_cacheManager->clean($tags); // \Magento\Framework\App\CacheInterface 156 | } 157 | return $this; 158 | } 159 | ``` 160 | 161 | Default `clean_cache_by_tags` event observers are: 162 | - [Magento\PageCache\Observer\FlushCacheByTags](https://github.com/magento/magento2/blob/2.3/app/code/Magento/PageCache/Observer/FlushCacheByTags.php#L57) - if Built-In caching is enabled 163 | - [Magento\CacheInvalidate\Observer\InvalidateVarnishObserver](https://github.com/magento/magento2/blob/2.3/app/code/Magento/CacheInvalidate/Observer/InvalidateVarnishObserver.php#L50)- if Varnish caching is enabled 164 | 165 | ###### Links 166 | - [Magento DevDocs - Magento cache overview](https://devdocs.magento.com/guides/v2.2/frontend-dev-guide/cache_for_frontdevs.html) 167 | - [Magento DevDocs - Configure caching](https://devdocs.magento.com/guides/v2.2/config-guide/cache.html) 168 | - [Magento DevDocs - Partial caching](https://devdocs.magento.com/guides/v2.2/extension-dev-guide/cache/partial-caching.html) 169 | - [Magento DevDocs - Full Page caching](https://devdocs.magento.com/guides/v2.2/extension-dev-guide/cache/page-caching.html) 170 | - [Magento DevDocs - Private content](https://devdocs.magento.com/guides/v2.2/extension-dev-guide/cache/page-caching/private-content.html) 171 | -------------------------------------------------------------------------------- /10. Customer Management/10.1 Demonstrate ability to customize My Account.md: -------------------------------------------------------------------------------- 1 | ## 10.1 Demonstrate ability to customize My Account 2 | 3 | Describe how to customize the “My Account” section. 4 | 5 | *How do you add a menu item?* 6 | - Create A theme or use an existing one 7 | - Create a folder in the theme Magento_Customer 8 | - Create a folder inside the theme Magento_Customer called layout 9 | - Create a file inside the theme/Magento_Customer/layout/customer_account.xml 10 | - Add similar xml 11 | ```xml 12 | 13 | 19 | 20 | 21 | 22 | 23 | 24 | Russell Special 25 | special/link 26 | 165 27 | 28 | 29 | 30 | 31 | 32 | ``` 33 | 34 | *How would you customize the “Order History” page?* 35 | -------------------------------------------------------------------------------- /10. Customer Management/10.2 Demonstrate ability to customize customer functionality.md: -------------------------------------------------------------------------------- 1 | ## 10.2 Demonstrate ability to customize customer functionality 2 | 3 | Describe how to add or modify customer attributes. 4 | 5 | Describe how to extend the customer entity. 6 | 7 | *How would you extend the customer entity using the extension attributes mechanism?* 8 | 9 | Describe how to customize the customer address. 10 | 11 | *How would you add another field into the customer address?* 12 | 13 | Describe customer groups and their role in different business processes. 14 | 15 | *What is the role of customer groups?* 16 | 17 | *What functionality do they affect?* 18 | 19 | Describe Magento functionality related to VAT. 20 | 21 | *How do you customize VAT functionality?* 22 | -------------------------------------------------------------------------------- /2. Request Flow Processing/1. Utilize modes and application initialization.md: -------------------------------------------------------------------------------- 1 | # Utilize modes and application initialization 2 | 3 | ## Identify the steps for application initialization. 4 | [app/bootstrap.php](https://github.com/magento/magento2/blob/2.3/app/bootstrap.php): 5 | 6 | - composer autoloader, functions, umask, timezone UTC, php precision 7 | 8 | [\Magento\Framework\App\Bootstrap](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/App/Bootstrap.php)::*create* 9 | 10 | - configure autoloader - PSR-4 prepend generation\Magento 11 | 12 | [\Magento\Framework\App\Bootstrap](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/App/Bootstrap.php)::*createApplication* 13 | 14 | - just call object manager->create 15 | 16 | [\Magento\Framework\App\Bootstrap](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/App/Bootstrap.php)::*run* 17 | 18 | - set error handler 19 | - assert maintenance 20 | - assert installed 21 | - response = application->*launch*() 22 | - response->*sendResponse*() 23 | - on error: application->catchException 24 | - on missed error: 25 | - dev mode: print exception trace 26 | - normal mode: log, message "An error has happened during application run. See exception log for details." 27 | 28 | ### Application class 29 | 30 | [bootstrap->createApplication()](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/App/Bootstrap.php#L230) 31 | 32 | - [\Magento\Framework\App\Http](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/App/Http.php) - index.php, pub/index.php 33 | load config area by front name 34 | front controller->dispatch 35 | event `controller_front_send_response_before` 36 | 37 | - [\Magento\Framework\App\Cron](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/App/Cron.php) - pub/cron.php 38 | config area `crontab` 39 | load translations 40 | dispatch event `default` 41 | 42 | - [\Magento\MediaStorage\App\Media](https://github.com/magento/magento2/blob/2.3/app/code/Magento/MediaStorage/App/Media.php) - pub/get.php 43 | access /media/* when using DB image storage and physical file doesn't exist 44 | 45 | - [\Magento\Framework\App\StaticResource](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/App/StaticResource.php) - pub/static.php 46 | 404 in production 47 | /$area/$resource/$file/... params, load config by params 48 | sends file in response 49 | assetRepo->createAsset - \Magento\Framework\View\Asset\File 50 | assetPublisher->publish - materialize (copy/symlink) file if doesn't exist 51 | 52 | - [\Magento\Indexer\App\Indexer](https://github.com/magento/magento2/blob/2.3/app/code/Magento/Indexer/App/Indexer.php) - module-indexer, unused? 53 | - [\Magento\Backend\App\UserConfig](https://github.com/magento/magento2/blob/2.3/app/code/Magento/Backend/App/UserConfig.php) - module-backend, unused? 54 | 55 | Notes: 56 | 57 | - Responsibility - launch() and return response 58 | - Roughly different application class matches different physical file entry points (index.php, media.php, get.php, static.php, cron.php) 59 | - Front controller exists only in Http application 60 | - There's no CLI application, Symfony command is used 61 | 62 | ### HTTP application 63 | 64 | [\Magento\Framework\App\Http::launch](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/App/Http.php#L128) 65 | 1. detect config area by front name 66 | 67 | ```php 68 | _areaList->getCodeByFrontName($this->_request->getFrontName()); 70 | $this->_state->setAreaCode($areaCode); 71 | $this->_objectManager->configure($this->_configLoader->load($areaCode)); 72 | ``` 73 | 74 | `\Magento\Framework\App\AreaList` - areas from argument di.xml ([AreaList](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/App/AreaList.php)) 75 | 76 | - frontend = [frontname null, router "standard"] --- *default when nothing matched* 77 | - adminhtml - [frontNameResolver=..., router "admin"] 78 | [\Magento\Backend\App\Area\FrontNameResolver::getFrontName(checkhost)](https://github.com/magento/magento2/blob/2.3/app/code/Magento/Backend/App/Area/FrontNameResolver.php#L83) 79 | system config `admin/url/use_custom`, `admin/url/custom` 80 | - crontab = null 81 | - webapi_rest = [frontName `/rest`] 82 | - webapi_soap = [frontname `/soap`] 83 | 84 | 1. [ObjectManagerInterface->configure()](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/ObjectManager/ObjectManager.php#L82) - selected area code 85 | 1. result = FrontControllerInterface->dispatch() 86 | 1. [ResultInterface->renderResult()](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Controller/AbstractResult.php#L122) into response object 87 | 1. event `controller_front_send_response_before` (request, response) 88 | 89 | 90 | ### How would you design a customization that should act on every request and capture output data regardless of the controller? 91 | - event `controller_front_send_response_before` 92 | 93 | 94 | ## Describe front controller responsibilities 95 | 96 | *Front controller* exists only in Http application (pub/index.php) 97 | 98 | - Same entry point, how to execute different logic? 99 | Via different DI preference depending on detected config area (areaList->getCodeByFrontName) 100 | - *Default* global preference app/etc/di.xml - Magento\Framework\App\FrontController 101 | - "frontend", "adminhtml", "crontab" area code - no preference, use default *App\FrontController* 102 | - "webapi_rest (frontName `/rest`) - preference module-webapi/etc/webapi_rest/di.xml - *\Magento\Webapi\Controller\Rest* 103 | - "webapi_soap" (frontname `/soap`) - preference module-webapi/etc/webapi_soap/di.xml - *\Magento\Webapi\Controller\Soap* 104 | 105 | ### [\Magento\Framework\App\FrontController](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/App/FrontController.php): 106 | 107 | - routerList 108 | - action = router[].match 109 | - result = action.dispatch() or action.execute() 110 | - noroute action fallback 111 | 112 | ### Router match - action can be: 113 | 114 | - generic \Magento\Framework\App\ActionInterface::execute - not used? 115 | - [\Magento\Framework\App\Action\AbstractAction](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/App/Action/AbstractAction.php)::dispatch - context, request, response, result factory, result redirect factory 116 | 117 | ### Dispatch/execute action - result can be: 118 | 119 | - [\Magento\Framework\Controller\ResultInterface](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Controller/ResultInterface.php) - renderResult, setHttpResponseCode, setHeader 120 | 121 | Implementations: 122 | - [Result\Raw](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Controller/Result/Raw.php) -> Result\AbstractResult 123 | - [Result\Json](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Controller/Result/Json.php) -> Result\AbstractResult 124 | - [Result\Forward](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Controller/Result/Forward.php) -> Result\AbstractResult 125 | - [\Magento\Framework\View\Result\Layout](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/View/Result/Layout.php) -> Result\AbstractResult 126 | - [\Magento\Framework\View\Result\Page](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/View/Result/Page.php) -> \Magento\Framework\View\Result\Layout 127 | - [Result\Redirect](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Controller/Result/Redirect.php) -> Result\AbstractResult 128 | 129 | - [\Magento\Framework\App\ResponseInterface](https://github.com/magento/magento2/blob/2.3/lib/internal//Magento/Framework/App/ResponseInterface.php) - sendResponse 130 | 131 | Implementations: 132 | - [Console\Response](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/App/Console/Response.php) 133 | - [\Magento\MediaStorage\Model\File\Storage\FileInterface](https://github.com/magento/magento2/blob/2.3/app/code/Magento/MediaStorage/Model/File/Storage/Response.php) -> \Magento\Framework\App\Response\Http 134 | - [\Magento\Framework\HTTP\PhpEnvironment\Response](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/HTTP/PhpEnvironment/Response.php) -> \Zend\Http\PhpEnvironment\Response 135 | - [\Magento\Framework\Webapi\Response](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Webapi/Response.php) -> \Magento\Framework\HTTP\PhpEnvironment\Response 136 | - [\Magento\Framework\Webapi\Rest\Response](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Webapi/Rest/Response.php) -> \Magento\Framework\Webapi\Response 137 | 138 | ### [\Magento\Webapi\Controller\Rest](https://github.com/magento/magento2/blob/2.3/app/code/Magento/Webapi/Controller/Rest.php) -> \Magento\Framework\App\FrontControllerInterface: 139 | 140 | - preference for FrontController set in [etc/webapi_rest/di.xml](https://github.com/magento/magento2/blob/2.3/app/code/Magento/Webapi/etc/webapi_rest/di.xml#L32) 141 | - process path [/$store]/... - specific store, [/all]/... - admin store (0), /... - default store 142 | - a. process schema request /schema 143 | - b. or process api request (resolve route, invoke route -> service class with params) 144 | 145 | ### [\Magento\Webapi\Controller\Soap](https://github.com/magento/magento2/blob/2.3/app/code/Magento/Webapi/Controller/Soap.php) -> \Magento\Framework\App\FrontControllerInterface: 146 | 147 | - process path (same as REST) 148 | - a. generate WSDL ?wsdl 149 | - b. or generate WSDL service list ?wsdl_list 150 | - c. or handle SOAP (native PHP) 151 | 152 | 153 | ### In which situations will the front controller be involved in execution, and how can it be used in the scope of customizations? 154 | 155 | - Front controller is only used in HTTP application - pub/index.php 156 | - involved in `frontend` and `adminhtml` (HTTP\App\FrontController), `webapi_rest` (Controller\Rest), `webapi_soap` (Controller\Soap) areas 157 | - HTTP customization - register router and place custom code in match() method 158 | -------------------------------------------------------------------------------- /2. Request Flow Processing/2. Demonstrate ability to process URLs in Magento.md: -------------------------------------------------------------------------------- 1 | # Demonstrate ability to process URLs in Magento 2 | ## Describe how Magento processes a given URL. 3 | 4 | urlBuilder - [\Magento\Framework\UrlInterface](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/UrlInterface.php): getUrl 5 | 6 | Instances: 7 | 8 | - [\Magento\Framework\Url](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Url.php) 9 | - [\Magento\Backend\Model\Url](https://github.com/magento/magento2/blob/2.3/app/code/Magento/Backend/Model/Url.php) 10 | 11 | 1. preprocess route params 12 | 13 | \Magento\Framework\Url\*RouteParamsPreprocessorInterface*::execute 14 | - composite - delegates to many instances 15 | - staging - Preview\RouteParamsPreprocessor 16 | * if frontend and preview, adds `?___version` 17 | * if store code in URL, adds `?___store` 18 | 19 | 1. Disable caching if object found in route params 20 | 1. *createUrl* (may be cached in memory) 21 | 22 | - stash all parameters `_fragment`, `_escape`, `_escape_params`, `_query`, `_nosid` 23 | - \Magento\Framework\Url::*getRouteUrl* - without any system params 24 | - then compile ?query and #fragment params 25 | 26 | 1. run modifier 27 | 28 | \Magento\Framework\Url\ModifierInterface::execute - mode entire/base 29 | - composite - delegates to many instances 30 | - staging - Url\BaseUrlModifier - only base mode 31 | In frontend and preview, replaces host part with `$_SERVER[HTTP_HOST]` 32 | 33 | *getRouterUrl*: 34 | 35 | - `_direct` option = baseUrl + `_direct` 36 | - $routeName/$controllerName/$actionName/$param1/$value1/... 37 | 38 | ### How do you identify which module and controller corresponds to a given URL? 39 | 40 | - first part is route name. Search routes.xml for route with matching *ID* 41 | - modules for this ID are sorted with "before" and "after" 42 | - controller/action classes is searched in matched modules 43 | 44 | ### What is necessary to create a custom URL structure? 45 | 46 | - register custom router, e.g. [Magento\Robots\Controller\Router](https://github.com/magento/magento2/blob/2.3/app/code/Magento/Robots/Controller/Router.php) 47 | - create rewrite record for specific URL 48 | 49 | 50 | ## Describe the URL rewrite process and its role in creating user-friendly URLs. 51 | 52 | Router `urlrewrite `: 53 | - `?____from_store` param, redirect to new URL if necessary. 54 | 55 | Example: 56 | - on English store category page /shoes switching to Norwegian store 57 | - _/no/shoes?____from_store=1_ 58 | - find new rewrite for norwegian store 59 | - 302 redirect to _/no/sko_ 60 | 61 | - find rewrite by request path 62 | - redirect if necessary 63 | - return forward action - mark request not dispatched, force continue router loop 64 | 65 | 66 | ### How is getUrl('catalog/product/view/id/1') replaced with rewrite? 67 | 68 | - Product->getProductUrl 69 | - Product\Url->getProductUrl 70 | - Product\Url->getUrl 71 | - UrlFinderInterface->findOneByData 72 | - new Url->getUrl -- `_direct` if rewrite found = baseUrl . requestPath 73 | 74 | Rewrite is not used with regular getUrl, only when module uses explicitly (catalog, CMS). 75 | 76 | ### CMS URL rewrite 77 | 78 | - On event `cms_page_save_after`, if identifier or store changed, deletes and creates new rewrites. 79 | - Doesn't create redirect rewrite for renamed redirects. 80 | - CMS page opens with UrlRewrite router (priority 20), not CMS router (priority 60). 81 | 82 | ### How are user-friendly URLs established, and how are they customized? 83 | 84 | Module UrlRewrite: 85 | - \Magento\UrlRewrite\Model\UrlPersistInterface::deleteByData 86 | - \Magento\UrlRewrite\Model\UrlPersistInterface::replace 87 | 88 | Product: 89 | 90 | - event `catalog_product_save_before` - generate URL key by product name (if url key wasn't provided) 91 | * [ProductUrlKeyAutogeneratorObserver](https://github.com/magento/magento2/blob/2.3/app/code/Magento/CatalogUrlRewrite/Observer/ProductUrlKeyAutogeneratorObserver.php) 92 | * [\Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator::getUrlKey](https://github.com/magento/magento2/blob/2.3/app/code/Magento/CatalogUrlRewrite/Model/ProductUrlPathGenerator.php#L125) 93 | 94 | - event `catalog_product_save_after` - generate and replace URL rewrites (when changed url_key, categories, websites or visibility) 95 | * [ProductProcessUrlRewriteSavingObserver](https://github.com/magento/magento2/blob/2.3/app/code/Magento/CatalogUrlRewrite/Observer/ProductProcessUrlRewriteSavingObserver.php) 96 | * [\Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator::generate](https://github.com/magento/magento2/blob/2.3/app/code/Magento/CatalogUrlRewrite/Model/ProductUrlRewriteGenerator.php#L128) 97 | * deleteByData, replace 98 | 99 | Category: 100 | 101 | - event `catalog_category_save_before` - generate URL key, update child categories 102 | * [CategoryUrlPathAutogeneratorObserver](https://github.com/magento/magento2/blob/2.3/app/code/Magento/CatalogUrlRewrite/Observer/CategoryUrlPathAutogeneratorObserver.php) 103 | * \Magento\CatalogUrlRewrite\Observer\CategoryUrlPathAutogeneratorObserver::updateUrlPathForChildren 104 | * \Magento\CatalogUrlRewrite\Observer\CategoryUrlPathAutogeneratorObserver::updateUrlPathForCategory 105 | * \Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator::getUrlPath 106 | * child category.url_path 107 | 108 | - event `catalog_category_save_after` - when changed (key, anchor, products) 109 | * [CategoryProcessUrlRewriteSavingObserver](https://github.com/magento/magento2/blob/2.3/app/code/Magento/CatalogUrlRewrite/Observer/CategoryProcessUrlRewriteSavingObserver.php#L90) 110 | * [\Magento\CatalogUrlRewrite\Observer\UrlRewriteHandler::generateProductUrlRewrites](https://github.com/magento/magento2/blob/2.3/app/code//Magento/CatalogUrlRewrite/Observer/UrlRewriteHandler.php#L124) 111 | * ... lots of logic 112 | 113 | 114 | ## Describe how action controllers and results function. 115 | 116 | [App\Action\Action::dispatch](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/App/Action/Action.php#L91): 117 | 118 | - event `controller_action_predispatch` 119 | - event `controller_action_predispatch_$routeName`, e.g. `..._checkout` 120 | - event `controller_action_predispatch_$fullActionName`, e.g. `..._checkout_cart_index` 121 | - stop if FLAG_NO_DISPATCH 122 | - *execute* - all action controllers implement this 123 | - stop if FLAG_NO_POST_DISPATCH 124 | - event `controller_action_postdispatch_$fullActionName` 125 | - event `controller_action_postdispatch_$routeName` 126 | - event `controller_action_postdispatch` 127 | - if action doesn't return result, response object is returned -- action can just modify response object 128 | 129 | 130 | ### How do controllers interact with another? 131 | 132 | - Controller\Response\Forward - changes request params, marks request not dispatched, so front controller 133 | will match again and new controller will be executed 134 | - Controller\Response\Redirect - another controller URL 135 | 136 | ### How are different response types generated? 137 | [\Magento\Framework\Controller\ResultInterface](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Controller/ResultInterface.php): 138 | - renderResult 139 | - setHttpResponseCode 140 | - setHeader 141 | 142 | [Controller\AbstractResult](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Controller/AbstractResult.php): 143 | 144 | - *renderResult* - required by interface - applies headers and calls *render*. children must implement this 145 | - setHttpResponseCode 146 | - setHeader 147 | - setStatusHeader 148 | 149 | [Controller\Result\Raw](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Controller/Result/Raw.php): 150 | 151 | - setContents 152 | - *render* - set response body 153 | 154 | [Controller\Result\Json](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Controller/Result/Json.php): 155 | 156 | - setData - array 157 | - setJsonData - string 158 | - *render* - processes inline translations, sets application/json header and response body json string 159 | 160 | [Controller\Result\Forward](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Controller/Result/Forward.php): 161 | 162 | - setModule, setController, setParams 163 | - *forward* - does the trick, modifies request object, marks request not dispatched 164 | - *render* - does nothing, forward must be called manually 165 | 166 | [Controller\Result\Redirect](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Controller/Result/Redirect.php): 167 | 168 | - setUrl, setPath - custom address 169 | - setRefererUrl, setRefererOrBaseUrl - go back function 170 | 171 | [View\Result\Layout](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/View/Result/Layout.php): - renders layout without `default` handle and page layout (1-column etc.) 172 | 173 | - *renderResult* 174 | * event `layout_render_before` 175 | * event `layout_render_before_$fullActionName`, e.g. `..._checkout_cart_index` 176 | * render 177 | 178 | - *render* - layout->getOutput, translate inline, set response body 179 | - addDefaultHandle = $fullActionName, e.g. `checkout_cart_index` 180 | - addHandle, addUpdate 181 | 182 | [View\Result\Page](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/View/Result/Page.php): - wraps layout into page layout 183 | - same events as above 184 | - *render* - renders layout, assigns vars and renders outer page template 185 | - assign - values into viewVars property. default 186 | Default: requireJs, headContent, headAdditional, htmlAttributes, headAttributes, bodyAttributes, loaderIcon, layoutContent 187 | - addDefaultHandle = $fullActionName + `default` 188 | -------------------------------------------------------------------------------- /2. Request Flow Processing/3. Demonstrate ability to customize request routing.md: -------------------------------------------------------------------------------- 1 | # 2.3 Demonstrate ability to customize request routing 2 | 3 | ## Describe request routing and flow in Magento. 4 | 5 | [Frontend routers](https://devdocs.magento.com/guides/v2.2/extension-dev-guide/routing.html): 6 | 7 | - robots (10) 8 | - urlrewrite (20) 9 | - standard (30) - module/controller/action 10 | * get modules by front name registered in routes.xml 11 | * find action class 12 | * if action not found in all modules, searches "noroute" action in *last* module 13 | 14 | - cms (60) 15 | - default (100) - noRouteActionList - 'default' noroute handler 16 | 17 | Adminhtml routers: 18 | 19 | - admin (10) - extends base router - module/controller/action, controller path prefix "adminhtml" 20 | - default (100) - noRouteActionList - 'backend' noroute handler 21 | 22 | Default router (frontend and backend): 23 | 24 | - noRouteHandlerList. process 25 | + backend (10) 26 | *Default admin 404 page* "adminhtml/noroute/index" when requested path starts with admin route. 27 | 28 | + default (100) 29 | *Default frontend 404 page* "cms/noroute/index" - admin config option `web/default/no_route`. 30 | 31 | - always returns forward action - just mark request not dispatched - this will continue match loop 32 | 33 | 34 | ### When is it necessary to create a new router or to customize existing routers? 35 | 36 | Create new router when URL structure doesn't fit into module/controller/action template, 37 | e.g. fixed robots.txt or dynamic arbitraty rewrites from DB. 38 | 39 | If you want to replace controller action, you can register custom module in controller lookup sequence - 40 | reference route by ID and add own module "before" original module. 41 | 42 | ### How do you handle custom 404 pages? 43 | 44 | 1. If [front controller](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/App/FrontController.php#L61-L65) catches [\Magento\Framework\Exception\NotFoundException](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Exception/NotFoundException.php), it changes action name *"noroute"* and continues loop. 45 | E.g. catalog/product/view/id/1 throws NotFoundException. catalog/product/noroute is checked. 46 | 47 | 1. If standard router recognizes front name but can't find controller, it tries to find *"noroute"* 48 | action from last checked module. 49 | E.g. catalog/brand/info controller doesn't exist, so catalog/brand/noroute will be checked. 50 | 51 | [\Magento\Framework\App\Router\Base::getNotFoundAction](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/App/Router/Base.php#L237) 52 | 53 | 1. If all routers didn't match, default controller provides two opportunities: 54 | - set default 404 route in admin config `web/default/no_route` (see: [\Magento\Framework\App\Router\NoRouteHandler::process](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/App/Router/NoRouteHandler.php#L34)) 55 | - register custom handler in [noRouteHandlerList](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/App/Router/NoRouteHandlerList.php): 56 | * backend (sortOrder: 10) [Magento\Backend\App\Router\NoRouteHandler](https://github.com/magento/magento2/blob/2.3/app/code/Magento/Backend/App/Router/NoRouteHandler.php#L44) -> `adminhtml/noroute/index` 57 | * default (sortOrder: 100) [Magento\Framework\App\Router\NoRouteHandler](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/App/Router/NoRouteHandler.php) 58 | -------------------------------------------------------------------------------- /3. Customizing the Magento UI/1. Demonstrate ability to utilize themes and the template structure.md: -------------------------------------------------------------------------------- 1 | # Demonstrate ability to utilize themes and the template structure 2 | 3 | app/design/frontend/Vendor/themename/ 4 | ``` 5 | - composer.json 6 | - registration.php 7 | - theme.xml - name, parent, logo 8 | - etc/view.xml - currently catalog images configuration 9 | - i18n/ 10 | - media/ - pub logo here 11 | - Magento_Checkout/ - extend specific module 12 | ---- layout/ - normally layouts are extended 13 | ------- override/base/ - special case: completely replace base layout for module 14 | ------- override/theme/Magento/luma/ - special case: completely replace specific theme layout 15 | ---- templates/ - replace module files 16 | ---- web/ - replace module files 17 | - web/ - replace theme files 18 | ---- js/ 19 | ---- css/ 20 | ------- source/ 21 | ---- fonts/ 22 | ---- images/ 23 | ---- i18n/en_US/ - locale-specific file replacement (without module) 24 | ---- i18n/en_US/Magento_Checkout/ - locale-specific module file replacement 25 | ``` 26 | 27 | - Magento\Theme\Model\View\Design implements View\DesignInterface 28 | - View\Model\Layout\Merge implements View\Layout\ProcessorInterface 29 | 30 | ## Layouts: 31 | 32 | Normally layouts are merged, extending parent ones. 33 | 34 | To completely replace *original module layout* file, e.g. to replace 35 | vendor/magento/module-checkout/view/frontend/layout/default.xml place new file in 36 | Magento_Checkout/layout/override/base/default.xml 37 | 38 | To completely replace some *parent theme's layout* customization, e.g. to replace 39 | vendor/magento/theme-frontend-luma/Magento_Checkout/layout/default.xml place new file in 40 | Magento_Checkout/layout/override/Magento/luma/default.xml 41 | 42 | It is *not possible* to use override trick inside *module*, only in theme. You cannot create 43 | new module app/code/MyVendor/MyModule/view/frontend/layout/override/... and replace layout files. 44 | 45 | ## Locale: 46 | 47 | You can replace static files with locale-specific - JS, logo, font etc. You have 2 places: 48 | - non-module specific, e.g. theme asset - web/i18n/en_US/ 49 | - from some module - web/i18n/en_US/Magento_Checkout/ 50 | 51 | ### how is theme loaded 52 | - plugin magento-store/etc/di.xml 53 | - App\Action\AbstractAction.beforeDispatch 54 | - View\DesignLoader::load 55 | - App\Area::load('design') 56 | - App\Area::_initDesign 57 | * singleton View\DesignInterface = \Magento\Theme\Model\View\Design 58 | * design.setArea('frontend') 59 | * design.getConfigurationDesignTheme 60 | * design.setDefaultDesignTheme 61 | + get config `design/theme/theme_id` (website scope if single store mode, otherwise store view scope) 62 | + EE only: if theme is not configured, use default from DI argument Magento/luma 63 | * design.setDesignTheme - area, theme model. loads theme model from DB 64 | + View\Design\Theme\FlyweightFactory::create 65 | + View\Design\Theme\ThemeProviderInterface::getThemeByFullPath 66 | Has 2 implementations, real DB and fake data collection - Can be used to deploy static or 67 | in other setup commands, even if Magento is not installed yet. 68 | 69 | 70 | singleton *View\DesignInterface* = \Magento\Theme\Model\View\Design - holds current area, locale and theme model 71 | - get/setDesignTheme = theme 72 | - get/setArea 73 | - getLocale 74 | - getConfigurationDesignTheme 75 | - setDefaultDesignTheme 76 | - getThemePath 77 | - getDesignParams - area, theme, locale 78 | 79 | *View\Design\ThemeInterface* = \Magento\Theme\Model\Theme - this is also DB model 80 | - getArea 81 | - getCode 82 | - getParentTheme 83 | - getInheritedThemes 84 | - getId 85 | - getThemePath 86 | - getFullPath 87 | - isPhysical 88 | 89 | *Theme type*: physical, virtual, staging 90 | 91 | _Physical themes_ 92 | 93 | Physical refers to the fact that those themes are defined by files. 94 | For example, the blank and luma theme are physically defined under app/design/frontend/ 95 | 96 | _Virtual themes_ 97 | 98 | This is yet unclear but I think virtual themes refer to themes you can create in the 99 | backend which extends existing physical themes but it seems like it's not fully implemented yet. 100 | You can see that there's two tabs available in the edit section only for virtual themes which let 101 | you provides custom CSS and JS for a virtual theme. 102 | I reckon a virtual theme would be something you setup temporarily (like Christmas theme) for a short 103 | period of theme and which requires only few design changes compared to the physical theme it extends. 104 | 105 | _Staging_ 106 | 107 | Must be something about EE campaign. 108 | 109 | 110 | ### how loadLayoutUpdates works 111 | 112 | - Loads ALL layout update files using file name as layout handle name - as XML object. 113 | - Selects only currently relevant handles (e.g. 'default', 'checkout_cart_index'). 114 | - Finds instructions, merges them recursively. 115 | - Selected file contents as added to `_updates[]` array as pieces of string - they will 116 | be converted to XML object later in generateLayoutXml. 117 | 118 | Trace: 119 | 120 | View\Layout\Builder::loadLayoutUpdates 121 | * event `layout_load_before` 122 | * View\Model\Layout\Merge::load 123 | + Collects loaded layout files contents into `updates[]` array. 124 | + Loads from cache when possible, XML contents (e.g. '') 125 | and separately pageLayout (e.g. '1column') 126 | 127 | merge.load(handle) 128 | - merge._merge(handle) 129 | 130 | * Called for every page handle, e.g. 'default', 'checkout_cart_index'. 131 | * Checks cyclic dependency - logs error in dev mode 132 | * _fetchPackageLayoutUpdates 133 | * _fetchDbLayoutUpdates 134 | 135 | - merge._validateMergedLayout 136 | * View\Model\Layout\Update\Validator::isValid with `urn:magento:framework:View/Layout/etc/layout_merged.xsd` 137 | 138 | 139 | *_fetchPackageLayoutUpdates* 140 | * merge.getFileLayoutUpdatesXml - loads ALL layouts regardless of handle 141 | * merge._loadFileLayoutUpdatesXml 142 | - get physical theme, checking parents until found 143 | - update files = theme file source.getFiles + page layout file source.getFiles 144 | - read files one by one 145 | - merge._substitutePlaceholders - replaces in raw XML string - {{baseUrl}} and {{baseSecureUrl}} 146 | - adds file contents to result as one of: 147 | 148 | ```xml 149 | {$fileContents} 150 | {$fileContents} 151 | ``` 152 | 153 | - all are wrapped in outer tag 154 | 155 | ```xml 156 | 157 | {$fileContents} 158 | {$fileContents} 159 | {$fileContents} 160 | ... 161 | 162 | ``` 163 | 164 | * finds all and with ID *matching handle* that is being loaded 165 | * merge._fetchRecursiveUpdates - finds xpath , calls _merge recursively 166 | * merge.validateUpdate 167 | plugin \Magento\PageCache\Model\Layout\MergePlugin::beforeValidateUpdate checks that entity-specific 168 | update handles don't have `ttl` declarations 169 | * merge.addUpdate -- main goal, adds content to `updates[]` 170 | 171 | *_fetchDbLayoutUpdates:* 172 | * does nothing itself, but there's a plugin 173 | \Magento\Widget\Model\ResourceModel\Layout\Plugin::aroundGetDbUpdateString - loads updates from DB by theme/store 174 | * merge._fetchRecursiveUpdates - finds xpath , calls _merge recursively 175 | * merge.validateUpdate 176 | * merge.addUpdate 177 | 178 | 179 | 180 | ### Layout file source 181 | 182 | merge.*fileSource*: 183 | View\Layout\File\Collector\Aggregated\Proxy - Source of layout files aggregated from a theme 184 | and its parents according to merging and overriding conventions 185 | 186 | - add *base files* - modules 187 | 188 | * View\File\Collector\Decorator\ModuleDependency as layoutFileSourceBaseSorted 189 | * View\File\Collector\Decorator\ModuleOutput as layoutFileSourceBaseFiltered 190 | * *View\File\Collector\Base* as layoutFileSourceBaseFiltered, subDir = 'layout' 191 | * Component\DirSearch::collect(type='module', pattern) - uses component registrar to get modules, then searches by pattern 192 | 193 | ``` 194 | /view/base/layout/*.xml --- isBase = true, shared between adminhtml and frontend themes 195 | /*_*/layout/*.xml -- sets module context, e.g. Magento_Checkout 209 | ``` 210 | 211 | - replace override base files 212 | 213 | * View\File\Collector\Decorator\ModuleDependency as layoutFileSourceOverrideBaseSorted 214 | * View\File\Collector\Decorator\ModuleOutput as layoutFileSourceOverrideBaseFiltered 215 | * *View\File\Collector\Override\Base* as layoutFileSourceOverrideBase, subDir = 'layout/override/base' 216 | 217 | ``` 218 | /*_*/layout/override/base/*.xml 219 | ``` 220 | 221 | * View\File\FileList::replace 222 | * View\File\FileList\Collator::collate - replaces by matching keys: is base, theme, module, filename 223 | 224 | - replace override theme files 225 | 226 | * View\File\Collector\Decorator\ModuleDependency as layoutFileSourceOverrideThemeSorted 227 | * View\File\Collector\Decorator\ModuleOutput as layoutFileSourceOverrideThemeFiltered 228 | * *View\File\Collector\Override\ThemeModular* as layoutFileSourceOverrideTheme, subDir = 'layout/override/theme' 229 | 230 | ``` 231 | *_*/layout/override/theme/*/*/*.xml 232 | ``` 233 | 234 | 235 | ### Page layout file source 236 | 237 | All same as layout, but subDir 'page_layout': 238 | 239 | ``` 240 | /view/base/page_layout/*.xml --- isBase = true, shared between adminhtml and frontend themes 241 | /*_*/page_layout/*.xml -- sets module context, e.g. Magento_Checkout 243 | /*_*/page_layout/override/base/*.xml 244 | *_*/page_layout/override/theme/*/*/*.xml 245 | ``` 246 | 247 | View\Layout\ProcessorInterface = View\Model\Layout\Merge 248 | - $updates - array of all found string XML file contents E.g. 249 | ``` 250 | updates[] = ' Design > Implementation > [Edit] > Design Rule > User Agent Rules_ 265 | Full page cache and design exception: 266 | ``` 267 | plugin magento-store/etc/di.xml: 268 | Magento\Framework\App\Action\AbstractAction.beforeDispatch: 269 | \Magento\Framework\View\DesignLoader::load 270 | \Magento\Framework\App\Area::load('design') 271 | \Magento\Framework\App\Area::_initDesign 272 | \Magento\Framework\App\Area::detectDesign 273 | \Magento\Framework\App\Area::_applyUserAgentDesignException 274 | \Magento\Framework\View\DesignExceptions::getThemeByRequest 275 | 276 | plugin \Magento\PageCache\Model\App\CacheIdentifierPlugin::afterGetValue - checks rule design exception 277 | View\DesignExceptions::getThemeByRequest 278 | ``` 279 | 280 | - etc/view.xml file *doesn't merge* with parent themes, it either inherits completely from parent 281 | or your theme has new version of it 282 | 283 | 284 | ### How do you define theme hierarchy for your project? 285 | 286 | theme.xml - parent 287 | 288 | Determine theme hierarchy of existing project: 289 | 290 | - Go to _Content > Design > Configuration_ 291 | - Check "Theme Name" column on row with store view under question 292 | - find paths to all available themes in app/etc/design/frontend/* or vendor/*/theme-* (conventionally) 293 | or find programmatically: 294 | ```php 295 | (new \Magento\Framework\Component\ComponentRegistrar())->getPaths('theme'); 296 | ``` 297 | - open theme path/theme.xml, check `` node and so on 298 | 299 | ## Demonstrate the ability to customize/debug templates using the template fallback process. 300 | 301 | 302 | 303 | ### How do you identify which exact theme file is used in different situations? 304 | 305 | 306 | ### How can you override native files? 307 | 308 | - theme layout override module file: /layout/override/base/*.xml 309 | - theme layout override one of parent theme layouts: /layout/override/Magento/luma/*.xml 310 | - static asset for theme, e.g. /web/js/theme.js 311 | - static asset for module, e.g. /Magento_Checkout/web/js/view/minicart.js 312 | - static asset for locale: 313 | ``` 314 | /web/i18n/en_US/Magento_Checkout/ 315 | /web/i18n/en_US/ 316 | ``` 317 | 318 | -------------------------------------------------------------------------------- /3. Customizing the Magento UI/2. Determine how to use blocks.md: -------------------------------------------------------------------------------- 1 | ## Demonstrate an understanding of block architecture and its use in development. 2 | 3 | ### [View\Element\AbstractBlock](https://github.com/magento/magento2/blob/2.2-develop/lib/internal/Magento/Framework/View/Element/AbstractBlock.php): 4 | - data automatically assigns to _data, can access data arguments later, e.g. in _construct 5 | - `jsLayout` data argument 6 | - toHtml: 7 | * event `view_block_abstract_to_html_before` 8 | * disable module output still works 9 | * if not in cache: 10 | + _beforeToHtml 11 | + _toHtml 12 | + save cache 13 | * _afterToHtml - always, even when cached 14 | * event `view_block_abstract_to_html_after` 15 | 16 | ### [View\Element\Template](https://github.com/magento/magento2/blob/2.2-develop/lib/internal/Magento/Framework/View/Element/Template.php): 17 | - `template` data argument 18 | - `_viewVars` property, assign() 19 | - `_toHtml` renders template when defined 20 | * getTemplateFile = [View\Element\Template\File\Resolver](https://github.com/magento/magento2/blob/2.2-develop/lib/internal/Magento/Framework/View/Element/Template/File/Resolver.php)::getTemplateFileName 21 | * fetchView 22 | + [View\Element\Template\File\Validator](https://github.com/magento/magento2/blob/2.2-develop/lib/internal/Magento/Framework/View/Element/Template/File/Validator.php)::isValid - checks allowed directory (view_preprocessed/module/theme) or symlink 23 | + if bad file, log and throw error in DEV mode 24 | + View\TemplateEnginePool::get by file extension 25 | - phtml - [View\TemplateEngine\Php](https://github.com/magento/magento2/blob/2.2-develop/lib/internal/Magento/Framework/View/TemplateEngine/Php.php) - extract vars, include file 26 | - xhtml - [View\TemplateEngine\Xhtml](https://github.com/magento/magento2/blob/2.2-develop/lib/internal/Magento/Framework/View/TemplateEngine/Xhtml.php) 27 | - \Magento\Developer\Model\TemplateEngine\Decorator\DebugHints 28 | + engine.render(block, template, viewVars) 29 | - default `getCacheKeyInfo` - store code, template, base URL. By default *store aware* 30 | - helper methods: 31 | * getMediaDirectory 32 | * getRootDirectory 33 | * getObjectData 34 | * getBaseUrl 35 | 36 | admin configuration `dev/template/allow_symlink`, default false 37 | 38 | #### Which objects are accessible from the block? 39 | 40 | - $this = \Magento\Framework\View\TemplateEngine\Php 41 | - $this->something() is proxied to $block->something() via magic `__call` function - only public available! 42 | - isset($this->something) -> isset($block->something) 43 | - $this->property = $block->property 44 | - $this->helper() gets singleton of AbstractHelper 45 | 46 | #### What is the typical block’s role? 47 | 48 | As much business logic as possible should be moved out of template, and blocks provide access to processed 49 | data for templates. Actual data crunching can (and should) be proxied further to models. 50 | 51 | ## Identify the stages in the lifecycle of a block. 52 | 53 | - block is generated 54 | * builder.generateLayoutBlocks 55 | * layout.generateElements 56 | * readerPool.interpret - View\Layout\Reader\Block::interpret - resolves params, schedules structure element 57 | + `` 58 | + `` 59 | + attributes: class, group, template, ttl, display, acl 60 | + visibilityConditions: attribute `ifconfig`, attribuet `aclResource`, child `` 61 | * generatorPool.process - View\Layout\Generator\Block::process - actually creates block 62 | + generate all blocks 63 | `__construct`, `_construct` 64 | + set layout to all blocks, event after each 65 | `block.setLayout`, `block._prepareLayout` 66 | event `core_layout_block_create_after` 67 | + call all actions 68 | 69 | generator.generateBlock: 70 | - generator.createBlock -> `__construct()` -> `_construct` -> check argument 'template' -> setTemplate 71 | * generator.getBlockInstance 72 | * block.setType(classname) 73 | * block.setNameInLayout 74 | * block.addData(arguments.data) 75 | - block.setTemplate (when available) 76 | - block.setTtl (when available) 77 | 78 | ### Arguments 79 | 80 | Block arguments are treated like data-arguments. Arguments values set in a layout file 81 | can be accessed in templates using the get{ArgumentName}() and has{ArgumentName}() methods. 82 | 83 | Arguments values set in a layout file can be accessed in templates using the get{ArgumentName}() 84 | and has{ArgumentName}() methods. The latter returns a boolean defining whether there's any value set. 85 | {ArgumentName} is obtained from the name attribute the following way: for getting the value of 86 | `` the method name is getSomeString(). 87 | 88 | [Magento docs - Layout instructions](http://devdocs.magento.com/guides/v2.0/frontend-dev-guide/layouts/xml-instructions.html#argument) 89 | 90 | ### Block viewModel concept 91 | 92 | Magento 2.2 suggests moving block business logic to separate viewModel classes. 93 | 94 | Avoid extending this template class, because with inheriting the template block is the constructor is very large. 95 | 96 | If you need custom presentation logic in your blocks, use this class as block, and declare 97 | custom view models in block arguments in layout handle file. 98 | 99 | View model parameter can have any name. 100 | Also, one block can have multiple view models injected via layout. 101 | 102 | Example: 103 | 104 | ```xml 105 | 106 | 107 | My\Module\ViewModel\Custom 108 | 109 | 110 | ``` 111 | 112 | In template, access your model instead of the block: 113 | 114 | ```php 115 | $viewModel = $block->getData('viewModel'); 116 | // or 117 | $viewModel = $block->getViewModel(); 118 | ``` 119 | 120 | [ViewModels in Magento 2](https://www.yireo.com/blog/1856-viewmodels-in-magento-2) 121 | 122 | 123 | ### In what cases would you put your code in the _prepareLayout(), _beforeToHtml(), and _toHtml() methods? 124 | 125 | `_prepareLayout` - most commonly extended: 126 | 127 | At this stage all blocks in layout are generated, but _prepareLayout is only running, 128 | so some blocks might still be not initialized completely. 129 | 130 | - set page config title 131 | 132 | ```php 133 | $this->pageConfig->getTitle()->set(__('Address Book')); 134 | ``` 135 | 136 | - edit head block, e.g. add RSS 137 | - add breadcrumbs 138 | - create new block, set as child - e.g. grid 139 | 140 | `_beforeToHtml`: 141 | - can't change page title? 142 | - some blocks are rendered, can't change them 143 | - assign additional template values 144 | - delay computation to the latest point until render. If block is not rendered we save computation 145 | 146 | `_toHtml`: 147 | - block without template, put custom rendering here, e.g. calling external API 148 | - suppress template output if should not render by condition - return empty string 149 | 150 | 151 | #### How would you use events fired in the abstract block? 152 | 153 | `view_block_abstract_to_html_before`: 154 | - edit cache params - lifetime, ttl, tags 155 | - add column to grid 156 | - edit template params - e.g. set disable edit flag 157 | 158 | `view_block_abstract_to_html_after`: 159 | - edit html - replace, add content, add wrappers 160 | 161 | 162 | ## Describe how blocks are rendered and cached. 163 | 164 | toHtml, load cache, [when not in cache: _beforeToHtml, _toHtml], _afterToHtml, save cache 165 | 166 | - data attribute `cache_lifetime` must be set 167 | - cache key one of: 168 | * data attribute `cache_key` = BLOCK_`$cache_key` 169 | * default getCacheKeyInfo: [name in layout] 170 | * or BLOCK_`getCacheKeyInfo()` - by default name in layout. By default *same for all stores*! 171 | - cache tags for automatic invalidation: 172 | * data attribute `cache_tags` 173 | * + implicit tag 'block_html' 174 | * if instanceof DataObject\IdentityInterface, `getIdentities` 175 | 176 | ## Identify the uses of different types of blocks. 177 | 178 | - View\Element\AbstractBlock - custom logic 179 | - View\Element\Template - any template 180 | - View\Element\Text - set text programmatically 181 | - etc. 182 | 183 | ### When would you use non-template block types? 184 | 185 | Something like CMS page - admin controls block content and layout. Another example - dynamic robots.txt 186 | whose content is stored in DB. 187 | 188 | ### In what situation should you use a template block or other block types? 189 | 190 | Use template block whenever possible to allow for theme markup customization. 191 | -------------------------------------------------------------------------------- /4. Working with Databases in Magento/1. Demonstrate ability to use data-related classes.md: -------------------------------------------------------------------------------- 1 | # Demonstrate ability to use data-related classes 2 | 3 | ## Describe repositories and data API classes. 4 | 5 | [Magento - Searching with Repositories](http://devdocs.magento.com/guides/v2.2/extension-dev-guide/searching-with-repositories.html) 6 | 7 | Magento 2.2 changed repository getList approach. Before you applied filters, sorts and pagination manually 8 | right in the `getList` method itself. 9 | 10 | Magento 2.2 takes this boilerplate routine off developer's shoulders. Just call `collectionProcessor.process()`. 11 | 12 | [Api\SearchCriteria\CollectionProcessorInterface](https://github.com/magento/magento2/blob/2.2-develop/lib/internal/Magento/Framework/Api/SearchCriteria/CollectionProcessorInterface.php) 13 | [Api\SearchCriteria\CollectionProcessor](https://github.com/magento/magento2/blob/2.2-develop/lib/internal/Magento/Framework/Api/SearchCriteria/CollectionProcessor.php) 14 | - this is a composite and default preference 15 | 16 | Default collection processors - always applied: 17 | - [FilterProcessor](https://github.com/magento/magento2/blob/2.2-develop/lib/internal/Magento/Framework/Api/SearchCriteria/CollectionProcessor/FilterProcessor.php#L45): 18 | * DI configurable *custom filters* by field name. E.g. store_id 19 | * *field mapping* 20 | * addFilterGroupToCollection when no custom filter defined - like before 21 | - [SortingProcessor](https://github.com/magento/magento2/blob/2.2-develop/lib/internal/Magento/Framework/Api/SearchCriteria/CollectionProcessor/SortingProcessor.php#L45): 22 | * DI configurable *default orders* 23 | * *field mapping* 24 | - [PaginationProcessor](https://github.com/magento/magento2/blob/2.2-develop/lib/internal/Magento/Framework/Api/SearchCriteria/CollectionProcessor/PaginationProcessor.php): 25 | * setCurPage, setPageSize 26 | 27 | Additional processors - add them via DI for your EAV repository: 28 | - EAV [FilterProcessor](https://github.com/magento/magento2/blob/2.2-develop/app/code/Magento/Eav/Model/Api/SearchCriteria/CollectionProcessor/FilterProcessor.php#L45) 29 | * same as normal filter processor, slightly different condition apply 30 | * *field mapping* 31 | * *custom filters* 32 | - [JoinProcessor](https://github.com/magento/magento2/blob/2.2-develop/lib/internal/Magento/Framework/Api/SearchCriteria/CollectionProcessor/JoinProcessor.php) - used only in tax rule repository 33 | 34 | For EAV, inject virtual collection processor `Magento\Eav\Model\Api\SearchCriteria\CollectionProcessor` 35 | 36 | ## How do you obtain an object or set of objects from the database using a repository? 37 | 38 | *Loading single object* 39 | 40 | While model->load() is deprecated, your repository is the place to work with resource model. 41 | - create empty model object with factory 42 | - call `resourceModel.load($object)` just like `model->load()` would. 43 | 44 | Example: 45 | ```xml 46 | public function getById($blockId) 47 | { 48 | $block = $this->blockFactory->create(); 49 | $this->resource->load($block, $blockId); 50 | if (!$block->getId()) { 51 | throw new NoSuchEntityException(__('CMS Block with id "%1" does not exist.', $blockId)); 52 | } 53 | return $block; 54 | } 55 | ``` 56 | 57 | 58 | ## How do you configure and create a SearchCriteria instance using the builder? 59 | 60 | - Api\SearchCriteriaBuilder 61 | - Api\SearchCriteria 62 | 63 | To build a searchCriteria object, use $searchCriteriaBuilder. You don't need a factory, this type is declared as `shared="false"`. Once `create()` method is caled, its data empties. 64 | 65 | 66 | ## How do you use Data/Api classes? 67 | 68 | Api/: 69 | - repository interfaces 70 | - put operational interfaces - helpers etc. business logic API 71 | - implementation lies in models 72 | - usually available via WebAPI 73 | 74 | Api/Data - entity container interfaces, usually extend AbstractSimpleObject 75 | 76 | In your repository, you should return data interfaces. Some models directly implement data interfaces (catalog product), while others are completely separate - there's customer model and customer data object. 77 | 78 | Example of converting collection models to data objects in customer repository: 79 | ```xml 80 | $customers = []; 81 | /** @var \Magento\Customer\Model\Customer $customerModel */ 82 | foreach ($collection as $customerModel) { 83 | $customers[] = $customerModel->getDataModel(); 84 | } 85 | $searchResults->setItems($customers); 86 | ``` 87 | 88 | `getDataModel` is not a standard method, you implement it yourself. 89 | 90 | ```xml 91 | public function getDataModel() 92 | { 93 | // ... 94 | $customerDataObject = $this->customerDataFactory->create(); 95 | $this->dataObjectHelper->populateWithArray( 96 | $customerDataObject, 97 | $customerData, // $customer->getData() 98 | \Magento\Customer\Api\Data\CustomerInterface::class 99 | ); 100 | // ... 101 | return $customerDataObject; 102 | } 103 | ``` 104 | 105 | ## populateWithArray helper 106 | 107 | Based on interface, calls all SETTERS with given data to fill data object 108 | 109 | [Api\DataObjectHelper::populateWithArray](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Api/DataObjectHelper.php#L80) 110 | - calls data object SETTERS `set*`, `setIs*` with given raw data 111 | - handles `custom_attributes` - data object.setCustomAttribute 112 | 113 | 114 | Example: [Magento\Customer\Controller\Account\Edit::execute](https://github.com/magento/magento2/blob/2.3/app/code/Magento/Customer/Controller/Account/Edit.php#L78) 115 | 116 | ## buildOutputDataArray 117 | 118 | Based on given interface, calls all GETTERS to make resulting data array. 119 | 120 | [Reflection\DataObjectProcessor::buildOutputDataArray](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Reflection/DataObjectProcessor.php#L81): 121 | - \Magento\Framework\Reflection\MethodsMap::getMethodsMap - method name and getter return types 122 | - filter only getters: is..., has..., get... 123 | - get data using interface getter, e.g. $object->getSku() - based on Interface definition 124 | - deduce field name by getter name, e.g. 'sku' 125 | - process custom_attributes \Magento\Framework\Reflection\CustomAttributesProcessor::buildOutputDataArray 126 | - process extension_attributes \Magento\Framework\Reflection\ExtensionAttributesProcessor::buildOutputDataArray 127 | - process return object: build return value objects with their return type annotation 128 | - process return array: cast each element to type, e.g. int[] => (int) each value 129 | - cast element to type 130 | 131 | Example: [Magento\Customer\Model\Address::updateData](https://github.com/magento/magento2/blob/2.3/app/code/Magento/Customer/Model/Address.php#L145) 132 | -------------------------------------------------------------------------------- /5. Using the Entity-Attribute-Value -EAV- Model/2. Demonstrate ability to use EAV entity load and save.md: -------------------------------------------------------------------------------- 1 | # Demonstrate ability to use EAV entity load and save 2 | 3 | Types of resource models: 4 | - flat resource - `Magento\Framework\Model\ResourceModel\AbstractResource` 5 | - EAV entity resource - `Magento\Eav\Model\Entity\AbstractEntity` 6 | - version controll aware EAV entity resource - `Magento\Eav\Model\Entity\VersionControl\AbstractEntity`. 7 | Used only by customer and customer address. 8 | 9 | New Entity Manager stands aside, you don't need to extend your resource model from it, 10 | but rather manually call its methods in your resource - load, save, delete. 11 | \Magento\Framework\EntityManager\EntityManager is suitable for both flat and EAV tables. 12 | 13 | ## Entity Manager 14 | 15 | Entity manager is a new way of saving both flat tables and EAV entities. 16 | Separates individual operations into own class - read, create, update, delete. 17 | This give a lot of freedom for extensions. 18 | 19 | Each operation still has a lot to do - save EAV data and run extensions. Default operations 20 | split these activities into smaller actions: 21 | - save main table - both for flat and EAV 22 | - save EAV attributes - only for EAV entities. see *attribute pool* 23 | - run extensions - see *extension pool* 24 | 25 | Entity manager is concept separate from regular flat and EAV resource models saving, it doesn't 26 | call them under the hood. It does the same things but in own fashion with extensibility in mind. 27 | 28 | This means that you can still build your code on regular resource models: 29 | - flat resource Magento\Framework\Model\ResourceModel\Db\AbstractDb 30 | - EAV entity resource Magento\Eav\Model\Entity\AbstractEntity 31 | 32 | Entity manager is currently used by: 33 | - bundle option 34 | - bundle selection 35 | - catalog rule 36 | - product 37 | - category 38 | - cms block 39 | - cms page 40 | - sales rule 41 | - gift card amount 42 | - and some others (staging etc.) 43 | 44 | Terms: 45 | - *entity manager* - calls appropiate operations. Holds type resolver and operations pool 46 | - *metadata pool* - DI injectable. Register Api Data interface and [`entityTableName`, `identifierField`] 47 | ```xml 48 | 49 | cms_page 50 | page_id 51 | 52 | 53 | 54 | 55 | 56 | ``` 57 | 58 | - *operation pool* - `checkIfExists`/`read`/`create`/`update`/`delete` operations per entity type. Operaton does ALL work. 59 | *Default operations - can extend in DI:* 60 | - checkIfExists - Magento\Framework\EntityManager\Operation\CheckIfExists - decides if entity is new or updated 61 | - read - Magento\Framework\EntityManager\Operation\Read 62 | - create - Magento\Framework\EntityManager\Operation\Create 63 | - update - Magento\Framework\EntityManager\Operation\Update 64 | - delete - Magento\Framework\EntityManager\Operation\Delete 65 | 66 | - *attribute pool* - read/create/update attributes in separate tables per entity type. DI injectable, has default, register only to override. 67 | ```xml 68 | 69 | Magento\Catalog\Model\ResourceModel\CreateHandler 70 | Magento\Catalog\Model\ResourceModel\UpdateHandler 71 | 72 | ``` 73 | *Default attribute pool actions - Module_Eav/etc/di.xml:* 74 | - read - Magento\Eav\Model\ResourceModel\ReadHandler 75 | - create - Magento\Eav\Model\ResourceModel\CreateHandler 76 | - update - Magento\Eav\Model\ResourceModel\UpdateHandler 77 | 78 | - *extension pool* - custom modifications on entity read/create/update. Put your extensions here, good for extension attributes 79 | + product gallery read/create 80 | + product option read/save 81 | + product website read/save 82 | + configurable product links read/save 83 | + category link read/save 84 | + bundle product read/save - set options 85 | + downloadable link create/delete/read/update 86 | + gift card amount read/save 87 | + cms block, cms page - store read/save 88 | 89 | 90 | ### *Entity manager.save:* 91 | - resolve entity type - implementing data interface `\Api\Data` when possible, or original class 92 | - check `has(entity)` - detect create new or update 93 | * operation pool.getOperation(`checkIfExists`) 94 | - if new, operation pool.getOperation(`create`).execute 95 | - if existing, operation pool.getOperation(`update`).execute 96 | - run callbacks 97 | 98 | ### `create` operation 99 | - event `entity_manager_save_before` 100 | - event `{$lower_case_entity_type}_save_before` 101 | - apply sequence - \Magento\Framework\EntityManager\Sequence\SequenceApplier::apply 102 | * entity[id] = sequence.getNextValue 103 | - *create main* 104 | * EntityManager\Db\CreateRow::execute - insert into main table by existing column 105 | - *create attributes* 106 | * attribute pool actions `create`[].execute 107 | * default action handler `Magento\Eav\Model\ResourceModel\CreateHandler`: 108 | + only if entity is EAV type, insert all attributes 109 | - \Magento\Eav\Model\ResourceModel\AttributePersistor::registerInsert 110 | - \Magento\Eav\Model\ResourceModel\AttributePersistor::flush 111 | - *create extensions* - \Magento\Framework\EntityManager\Operation\Create\CreateExtensions 112 | * extension pool actions `create`[].execute - save many-to-many records, custom columns etc. 113 | - event `{$lower_case_entity_type}_save_after` 114 | - event `entity_manager_save_after` 115 | 116 | 117 | ## Object converters 118 | \Magento\Framework\Reflection\DataObjectProcessor::buildOutputDataArray(object, interface) 119 | \Magento\Framework\Reflection\CustomAttributesProcessor::buildOutputDataArray 120 | \Magento\Framework\Reflection\ExtensionAttributesProcessor::buildOutputDataArray 121 | 122 | 123 | \Magento\Framework\Api\\`SimpleDataObjectConverter`: 124 | - toFlatArray: 125 | * data object processor.buildOutputDataArray 126 | * ConvertArray::toFlatArray 127 | - convertKeysToCamelCase - used by Soap API, supports `custom_attributes` 128 | 129 | \Magento\Framework\Api\\`ExtensibleDataObjectConverter`: 130 | - toNestedArray(entity, skip custom attributes list, interface) - used by entity repositories.save: 131 | * data object processor.buildOutputDataArray 132 | * add `custom_attributes` 133 | * add `extension_attributes` 134 | - toFlatArray: 135 | * toNestedArray 136 | * ConvertArray::toFlatArray 137 | - (static) convertCustomAttributesToSequentialArray 138 | 139 | 140 | \Magento\Framework\Reflection\DataObjectProcessor::buildOutputDataArray: 141 | - \Magento\Framework\Reflection\MethodsMap::getMethodsMap - method name and getter return types 142 | - filter only getters: is..., has..., get... 143 | - get data using interface getter, e.g. $object->getSku() - based on Interface definition 144 | - deduce field name by getter name, e.g. 'sku' 145 | - process custom_attributes \Magento\Framework\Reflection\CustomAttributesProcessor::buildOutputDataArray 146 | - process extension_attributes \Magento\Framework\Reflection\ExtensionAttributesProcessor::buildOutputDataArray 147 | - process return object: build return value objects with their return type annotation 148 | - process return array: cast each element to type, e.g. int[] => (int) each value 149 | - cast element to type 150 | 151 | Product.save: 152 | - buildOutputDataArray 153 | - product data = call all getters + original data 154 | - product = initializeProductData. create new product/get existing by SKU. set product data 155 | - process links, unless product data `ignore_links_flag` 156 | - abstract entity.validate 157 | - product resource.save 158 | - entity manager.save 159 | -------------------------------------------------------------------------------- /6. Developing with Adminhtml/1. Describe common structure architecture.md: -------------------------------------------------------------------------------- 1 | # Describe common structure/architecture 2 | 3 | ## Some URLs in backend start with "admin/admin/.." - 2 times admin, some only 1 - "admin/...". How? 4 | 5 | Sample router: 6 | ```xml 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | ``` 16 | 17 | Sample menu items: 18 | - menu add action="adminhtml/system_design_theme" 19 | http://example.com/admin/admin/system_design_theme/index/key/$secret/ 20 | \Magento\Theme\Controller\Adminhtml\System\Design\Theme\Index 21 | * /admin/admin/system_design_theme/index 22 | * App\AreaList.getCodeByFrontName('admin') 23 | * \Magento\Backend\App\Area\FrontNameResolver::getFrontName 24 | * default front name = deployment config `backend/frontName` - from env.php 25 | * if config `admin/url/use_custom_path`, admin front name = config `admin/url/custom_path` 26 | 27 | - menu add action="theme/design_config" 28 | http://example.com/admin/theme/design_config/index/key/$secret/ 29 | \Magento\Theme\Controller\Adminhtml\Design\Config\Index 30 | 31 | Fun fact: same "Content > Design > Themes" link can open by 2 different URLs: 32 | - http://example.com/admin/admin/system_design_theme/index/key/82c8...6bfa/ 33 | - http://example.com/admin/theme/system_design_theme/index/key/0ed6...6c75/ 34 | 35 | First link "hides" behind Magento_Backend original route "adminhtml"="admin" frontName. 36 | Second link has own frontName="theme" and is more beautiful. 37 | 38 | 39 | Admin app flow: 40 | - bootstrap.run(application) 41 | - application.launch 42 | - front controller.dispatch 43 | - admin routerList: 44 | * `admin` 45 | * `default` 46 | - Magento\Backend\App\Router.match - same as frontend, but admin parses *4* sections: 47 | * parseRequest 48 | + `_requiredParams = ['areaFrontName', 'moduleFrontName', 'actionPath', 'actionName']` 49 | + "admin/admin/system_design_theme/index" 50 | 51 | `router id="admin"` 52 | 53 | Admin router - Base, same as frontend, BUT: 54 | - `_requiredParams = ['areaFrontName', 'moduleFrontName', 'actionPath', 'actionName']` - *4* sections, rest is params. 55 | Example: "kontrollpanel/theme/design_config/index/key/3dd89..7f6e/": 56 | * moduleFrontName = "theme", actionPath = "design_config", actionName = "index", 57 | * parameters = [key: 3dd89..7f6e] 58 | * fromt name "theme" - search adminhtml/routers.xml for ....? 59 | Example 2 "/kontrollpanel/admin/system_design_theme/index/key/4bd2...13/: 60 | * moduleFrontName = "admin", actionPath = "system_design_theme", actionName = "index" 61 | * parameters = [key: 4bd2...13] 62 | 63 | - `pathPrefix = 'adminhtml'` -- all controller classes are searched in `Module\Controller\Adminhtml\...` 64 | 65 | When in *adminhtml area*, ALL controllers will reside in Controller/Adminhtml/ modules path. 66 | 67 | Module_Backend registers router `` => ``. 68 | This means that `getUrl("adminhtml/controller/action")`: 69 | - Magento\Backend\Model\Url adds prefix `$areaFrontName/` in `_getActionPath` 70 | - *route name* "adminhtml", frontName will always be "admin/" 71 | - default controllers search module is *Magento_Backend* 72 | - declaring other modules before "Magento_Backend" you risk overriding some system backend controllers: 73 | + ajax/translate, auth/login, dashboard/index, index/index, system/store etc. 74 | 75 | You can in turn *register own router* `` => ``. 76 | You will generate links with `getUrl("theme/controller/action")`: 77 | - backend URL model._getActionPath adds prefix areaFrontName 78 | - *route name* "theme", frontName "theme" 79 | - controlles are searched *only in your module* Module_Alias, no interference with Magento_Backend controllers. 80 | 81 | ### What does `router id="admin"`, `router id="standard"` mean in routes.xml? 82 | ```xml 83 | 84 | 85 | 86 | ... 87 | 88 | 89 | 90 | 91 | ... 92 | 93 | 94 | 95 | ``` 96 | We know that frontend and admin areas main *router* is almost the same - \Magento\Framework\App\Router\Base. 97 | It simply finds *routes* by matching `frontName`. 98 | 99 | Key difference is matched *area* in *area list* defined `router` name: 100 | - `adminhtml` area - `admin` *router*. 101 | - `frontend` area - `standard` *router*. 102 | All routes and frontNames are read only from router by ID. 103 | 104 | This means that admin routes always lie in etc/adminhtml/routes.xml and have `router id="admin"`. 105 | Frontend routes - etc/frontend/routes.xml and always have `router id="standard"`. 106 | This looks like redundancy to me. 107 | 108 | *Admin controller load summary (one more time)*: 109 | - match backend route name 110 | - match adminhtml area, router = 'admin' 111 | - load adminhtml configuration 112 | - adminhtml/di.xml - routerList = [admin, default] 113 | - admin router = extends frontend Base router 114 | - parse params - 4 parts: [front area from env.php, frontName, controllerPath, actionName] 115 | - search routes.xml/router id="admin"/route by frontName=$frontName - second parameter 116 | - matched route has 1+ ordered module names, e.g. [Module_Theme, Module_Backend] 117 | - search controller class in each Module_Name/Controller/Adminhtml/[Controller/Path]/[ActionName] 118 | 119 | 120 | *URL generation*: 121 | 122 | In *frontend* URLs are generated with `\Magento\Framework\Url.getUrl`: 123 | - getUrl() -> createUrl() = getRouterUrl + ?query + #fragment 124 | - getRouterUrl() = getBaseUrl() + `_getRoutePath` 125 | - getBaseUrl() example 'http://www.example.com/' 126 | - `_getRoutePath` = `_getActionPath` + params pairs. For example: 'catalog/product/view/id/47' 127 | - `_getActionPath` example: 'catalog/product/view' 128 | 129 | In *backend* use customized version `\Magento\Backend\Model\Url`: 130 | - `_getActionPath` *prepends* area front name, e.g. 'kontrollpanel/catalog/product/view'. 131 | Now all backend links have this prefix. 132 | - getUrl() adds secret key /key/hash 133 | - can disable secret key generation with turnOffSecretKey() and revert with turnOnSecretKey(). 134 | Global admin config `admin/security/use_form_key`. 135 | 136 | *Secret key* = `hash($routeName . $controllerName . $actionName . $sessionFormKey)` 137 | 138 | 139 | 140 | 141 | ## Describe the difference between Adminhtml and frontend. What additional tools and requirements exist in the admin? 142 | - ACL permissions - `_isAllowed`, change static const `ADMIN_RESOURCE` 143 | - base controller Magento\Backend\App\Action = Magento\Backend\App\AbstractAction 144 | - custom *URL model* - secret key `key` 145 | - layout `acl` block attribute 146 | - ... 147 | -------------------------------------------------------------------------------- /6. Developing with Adminhtml/4. Utilize ACL to set menu items and permissions.md: -------------------------------------------------------------------------------- 1 | # Utilize ACL to set menu items and permissions 2 | 3 | ## Describe how to set up a menu item and permissions. 4 | 5 | menu.xml - `urn:magento:module:Magento_Backend:/etc/menu.xsd` - flat structure 6 | ```xml 7 | 8 | 12 | 13 | 14 | 15 | ``` 16 | 17 | *How would you add a new tab in the Admin menu?* 18 | - don't specify parent and action 19 | 20 | *How would you add a new menu item in a given tab?* 21 | - just set one of top level parents, e.g. 'Magento_Backend::system' 22 | 23 | *How do menu items relate to ACL permissions?* 24 | 25 | ## Describe how to check for permissions in the permissions management tree structures. 26 | *How would you add a new user with a given set of permissions?* 27 | 28 | - System > Permissions > User Roles has all the differnet roles and associated permissions. Each role can be scoped to Website level and granular permissions based on resource options. 29 | - System > Permissions > All Users to view and create new users and associate to a role. There are two tabs 1 is user info the other is User Role where you define the Role for this user. You can only select 1 role per user. 30 | 31 | *How can you do that programmatically?* 32 | - You can leverage [\Magento\Authorization\Model\Acl\AclRetriever](https://github.com/magento/magento2/blob/2.2-develop/app/code/Magento/Authorization/Model/Acl/AclRetriever.php). That as a few methods that will help 33 | 34 | ```php 35 | /** 36 | * Get a list of available resources using user details 37 | * 38 | * @param string $userType 39 | * @param int $userId 40 | * @return string[] 41 | * @throws AuthorizationException 42 | * @throws LocalizedException 43 | */ 44 | public function getAllowedResourcesByUser($userType, $userId) 45 | { 46 | if ($userType == UserContextInterface::USER_TYPE_GUEST) { 47 | return [self::PERMISSION_ANONYMOUS]; 48 | } elseif ($userType == UserContextInterface::USER_TYPE_CUSTOMER) { 49 | return [self::PERMISSION_SELF]; 50 | } 51 | try { 52 | $role = $this->_getUserRole($userType, $userId); 53 | if (!$role) { 54 | throw new AuthorizationException( 55 | __('We can\'t find the role for the user you wanted.') 56 | ); 57 | } 58 | $allowedResources = $this->getAllowedResourcesByRole($role->getId()); 59 | } catch (AuthorizationException $e) { 60 | throw $e; 61 | } catch (\Exception $e) { 62 | $this->logger->critical($e); 63 | throw new LocalizedException( 64 | __( 65 | 'Something went wrong while compiling a list of allowed resources. ' 66 | . 'You can find out more in the exceptions log.' 67 | ) 68 | ); 69 | } 70 | return $allowedResources; 71 | } 72 | 73 | /** 74 | * Get a list of available resource using user role id 75 | * 76 | * @param string $roleId 77 | * @return string[] 78 | */ 79 | public function getAllowedResourcesByRole($roleId) 80 | { 81 | $allowedResources = []; 82 | $rulesCollection = $this->rulesCollectionFactory->create(); 83 | $rulesCollection->getByRoles($roleId)->load(); 84 | $acl = $this->aclBuilder->getAcl(); 85 | /** @var \Magento\Authorization\Model\Rules $ruleItem */ 86 | foreach ($rulesCollection->getItems() as $ruleItem) { 87 | $resourceId = $ruleItem->getResourceId(); 88 | if ($acl->has($resourceId) && $acl->isAllowed($roleId, $resourceId)) { 89 | $allowedResources[] = $resourceId; 90 | } 91 | } 92 | return $allowedResources; 93 | } 94 | ``` 95 | 96 | However the actual code to set privilage permission may look like this in the core code 97 | vendor/magento/magento2-base/setup/src/Magento/Setup/Fixtures/AdminUsersFixture.php 98 | 99 | In particular this section: 100 | 101 | ```php 102 | $adminUser = $this->userFactory->create(); 103 | $adminUser->setRoleId($role->getId()) 104 | ->setEmail('admin' . $i . '@example.com') 105 | ->setFirstName('Firstname') 106 | ->setLastName('Lastname') 107 | ->setUserName('admin' . $i) 108 | ->setPassword('123123q') 109 | ->setIsActive(1); 110 | $adminUser->save(); 111 | ``` 112 | 113 | ```php 114 | 135 | * {int} 136 | */ 137 | class AdminUsersFixture extends Fixture 138 | { 139 | /** 140 | * @var int 141 | */ 142 | protected $priority = 5; 143 | 144 | /** 145 | * @var UserFactory 146 | */ 147 | private $userFactory; 148 | 149 | /** 150 | * @var RoleFactory 151 | */ 152 | private $roleFactory; 153 | 154 | /** 155 | * @var UserCollectionFactory 156 | */ 157 | private $userCollectionFactory; 158 | 159 | /** 160 | * @var RulesFactory 161 | */ 162 | private $rulesFactory; 163 | 164 | /** 165 | * @var RootResource 166 | */ 167 | private $rootResource; 168 | 169 | /** 170 | * @param FixtureModel $fixtureModel 171 | * @param UserFactory $userFactory 172 | * @param UserCollectionFactory $userCollectionFactory 173 | * @param RoleFactory $roleFactory 174 | * @param RulesFactory $rulesFactory 175 | * @param RootResource $rootResource 176 | */ 177 | public function __construct( 178 | FixtureModel $fixtureModel, 179 | UserFactory $userFactory, 180 | UserCollectionFactory $userCollectionFactory, 181 | RoleFactory $roleFactory, 182 | RulesFactory $rulesFactory, 183 | RootResource $rootResource 184 | ) { 185 | parent::__construct($fixtureModel); 186 | $this->userFactory = $userFactory; 187 | $this->roleFactory = $roleFactory; 188 | $this->userCollectionFactory = $userCollectionFactory; 189 | $this->rulesFactory = $rulesFactory; 190 | $this->rootResource = $rootResource; 191 | } 192 | 193 | /** 194 | * {@inheritdoc} 195 | */ 196 | public function execute() 197 | { 198 | $adminUsersNumber = $this->fixtureModel->getValue('admin_users', 0); 199 | $adminUsersStartIndex = $this->userCollectionFactory->create()->getSize(); 200 | 201 | if ($adminUsersStartIndex >= $adminUsersNumber) { 202 | return; 203 | } 204 | 205 | $role = $this->createAdministratorRole(); 206 | 207 | for ($i = $adminUsersStartIndex; $i <= $adminUsersNumber; $i++) { 208 | $adminUser = $this->userFactory->create(); 209 | $adminUser->setRoleId($role->getId()) 210 | ->setEmail('admin' . $i . '@example.com') 211 | ->setFirstName('Firstname') 212 | ->setLastName('Lastname') 213 | ->setUserName('admin' . $i) 214 | ->setPassword('123123q') 215 | ->setIsActive(1); 216 | $adminUser->save(); 217 | } 218 | } 219 | 220 | /** 221 | * {@inheritdoc} 222 | */ 223 | public function getActionTitle() 224 | { 225 | return 'Generating admin users'; 226 | } 227 | 228 | /** 229 | * {@inheritdoc} 230 | */ 231 | public function introduceParamLabels() 232 | { 233 | return [ 234 | 'admin_users' => 'Admin Users' 235 | ]; 236 | } 237 | 238 | /** 239 | * Create administrator role with all privileges. 240 | * 241 | * @return \Magento\Authorization\Model\Role 242 | */ 243 | private function createAdministratorRole() 244 | { 245 | $role = $this->roleFactory->create(); 246 | $role->setParentId(0) 247 | ->setTreeLevel(1) 248 | ->setSortOrder(1) 249 | ->setRoleType(Group::ROLE_TYPE) 250 | ->setUserId(0) 251 | ->setUserType(UserContextInterface::USER_TYPE_ADMIN) 252 | ->setRoleName('Example Administrator'); 253 | $role->save(); 254 | 255 | /** @var \Magento\Authorization\Model\Rules $rule */ 256 | $rule = $this->rulesFactory->create(); 257 | $rule->setRoleId($role->getId()) 258 | ->setResourceId($this->rootResource->getId()) 259 | ->setPrivilegies(null) 260 | ->setPermission('allow'); 261 | $rule->save(); 262 | 263 | return $role; 264 | } 265 | } 266 | 267 | ``` 268 | -------------------------------------------------------------------------------- /7. Customizing the Catalog/1. Demonstrate ability to use products and product types.md: -------------------------------------------------------------------------------- 1 | # Demonstrate ability to use products and product types 2 | 3 | ## Identify/describe standard product types (simple, configurable, bundled, etc.). 4 | 5 | Product type model - [Magento\Catalog\Model\Product\Type\AbstractType](https://github.com/magento/magento2/blob/2.2-develop/app/code/Magento/Catalog/Model/Product/Type/AbstractType.php) 6 | 7 | 7 product types: 8 | 9 | - [*virtual*](https://github.com/magento/magento2/blob/2.2-develop/app/code/Magento/Catalog/Model/Product/Type/Virtual.php) 10 | 11 | Not tangible products - services, memberships, warranties, and subscriptions. No weight, can't be shipped. 12 | 13 | - [*simple*](https://github.com/magento/magento2/blob/2.2-develop/app/code/Magento/Catalog/Model/Product/Type/Simple.php) 14 | 15 | Single SKU, has weight and can be shipped. 16 | 17 | - [*downloadable*](https://github.com/magento/magento2/blob/2.2-develop/app/code/Magento/Downloadable/Model/Product/Type.php) 18 | 19 | Same as virtual + downloadable links 20 | 21 | - [*configurable*](https://github.com/magento/magento2/blob/2.2-develop/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php) 22 | 23 | Add 1 composite product, consisting of 1 selected variation. All options are connected. 24 | Child product inventory tracked separately. 25 | 26 | - [*grouped*](https://github.com/magento/magento2/blob/2.2-develop/app/code/Magento/GroupedProduct/Model/Product/Type/Grouped.php) 27 | 28 | Groups multiple products on same page. Customer can buy all of some of them, edit qty. 29 | After adding to cart, they are separate, same as if you added each of them one by one. 30 | 31 | - [*bundle*](https://github.com/magento/magento2/blob/2.2-develop/app/code/Magento/Bundle/Model/Product/Type.php) 32 | 33 | Add 1 composite product, consisting of many selected variations, options independent. 34 | SKU dynamic/fixed, weight dynamic/fixed, price dynamic/fixed. Each qty x total qty. 35 | Inventory is tracked separately for each child. 36 | 37 | - *gift card (Commerce edition)* 38 | 39 | Like downloadable, purchase gift card code instead of link. 40 | Can be virtual/physical/both. Thus can be shipped. 41 | 42 | ## product_types.xml - maximum configuration: 43 | ```xml 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | ``` 65 | 66 | ### How would you obtain a product of a specific type? 67 | 68 | Product type code is stored in catalog_product_entity.type_id. Thus, we can use collection: 69 | `$productCollection->addFieldToFilter('type_id', 'simple');` 70 | 71 | ### What tools (in general) does a product type model provide? 72 | 73 | *_prepareProduct* - given add to cart request, returns product items that will be converted to quote items 74 | 75 | When product is added to cart: 76 | - [\Magento\Checkout\Controller\Cart\Add::execute](https://github.com/magento/magento2/blob/2.2-develop/app/code/Magento/Checkout/Controller/Cart/Add.php#L80) 77 | - [\Magento\Checkout\Model\Cart::addProduct](https://github.com/magento/magento2/blob/2.2-develop/app/code/Magento/Checkout/Model/Cart.php#L354) 78 | - [\Magento\Quote\Model\Quote::addProduct](https://github.com/magento/magento2/blob/2.2-develop/app/code/Magento/Quote/Model/Quote.php#L1583) 79 | - `$cartCandidates = $product->getTypeInstance()->prepareForCartAdvanced($request, $product, $processMode);` 80 | - [`prepareForCartAdvanced`](https://github.com/magento/magento2/blob/2.2-develop/app/code/Magento/Catalog/Model/Product/Type/AbstractType.php#L455) simply calls [`typeInstance._prepareProduct`](https://github.com/magento/magento2/blob/2.2-develop/app/code/Magento/Catalog/Model/Product/Type/AbstractType.php#L371) 81 | - type analyzes request and initializes one or many product items 82 | - quote converts product items to quote_items 83 | 84 | Standard types: 85 | - `simple._prepareProduct` - just returns [product]. one record will be saved in quote_item 86 | - `configurable._prepareProduct` - prepares and returns [parentProduct, childProduct]. 87 | Configures parent, configures child, then adds some useful linking info to their custom options. 88 | 89 | * adds parent configurable product normally 90 | * based on param `super_attribute` selections (color=red, size=XL), loads sub product 91 | * parentProduct.setCustomOptions: 92 | + `attributes = {color: 12, size: 35}`, 93 | + `product_qty_{$subProduct.id} = 1` 94 | + `simple_product = $subProduct` 95 | + recursively calls subProduct.getTypeInstance._prepareProduct. this will configure child product as well 96 | * subProduct.setCustomOption(`parent_product_id`, ID) 97 | - `grouped._prepareProduct` 98 | * loads sub products individually from `catalog_product_link` by type id LINK_TYPE_GROUPED = 3 99 | * subProduct[].getTypeInstance._prepareProduct 100 | * some meta info is added to custom options: 101 | + `product_type = 'grouped'` 102 | * all configured products are returned as separate items, not linked to any parent 103 | - `bundle._prepareProduct` - [parentProduct, selectedChild1, ..., selectedChildN] 104 | - `downloadable._prepareProduct` - same as simple, plus additionally generates product links 105 | * shows error if required links not selected 106 | * product.setCustomOption(`downloadable_link_ids`, '1,2,...') 107 | 108 | `_prepareProduct` is mostly where product type customization are placed. Here you analyze request (`$_POST` values), 109 | validate input, can set prices, custom option. 110 | Primary goal is to return configured products with custom options (`product.setCustomOption`). 111 | Each product will be converted to `quote_item` records, custom options will be converted to `quote_item_option` records. 112 | 113 | 114 | *processBuyRequest()* 115 | 116 | Convert buyRequest back to options to configure when you added product to cart and click Configure. 117 | 118 | 119 | *checkProductBuyState()* 120 | 121 | Check product has all required options. read product.getCustomOption, throws exception on error. 122 | Used by quote_item.checkData() - checks if product in cart is healthy. 123 | 124 | 125 | *getOrderOptions()* 126 | 127 | Prepare additional options/information for order item which will be created from this product. 128 | 129 | - product_calculations - parent/child 130 | - shipment_type - together/separately 131 | 132 | 133 | *getSku()*, *getOptionSku()* 134 | 135 | Glues SKU parts for custom options, bundle selections etc. 136 | 137 | 138 | *getRelationInfo()* - [table, parent_field_name, child_field_name, where] 139 | 140 | Used in: 141 | - fulltext search to get attributes of product+children 142 | - product flat indexer - insert children into flat 143 | 144 | 145 | *getWeight()* 146 | 147 | - configurable product used simple product's weight 148 | - bundle product composes sum weight of components (if weight type dynamic) 149 | 150 | 151 | *isSalable()* 152 | 153 | - by default true if product status is enabled 154 | - data['is_salable'] can force not salable (data attribute set by inventory from stock index?) 155 | - configurable - at least one enabled child product must be in stock 156 | - bundle - all required options are salable 157 | - downloadable - must have links 158 | 159 | 160 | *processBuyRequest()* 161 | 162 | Used for setting product preconfigured values - color, size, bundle selections - when 163 | configuring added product. 164 | 165 | [Helper\Product.prepareProductOptions](https://github.com/magento/magento2/blob/2.2-develop/app/code/Magento/Catalog/Helper/Product.php#L485): 166 | ```php 167 | $optionValues = $product->processBuyRequest($buyRequest); 168 | // $type->processBuyRequest 169 | $optionValues->setQty($buyRequest->getQty()); 170 | $product->setPreconfiguredValues($optionValues); 171 | ``` 172 | 173 | - configurable - assigns super_attribute selection. 174 | - gift card - assigns amount, messages, emails etc. - all form data from remembered buy request. 175 | -------------------------------------------------------------------------------- /7. Customizing the Catalog/3. Demonstrate ability to use and customize categories.md: -------------------------------------------------------------------------------- 1 | # Demonstrate ability to use and customize categories 2 | 3 | ## Describe category properties and features. 4 | 5 | Category features: 6 | - can be anchored/non-anchored. When anchored, all products from subcategories "bubble up" and show up in 7 | the category listing even when _not assigned_ explicitly. 8 | - include in menu switch 9 | - display type - products only, cms block only, both 10 | - you can assign direct category products and set their individual positions. Directly assigned products 11 | always show before "bubbled up" for anchor. 12 | 13 | ## How do you create and manage categories? 14 | - as always, prefer using repository over directly saving model 15 | - [Magento\Catalog\Model\CategoryRepository](https://github.com/magento/magento2/blob/2.2-develop/app/code/Magento/Catalog/Model/CategoryRepository.php) 16 | - [Magento\Catalog\Model\CategoryManagement](https://github.com/magento/magento2/blob/2.2-develop/app/code/Magento/Catalog/Model/CategoryManagement.php) - getTree, move, getCount 17 | - [category.move](https://github.com/magento/magento2/blob/2.2-develop/app/code/Magento/Catalog/Model/Category.php#L384)(newParent, afterId) -- old approach 18 | 19 | ## [Category model](https://github.com/magento/magento2/blob/2.2-develop/app/code/Magento/Catalog/Model/Category.php) - few changes since M1 20 | - getTreeModel, getTreeModelInstance 21 | - move - event `catalog_category_move_before`, `catalog_category_move_after`, event `category_move`, 22 | indexer.reindexList, clean cache by tag 23 | - getProductCollection 24 | - getStoreIds 25 | 26 | - getPathIds, getLevel 27 | - getParentId, getParentIds - without self ID 28 | - getParentCategory 29 | - getParentDesignCategory - Category 30 | - isInRootCategoryList 31 | 32 | - getAllChildren - = by default joined ids ("2,3,4...") + parent id, recursive, only active 33 | - getChildren - joined ids ("2,3,4..."), only immediate, only active 34 | - hasChildren - recursive, only active 35 | - getAnchorsAbove - array of ids - parents that are anchor 36 | - getProductCount - only directly assigned, all 37 | 38 | - getCategories - tree|collection. recursive, only active, only include_in_menu, rewrites joined 39 | - getParentCategories - Category[], only active 40 | - getChildrenCategories - collection, only active, only immediate, include_in_menu*, rewrites joined 41 | 42 | ## Describe the category hierarchy tree structure implementation (the internal structure inside the database). 43 | - Every category has hierarchy fields: parent_id, level, path 44 | - To avoid recursive parent-child traversing, flat category path is saved for every category. It includes 45 | all parents IDs. This allows to optimize children search with single query. E.g. to search children of 46 | category id 2 and knowing its path '1/2', we can get children: 47 | 48 | `select * from catalog_category_entity where path like "1/2/%"`: 49 | 50 | ``` 51 | +-----------+-----------+--------------+-------+ 52 | | entity_id | parent_id | path | level | 53 | +-----------+-----------+--------------+-------+ 54 | | 3 | 2 | 1/2/3 | 2 | 55 | | 4 | 3 | 1/2/3/4 | 3 | 56 | | 5 | 3 | 1/2/3/5 | 3 | 57 | | 6 | 3 | 1/2/3/6 | 3 | 58 | | 7 | 2 | 1/2/7 | 2 | 59 | | 8 | 7 | 1/2/7/8 | 3 | 60 | .... 61 | ``` 62 | 63 | 64 | ## What is the meaning of parent_id 0? 65 | Magento\Catalog\Model\Category constants: 66 | - ROOT_CATEGORY_ID = 0 -- looks like some reserved functionality, has only one child 67 | - TREE_ROOT_ID = 1 -- all store group root categories are assigned to this node 68 | 69 | - These categories are not visible in admin panel in category management. 70 | - When creating root category from admin, parent_id is always TREE_ROOT_ID = 1. 71 | - [Magento\Store\Model\Store::getRootCategoryId](https://github.com/magento/magento2/blob/2.2-develop/app/code/Magento/Store/Model/Store.php#L998): 72 | * TREE_ROOT_ID = 1 when store group is not selected 73 | * store group root category ID 74 | - store group root category allows only selecting from categoryes with parnet_id = TREE_ROOT_ID = 1 75 | 76 | ## How are paths constructed? 77 | 78 | Slash-separates parents and own ID. 79 | 80 | ## Which attribute values are required to display a new category in the store? 81 | 82 | - is_active = 1 83 | - include_in_menu = 1, but even if not displayed in menu, can be opened by direct link 84 | - url_key can be created automatically based on name 85 | 86 | ## What kind of strategies can you suggest for organizing products into categories? 87 | -------------------------------------------------------------------------------- /7. Customizing the Catalog/4. Determine and manage catalog rules.md: -------------------------------------------------------------------------------- 1 | # Determine and manage catalog rules 2 | 3 | *Primary tables*: 4 | - `catalogrule` - dates from/to, conditions serialized, actions serialized, simple action, discount amount 5 | - `catalogrule_website` - rule-website 6 | - `catalogrule_customer_group` - rule-customer group 7 | 8 | *Index tables*: 9 | - `catalogrule_product` - time from/to, customer group, action operator/amount 10 | - `catalogrule_product_price` - customer group, rule date, rule price, latest start date, earlier end date 11 | - `catalogrule_group_website` - rule to customer group, website. 12 | Just unique rule/customer group/websites that currently exist in index table `catalogrule_product` for *today*. 13 | 14 | *Replica tables* - used to fast switch new index data: 15 | - `catalogrule_product_replica` 16 | - `catalogrule_product_price_replica` 17 | - `catalogrule_group_website_replica` 18 | 19 | ## Apply rules 20 | - [*Cron job*](https://github.com/magento/magento2/tree/2.2-develop/app/code/Magento/CatalogRule/Cron/DailyCatalogUpdate.php) 01:00 am invalidates `catalogrule_rule` indexer. 21 | - Clicking ["Apply rules"](https://github.com/magento/magento2/tree/2.2-develop/app/code/Magento/CatalogRule/Model/Rule/Job.php#L54), ["Save and apply"](https://github.com/magento/magento2/tree/2.2-develop/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/Save.php#L97) in admin panel invalidates indexer as well. 22 | - Cron job `indexer_reindex_all_invalid`, running every minute, will pick it up and call indexer 23 | * Magento\CatalogRule\Model\Indexer\AbstractIndexer::executeFull 24 | * [Magento\CatalogRule\Model\Indexer\IndexBuilder::reindexFull](https://github.com/magento/magento2/tree/2.2-develop/app/code/Magento/CatalogRule/Model/Indexer/IndexBuilder.php#L280) 25 | 26 | index builder.[doReindexFull](https://github.com/magento/magento2/tree/2.2-develop/app/code/Magento/CatalogRule/Model/Indexer/IndexBuilder.php#L297): 27 | - clear tables `catalogrule_product`, `catalogrule_product_price` 28 | - for every rule, reindex rule product. 29 | "one day, maybe today, maybe in a year, this rule will match these products" 30 | 31 | * this is regardless of dates, dates are simply written in table for later uage 32 | * get matching product IDs 33 | * insert into `catalogrule_product` (`_replica`) combinations: product/website/customer group 34 | - reindex product prices `catalogrule_product_price` (`_replica`). 35 | For yesterday, today and tomorrow reads "planned" products, and executes price update 36 | starting from initial product.price attribute value. If multiple rules match in same date, 37 | they will all apply (unless stop flag). 38 | 39 | * dates = [yesterday; today; tomorrow] 40 | * for each website: 41 | * read ALL planned products from `catalogrule_product` from all rules, join `price` product attribute 42 | + \Magento\CatalogRule\Model\Indexer\RuleProductsSelectBuilder::build 43 | * programmatically filter dates (from/to) 44 | * calculates price 45 | + \Magento\CatalogRule\Model\Indexer\ProductPriceCalculator::calculate 46 | + to fixed/to percent/by fixed/by percent 47 | * groups by [date, product id, website id, customer group id] 48 | * persists to `catalogrule_product_price` 49 | + \Magento\CatalogRule\Model\Indexer\RuleProductPricesPersistor::execute 50 | - reindex *rule relations* with *customer groups and websites* `catalogrule_group_website` (`_replica`) 51 | * `catalogrule_group_website` - distinct combinations of customer group and website matched for *today* 52 | - switches tables from replica to active 53 | 54 | ## Catalog rule price info 55 | [Magento\CatalogRule\Pricing\Price\CatalogRulePrice::getValue](https://github.com/magento/magento2/tree/2.2-develop/app/code/Magento/CatalogRule/Pricing/Price/CatalogRulePrice.php#L88) 56 | - product.data('catalog_rule_price') can be preselected by collection from index. 57 | Used only by bundle and is deprecated. 58 | * \Magento\CatalogRule\Model\ResourceModel\Product\CollectionProcessor::addPriceData - join catalogrule_product_price 59 | - [Magento\CatalogRule\Model\ResourceModel\Rule::getRulePrice](https://github.com/magento/magento2/tree/2.2-develop/app/code/Magento/CatalogRule/Model/ResourceModel/Rule.php#L162) by (date, website, customer group, product) 60 | - rule.getRulePrices 61 | - loads directly from DB `catalogrule_product_price` 62 | 63 | 64 | ## Identify how to implement catalog price rules. 65 | Matching products - conditions and actions serialized, are implemented on 66 | foundation of abstract rules [Magento\Rule\Model\AbstractModel](https://github.com/magento/magento2/tree/2.2-develop/app/code/Magento/Rule/Model/AbstractModel.php): 67 | - conditions - [Magento\Rule\Model\Condition\AbstractCondition](https://github.com/magento/magento2/tree/2.2-develop/app/code/Magento/Rule/Model/Condition/AbstractCondition.php), [combine](https://github.com/magento/magento2/tree/2.2-develop/app/code/Magento/Rule/Model/Condition/Combine.php) 68 | - actions - [Magento\Rule\Model\Action\AbstractAction](https://github.com/magento/magento2/tree/2.2-develop/app/code/Magento/Rule/Model/Action/AbstractAction.php), collection 69 | 70 | Admin form: 71 | - \Magento\CatalogRule\Block\Adminhtml\Promo\Catalog\Edit\Tab\Conditions 72 | - conditions renderer \Magento\Rule\Block\Conditions 73 | - \Magento\Rule\Model\Condition\AbstractCondition::asHtmlRecursive: 74 | * TypeElementHtml 75 | * AttributeElementHtml 76 | * OperatorElementHtml 77 | * ValueElementHtml 78 | * RemoveLinkHtml 79 | * ChooserContainerHtml 80 | 81 | Matching products: 82 | - [Magento\CatalogRule\Model\Rule::getMatchingProductIds](https://github.com/magento/magento2/tree/2.2-develop/app/code/Magento/CatalogRule/Model/Rule.php#L294) 83 | - get product collection 84 | - for each product, run [rule::callbackValidateProduct](https://github.com/magento/magento2/tree/2.2-develop/app/code/Magento/CatalogRule/Model/Rule.php#L329) 85 | - check if product matches conditions [Magento\Rule\Model\Condition\Combine::validate](https://github.com/magento/magento2/tree/2.2-develop/app/code/Magento/Rule/Model/Condition/Combine.php#L331) 86 | 87 | ## When would you use catalog price rules? 88 | - set up discount 30% on Helloween that starts in a month 89 | - for b2b website, discount 10% from all prices 90 | - VIP customer group members get 91 | 92 | ## How do they impact performance? 93 | - one SQL query on every final price request 94 | - reindexing seems not optimized, dates match in PHP and not SQL 95 | 96 | ## How would you debug problems with catalog price rules? 97 | - Check indexes `catalogrule_product` - find row by product id, ensure found - rule matched 98 | - Ensure needed rule matched and unwanted rules didn't match, check sort_order, check from_time and to_time, ensure correct 99 | - check `catalogrule_product_price` contains product within +-1 day 100 | -------------------------------------------------------------------------------- /8. Customizing the Checkout Process/1. Demonstrate ability to use quote, quote item, address, and shopping cart rules in checkout.md: -------------------------------------------------------------------------------- 1 | # Demonstrate ability to use quote, quote item, address, and shopping cart rules in checkout 2 | 3 | Interesting *quote* fields or methods: 4 | - `ext_shipping_info` - TEXT 64k, existed in M1, not used. Good place to keep shipping API response? 5 | - `trigger_recollect` 6 | * quoteResource.markQuotesRecollectOnCatalogRules - Mark quotes that depend on catalog price rules 7 | to be recollected on demand. Called on event `catalogrule_before_apply`. Good way to update carts 8 | after "hacking" prices? 9 | * quoteResource.markQuotesRecollect - by product IDs. Mark recollect contain product(s) quotes 10 | + global plugin after product save, _if price or tier price changed_, trigger recollect 11 | + adminhtml event after product save, _if status disabled_. 12 | - `is_changed` - not used 13 | * before quote save - see temporary data-attribute quote.`changed_flag` - not used 14 | - `orig_order_id` - not used 15 | - `items_count` - INT "Display number of items in cart" 16 | - `items_qty` - FLOAT "Display item quantities", e.g. 1.5 kg, 3 same chairs etc. 17 | - `converted_at` - not used 18 | - getItemsByProduct - matching product id and custom options. good to use right after add to cart 19 | + quote.addProduct 20 | + after withlist aded to cart, finds and changes store id 21 | + withlist add all to cart, on error find and remove from cart 22 | 23 | Quote extends Model\AbstractExtensibleModel - has support of `extension_attributes`. 24 | Register extensions in `extension_attributes.xml`: 25 | ```xml 26 | 27 | 28 | 29 | ``` 30 | Use quote repository plugins afterLoad, beforeSave or whenever needed to populate your values. 31 | Quote does not utilize `custom_attributes` as it is not EAV by nature. 32 | 33 | Quote address custom attributes: 34 | * community \Magento\Quote\Model\Quote\Address\CustomAttributeList - empty, use plugin to add 35 | * EE \Magento\CustomerCustomAttributes\Model\Quote\Address\CustomAttributeList::getAttributes 36 | + customer address attributes + customer attributes 37 | 38 | Quote Item shares 2 models, quote item and quote address item. 39 | 40 | *Quote item* interesting methods: 41 | - checkData - called after adding to cart and updating options 42 | * quote item.setQty - triggers stock validation 43 | * product type instance.checkProductBuyState 44 | - `custom_price` 45 | - getCalculationPrice - `custom_price` or converted price 46 | - isChildrenCalculated - product.getPriceType = CALCULATE_CHILD 47 | - isShipSeparately - product.getShipmentType = SHIPMENT_SEPARATELY 48 | - `additional_data` - TEXT 49 | - getOptions 50 | - getQtyOptions: 51 | * for composite products in cart when sub-products are added, returns array something like 52 | `[$subProductId = qty (from option `product_qty_{$subProductId}`), otherSubId = qty, ...]` 53 | + used in \Magento\CatalogInventory\Model\Quote\Item\QuantityValidator::validate 54 | - compare - \Magento\Quote\Model\Quote\Item\Compare::compare - merge items and add qty instead of new item 55 | + \Magento\Quote\Model\Quote::updateItem 56 | - representProduct - compares quote item with some new product, checks product id and custom options 57 | + quote.getItemsByProduct 58 | - compareOptions 59 | + representProduct 60 | 61 | ## Describe how to modify these models and effectively use them in customizations. 62 | - All support extension attributes 63 | - Quote address supports custom_attributes 64 | 65 | ## Inventory validation 66 | Qty can be updated from: 67 | - cart model.updateItems by: 68 | * checkout/cart/updatePost with update_cart_action='update_qty' 69 | - quote.updateItem by: 70 | * admin/sales/order/create 71 | * advanced checkout 72 | * checkout/cart/updateItemOptions 73 | * admin/cart_product_composite_cart/update 74 | * quote repository.save via Savehandler and CartItemPersister 75 | 76 | on qty update: 77 | - quote item.setQty 78 | - event `sales_quote_item_qty_set_after` 79 | - \Magento\CatalogInventory\Observer\QuantityValidatorObserver::execute 80 | - \Magento\CatalogInventory\Model\Quote\Item\QuantityValidator::validate 81 | * all validation errors can set quote.addErrorInfo, quote item.addErrorInfo 82 | * check out of stock, parent out of stock 83 | * when there's sub products with qty options: 84 | + check qty increments 85 | + for each sub product - initializer option.initialize 86 | \Magento\CatalogInventory\Model\Quote\Item\QuantityValidator\Initializer\Option::initialize 87 | - multiply parent qty`*`sub qty, check resulting min sale qty, max sale qty, qty increments, in stock, backorders 88 | \Magento\CatalogInventory\Model\StockState::checkQuoteItemQty, 89 | \Magento\CatalogInventory\Model\StockStateProvider::checkQuoteItemQty 90 | * when no sub products qty: 91 | + initializer stock item.initialize 92 | \Magento\CatalogInventory\Model\Quote\Item\QuantityValidator\Initializer\StockItem::initialize 93 | \Magento\CatalogInventory\Model\StockState::checkQuoteItemQty 94 | \Magento\CatalogInventory\Model\StockStateProvider::checkQuoteItemQty 95 | 96 | ## Add to cart 97 | cart model - deprecated. 98 | 99 | - checkout/cart/add [product, qty, related_product] 100 | * cart model.addProduct 101 | + filter request here, can register own via argument for \Magento\Checkout\Model\Cart\RequestInfoFilter 102 | + by default filtered out `form_key` and `custom_price` 103 | * quote.addProduct 104 | * product type instance.prepareForCartAdvanced 105 | * get same quote item or create new 106 | * \Magento\Quote\Model\Quote\Item\Processor::prepare 107 | + quote item.addQty 108 | + support request.customPrice -> quote item.customPrice 109 | * event `checkout_cart_product_add_after` 110 | * checkout session.setLastAddedProductId 111 | * cart.save 112 | + quote collect totals 113 | + event `checkout_cart_save_after` 114 | + reinitialize state - remove addresses and payments 115 | * event `checkout_cart_add_product_complete` 116 | 117 | - checkout/cart/updatePost 118 | * cart.suggestItemsQty => \Magento\CatalogInventory\Model\StockStateProvider::suggestQty - qty increments, min/max qty 119 | * cart.updateItems 120 | + events `checkout_cart_update_items_before`, `checkout_cart_update_items_after` 121 | + quote item.setQty -- triggers stock validation 122 | * cart.save 123 | 124 | ## Describe how to customize the process of adding a product to the cart. 125 | - plugin over product type `prepareForCartAdvanced` 126 | - event `catalog_product_type_prepare_full_options` - custom options in `_prepareOptions` 127 | - plugin over \Magento\Quote\Model\Quote\Item\Processor::prepare - qty, custom price 128 | - event `checkout_cart_product_add_after` 129 | 130 | ## Which different scenarios should you take into account? 131 | - add to cart from catalog 132 | - add to cart from wishlist 133 | - move all wishlist to cart 134 | - merge quote when existing customer has quote, then shops as a guest and logs in 135 | - admin create order 136 | - admin reorder 137 | - configure added product - change custom options 138 | -------------------------------------------------------------------------------- /8. Customizing the Checkout Process/2. Demonstrate ability to use totals models.md: -------------------------------------------------------------------------------- 1 | # Demonstrate ability to use totals models 2 | 3 | sales.xml 4 | ```xml 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | ``` 14 | 15 | Classes: 16 | - Quote\TotalsCollector - does the job, `collect` and `collectAddressTotals`. (collectQuoteTotals not used) 17 | 18 | ## Quote totals 19 | - quote.collectTotals 20 | - total = totalsCollector.collect - crunch numbers, return data to set on quote 21 | * create *quote total object* quote\address\total. all you set here will be copied to quote 22 | * event `sales_quote_collect_totals_before` 23 | * collect item qtys - quote.itemsCount, quote.itemsQty, quote.virtualItemsQty 24 | * zero total object amounts - subtotal, grand total etc. 25 | * collect each *address totals*: 26 | + *collect address totals* into new address total object 27 | - address total = quote.*collectAddressTotals* _(see below)_ 28 | + *add* to quote total object: shipping amount, shipping description, subtotal, subtotal with discount, grant total 29 | (+ base versions when applicable) 30 | * validate max grand total amount 99999999 31 | * validate coupon code - check that at least one address has it, otherwise reset to '' - code is invalid 32 | * event `sales_quote_collect_totals_after` 33 | - quote.addData(total.getData) 34 | 35 | ## Address totals 36 | quote.collectAddressTotals for each address (billing, shipping) - set address fields, return some fields for quote 37 | - new shipping assignment obj: 38 | + shipping = [method, address] 39 | + items = address items. empty for normal checkout? 40 | - create new *address total object* quote\address\total. all you set here will be copied to address 41 | - event `sales_quote_address_collect_totals_before` 42 | - get collectors: 43 | + _initModels -> _initModelInstance -> model.processConfigArray -- tax collector can change its sort programmaticaly 44 | + sales/totals_sort/* - admin configurable *totals retrievers* ordering - display order 45 | - *every collector[].collect* 46 | + all data set on address total object will be copied to address. Main amounts will also copy to quote total object. 47 | + has direct access to quote 48 | + has access to address via shipping assignment.getShipping.getAddress 49 | - event `sales_quote_address_collect_totals_after` 50 | - address.addData(address total.getData) 51 | 52 | ## Display totals 53 | Totals rendered in cart in UI-component fashion with JS configuration. 54 | 55 | Totals JS models extend `Magento_Checkout/js/view/summary/abstract-total` and implement: 56 | - getPureValue - raw float 57 | - getValue - formatted via `Magento_Catalog/js/price-utils` in format `window.checkoutConfig.priceFormat`. 58 | 59 | Models get their values from model quote.getTotals(), e.g. `quote.getTotals()().subtotal`. 60 | Initial totals values are in `window.checkoutConfig.totalsData`, any extended attributes are merged into main totals. 61 | 62 | `window.checkoutConfig` values are provided by `\Magento\Checkout\Model\CompositeConfigProvider`. 63 | Default config provider `\Magento\Checkout\Model\DefaultConfigProvider`. 64 | Register your classes via arguments. 65 | 66 | Default config provider.getTotalsData and REST API reuse same repository. 67 | \Magento\Quote\Model\Cart\CartTotalRepository::get: 68 | - billing or shipping address.getTotals 69 | * \Magento\Quote\Model\Quote\TotalsReader::fetch 70 | * each collector[].*fetch*(quote, data) 71 | + can return \Magento\Quote\Model\Quote\Address\Total or array [code, title, value] 72 | + title can be object with method *render()* - by default this is just __ Phrase translation 73 | + can return multiple totals as once 74 | + can overwrite other totals by code 75 | - convert address data to interface Cart\Totals - totals by code + quote visible items 76 | - *totals segments* - totals by code as return from fetch: 77 | * \Magento\Quote\Model\Cart\TotalsConverter::process - convert address totals to total segment 78 | * title = titleRenderer.render 79 | - add other info - coupon code, grand total, items, items qty, currency code 80 | 81 | js model quote.getTotals: 82 | ``` 83 | [ 84 | {address data fields = populate \Magento\Quote\Api\Data\TotalsInterface with address.getData}, 85 | items = quote visible items, 86 | total_segments = as returned from totals collector.fetch 87 | ] 88 | ``` 89 | 90 | ## Invoice totals, credit memo totals 91 | ```xml 92 |
93 | 94 | 95 | 96 |
97 |
98 | 99 | 100 | 101 |
102 | ``` 103 | 104 | Invoice totals: 105 | - \Magento\Sales\Model\Order\Invoice::collectTotals 106 | - every total - \Magento\Sales\Model\Order\Invoice\Total\AbstractTotal::collect(invoice) 107 | + directly update invoice object 108 | 109 | Credit memo totals: 110 | - \Magento\Sales\Model\Order\Creditmemo::collectTotals 111 | - every total - \Magento\Sales\Model\Order\Creditmemo\Total\AbstractTotal::collect 112 | + directly update credit memo object 113 | 114 | ## Render totals in admin area: 115 | Example - admin invoice totals: 116 | 117 | handle `sales_order_invoice_view`: 118 | ```xml 119 | 120 | ``` 121 | 122 | - block totals.`_beforeToHtml` 123 | - block order totals.`_initTotals` - add hardcoded totals - subtotal, shipping, discount, grand_total, base_grandtotal 124 | - every child block.initTotals 125 | - child blocks call block order totals.addTotal(total, after={'first'|'last'|$code}) or addTotalBefore(total, before) 126 | + total = DataObject [code, value, base_value, label, optional: {area, block_name, is_formated, strong}] 127 | - block order totals template 128 | + totals with area = 'footer' 129 | + totals with empty area 130 | + if 'block_name', getChildHtml - simply renders child block 131 | + otherwise print [label, value], value will be price formated unless flag 'is_formated' 132 | 133 | 134 | ## Describe how to modify the price calculation process in the shopping cart. 135 | - when preparing product in product type model, can set `product.custom_price` 136 | - register quote totals collector in sales.xml. Edit given address_total. 137 | Examples: 138 | * `$total->setGrandTotal($total->getGrandTotal() - $pointsCurrencyAmountUsed);` 139 | * call `$total->addTotalAmount(code, value)`. Grand total will then be sum of all total amounts - they can be negative. 140 | 141 | ## How can you add a custom totals model or modify existing totals models? 142 | - add quote totals collector - declare in sales.xml, implement `collect` and `fetch` 143 | + in collect can modify address totals object (preferred), or edit directly quote or quote address via shipping assignment 144 | + fetch can return null, or one or many declarations in array format or ready Address\Total objects 145 | + fetch can rewrite other totals with same 'code' 146 | + in cart block JS config, add view model and implement getPureValue, getValue. Can read values from model quote.getTotals().totals_segments 147 | - Render totals in admin: 148 | + sales_invoice_view, referenceBlock 'invoice_totals', add block with method `initTotals`, call parentBlock.`addTotal` 149 | -------------------------------------------------------------------------------- /8. Customizing the Checkout Process/3. Demonstrate ability to customize the shopping cart.md: -------------------------------------------------------------------------------- 1 | # Demonstrate ability to customize the shopping cart 2 | 3 | ## Describe how to implement shopping cart rules. 4 | DB tables: 5 | - `salesrule` 6 | * name, description, is_active, is_rss 7 | * from_date, to_date, conditions_serialized, is_advanced, sort_order, product_ids 8 | * actions_serialized, simple_action, discount_amount, discount_qty, discount_step, apply_to_shipping 9 | * stop_rules_processing, uses_per_customer, times_used 10 | * coupon_type, use_auto_generation 11 | - `salesrule_label` - label translation 12 | * rule_id, store_id, label 13 | - `salesrule_product_attribute` 14 | * rule_id, website_id, customer_group_id, attribute_id 15 | 16 | Multiple selection: 17 | - `salesrule_website` 18 | - `salesrule_customer_group` 19 | 20 | - `salesrule_coupon` 21 | * rule_id, code, usage_limit, usage_per_customer, times_used, expiration_date, is_primary, created_at, type 22 | 23 | Usage tracking: 24 | - `salesrule_coupon_usage` 25 | * coupon_id, customer_id, times_used 26 | - `salesrule_customer` 27 | * rule_id, customer_id, times_used 28 | - `salesrule_coupon_aggregated`, `salesrule_coupon_aggregated_updated`, `salesrule_coupon_aggregated_order` - reporting stuff 29 | 30 | Notes: 31 | - ui component `sales_rule_form` 32 | - conditions - html content block - *generic form* Edit\Tab\Conditions 33 | - conditions fieldset renderer \Magento\Rule\Block\Conditions.render 34 | - `$element->getRule()->getConditions()->asHtmlRecursive();` 35 | - Magento\SalesRule\Model\Rule extends Magento\Rule\Model\AbstractModel 36 | + store labels 37 | + coupon generation 38 | - Conditions model instance - \Magento\SalesRule\Model\Rule\Condition\Combine, extends getNewChildSelectOptions: 39 | + \Magento\SalesRule\Model\Rule\Condition\Product\Found - Product attribute combination 40 | + \Magento\SalesRule\Model\Rule\Condition\Product\Subselect - Products subselection 41 | + Conditions combination - self, recursion 42 | + \Magento\SalesRule\Model\Rule\Condition\Address - Cart Attribute: 43 | base_subtotal, total_qty, weight, shipping_method, postcode, region, region_id, country_id 44 | 45 | what is: 46 | - `is_advanced` column - not used 47 | - `product_ids` column - not used 48 | - `salesrule_product_attribute` table - attributes currently used in conditions and actions of rule. 49 | 50 | Used in plugin to \Magento\Quote\Model\Quote\Config::`getProductAttributes` to ensure all needed 51 | attributes are loaded with quote. 52 | * quote.load() 53 | * quoteResource.`_assignProducts` 54 | * product collection `->addAttributeToSelect($this->_quoteConfig->getProductAttributes())`. 55 | 56 | 57 | ## What is the difference between sales rules and catalog rules? 58 | - Catalog rules update product final price, they are visible in all catalog - category listing, product page. 59 | - Sales rule is visible only in checkout and can require a coupon. 60 | - Sales rules can affect shipping price 61 | - Sales rules can trigger on products combination 62 | 63 | 64 | ## How do sales rules affect performance? 65 | Performance is not affected, all calculation happens in totals collectors. 66 | 67 | ## What are the limitations of the native sales rules engine? 68 | Only one coupon can be applied. 69 | 70 | 71 | Implementing rules: 72 | - Rule model 73 | * getConditionsInstance - will be used in form as `$model->getConditions()->asHtmlRecursive()`. 74 | Usually starting conditions are composite - selector [all/any] and sub-conditions selection. 75 | - Composite conditions model 76 | * getNewChildSelectOptions - used by Consition\Combine.getNewChildElement 77 | 78 | 79 | ## Describe add-to-cart logic in different scenarios. 80 | 81 | ### Add to cart 82 | cart model - used only on frontend, marked deprecated. 83 | 84 | - checkout/cart/add [product, qty, related_product] 85 | * cart model.addProduct 86 | + filter request here, can register own via argument for \Magento\Checkout\Model\Cart\RequestInfoFilter 87 | + by default filtered out `form_key` and `custom_price` 88 | * quote.addProduct 89 | * product type instance.`prepareForCartAdvanced` 90 | * get same quote item or create new 91 | * \Magento\Quote\Model\Quote\Item\Processor::prepare 92 | + quote item.addQty 93 | + support request.customPrice -> quote item.customPrice 94 | * event `checkout_cart_product_add_after` 95 | * checkout session.setLastAddedProductId 96 | * cart.save 97 | + quote collect totals 98 | + event `checkout_cart_save_after` 99 | + reinitialize state - remove addresses and payments 100 | * event `checkout_cart_add_product_complete` 101 | 102 | - checkout/cart/updatePost 103 | * cart.suggestItemsQty => \Magento\CatalogInventory\Model\StockStateProvider::suggestQty - qty increments, min/max qty 104 | * cart.updateItems 105 | + events `checkout_cart_update_items_before`, `checkout_cart_update_items_after` 106 | + quote item.setQty -- triggers stock validation 107 | * cart.save 108 | 109 | ### *controller checkout/cart/add*: 110 | - cart.addProduct: 111 | * quote.addProduct: type.`prepareForCartAdvanced`, event `sales_quote_product_add_after` 112 | * event `checkout_cart_product_add_after` 113 | - event `checkout_cart_add_product_complete` -- this event only when normal add to cart 114 | * withlist observer, marked to be deleted 115 | 116 | 117 | ### *add to withlist* `wishlist/index/add`: 118 | - same buy request 119 | - cart candidates = type instance.processConfiguration -> `_prepareProduct`. Same as with add to cart. 120 | - create/update qty withlist item, just like quote item 121 | - event `wishlist_product_add_after` 122 | - event `wishlist_add_product` 123 | 124 | ### *add to cart from wishlist* `withlist/index/cart`: 125 | 126 | Can override original buyRequest when adding to cart. 127 | NO event _checkout_cart_add_product_complete_. 128 | 129 | - load withlist item and options 130 | - merge buy request - saved and current. 131 | - wishlist item.addToCart 132 | - `cart.addProduct` with merged buyRequest: 133 | * events `sales_quote_product_add_after`, `checkout_cart_product_add_after` 134 | - cart.save 135 | * event `checkout_cart_save_before` 136 | * event `checkout_cart_save_after` 137 | - quote.collectTotals 138 | - \Magento\Wishlist\Helper\Data::calculate 139 | * write to customer session 140 | * event `wishlist_items_renewed` 141 | 142 | ### *merge quotes* controller `customer/account/loginPost`: 143 | 144 | Cart model is not used at all. 145 | 146 | - \Magento\Customer\Model\Session::setCustomerDataAsLoggedIn 147 | - event `customer_login` 148 | - \Magento\Checkout\Observer\LoadCustomerQuoteObserver 149 | - checkout session.`loadCustomerQuote`: 150 | * event `load_customer_quote_before` 151 | * finds existing customer quote 152 | * detects that guest quote <> customer quote 153 | * customer quote.`merge`(guest quote): 154 | + event `sales_quote_merge_before` 155 | + for each quote item.`compare` - product id, options same 156 | + if items same, qty++ 157 | + if guest item is new, clone quote item, quote.addItem, event `sales_quote_add_item` 158 | + event `sales_quote_merge_after` 159 | * *delete guest quote* 160 | 161 | ### *reorder* controller `sales/order/reorder`: 162 | 163 | Like normal add to cart, but NO event _checkout_cart_add_product_complete_. 164 | 165 | - load order, ensure can view 166 | - for each order item, `cart.addOrderItem` - converts order item to quote item: 167 | * load product 168 | * get item option buyRequest 169 | * `cart.addProduct` with old buy request 170 | + events `sales_quote_product_add_after`, `checkout_cart_product_add_after` 171 | 172 | 173 | ## Describe how to customize the process of adding a product to the cart. 174 | - plugin over product type `prepareForCartAdvanced` 175 | - event `catalog_product_type_prepare_full_options` - custom options in `_prepareOptions` 176 | - plugin over \Magento\Quote\Model\Quote\Item\Processor::prepare - qty, custom price 177 | - event `checkout_cart_product_add_after` 178 | 179 | ## Which different scenarios should you take into account? 180 | - add to cart from catalog 181 | - add to cart from wishlist 182 | - move all wishlist to cart 183 | - merge quote when existing customer has quote, then shops as a guest and logs in 184 | - admin create order 185 | - admin reorder 186 | - configure added product - change custom options 187 | 188 | 189 | ## Render product types 190 | checkout cart index: 191 | - `` 192 | - `` 193 | 194 | - block \Magento\Checkout\Block\Cart\AbstractCart::getItems - quote visible items 195 | * can limit with *pagination* 196 | * *custom_items* can override 197 | - abstract cart.`getItemHtml`(quote item) 198 | - render via abstract cart.`getItemRenderer` by product type 199 | * renderer child block `renderer.list` \Magento\Framework\View\Element\RendererList 200 | * renderer list block.getRenderer(type, template) 201 | * finds child block by alias = product type 202 | * \Magento\Checkout\Block\Cart\Item\Renderer 203 | * 204 | 205 | Customizations: 206 | - provide cart block data arguments ``: 207 | * `renderer_list_name` - use custom renderer list 208 | * `overriden_templates` - array by product type 209 | * `renderer_template` - regardless of product type 210 | - in `checkout_cart_item_renderers`, ``, add child blocks by alias = product type. E.g. 211 | 212 | ```xml 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | ``` 222 | 223 | ## Describe the available shopping cart operations. 224 | 225 | ### Configure product in cart - controller `checkout/cart/configure` 226 | Product view and product configure are rendered via same helper 227 | \Magento\Catalog\Helper\Product\View.prepareAndRender. 228 | 229 | The only difference is params: 230 | - category_id - can be `false` 231 | - configure_mode - optional 232 | - buy_request - optional 233 | - specify_options - error message if missing options 234 | 235 | *Normal product view*: 236 | - params[category_id] = current 237 | - params[specify_options] - from product type 238 | - helper product view.prepareAndRender 239 | * \Magento\Catalog\Helper\Product::initProduct - load product, check visible/enabled 240 | + event `catalog_controller_product_init_before` 241 | + event `catalog_controller_product_init_after` 242 | * event `catalog_controller_product_view` 243 | 244 | *Configure product*: 245 | - params[`buyRequest`] = quote item.buyRequest 246 | - helper product view.prepareAndRender 247 | * product helper.initProduct 248 | * product helper.`prepareProductOptions` - set default selections from buy request, e.g. selected size=XXL 249 | + product.processBuyRequest, `product type.processBuyRequest`, `product type.checkProductConfiguration` 250 | + product.`setPreconfiguredValues` 251 | * product.setConfigureMode -- used to show all hidden customization options instantly in edit mode 252 | * event `catalog_controller_product_view` 253 | 254 | product.getPreconfiguredValues is later used: 255 | - default product qty = product.preconfigured values.qty 256 | - custom option values = product.preconfigured values.option_{$id} 257 | 258 | ### How do you add a field to the shipping address? 259 | 260 | Quote address extends AbstractExtensibleModel, and so should implement `getCustomAttributesCodes()` 261 | to add custom attributes. Custom attributes should load and save automatically, assuming you added 262 | a column in migration and registered custom attribute in plugin (see below). 263 | 264 | Quote address custom attributes: 265 | * community \Magento\Quote\Model\Quote\Address\CustomAttributeList - empty, use plugin to add 266 | * EE \Magento\CustomerCustomAttributes\Model\Quote\Address\CustomAttributeList::getAttributes 267 | + customer address attributes + customer attributes 268 | 269 | Alternatively, you can always register extension attribute and load/save it manually. 270 | -------------------------------------------------------------------------------- /9. Sales Operations/9.1 Demonstrate ability to customize sales operations.md: -------------------------------------------------------------------------------- 1 | ## 9.1 Demonstrate ability to customize sales operations 2 | 3 | Describe how to modify order processing and integrate it with a third-party ERP system. 4 | 5 | *Describe how to modify order processing flow. How would you add new states and statuses for an order?* 6 | 7 | *How do you change the behavior of existing states and statuses?* 8 | 9 | Described how to customize invoices. 10 | 11 | *How would you customize invoice generation, capturing, and management?* 12 | 13 | Describe refund functionality in Magento. 14 | 15 | *Which refund types are available, and how are they used?* 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | Magento 2 Certified Professional Developer notes 4 | 5 | # 1. Topics 6 | 7 | 1. 18% 1 - Magento Architecture and Customization Techniques (11 questions) 8 | - modules, config, di, plugins, events, cron, cli, cache 9 | 2. 12% 2 - Request Flow Processing (7 questions) 10 | - modes, front contr., url, rewrite, action contr., response, routes, 404, layout, page layout 11 | 3. 10% 3 - Customizing the Magento UI (6 questions) 12 | - theme, template, block, block cache, JS, UI component (briefly) 13 | 4. 7% 4 - Working with Databases in Magento (4 questions) 14 | - repository, api data class, search criteria, table, load/save, collection, select, migration 15 | 5. 8% 5 - Using the Entity-Attribute-Value (EAV) Model (5 questions) 16 | - hierarchy, storage, load/save, attributes, frontend/source/backend 17 | 6. 10% 6 - Developing with Adminhtml (6 questions) 18 | - form, grid, system.xml, menu, acl 19 | 7. 12% 7 - Customizing the Catalog (7 questions) 20 | - product types, price, price render, category, catalog rules 21 | 8. 13% 8 - Customizing the Checkout Process (8 questions) 22 | - cart rule, add to cart, quote totals, product type render, shipping method, payment method 23 | * normal/wishlist/reorder/quote merge 24 | 9. 5% 9 - Sales Operations (3 questions) 25 | - order processing, status, invoice, refund 26 | 10. 5% 10 - Customer Management (3 questions) 27 | - my account, customer extension attributes, address, customer group, tax 28 | 29 | 30 | ### 1.2 Magento 2 Certified Professional Developer 31 | 32 | + 60 Multiple Choice items 33 | + 90 minutes to complete the exam 34 | + A score of 64% or higher is needed to pass the Magento 2 Certified Professional Developer exam 35 | + Based on Magento Open Source (2.2) and Magento Commerce (2.2), but applicable to those using any version of Magento 2. 36 | 37 | # 2. Resources 38 | 39 | ## 2.1. Links 40 | + [Magento 2 Certified Professional Developer](https://u.magento.com/magento-2-certified-professional-developer) 41 | + [Magento 2.2 Developer Documentation](http://devdocs.magento.com/) 42 | + [Markdown Cheatsheet](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet) 43 | + [Markdown Cheatsheet RU](https://github.com/sandino/Markdown-Cheatsheet) 44 | + [Swiftotter Study Guide](https://swiftotter.com/technical/certifications/magento-2-certified-developer-study-guide) 45 | 46 | ## 2.2. Credits 47 | 48 | Inspired by https://github.com/colinmurphy/magento-exam-notes 49 | -------------------------------------------------------------------------------- /xx. Magento Commerce Features/Customer segments.md: -------------------------------------------------------------------------------- 1 | # Customer Segments 2 | 3 | Conditions: 4 | 5 | - customer address attributes 6 | - customer attributes 7 | - cart item, total qty, totals 8 | - products in cart, in wishlist, viewed products, ordered products, viewed date, ordered date 9 | - order address, totals, number of orders, average total amount, order date, order status - only logged in 10 | 11 | Listens all model evetns, matches conditions by \Magento\CustomerSegment\Model\Segment\Condition\Order\Address::getMatchedEvents 12 | 13 | 14 | ## Database 15 | 16 | - magento_customersegment_segment - name, conditions, actions 17 | - magento_customersegment_website - many-to-many 18 | - magento_customersegment_customer - website, added/updated date 19 | - magento_customersegment_event - after saving segment rule, holds events that related to selected conditions 20 | 21 | 22 | ## Admin conditions 23 | 24 | namespace Magento\CustomerSegment; 25 | 26 | Admin form - old widget style tabs: 27 | 28 | - Block\Adminhtml\Customersegment\Edit\Tab\General 29 | - Block\Adminhtml\Customersegment\Edit\Tab\Conditions 30 | 31 | Rendering conditions form: 32 | 33 | render conditions -> renderer \Magento\Rule\Block\Conditions -> segment.getConditions -> root Model\Segment\Condition\Combine\Root 34 | 35 | 36 | Condition classes - hold matching rules: 37 | 38 | - Model\Condition\Combine\AbstractCombine 39 | - Model\Segment\Condition\Order\Address - order_address join sales_order join customer_entity 40 | - Model\Segment\Condition\Sales\Combine 41 | - Model\Segment\Condition\Sales\Ordersnumber - COUNT(*) 42 | - Model\Segment\Condition\Sales\Salesamount - sum/avg sales_order.base_grand_total 43 | - Model\Segment\Condition\Sales\Purchasedquantity - sum/avg sales_order.total_qty_ordered 44 | - Model\Segment\Condition\Customer\Address 45 | - Model\Segment\Condition\Customer\Address\DefaultAddress 46 | 47 | 48 | ## Refresh Segment Data 49 | 50 | Some conditions don't work for guest visitors, despite description! Example - cart grand total amount: 51 | 52 | - click "Refresh" inside segment admin 53 | - Controller\Adminhtml\Report\Customer\Customersegment\Refresh::execute 54 | - Model\Segment::matchCustomers 55 | - Model\ResourceModel\Segment::aggregateMatchedCustomers 56 | - Model\ResourceModel\Segment::processConditions 57 | - Model\Segment\Condition\Combine\Root::getSatisfiedIds 58 | - // individual conditions 59 | - Model\Segment\Condition\Shoppingcart\Amount::getSatisfiedIds 60 | 61 | ```SQL 62 | SELECT `quote`.`customer_id` FROM `quote` 63 | WHERE (quote.is_active=1) AND (quote.store_id IN('1')) AND (quote.base_grand_total >= '100') AND (customer_id IS NOT NULL) 64 | ``` 65 | 66 | 67 | ## Viewed product index 68 | 69 | Condition by recently viewed events **SEEMS BROKEN** when full page cache is enabled. 70 | This condition relies on `report_viewed_product_index` table, which is handled by report module. 71 | 72 | Configuration: 73 | 74 | - `reports/options/enabled` 75 | - `reports/options/product_view_enabled` 76 | 77 | \Magento\Reports\Model\ReportStatus::isReportEnabled 78 | 79 | \Magento\Reports\Model\Product\Index\Viewed 80 | 81 | `catalog_controller_product_view` -> \Magento\Reports\Observer\CatalogProductViewObserver 82 | - save into `report_viewed_product_index` 83 | - save into `report_event`, take customer from session 84 | 85 | This does not work when full page cache is enabled and Varnish returns HTML directly. 86 | 87 | 88 | ## Listening to events and updating matched customers 89 | 90 | Segments needs to listen to some events related to conditions. 91 | 92 | After segment with conditions is saved, table `magento_customersegment_event` holds events that it listens to: 93 | 94 | - Model\Segment::beforeSave 95 | - Model\Segment::collectMatchedEvents 96 | - recursively for each added conditions children: 97 | - Model\Condition\Combine\AbstractCombine::getMatchedEvents 98 | 99 | 100 | Segment rule table holds rendered `condition_sql`: 101 | 102 | - Model\Segment::beforeSave 103 | - Model\Rule\Condition\Combine\Root::getConditionsSql 104 | - recursively for each added conditions children: 105 | - Model\Condition\Combine\AbstractCombine::getConditionsSql 106 | - `_prepareConditionsSql` 107 | 108 | 109 | `condition_sql` column placeholders: 110 | - :customer_id 111 | - :website_id 112 | - :quote_id 113 | - :visitor_id 114 | 115 | Updating customer segments on events: 116 | 117 | - event `customer_login` 118 | - Observer\ProcessEventObserver::execute 119 | - Model\Customer::processEvent 120 | - Model\Customer::getActiveSegmentsForEvent 121 | - Model\ResourceModel\Segment\Collection::addEventFilter - join table `magento_customersegment_event` 122 | - Model\Customer::_processSegmentsValidation 123 | - Model\Segment::validateCustomer 124 | * visitor_id, quote_id passed for guest 125 | - Model\Segment\Condition\Combine\Root::isSatisfiedBy 126 | * all conditions[].isSatisfiedBy 127 | * all conditions[].Model\Condition\Combine\AbstractCombine::getConditionsSql 128 | 129 | 130 | Result: 131 | 132 | - Matched segments are added to `magento_customersegment_customer` 133 | - http context \Magento\CustomerSegment\Helper\Data::CONTEXT_SEGMENT = 'customer_segment' is updated with new segments 134 | - customerSession.setCustomerSegmentIds 135 | 136 | 137 | Event -> interested segment rules. 138 | 139 | 140 | ## Impact on performance 141 | 142 | 1. Customer segments are added to full page cache keys 143 | 144 | \Magento\CustomerSegment\Helper\Data::CONTEXT_SEGMENT = 'customer_segment' 145 | 146 | \Magento\PageCache\Model\App\Response\HttpPlugin::beforeSendResponse 147 | \Magento\Framework\App\Response\Http::sendVary 148 | \Magento\Framework\App\Http\Context::getVaryString 149 | 150 | Example: 151 | 152 | ```php 153 | sha1($this->serializer->serialize([ 154 | 'customer_group' => '1', 155 | 'customer_logged_in' => true, 156 | 'customer_segment' => ["1"] 157 | ])) 158 | ``` 159 | 160 | This lowers chances of hitting cached page, increasing load on the server. 161 | 162 | 1. Listens to many object save events, calculates matching conditions 163 | 164 | 165 | ## DepersonalizePlugin 166 | 167 | Does 2 things: 168 | 169 | 1. Makes sure customerSession.CustomerSegmentIds is not deleted by Customer module DepersonalizePlugin 170 | 1. Sets full page cache key httpContext->setValue(Data::CONTEXT_SEGMENT) 171 | 172 | 173 | \Magento\Framework\Pricing\Render\Layout::loadLayout: 174 | ``` 175 | $this->layout->getUpdate()->load(); 176 | // 1. \Magento\Customer\Model\Layout\DepersonalizePlugin::beforeGenerateXml: 177 | // - remember customerGroupId and formKey from session 178 | // 2. \Magento\CustomerSegment\Model\Layout\DepersonalizePlugin::beforeGenerateXml: 179 | // - remember customerSegmentIds from customer session 180 | $this->layout->generateXml(); 181 | $this->layout->generateElements(); 182 | // 3. \Magento\PageCache\Model\Layout\DepersonalizePlugin::afterGenerateElements: 183 | // - event `depersonalize_clear_session` 184 | // - session_write_close(); 185 | // - clear message session 186 | // 4. \Magento\Customer\Model\Layout\DepersonalizePlugin::afterGenerateElements: 187 | // - clear customer session 188 | // - restore session._form_key 189 | // - restore customer session.customerGroupId, empty customer object with customer group 190 | // 5. \Magento\Catalog\Model\Layout\DepersonalizePlugin::afterGenerateElements: 191 | // - clear catalog session 192 | // 6. \Magento\Persistent\Model\Layout\DepersonalizePlugin::afterGenerateElements: 193 | // - clear persistent session 194 | // 7. \Magento\CustomerSegment\Model\Layout\DepersonalizePlugin::afterGenerateElements: 195 | // - httpContext->setValue(Data::CONTEXT_SEGMENT) 196 | // - restore customerSession->setCustomerSegmentIds 197 | // 8. \Magento\Checkout\Model\Layout\DepersonalizePlugin::afterGenerateElements: 198 | // - clear checkout session 199 | ``` 200 | 201 | ## Create segment programmatically 202 | 203 | Doesn't have API interfaces, repository. 204 | 205 | ```PHP 206 | /** @var \Magento\CustomerSegment\Model\Segment $segment */ 207 | $segment = $this->segmentFactory->create(); 208 | $segment->setName($name); 209 | $segment->setDescription(null); 210 | $segment->setIsActive(1); 211 | $segment->setWebsiteIds(array_keys($this->storeManager->getWebsites())); 212 | $segment->setApplyTo(\Magento\CustomerSegment\Model\Segment::APPLY_TO_VISITORS_AND_REGISTERED); 213 | // @see \Magento\Rule\Model\AbstractModel::loadPost 214 | $segment->getConditions()->setConditions([])->loadArray([ 215 | 'type' => \Magento\CustomerSegment\Model\Segment\Condition\Combine\Root::class, 216 | 'aggregator' => 'all', 217 | 'operator' => '', 218 | 'value' => '1', 219 | 'conditions' => [ 220 | [ 221 | 'type' => \Magento\CustomerSegment\Model\Segment\Condition\Shoppingcart\Amount::class, 222 | 'attribute' => 'grand_total', 223 | 'operator' => '>=', 224 | 'value' => '100', 225 | ] 226 | ], 227 | ]); 228 | $segment->save(); 229 | ``` 230 | 231 | 232 | ## Catalog frontend action 233 | 234 | Magento has alternative implementation of recently viewed products, handled by Catalog module. 235 | It works with full page cache, but: 236 | 237 | - is **not integrated** into customer segment conditions 238 | - frontend synchronization must be enabled, adding extra calls on every page 239 | 240 | When sync is enabled, it makes 2 AJAX calls with every page opening: 241 | - synchronize recently viewed products from local storage 242 | - synchronize recently compared products from local storage 243 | 244 | Configuration: 245 | 246 | - `catalog/recently_products/synchronize_with_backend` = 0 247 | - `catalog/recently_products/recently_viewed_lifetime` = 1000 248 | - `catalog/recently_products/scope` - store view, store, website 249 | 250 | Other: 251 | 252 | - db table `catalog_product_frontend_action` 253 | - cron `catalog_product_frontend_actions_flush` - every minute \Magento\Catalog\Cron\FrontendActionsFlush: 254 | - deletes compared, viewed older than (default 1000) seconds 255 | - frontend controller 'catalog/product_frontend_action/synchronize' 256 | 257 | Notable classes: 258 | 259 | - Magento\Catalog\Model\FrontendStorageConfigurationPool 260 | - Magento\Catalog\Model\Widget\RecentlyViewedStorageConfiguration 261 | 262 | 263 | \Magento\Catalog\Block\FrontendStorageManager: 264 | 265 | - recently_viewed_product 266 | - recently_compared_product 267 | - product_data_storage 268 | - vendor/magento/module-catalog/view/frontend/web/js/storage-manager.js 269 | 270 | customer data section: 271 | 272 | - recently_viewed_product 273 | - recently_compared_product 274 | - product_data_storage 275 | 276 | Triggering recently viewed update on frontend: 277 | 278 | - catalog_product_view.xml 279 | - Magento\Catalog\Block\Ui\ProductViewCounter 280 | - vendor/magento/module-catalog/view/frontend/templates/product/view/counter.phtml 281 | - vendor/magento/module-catalog/view/frontend/web/js/product/view/provider.js 282 | 283 | \Magento\Catalog\Model\ProductRender - DTO 284 | 285 | \Magento\Catalog\Ui\DataProvider\Product\ProductRenderCollectorComposite::collect: 286 | 287 | - Url - url, addToCartButton, addToCompareButton 288 | - AdditionalInfo - isSalable, type, name, id 289 | - Image - images 290 | - Price - priceInfo 291 | - ... wishlist, review, tax, gift card, bundle price 292 | 293 | 294 | widget `catalog_recently_compared` template `product/widget/compared/list.phtml`: 295 | 296 | - UI component `widget_recently_compared` - listing, columns 297 | - Magento_Catalog/js/product/provider-compared.js 298 | - Magento_Catalog/js/product/storage/storage-service.js 299 | - Magento_Catalog/js/product/storage/data-storage.js - get product data from customer section. recently viewed, compared 300 | -------------------------------------------------------------------------------- /xx. Magento Commerce Features/Staging.md: -------------------------------------------------------------------------------- 1 | # Magento 2 Staging 2 | 3 | ## High level staging implementation overview 4 | 5 | - Future editions of products/categories etc. are saved as a copy with a timestamped from-to columns. 6 | - Current staging version is saved as a flag, used in all collections to add WHERE condition 7 | - You can browse (preview) frontend site seeing future versions passing version and signature GET parameters 8 | 9 | 10 | ## Demonstrate an understanding of the events processing flow 11 | 12 | Influence of Staging on the event processing. 13 | 14 | What is a modification of the event processing mechanism introduced by the staging module? 15 | 16 | 17 | \Magento\Staging\Model\Event\Manager: 18 | 19 | - ban some events in preview mode - none by default 20 | - ban some observers in preview mode 21 | 22 | - `catalog_category_prepare_save` 23 | - `catalog_category_save_after` 24 | - `catalog_category_save_before` 25 | - `catalog_category_move_after` 26 | 27 | 28 | ## DB collection flag `disable_staging_preview` 29 | 30 | - connection->select() 31 | - \Magento\Framework\DB\Adapter\Pdo\Mysql::select 32 | - Magento\Framework\DB\SelectFactory::create 33 | - Magento\Framework\DB\Select\SelectRenderer 34 | - // work with select... 35 | - \Magento\Framework\DB\Select::assemble 36 | - \Magento\Framework\DB\Select\SelectRenderer::render 37 | - \Magento\Framework\DB\Select\FromRenderer 38 | - \Magento\Staging\Model\Select\FromRenderer 39 | 40 | 41 | ``` 42 | from = [ 43 | flag = [ 44 | joinType = 'from' 45 | tableName = 'flag' 46 | ] 47 | ] 48 | ``` 49 | 50 | ``` 51 | $select->where($alias . '.created_in <= ?', $versionId); 52 | $select->where($alias . '.updated_in > ?', $versionId); 53 | ``` 54 | 55 | Only known staged tables are affected - \Magento\Staging\Model\StagingList::getEntitiesTables - `*_entity` 56 | 57 | 58 | ## Staging modification to the Magento database operations (row_id, entity managers) 59 | 60 | Flow modifications introduced by staging (triggers, row_id, data versions). 61 | 62 | - deletes foreign keys to removed entity_id column 63 | - creates foreign key to row_id 64 | - replaces indexes 65 | 66 | **Triggers** created differently! Attributes no longer have entity_id for `_cl` tables, so one more 67 | extra call to load entity_id by row_id is added to each update by schedule trigger. 68 | 69 | \Magento\Framework\Mview\View\Subscription::buildStatement 70 | vs 71 | \Magento\CatalogStaging\Model\Mview\View\Attribute\Subscription::buildStatement 72 | 73 | 74 | trg_catalog_product_entity_int_after_insert: 75 | ```SQL 76 | -- before 77 | INSERT IGNORE INTO `catalog_product_flat_cl` (`entity_id`) VALUES (NEW.`entity_id`); 78 | -- after 79 | SET @entity_id = (SELECT `entity_id` FROM `catalog_product_entity` WHERE `row_id` = NEW.`row_id`); 80 | INSERT IGNORE INTO `catalog_product_flat_cl` (`entity_id`) values(@entity_id); 81 | ``` 82 | 83 | trg_catalog_product_entity_int_after_update: 84 | ```SQL 85 | -- before 86 | IF (NEW.`value_id` <=> OLD.`value_id` OR NEW.`attribute_id` <=> OLD.`attribute_id` OR NEW.`store_id` <=> OLD.`store_id` OR NEW.`entity_id` <=> OLD.`entity_id` OR NEW.`value` <=> OLD.`value`) THEN INSERT IGNORE INTO `catalog_product_flat_cl` (`entity_id`) VALUES (NEW.`entity_id`); END IF; 87 | -- after 88 | SET @entity_id = (SELECT `entity_id` FROM `catalog_product_entity` WHERE `row_id` = NEW.`row_id`); 89 | IF (NOT(NEW.`value_id` <=> OLD.`value_id`) OR NOT(NEW.`attribute_id` <=> OLD.`attribute_id`) OR NOT(NEW.`store_id` <=> OLD.`store_id`) OR NOT(NEW.`value` <=> OLD.`value`) OR NOT(NEW.`row_id` <=> OLD.`row_id`)) THEN INSERT IGNORE INTO `catalog_product_flat_cl` (`entity_id`) values(@entity_id); END IF; 90 | ``` 91 | 92 | trg_catalog_product_entity_int_after_delete: 93 | ```SQL 94 | -- before 95 | INSERT IGNORE INTO `catalog_product_flat_cl` (`entity_id`) VALUES (OLD.`entity_id`); 96 | -- after 97 | SET @entity_id = (SELECT `entity_id` FROM `catalog_product_entity` WHERE `row_id` = OLD.`row_id`); 98 | INSERT IGNORE INTO `catalog_product_flat_cl` (`entity_id`) values(@entity_id); 99 | ``` 100 | 101 | 102 | ## sequence tables 103 | 104 | Staging migration scripts do this: `row_id` becomes auto_increment, and `entity_id` (`page_id` etc.) column 105 | is no longer auto incremented, but still required! 106 | 107 | Requests like `INSERT INTO cms_page (identified) VALUES("test_page")` would result in error because page_id 108 | is normally auto_increment and not passed in request. 109 | 110 | Magento fixes this by manually generating auto increments for entity_id in separate sequence tables. 111 | 112 | Exmple: 113 | 114 | - \Magento\Framework\Model\ResourceModel\Db\CreateEntityRow::prepareData 115 | - `$output[$metadata->getIdentifierField()] = $metadata->generateIdentifier();` 116 | - \Magento\Framework\EntityManager\Sequence\Sequence::getNextValue 117 | - insert and return next ID into `sequence_*` table 118 | 119 | `sequence_cms_page`, `sequence_catalog_category`, `sequence_catalogrule`, ... 120 | 121 | In community edition (no staging), `$metadata->generateIdentifier()` because sequenceTable is not passed. 122 | 123 | 124 | - \Magento\Framework\EntityManager\MetadataPool::getMetadata 125 | - \Magento\Framework\EntityManager\MetadataPool::createMetadata 126 | - new \Magento\Framework\EntityManager\EntityMetadata 127 | - `sequence` argument: \Magento\Framework\EntityManager\Sequence\SequenceFactory::create: 128 | - no staging - sequence = null 129 | - staging defines `sequenceTable` - `new \Magento\Framework\EntityManager\Sequence\Sequence(connection, sequenceTable)` 130 | 131 | 132 | 133 | ## Common staging plugins 134 | 135 | - FrontControllerInterface::beforeDispatch - validate `___version`, `___timestamp`, `___signature` 136 | - Magento\PageCache\Model\Config::afterIsEnabled - disabled in preview mode 137 | - Magento\Store\Model\BaseUrlChecker - disabled in preview mode 138 | - Magento\Framework\Stdlib\DateTime\Timezone::isScopeDateInInterval - interval never validated in preview mode 139 | - Magento\Store\Model\StoreResolver::getCurrentStoreId - use `___store` in preview mode 140 | - Magento\Customer\Model\Session::regenerateId, destroy - never regenerate/destroy in preview mode 141 | - getUrl - adds `___version`, `___store`, possibly `__timestamp`, `__signature` in preview mode 142 | - disables block_html caching 143 | 144 | 145 | ## Staging-related modifications of the indexing process 146 | 147 | - plugin before Magento\Catalog\Controller\Category\View 148 | - \Magento\CatalogStaging\Model\Plugin\Controller\View::beforeExecute 149 | - \Magento\CatalogStaging\Model\Indexer\Category\Product\Preview::execute 150 | - \Magento\Catalog\Model\Indexer\Category\Product\AbstractAction::reindex 151 | 152 | Reindex using temporary tables with suffix `_catalog_staging_tmp`, 153 | e.g. `catalog_category_product_index_store1_catalog_staging_tmp` 154 | 155 | 156 | ## Save staging update 157 | 158 | \Magento\SalesRuleStaging\Controller\Adminhtml\Update\Save::execute 159 | \Magento\Staging\Model\Entity\Update\Save::execute 160 | 161 | ``` 162 | staging[mode]: save 163 | staging[update_id]: 164 | staging[name]: 3 September 165 | staging[description]: Everything is new 166 | staging[start_time]: 2020-09-03T07:00:00.000Z 167 | staging[end_time]: 168 | ``` 169 | 170 | \Magento\Staging\Model\Entity\Update\Action\Pool::getAction( 171 | entityType = 'Magento\SalesRule\Api\Data\RuleInterface', 172 | namespace = 'save', 173 | actionType = mode = 'save' 174 | ) 175 | 176 | - \Magento\Staging\Model\Entity\Update\Action\Save\SaveAction: 177 | - \Magento\Staging\Model\Entity\Update\Action\Save\SaveAction::createUpdate 178 | - \Magento\Staging\Model\EntityStaging::schedule 179 | - \Magento\CatalogRuleStaging\Model\CatalogRuleStaging::schedule 180 | - validates intersecting updates 181 | - sets `created_in`, saves entity model (catalog rule) 182 | - Magento\CatalogRuleStaging\Model\Rule\Hydrator 183 | - \Magento\CatalogRuleStaging\Model\Rule\Retriever::getEntity - $this->ruleRepository->get($entityId) 184 | 185 | 186 | ## Preview 187 | 188 | All links are automatially updated: 189 | - \Magento\Framework\Url::getUrl 190 | - \Magento\Framework\Url\RouteParamsPreprocessorComposite::execute 191 | - \Magento\Staging\Model\Preview\RouteParamsPreprocessor::execute 192 | - `[_query][___version]=`, `[_query][___store]=` 193 | - `__timestamp=`, `__signature=` 194 | 195 | Example: 196 | 197 | vendor/magento/module-catalog/view/frontend/layout/default.xml: 198 | ```XML 199 | 201 | 202 | 203 | 204 | 205 | 206 | ``` 207 | 208 | - \Magento\Store\Model\Argument\Interpreter\ServiceUrl::evaluate 209 | - https://m24ee.local/rest/default/V1/?___store=default&___version=1599422820&__signature=b5cd66d9ea383ea57356f28cf2dd0f8edeecbc516b4d7982f8aef2ce4bcd4d32&__timestamp=1599165421products-render-info 210 | 211 | 212 | URL examples: 213 | 214 | - REST - `?___version=` 215 | - category - `?update_id=` 216 | 217 | 218 | Preview from admin panel: 219 | 220 | - https://m24ee.local/admin/staging/update/preview/key/.../?preview_store=default&preview_url=https:/m24ee.local/&preview_version=1599116400 221 | - header and iframe 222 | - https://m24ee.local/?___version=1599116400&___store=default&__timestamp=1599077245&__signature=a8f3a05adb92374873a3f9c4e1d36c1bddcc9748b32b046af4264b496243f95d 223 | 224 | 225 | \Magento\Staging\Plugin\Framework\App\FrontController::beforeDispatch - validate signature: 226 | 227 | ``` 228 | __signature = hash(sha256, "__version,__timestamp", secret key from env.php) 229 | ``` 230 | 231 | 232 | ## Set current version 233 | 234 | \Magento\CatalogStaging\Model\Category\DataProvider::getCurrentCategory 235 | 236 | ``` 237 | $updateId = (int) $this->request->getParam('update_id'); 238 | $this->versionManager->setCurrentVersionId($update->getId()); 239 | ``` 240 | 241 | 242 | ## checkout-staging 243 | 244 | - disable place order in preview 245 | - any quote, saved in preview mode, is saved in db table `quote_preview` and deleted by cron 246 | --------------------------------------------------------------------------------