diff --git "a/1. Magento Architecture and Customization Techniques/1. Describe Magento\342\200\231s module-based architecture.md" "b/1. Magento Architecture and Customization Techniques/1. Describe Magento\342\200\231s module-based architecture.md" deleted file mode 100755 index 775852f..0000000 --- "a/1. Magento Architecture and Customization Techniques/1. Describe Magento\342\200\231s module-based architecture.md" +++ /dev/null @@ -1,196 +0,0 @@ -# Magento module overview ->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) -> ->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) -> ->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) - - -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: -1. `module` - Magento module -1. `theme` - admin or frontend themes -1. `language` - language packs - -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. - -### Module registration - ->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) - ->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) - -- How to register modules - registration.php [1] -```php -_', __DIR__); -``` - -- how to register themes - registration.php [3] -```php -//', __DIR__); -``` - - -- composer.json autoload/files[] = "registration.php" [4] -```json - { -"name": "Acme-vendor/bar-component", -"autoload": { - "psr-4": { "AcmeVendor\\BarComponent\\": "" }, - "files": [ "registration.php" ] -} } -``` - -- at what stage - when including vendor/autoload.php [5] -```php - 1. Name and declare the module in the module.xml file. ->1. Declare any dependencies that the module has (whether on other modules or on a different component) in the module’s composer.json file. ->1. (Optional) Define the desired load order of config files and .css files in the module.xml file. -> -> -- [Magento DevDocs - Module dependencies](http://devdocs.magento.com/guides/v2.2/architecture/archi_perspectives/components/modules/mod_depend.html) - -> 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) - -### Dependency types - -##### Hard dependency -> Modules with a hard dependency on another module cannot function without the module it depends on. -Specifically: ->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). ->1. The module contains strings that include class names, method names, class constants, class properties, interfaces, and traits from another module. ->1. The module deserializes an object declared in another module. ->1. The module uses or modifies the database tables used by another module. -> -> -- [Magento DevDocs - Module dependency types](http://devdocs.magento.com/guides/v2.2/architecture/archi_perspectives/components/modules/mod_depend_types.html) - -##### Soft dependency ->Modules with a soft dependency on another module can function properly without the other module, even if it has a dependency upon it. Specifically: ->The module directly checks another module’s availability. ->The module extends another module’s configuration. ->The module extends another module’s layout. -> -> -- [Magento DevDocs - Module dependency types](http://devdocs.magento.com/guides/v2.2/architecture/archi_perspectives/components/modules/mod_depend.html) - -Magento module install order flow: ->1. The module serving as a dependency for another module ->2. The module dependent on it - -Following dependencies should not be created: ->1. Circular (both direct and indirect) ->1. Undeclared ->1. Incorrect -> -> -- [Magento DevDocs - Module dependency types](http://devdocs.magento.com/guides/v2.2/architecture/archi_perspectives/components/modules/mod_depend.html) - - ->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) - -### Magento areas -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. - ->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. -If your extension works in several different areas, ensure it has separate behavior and view components for each area. -Each area declares itself within a module. All resources specific for an area are located within the same module as well. -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. -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. -> -> -- [Magento DevDocs - Modules and areas](http://devdocs.magento.com/guides/v2.2/architecture/archi_perspectives/components/modules/mod_and_areas.html) - -Magento has 5 areas types: -> 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. -> 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. -> 1. Basic (`base`): used as a fallback for files absent in adminhtml and frontend areas. -> 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. -> -> You can also send requests to Magento using the SOAP and REST APIs. These two areas: -> 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. -> 1. Web API SOAP (`webapi_soap`): entry point for this area is index.php or pub/index.php. -> -> -- [Magento DevDocs - Modules and areas](http://devdocs.magento.com/guides/v2.2/architecture/archi_perspectives/components/modules/mod_and_areas.html) - -Not documented but used in code [Magento/Framework/App/Area.php]( -https://github.com/magento/magento2/blob/2.2/lib/internal/Magento/Framework/App/Area.php#L18-L25): -1. Documentation (doc). Deprecated. -1. Admin (admin). Deprecated. - - -# What side effects can come from this interaction? -- error when module is missing or disabled -- error when injecting missing class -- (?) null or error when using object manager for missing class - ReflectionException - Class MissingClass does not exist - objectManager->create() = new $type() or new $type(...args) --> PHP Warning: Uncaught Error: Class 'MissingClass' not found diff --git "a/1. Magento Architecture and Customization Techniques/2. Describe Magento\342\200\231s directory structure.md" "b/1. Magento Architecture and Customization Techniques/2. Describe Magento\342\200\231s directory structure.md" deleted file mode 100755 index e6c9eb8..0000000 --- "a/1. Magento Architecture and Customization Techniques/2. Describe Magento\342\200\231s directory structure.md" +++ /dev/null @@ -1,152 +0,0 @@ -# Determine how to locate different types of files in Magento. - ->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. -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) - -### Where are the files containing JavaScript, HTML, and PHP located? -- view/frontend/web/js -- view/frontend/requirejs-config.js -- view/frontend/layout -- view/frontend/templates - -### Root directory location - ->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) - -##### `/app:` -* Modules - app/code. -* Storefront themes - app/design/frontend. -* Admin themes - app/design/adminhtml. -* Language packages - use app/i18n. - -##### `/vendor` - ->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. -> ->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. -> -> -- [Magento DevDocs - Create your component file structure](https://devdocs.magento.com/guides/v2.2/extension-dev-guide/build/module-file-structure.html) - -### Required files - ->`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. -> ->`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. -> ->`composer.json`: Specifies component dependencies and other metadata. For more information, see Composer integration. -> -> -- [Magento DevDocs - About component file structure](https://devdocs.magento.com/guides/v2.2/extension-dev-guide/prepare/prepare_file-str.html) - -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. -The sequence use for sorting events, plugins, preferences and layouts. - - -### Common directories - -- `Api` - Any PHP classes exposed to the API. -- `Block` - PHP view classes as part of Model View Controller(MVC) vertical implementation of module logic. -- `Console` - Console commands -- `Controller` - PHP controller classes as part of MVC vertical implementation of module logic. -- `Controller/Adminhtml` - Admin controllers -- `Cron` - Cron job classes -- `etc` - Configuration files; in particular, module.xml, which is required. -- `Helper` - Helpers -- `i18n` - Localization files in CSV format -- `Model` - PHP model classes as part of MVC vertical implementation of module logic. -- `Model/ResourceModel` - Database interactions -- `Observer` - Event listeners -- `Plugin` - Contains any needed plug-ins. -- `Setup` - Classes for module database structure and data setup which are invoked when installing or upgrading. -- `Test` - Unit tests -- `Ui` - UI component classes -- `view` - View files, including static view files, design templates, email templates, and layout files. - - view/{area}/email - - view/{area}/layout - - view/{area}/templates - - view/{area}/ui_component - - view/{area}/ui_component/templates - - view/{area}/web - - view/{area}/web/template - - view/{area}/requirejs-config.js - -see [Magento DevDocs - Create your component file structure](https://devdocs.magento.com/guides/v2.2/extension-dev-guide/build/module-file-structure.html) - -### Service contract - -> The service contract of a module is defined by the set of interfaces in the module’s `/Api` directory. -> -> This directory contains: -> -> * Service interfaces in the `Api` namespace of the module ([Catalog API](https://github.com/magento/magento2/tree/2.0/app/code/Magento/Customer/Api)). -> -> * 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. -> -> -- [Service contract anatomy](https://devdocs.magento.com/guides/v2.2/architecture/archi_perspectives/service_layer.html#service-contract-anatomy) - - - -### Theme file structure -Example #1 -``` -├── composer.json -├── theme.xml -├── etc -│ └── view.xml -├── i18n -│ └── en_US.csv -├── LICENSE_AFL.txt -├── LICENSE.txt -├── media -│ └── preview.jpg -├── registration.php -└── web - ├── css - │ ├── email.less - │ ├── print.less - │ ├── source - │ │ ├── _actions-toolbar.less - │ │ ├── _breadcrumbs.less - │ │ ├── _buttons.less - │ │ ├── components - │ │ │ └── _modals_extend.less - │ │ ├── _icons.less - │ │ ├── _layout.less - │ │ ├── _theme.less - │ │ ├── _tooltips.less - │ │ ├── _typography.less - │ │ └── _variables.less - │ ├── _styles.less - │ ├── styles-l.less - │ └── styles-m.less - ├── images - │ └── logo.svg - └── js - ├── navigation-menu.js - ├── responsive.js - └── theme.js -``` - -### Language package file structure -Example #2 -``` -├── de_DE -│ ├── composer.json -│ ├── language.xml -│ ├── LICENSE_AFL.txt -│ ├── LICENSE.txt -│ └── registration.php -├── en_US -│ ├── composer.json -│ ├── language.xml -│ ├── LICENSE_AFL.txt -│ ├── LICENSE.txt -│ └── registration.php -├── pt_BR -│ ├── composer.json -│ ├── language.xml -│ ├── LICENSE_AFL.txt -│ ├── LICENSE.txt -│ └── registration.php -``` - -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) diff --git a/1. Magento Architecture and Customization Techniques/3. Utilize configuration XML and variables scope.md b/1. Magento Architecture and Customization Techniques/3. Utilize configuration XML and variables scope.md deleted file mode 100644 index 999d18e..0000000 --- a/1. Magento Architecture and Customization Techniques/3. Utilize configuration XML and variables scope.md +++ /dev/null @@ -1,163 +0,0 @@ -# Determine how to use configuration files in Magento. Which configuration files correspond to different features and functionality? - -### List of existed *.xml configs -- `acl.xml` - resource title, sort -- `adminhtml/rules/payment_{country}.xml` - paypal -- `address_formats.xml` -- `address_types.xml` - format code and title only -- `cache.xml` - name, instance - e.g. full_page=Page Cache -- `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/) -- `communication.xml` -- `config.xml` - defaults -- `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) -- `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) -- `di.xml` - preference, plugins, virtual type [*](https://devdocs.magento.com/guides/v2.2/extension-dev-guide/build/di-xml-file.html) -- `eav_attributes.xml` - locked entity attributes (global, unique etc.) -- `email_templates.xml` - id label file type module -- view/frontend/email/name.html -- `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) -- `export.xml` -- `extension_attributes.xml` - for, attribute code, attribute type -- `fieldset.xml` -- `import.xml` -- `indexer.xml` - class, view_id, title, description -- `integration.xml` -- `integration/api.xml` -- `integration/config.xml` -- `menu.xml` - admin menu -- `module.xml` - version, sequence -- `mview.xml` - scheduled updates, subscribe to table changes, indexer model -- `page_types.xml` -- `payment.xml` - groups, method allow_multiple_address -- `pdf.xml` - renders by type (invoice, shipment, creditmemo) and product type -- `product_types.xml` - label, model instance, index priority, (?) custom attributes, (!) composable types -- `product_options.xml` -- `resources.xml` -- `routes.xml` -- `sales.xml` - collectors (quote, order, invoice, credit memo) -- `search_engine.xml` -- `search_request.xml` - index, dimensions, queries, filters, aggregations, buckets -- `sections.xml` - action route placeholder -> invalidate customer sections -- `system.xml` - adminhtml config -- `validation.xml` - entity, rules, constraints -> class -- `view.xml` - vars by module -- `webapi.xml` - route, method, service class and method, resources -- `widget.xml` - class, email compatible, image, ttl (?), label, description, parameters -- `zip_codes.xml` - -# Interfaces for work with configs - -#### [\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) -Gets .xsd names from schema locator, gets full .xml file list from file resolver, merges all files, validates, runs converter to get resulting array. -- read(scope) - + `fileResolver->get(_filename)` - + merge and validate each file (if mode developer) - + validate merged DOM - + converter->convert(dom) => array -- `_idAttributes`, `_fileName`, `_schemaFile` (from schemaLocator), `_perFileSchema` (from schemaLocator), filename (menu.xml) -- schemaFile from schemaLocator - -#### [\Magento\Framework\Config\ConverterInterface](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Config/ConverterInterface.php) -Convert an array to any format -- `convert(\DOMDocument $source)` - -#### [\Magento\Framework\Config\SchemaLocatorInterface](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Config/SchemaLocatorInterface.php) - full path to .xsd -- `getPerFileSchema` - per file before merge -- `getSchema` - merged file - -#### [\Magento\Framework\Config\ValidationStateInterface](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Config/ValidationStateInterface.php) - -This interface retrieves the validation state. -- `isValidationRequired()` - -[\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. - -#### [\Magento\Framework\Config\ScopeListInterface](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Config/ScopeListInterface.php) - -This interface the list of all scopes. -- `getAllScopes()` - -#### [\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) - -Helps to get the configuration data in a specified scope. -- `merge(array $config);` -- `get($key, $default = null)` - - -###### Links and examples: -- 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) -- 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) -- https://www.atwix.com/magento-2/working-with-custom-configuration-files/ - - -### Configuration load and merge flow - -#### Loading order - 1. First step is a `app/etc/di.xml` loading - 1. Next step is collecting configuration files from all modules and merging them: `vendor_name/component_name/etc/*.xml` - 1. At the last step Magento will collect all configs from `vendor_name/component_name/etc//*.xml` - -#### Merge flow - ->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. -> -> * 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. -> * If node identifiers are not equal, the node is a new child of the parent node. -> * If the original document has multiple nodes with the same identifier, an error is triggered because the identifiers cannot be distinguished. -> * After configuration files are merged, the resulting document contains all nodes from the original files. -> -> -- [Magento DevDocs - Module configuration files](https://devdocs.magento.com/guides/v2.2/config-guide/config/config-files.html) - -config merger = \Magento\Framework\Config\Dom -- when merging each file - + createConfigMerger|merge - + \Magento\Framework\Config\Dom::_initDom(dom, perFileSchema) - + \Magento\Framework\Config\Dom::validateDomDocument - + $dom->schemaValidate -- after all merged - + \Magento\Framework\Config\Dom::validate(mergedSchema) - + \Magento\Framework\Config\Dom::validateDomDocument - -## Sensitive and environment settings - -This scope is a very huge part which includes a lot of things and there is a short list of useful links -to the official Magento DevDocs documentation: - -- [Set configuration values](https://devdocs.magento.com/guides/v2.2/config-guide/cli/config-cli-subcommands-config-mgmt-set.html) -- [Sensitive and system-specific](https://devdocs.magento.com/guides/v2.2/config-guide/prod/config-reference-sens.html) -- [Magento Enterprise B2B Extension configuration paths reference](https://devdocs.magento.com/guides/v2.2/config-guide/prod/config-reference-b2b.html) -- [Other configuration paths reference](https://devdocs.magento.com/guides/v2.2/config-guide/prod/config-reference-most.html) - -### Example of how to set sensitive settings - -- shared config app/etc/config.php -- sensitive or system-specific app/etc/env.php: - -```xml - - - - - 1 - - - - - 1 - - - - -``` - - Sensitive info doesn't get exported with `bin/magento app:config:dump`. use env. params, e.g. -`CONFIG__DEFAULT__PAYMENT__TEST__PASWORD` for `payment/test/password` - -`bin/magento app:config:dump`: - -- system-specific > app/etc/env.php -- shared > app/etc/config.php -- sensitive - skipped - -`bin/magento config:sensitive:set`: - -- writes to app/etc/env.php diff --git a/1. Magento Architecture and Customization Techniques/4. Demonstrate how to use dependency injection.md b/1. Magento Architecture and Customization Techniques/4. Demonstrate how to use dependency injection.md deleted file mode 100644 index d3c7c92..0000000 --- a/1. Magento Architecture and Customization Techniques/4. Demonstrate how to use dependency injection.md +++ /dev/null @@ -1,58 +0,0 @@ -# Demonstrate how to use dependency injection - -Magento 2 uses constructor injection, where all injected objects or factories are provided when the object is constructed. - -Magento loads di.xml files and merges them all together from the following stages: -* Initial (app/etc/di.xml) -* Global ({moduleDir}/etc/di.xml) -* Area-specific ({moduleDir}/etc/{area}/di.xml) - -### Object manager - -Under the hood, the [ObjectManager](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/ObjectManager/ObjectManager.php) -will use PHP reflection features to look at a class’s __construct type hints/parameters, -automatically instantiate the object for us, and then pass it into the constructor as an argument. - -[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) -and their implementations use to resolve arguments and create new objects. - -By default, all objects created via automatic constructor dependency injection are singleton objects, -because they created via ObjectManager::get() method. - -``` -if ($isShared) { - $argument = $this->objectManager->get($argumentType); -} else { - $argument = $this->objectManager->create($argumentType); -} -``` -[\Magento\Framework\ObjectManager\Factory::resolveArgument()](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/ObjectManager/Factory/AbstractFactory.php#L143-L147) - -### Arguments - -``` - {typeName} - {typeName} - - {strValue} - {strValue} - - {boolValue} - {numericValue} - {Constant::NAME} - {Constant::NAME} - - - - someVal - -``` - -###### Links -- [Dependency injection](https://devdocs.magento.com/guides/v2.2/extension-dev-guide/depend-inj.html) -- [The di.xml file](https://devdocs.magento.com/guides/v2.2/extension-dev-guide/build/di-xml-file.html) -- [ObjectManager](https://devdocs.magento.com/guides/v2.2/extension-dev-guide/object-manager.html) -- [Proxies](https://devdocs.magento.com/guides/v2.2/extension-dev-guide/proxies.html) -- [Factories](https://devdocs.magento.com/guides/v2.2/extension-dev-guide/factories.html) -- [Alan Storm, The Magento 2 Object System](https://alanstorm.com/category/magento-2/#magento-2-object-system) -- [Alan Kent, Magento 2 Dependency injection](https://alankent.me/2014/06/07/magento-2-dependency-injection-the-m2-way-to-replace-api-implementations/) diff --git a/1. Magento Architecture and Customization Techniques/5. Demonstrate ability to use plugins.md b/1. Magento Architecture and Customization Techniques/5. Demonstrate ability to use plugins.md deleted file mode 100644 index 33ee2a7..0000000 --- a/1. Magento Architecture and Customization Techniques/5. Demonstrate ability to use plugins.md +++ /dev/null @@ -1,210 +0,0 @@ -# Demonstrate ability to use plugins - -There are three types of plugins in magento: around, before and after. - -__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. -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), -plugin methods will be called for all child classes, such as \Magento\Catalog\Block\Product\View, \Magento\Catalog\Block\Product\ProductList\Upsell etc. - -### Limitations - -Plugins only work on __public methods__ for any __classes__ or __interfaces__. -So plugins can not be used on following: - -> 1. Final methods -> 1. Final classes -> 1. Non-public methods -> 1. Class methods (such as static methods) -> 1. __construct -> 1. Virtual types -> 1. Objects that are instantiated before Magento\Framework\Interception is bootstrapped - -@since 2.2 *after* plugin has access to *arguments*! - -### Prioritizing plugins - -The `sortOrder` property controls how your plugin interacts with other plugins on the same class. - -> The prioritization rules for ordering plugins: -> -> * Before the execution of the observed method, Magento will execute plugins from lowest to greatest `sortOrder`. -> -> * During each plugin execution, Magento executes the current plugin's before method. -> * After the before plugin completes execution, the current plugin's around method will wrap and execute the next plugin or observed method. -> -> * Following the execution of the observed method, Magento will execute plugins from greatest to lowest `sortOrder`. -> -> * During each plugin execution, the current plugin will first finish executing its around method. -> * When the around method completes, the plugin executes its after method before moving on to the next plugin. -> -> -- [Magento DevDocs - Plugins](https://devdocs.magento.com/guides/v2.2/extension-dev-guide/plugins.html#prioritizing-plugins) - -Plugin sortOrder: - -- before sortOrder=10, before sortOrder=20, before sortOrder=30 ... -- before and around (first half) called together for same plugin! -- around (second half) and after called together for same plugin! - -Example: - -- pluginA.beforeMethod, pluginA.aroundMethod first half -- pluginB.beforeMethod, pluginB.aroundMethod first half -- pluginC.beforeMethod, `____________________ -- ____________________, pluginD.aroundMethod first half -- method() -- ____________________, pluginD.aroundMethod second half -- pluginC.afterMethod , ________________________________ -- pluginB.aroundMethod second half, pluginB.afterMethod -- pluginA.aroundMethod second half, pluginA.afterMethod - -### Identify strengths and weaknesses of plugins. - -> The greatest weakness is exploited in the hands of a developer who is either not -> experienced or not willing to take the time to evaluate the fallout. For example, -> used improperly, an around plugin can prevent the system from functioning. -> They can also make understanding what is going on by reading source code hard -> (spooky action at a distance). -> -> -- [Swiftotter Developer Study Guide](https://swiftotter.com/technical/certifications/magento-2-certified-developer-study-guide) - -Using around plugins: -> Avoid using around method plugins when they are not required because they increase stack traces and affect performance. -> The only use case for around method plugins is when you need to terminate the execution of all further plugins and original methods. -> -> -- [Magento DevDocs - Programming Best Practices](https://devdocs.magento.com/guides/v2.2/ext-best-practices/extension-coding/common-programming-bp.html#using-around-plugins) - -### In which cases should plugins be avoided? - -> Plugins are useful to modify the input, output, or execution of an existing method. -> Plugins are also best to be avoided in situations where an event observer will work. -> Events work well when the flow of data does not have to be modified. -> -> -- [Swiftotter Developer Study Guide](https://swiftotter.com/technical/certifications/magento-2-certified-developer-study-guide) - -Info from Magento technical guidelines: -> `4. Interception` -> -> 4.1. Around-plugins SHOULD only be used when behavior of an original method is supposed to be substituted in certain scenarios. -> -> 4.2. Plugins SHOULD NOT be used within own module . -> -> 4.3. Plugins SHOULD NOT be added to data objects. -> -> 4.4. Plugins MUST be stateless. -> -> ... -> -> `14. Events` -> -> 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. -> -> -- [Magento DevDocs - Technical guidelines](https://devdocs.magento.com/guides/v2.2/coding-standards/technical-guidelines.html) - - -Also check: -- [Yireo - Magent 2 observer or Plugin?](https://www.yireo.com/blog/1875-magent-2-observer-or-plugin) -- [Magento DevDocs - Observers Best Practices](https://devdocs.magento.com/guides/v2.2/ext-best-practices/extension-coding/observers-bp.html) - - -### how it works? - -Magento automatically generates `Interceptor` class for the plugin target and store it in the `generated\code` directory. - -``` - namespace \Notes; - - class Interceptor extends \Notes\MyBeautifulClass implements \Magento\Framework\Interception\InterceptorInterface - { - use \Magento\Framework\Interception\Interceptor; - - public function __construct($specificArguments1, $someArg2 = null) - { - $this->___init(); - parent:__construct($specificArguments1, $someArg2); - } - - public function sayHello() - { - pluginInfo = pluginList->getNext('MyBeautifulClass', 'sayHello') - __callPlugins('sayHello', [args], pluginInfo) - } - } -``` - -[\Magento\Framework\Interception\Interceptor](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Interception/Interceptor.php): - -- $pluginList = [\Magento\Framework\Interception\PluginListInterface](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Interception/PluginListInterface.php) -- $subjectType = 'MyBeautifulClass' -- `___init` - called in in constructor, pluginList = get from object manager, subjectType = class name -- pluginList->getNext -- `___callPlugins` -- `___callParent` - -### how generated? - - \Magento\Framework\App\Bootstrap::create - \Magento\Framework\App\Bootstrap::__construct - \Magento\Framework\App\ObjectManagerFactory::create - \Magento\Framework\ObjectManager\DefinitionFactory::createClassDefinition - \Magento\Framework\ObjectManager\DefinitionFactory::getCodeGenerator - \Magento\Framework\Code\Generator\Io::__construct - \Magento\Framework\Code\Generator::__construct - spl_autoload_register([new \Magento\Framework\Code\Generator\Autoloader, 'load']); - - \Magento\Framework\App\ObjectManagerFactory::create - \Magento\Framework\Code\Generator::setGeneratedEntities - \Magento\Framework\App\ObjectManager\Environment\Developer::configureObjectManager - - \Magento\Framework\Code\Generator\Autoloader::load - \Magento\Framework\Code\Generator::generateClass - -### Decide how to generate based on file suffix - generator \Magento\Framework\Code\Generator\EntityAbstract -``` -array ( - 'extensionInterfaceFactory' => '\\Magento\\Framework\\Api\\Code\\Generator\\ExtensionAttributesInterfaceFactoryGenerator', - 'factory' => '\\Magento\\Framework\\ObjectManager\\Code\\Generator\\Factory', - 'proxy' => '\\Magento\\Framework\\ObjectManager\\Code\\Generator\\Proxy', - 'interceptor' => '\\Magento\\Framework\\Interception\\Code\\Generator\\Interceptor', - 'logger' => '\\Magento\\Framework\\ObjectManager\\Profiler\\Code\\Generator\\Logger', - - logs all public methods call - - Magento\Framework\ObjectManager\Factory\Log -- missing? - 'mapper' => '\\Magento\\Framework\\Api\\Code\\Generator\\Mapper', - - extractDto() = $this->{$name}Builder->populateWithArray()->create - 'persistor' => '\\Magento\\Framework\\ObjectManager\\Code\\Generator\\Persistor', - - getConnection, loadEntity, registerDelete, registerNew, registerFromArray, doPersist, doPersistEntity - 'repository' => '\\Magento\\Framework\\ObjectManager\\Code\\Generator\\Repository', -- deprecated - 'convertor' => '\\Magento\\Framework\\ObjectManager\\Code\\Generator\\Converter', - - Extract data object from model - - getModel(AbstractExtensibleObject $dataObject) = getProductFactory()->create()->setData($dataObject)->__toArray() - 'searchResults' => '\\Magento\\Framework\\Api\\Code\\Generator\\SearchResults', - - extends \Magento\Framework\Api\SearchResults - 'extensionInterface' => '\\Magento\\Framework\\Api\\Code\\Generator\\ExtensionAttributesInterfaceGenerator', - 'extension' => '\\Magento\\Framework\\Api\\Code\\Generator\\ExtensionAttributesGenerator', - - extension_attributes.xml - - extends \Magento\Framework\Api\AbstractSimpleObject - - implements {name}\ExtensionInterface - - for every custom attribute, getters and setters - 'remote' => '\\Magento\\Framework\\MessageQueue\\Code\\Generator\\RemoteServiceGenerator', -) -``` - -``` -Magento\Framework\App\ResourceConnection\Proxy -> type Proxy, name Magento\Framework\App\ResourceConnection -Magento\Framework\Code\Generator::shouldSkipGeneration - type not detected, or class exists -\Magento\Framework\Code\Generator::createGeneratorInstance -- new for every file -``` - -### \Magento\Framework\Code\Generator\EntityAbstract - code generation -``` -\Magento\Framework\Code\Generator\EntityAbstract::generate -\Magento\Framework\Code\Generator\EntityAbstract::_validateData - class not existing etc. -\Magento\Framework\Code\Generator\EntityAbstract::_generateCode - - \Magento\Framework\Code\Generator\ClassGenerator extends \Zend\Code\Generator\ClassGenerator -- \Magento\Framework\Code\Generator\EntityAbstract::_getDefaultConstructorDefinition -- \Magento\Framework\Code\Generator\EntityAbstract::_getClassProperties -- \Magento\Framework\Code\Generator\EntityAbstract::_getClassMethods -``` - -###### Links -- [Magento DevDocs - Plugins (Interceptors)](https://devdocs.magento.com/guides/v2.2/extension-dev-guide/plugins.html) -- [Alan Storm - Magento 2 Object Manager Plugin System](https://alanstorm.com/magento_2_object_manager_plugin_system/) \ No newline at end of file diff --git a/1. Magento Architecture and Customization Techniques/6. Configure event observers and scheduled jobs.md b/1. Magento Architecture and Customization Techniques/6. Configure event observers and scheduled jobs.md deleted file mode 100644 index 2bbc44b..0000000 --- a/1. Magento Architecture and Customization Techniques/6. Configure event observers and scheduled jobs.md +++ /dev/null @@ -1,214 +0,0 @@ -# Configure event observers and scheduled jobs - -## Observers - -Events are dispatched by modules when certain actions are triggered. -In addition to its own events, Magento allows you to create your own events that can be dispatched in your code. -When an event is dispatched, it can pass data to any observers configured to watch that event. - -Best practices: -- Make your observer efficient -- Do not include business logic -- Declare observer in the appropriate scope -- Avoid cyclical event loops -- Do not rely on invocation order - -> -> `14. Events` -> -> 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. -> -> -- [Magento DevDocs - Technical guidelines](https://devdocs.magento.com/guides/v2.2/coding-standards/technical-guidelines.html) - - -### Demonstrate how to configure observers -- can define observer in global area, then disable in specific area - -Observers can be configured in the `events.xml` file. - -Properties: -- name (required) - Name of the observer for the event definition -- instance (required) - Class name of the observer -- disabled - Is observer active or not (Default: false) -- shared - Class lifestyle (Default: false) - -Example: -```xml - - - - - - - - -``` - -Observer class should be placed in the /Observer directory and implement -[Magento\Framework\Event\ObserverInterface](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Event/ObserverInterface.php) -interface. - -```php -getEvent()->getData('order'); // $observer->getEvent()->getOrder(); - // observer code... - } -} -``` - -### Dispatching events - -Events dispatch by [Magento\Framework\Event\Manager](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Event/Manager.php) class -that implement [Magento\Framework\Event\ManagerInterface](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Event/ManagerInterface.php) interface: -> dispatch($eventName, array $data = []); - -Example: -> $this->eventManager->dispatch('cms_page_prepare_save', ['page' => $model, 'request' => $this->getRequest()]); - -###### Links -- [Magento DevDocs - Events and observers](https://devdocs.magento.com/guides/v2.2/extension-dev-guide/events-and-observers.html) -- [Magento DevDocs - Observers Best Practices](https://devdocs.magento.com/guides/v2.2/ext-best-practices/extension-coding/observers-bp.html) - - -## Scheduled jobs - -### Demonstrate how to configure a scheduled job - - -##### Cron groups - -A cron group is a logical group that enables you to easily run cron for more than one process at a time. -Most Magento modules use the default cron group. - -To declare new group and specify settings, create `/etc/cron_groups.xml` file (store view scope): - -```xml - - - 15 - 20 - 15 - 10 - 10080 - 10080 - 0 - - -``` - -Existing groups: -- default (no separate process) -- index - mview, targetrule -- catalog_event - catalog_event_status_checker - mark event open/closed -- consumers - consumers_runner if configured to run by cron. `bin/magento queue:consumers:start`. PID file var/{$consumer}.pid -- staging - staging_apply_version, staging_remove_updates, staging_synchronize_entities_period -- ddg_automation (dotmailer) - -##### crontab.xml - -`crontab.xml` file is used to execute an action on schedule. -This file always located in the /etc/ folder (not in /etc/{area}/)! - -```xml - - - - some/config/path - - - 0 * * * * - - - -``` - -run: - -- magento cron:run [–group=”"] -- pub/cron.php?[group=] in a web browser, protect with basic auth - -Stack trace: -``` -\Magento\Cron\Console\Command\CronCommand::execute -\Magento\Framework\App\Cron::launch -`default` event -\Magento\Cron\Observer\ProcessCronQueueObserver -check for specific group -cleanup -generate -check for standalone process -``` - -[\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) -- 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 - - Sample DB structure: -``` -default/crontab/GROUP/jobs/JOB/schedule/cron_expr = '* * * * *' -default/crontab/GROUP/jobs/JOB/schedule/config_path = 'some/config/path' -- try to read schedule from this config, store view scope -default/crontab/GROUP/jobs/JOB/run/model = 'class::method' -``` -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) - -`bin/magento cron:install` example: -``` -#~ MAGENTO START 4d557a63fe1eac8a2827a4eca020c6bb -* * * * * /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 -* * * * * /usr/bin/php7.0 /var/www/m22ee/update/cron.php >> /var/www/m22ee/var/log/update.cron.log -* * * * * /usr/bin/php7.0 /var/www/m22ee/bin/magento setup:cron:run >> /var/www/m22ee/var/log/setup.cron.log -#~ MAGENTO END 4d557a63fe1eac8a2827a4eca020c6bb -``` - -how is separate process ran? -`bin/magento cron:run --group=NAME --bootstrap=standaloneProcessStarted=1` - -what is update/cron.php? -TODO: find out - -what is setup:cron:run? -TODO: find out - -###### Links -- [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) -- [Magento DevDocs - Custom cron job and cron group reference](https://devdocs.magento.com/guides/v2.2/config-guide/cron/custom-cron-ref.html) - - -### Identify the function and proper use of automatically available events - -Model events [\Magento\Framework\Model\AbstractModel](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Model/AbstractModel.php): - -- `model_load_before`, `{$_eventPrefix}_load_before` -- `model_load_after`, `{$_eventPrefix}_load_after` -- `model_save_commit_after`, `{$_eventPrefix}_save_commit_after` -- `model_save_before`, `{$_eventPrefix}_save_before` -- `model_save_after`, `{$_eventPrefix}_save_after` -- `model_delete_before`, `{$_eventPrefix}_delete_before` -- `model_delete_after`, `{$_eventPrefix}_delete_after` -- `model_delete_commit_after`, `{$_eventPrefix}_delete_commit_after` - -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): - -- `core_collection_abstract_load_before`, `{_eventPrefix}_load_before` -- `core_collection_abstract_load_after`, `{_eventPrefix}_load_after` - -only if `_eventPrefix` and `_eventObject` defined: `{prefix}_load_before`, `{prefix}_load_after` - -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): - -- eav_collection_abstract_load_before - -[\Magento\Framework\Model\AbstractModel](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Model/AbstractModel.php): - -- _eventObject = 'object' -- _eventPrefix = 'core_abstract', e.g. 'catalog_category' -- _getEventData() - 'data_object' + $_eventObject -- `model_load_before` (object, field=null, value=ID) -- `{_eventPrefix}_load_before`, e.g. `catalog_category_load_before` (object, field, value, data_object, category) - -###### Links -- [Cyrill Schumacher's Blog - List of all dispatched events](https://cyrillschumacher.com/magento-2.2-list-of-all-dispatched-events/) diff --git a/1. Magento Architecture and Customization Techniques/7. Utilize the CLI.md b/1. Magento Architecture and Customization Techniques/7. Utilize the CLI.md deleted file mode 100644 index 0738dae..0000000 --- a/1. Magento Architecture and Customization Techniques/7. Utilize the CLI.md +++ /dev/null @@ -1,122 +0,0 @@ -# Utilize the CLI - -### Describe the usage of bin/magento commands in the development cycle. - -### Demonstrate an ability to create a deployment process. -Modes: *default*, *developer*, *production*. MAGE-MODE env variable - -Commands: - -- `bin/magento deploy:mode:show` -- `magento deploy:mode:set {mode} [-s|--skip-compilation]` -- skip compilation when changing to production - -cannot switch to default mode, only developer or production - -Default: - -- errors logged in var/report, not displayed -- static created dynamically - copied! changes not visible. cached - -Developer: - -- exceptions displayed, not logged -- exception thrown if bad event subscriber. -- var/report detailed -- static created dynamically - symlinked???, changes visible immediately -- error handler - throws exception instead of logging (notice etc.) - -Production - max speed, no errors, no file generation: - -- admin can't enable/disable cache types -- errors logged, not displayed -- static not created dynamically, must be deployed -- not need for www-data to write, pub/static can be read-only - -### Commands explaination -#### setup:di:compile -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) -* Application code generation (factories, proxies) -* Area configuration aggregation (optimized dependency injection configurations per area) - - see [generated/metadata](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/code-generation.html#codegen-om) -* Interceptor generation (optimized code generation of interceptors) -* Interception cache generation -* Repositories code generation (generated code for APIs) -* Service data attributes generation (generated extension classes for data objects) -* [Running setup:di:compile places the app into a special mode](https://www.cadence-labs.com/2017/07/magento-2-run-setupdicompile/) - -### How to add CLI commands (off-topic) - -Magento 2 CLI is based on the Symfony Console component. - -To create new CLI command you need: -- Create command class (the recommended location is {module}/Console/Command) -This class must extends from [\Symfony\Component\Console\Command\Command](https://github.com/symfony/console/blob/master/Command/Command.php) and have 2 methods: - -`configure` - to set the name, description, command line arguments etc - -`execute` - is the place where you write your code - -```php -setName('example:hello') - ->setDescription('Hello world command'); - - // Positional argument - $this->addArgument( - 'myargument', - InputArgument::REQUIRED, - 'Positional required argument example' - ); - - // Not required option - $this->addOption( - 'myoption', - null, - InputOption::VALUE_OPTIONAL, - 'Option example', - ScopeConfigInterface::SCOPE_TYPE_DEFAULT - ); - - parent::configure(); - } - - protected function execute(\Symfony\Component\Console\Input\InputInterface $input, \Symfony\Component\Console\Output\OutputInterface $output) - { - $output->writeln('hello world'); - } -} -``` - -- 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). -See also CommandListInterface implementation: [\Magento\Framework\Console\CommandList](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Console/CommandList.php) - -{module}/etc/di.xml: -```xml - - - - Vendor\Module\Console\Command\ExampleCommand - - - -``` - -- Clean the cache and compiled code directories - -rm -rf cache/* page_cache/* di/* generation/* - -###### Links -- [Magento DevDocs - How to add CLI commands](https://devdocs.magento.com/guides/v2.2/extension-dev-guide/cli-cmds/cli-howto.html) -- [Magento DevDocs - Command naming guidelines](https://devdocs.magento.com/guides/v2.2/extension-dev-guide/cli-cmds/cli-naming-guidelines.html) -- [Symfony Documentation - The Console Component](https://symfony.com/doc/3.4/components/console.html) -- [Magento - sample-module-command](https://github.com/magento/magento2-samples/tree/master/sample-module-command) diff --git a/1. Magento Architecture and Customization Techniques/8. Demonstrate the ability to manage the cache.md b/1. Magento Architecture and Customization Techniques/8. Demonstrate the ability to manage the cache.md deleted file mode 100644 index 7882399..0000000 --- a/1. Magento Architecture and Customization Techniques/8. Demonstrate the ability to manage the cache.md +++ /dev/null @@ -1,170 +0,0 @@ -# Demonstrate the ability to manage the cache - -### Describe cache types and the tools used to manage caches. - -- config -- layout -- block_html -- collections -- db_ddl -- eav -- full_page -- reflection -- translate -- config_integration -- config_integration_api -- config_webservice - -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) - -Commands: - -- `magento setup:db-schema:upgrade` -- `magento cache:status`, `magento cache:enable`, `magento cache:disable` -- `magento cache:clean`, `magento cache:flush` - -### Init: - -frontend: -``` -\Magento\Framework\App\ObjectManager\ConfigLoader::load -cacheType = config, frontend = default -\Magento\Framework\App\Cache\Frontend\Pool::_initialize -\Magento\Framework\App\Cache\Frontend\Factory::create -\Zend_Cache::_makeFrontend -\Zend_Cache_Core::__construct -``` - -backend: - -- \Zend_Cache_Backend -- \Zend_Cache_Backend_File -- \Magento\Framework\Cache\Backend\Database - -How do you add dynamic content to pages served from the full page cache? - -1. Mark any block `cacheable="false"` in layout xml - whole page is uncacheable. Example -checkout -1. Disable caching in controller using *headers*: -`$page->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0', true);` -1. Marking block property isScopePrivate - will be loaded via AJAX - deprecated -1. ESI when Varnish enabled, set TTL - Example - menu block -1. Configure page variations - extend *http context*, more cached versions of same page - store view, customer group, language, currency, is logged in -`\Magento\Framework\App\Http\Context::getVaryString` - -Only _GET_ and _HEAD_ are cached - -Clear cache -\Magento\Framework\DataObject\IdentityInterface - -### when giving product page, must somehow send varnish tags -Any block can implement IdentityInterface. After rendering layout and before sending output, -all blocks are examined for implementing this interface. Cache tags are collected as merge of all blocks -getIdentities() tags. - -``` -\Magento\PageCache\Model\Layout\LayoutPlugin::afterGetOutput - X-Magento-Tags = merge(all blocks.getIdentities) -``` - -block ListProduct: - -- every product[].getIdentities - - cat_p_{productId} - - *if changed categories* - cat_p_c_{categoryId} - - *if changed status* - every category[] cat_p_c_{categoryId} - - *if frontend* - 'cat_p' -- cat_c_p_{categoryId} - -block product/view: - -- \Magento\Catalog\Model\Product::getIdentities: - - cat_p_{productId} - - *if changed categories* - cat_p_c_{categoryId} - - *if changed status* - every category[] cat_p_c_{categoryId} - - *if frontend* - 'cat_p' -- *if current_category* - cat_c_{categoryId} - -### after reindex, must somehow clean cache - -- any indexer.execute -- by MView -- any indexer.executeFull -- \Magento\Framework\Indexer\CacheContext::registerTags - -plugin \Magento\Indexer\Model\Processor: - -\Magento\Indexer\Model\Processor\CleanCache::afterUpdateMview - -- event `clean_cache_after_reindex` -- clean cache cacheContext->getIdentities() - -\Magento\Indexer\Model\Processor\CleanCache::afterReindexAllInvalid - -- event `clean_cache_by_tags` -- clean cache cacheContext->getIdentities() - -module-cache-invalidate observer `clean_cache_after_reindex` -\Magento\CacheInvalidate\Observer\InvalidateVarnishObserver::execute -\Magento\CacheInvalidate\Model\PurgeCache::sendPurgeRequest - -### Describe how to operate with cache clearing. - -How would you clean the cache? In which case would you refresh cache/flash cache storage? - -> To purge out-of-date items from the cache, you can clean or flush cache types: -> -> - 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. -> -> Disabled cache types are not cleaned. -> -> - Flushing a cache type purges the cache storage, which might affect other processes applications that are using the same storage. -> -> Flush cache types if you’ve already tried cleaning the cache and you’re still having issues that you cannot isolate. -> -> -- [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) - -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. - -### Describe how to clear the cache programmatically. - -To clear the cache programmatically you neeed to call next the methods: -- [\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 -- [\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 - -##### What mechanisms are available for clearing all or part of the cache? - -Dispatch a `clean_cache_by_tags` event with parameter of the object you want to clear from the cache. - -Example: [\Magento\Framework\Model\AbstractModel](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Model/AbstractModel.php#L817) (afterSave, afterDelete methods) - -```php -cleanModelCache(); - $this->_eventManager->dispatch('model_save_after', ['object' => $this]); - $this->_eventManager->dispatch('clean_cache_by_tags', ['object' => $this]); - $this->_eventManager->dispatch($this->_eventPrefix . '_save_after', $this->_getEventData()); - $this->updateStoredData(); - return $this; -} - -public function cleanModelCache() -{ - $tags = $this->getCacheTags(); - if ($tags !== false) { - $this->_cacheManager->clean($tags); // \Magento\Framework\App\CacheInterface - } - return $this; -} -``` - -Default `clean_cache_by_tags` event observers are: -- [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 -- [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 - -###### Links -- [Magento DevDocs - Magento cache overview](https://devdocs.magento.com/guides/v2.2/frontend-dev-guide/cache_for_frontdevs.html) -- [Magento DevDocs - Configure caching](https://devdocs.magento.com/guides/v2.2/config-guide/cache.html) -- [Magento DevDocs - Partial caching](https://devdocs.magento.com/guides/v2.2/extension-dev-guide/cache/partial-caching.html) -- [Magento DevDocs - Full Page caching](https://devdocs.magento.com/guides/v2.2/extension-dev-guide/cache/page-caching.html) -- [Magento DevDocs - Private content](https://devdocs.magento.com/guides/v2.2/extension-dev-guide/cache/page-caching/private-content.html) diff --git a/1. Magento Architecture and Customization Techniques/Magento Architecture and Customization Techniques.md b/1. Magento Architecture and Customization Techniques/Magento Architecture and Customization Techniques.md deleted file mode 100644 index 5a14299..0000000 --- a/1. Magento Architecture and Customization Techniques/Magento Architecture and Customization Techniques.md +++ /dev/null @@ -1,540 +0,0 @@ -# 1-Magento Architecture and Customization Techniques - -## 1.1 Describe Magento’s module-based architecture -- registration.php -- composer.json autoload/files[] = "registration.php" -- at what stage - when including vendor/autoload.php -- how registered when not in composer - project composer.json autoload/files[] = app/etc/NonComposerComponentRegistration.php - - app/code/*/*/cli_commands.php, registration.php - app/design/*/*/*/registration.php - app/i18n/*/*/registration.php - lib/internal/*/*/registration.php - lib/internal/*/*/*/registration.php - -pub/index.php -app/bootstrap.php -app/autoload.php -vendor/autoload.php -vendor/module[]/registration.php -- last step in Composer init -Magento\Framework\Component\ComponentRegistrar::register(type='module', name='Prince_Productattach', path='/var/www/...') - -How do different modules interact with each other? -- composer require -- module.xml sequence - hard requirement, affects load order -- DI require instance - -What side effects can come from this interaction? -- error when module is missing or disabled -- error when injecting missing class -- (?) null or error when using object manager for missing class - ReflectionException - Class MissingClass does not exist - objectManager->create() = new $type() or new $type(...args) --> PHP Warning: Uncaught Error: Class 'MissingClass' not found - -## 1.2 Describe Magento’s directory structure - -- app/code/Vendor/Module -- app/design/frontend/VendorName/theme_name_lowercase -- app/i18n/VendorName/en_US -- vendor/vendor-name/module-theme-package/ - -Where are the files containing JavaScript, HTML, and PHP located? -- view/frontend/web/js -- view/frontend/requirejs-config.js -- view/frontend/layout -- view/frontend/templates - -How do you find the files responsible for certain functionality? - -## 1.3 Utilize configuration XML and variables scope - -Determine how to use configuration files in Magento. Which configuration files correspond to different features and -functionality? - -- acl.xml - resource title, sort -- adminhtml/rules/payment_{country}.xml - paypal -- address_formats.xml -- address_types.xml - format code and title only -- cache.xml - name, instance - e.g. full_page=Page Cache -- catalog_attributes.xml - catalog_category, catalog_product, unassignable, used_in_autogeneration, quote_item -- communication.xml -- config.xml - defaults -- crontab.xml - group[], job instance, method, schedule -- cron_groups.xml -- di.xml - preference, plugins, virtual type -- eav_attributes.xml - locked entity attributes (global, unique etc.) -- email_templates.xml - id label file type module -- view/frontend/email/name.html -- events.xml - observers, shared, disabled -- export.xml -- extension_attributes.xml - for, attribute code, attribute type -- fieldset.xml -- import.xml -- indexer.xml - class, view_id, title, description -- integration.xml - API resources + pre-configure the integration (After Magento 2.2) -- integration/api.xml - file defines which API resources the integration has access to(Deprecated in Magento 2.2 and all config are moved in integration.xml) -- integration/config.xml - pre-configure the integration, the values cannot be edited from the admin panel ((Deprecated in Magento 2.2 and all config are moved in integration.xml) -- menu.xml -- module.xml - version, sequence -- mview.xml - scheduled updates, subscribe to table changes, indexer model -- page_types.xml -- payment.xml - groups, method allow_multiple_address -- pdf.xml - renders by type (invoice, shipment, creditmemo) and product type -- product_types.xml - label, model instance, index priority, (?) custom attributes, (!) composable types -- product_options.xml -- resources.xml -- routes.xml - Routes, Frontend name, module and extending routes -- sales.xml - collectors (quote, order, invoice, credit memo) -- search_engine.xml - Add custom search engine -- search_request.xml - index, dimensions, queries, filters, aggregations, buckets -- sections.xml - action route placeholder -> invalidate customer sections -- system.xml - adminhtml config -- validation.xml - entity, rules, constraints -> class -- view.xml - vars by module -- webapi.xml - route, method, service class and method, resources -- widget.xml - class, email compatible, image, ttl (?), label, description, parameters -- zip_codes.xml - -### \Magento\Framework\Config\Reader\Filesystem, \Magento\Framework\Config\ReaderInterface -Gets .xsd names from schema locator, gets full .xml file list from file resolver, merges all files, validates, runs converter to get resulting array. - -- read(scope) - + fileResolver->get(_filename) - + merge and validate each file (if mode developer) - + validate merged dom - + converter->convert(dom) => array -- _idAttributes, _fileName, _schemaFile (from schemaLocator), _perFileSchema (from schemaLocator), filename (menu.xml) -- schemaFile from schemaLocator - -### \Magento\Catalog\Model\ProductTypes\Config\Converter, \Magento\Framework\Config\ConverterInterface - array in any format -- convert(\DOMDocument $source) - -### \Magento\Framework\Config\SchemaLocatorInterface - full path to .xsd -- getPerFileSchema - per file before merge -- getSchema - merged file - -### config merger = \Magento\Framework\Config\Dom -- when merging each file - + createConfigMerger|merge - + \Magento\Framework\Config\Dom::_initDom(dom, perFileSchema) - + \Magento\Framework\Config\Dom::validateDomDocument - + $dom->schemaValidate -- after all merged - + \Magento\Framework\Config\Dom::validate(mergedSchema) - + \Magento\Framework\Config\Dom::validateDomDocument - -## Sensitive and environment settings -- shared config app/etc/config.php -- sensitive or system-specific app/etc/env.php: - -``` - - - - - 1 - - - - - 1 - - - - -``` - -sensitive info doesn't get exported with `bin/magento app:config:dump`. use env. params, e.g. -`CONFIG__DEFAULT__PAYMENT__TEST__PASWORD` for `payment/test/password` - -`bin/magento app:config:dump`: - -- system-specific > app/etc/env.php -- shared > app/etc/config.php -- sensitive - skipped - -`bin/magento config:sensitive:set`: - -- writes to app/etc/env.php - -## 1.4 Demonstrate how to use dependency injection - -``` - {typeName} - {typeName} - - {strValue} - {strValue} -``` - -* Initial (app/etc/di.xml) -* Global (/etc/di.xml) -* Area-specific (/etc//di.xml) - -## 1.5 Demonstrate ability to use plugins -- can't use before Magento\Framework\Interception\... is initialized -- *after* plugin has access to *arguments*! @since 2.2 - -``` - class MyBeautifulClass - { - use \Magento\Framework\Interception\Interceptor; - - public function __construct($specificArguments1, $someArg2 = null) - { - $this->___init(); - parent:__construct($specificArguments1, $someArg2); - } - - public function sayHello() - { - pluginInfo = pluginList->getNext('MyBeautifulClass', 'sayHello') - __callPlugins('sayHello', [args], pluginInfo) - } - } -``` - -Magento\Framework\Interception\Interceptor: - -- $pluginList = \Magento\Framework\Interception\PluginListInterface -- $subjectType = 'MyBeautifulClass' -- `___init` - called in in constructor, pluginList = get from object manager, subjectType = class name -- pluginList->getNext -- `___callPlugins` -- `___callParent` - -### how generated? - - \Magento\Framework\App\Bootstrap::create - \Magento\Framework\App\Bootstrap::__construct - \Magento\Framework\App\ObjectManagerFactory::create - \Magento\Framework\ObjectManager\DefinitionFactory::createClassDefinition - \Magento\Framework\ObjectManager\DefinitionFactory::getCodeGenerator - \Magento\Framework\Code\Generator\Io::__construct - \Magento\Framework\Code\Generator::__construct - spl_autoload_register([new \Magento\Framework\Code\Generator\Autoloader, 'load']); - - \Magento\Framework\App\ObjectManagerFactory::create - \Magento\Framework\Code\Generator::setGeneratedEntities - \Magento\Framework\App\ObjectManager\Environment\Developer::configureObjectManager - - \Magento\Framework\Code\Generator\Autoloader::load - \Magento\Framework\Code\Generator::generateClass - -### Decide how to generate based on file suffix - generator \Magento\Framework\Code\Generator\EntityAbstract -``` -array ( - 'extensionInterfaceFactory' => '\\Magento\\Framework\\Api\\Code\\Generator\\ExtensionAttributesInterfaceFactoryGenerator', - 'factory' => '\\Magento\\Framework\\ObjectManager\\Code\\Generator\\Factory', - 'proxy' => '\\Magento\\Framework\\ObjectManager\\Code\\Generator\\Proxy', - 'interceptor' => '\\Magento\\Framework\\Interception\\Code\\Generator\\Interceptor', - 'logger' => '\\Magento\\Framework\\ObjectManager\\Profiler\\Code\\Generator\\Logger', - - logs all public methods call - - Magento\Framework\ObjectManager\Factory\Log -- missing? - 'mapper' => '\\Magento\\Framework\\Api\\Code\\Generator\\Mapper', - - extractDto() = $this->{$name}Builder->populateWithArray()->create - 'persistor' => '\\Magento\\Framework\\ObjectManager\\Code\\Generator\\Persistor', - - getConnection, loadEntity, registerDelete, registerNew, registerFromArray, doPersist, doPersistEntity - 'repository' => '\\Magento\\Framework\\ObjectManager\\Code\\Generator\\Repository', -- deprecated - 'convertor' => '\\Magento\\Framework\\ObjectManager\\Code\\Generator\\Converter', - - Extract data object from model - - getModel(AbstractExtensibleObject $dataObject) = getProductFactory()->create()->setData($dataObject)->__toArray() - 'searchResults' => '\\Magento\\Framework\\Api\\Code\\Generator\\SearchResults', - - extends \Magento\Framework\Api\SearchResults - 'extensionInterface' => '\\Magento\\Framework\\Api\\Code\\Generator\\ExtensionAttributesInterfaceGenerator', - 'extension' => '\\Magento\\Framework\\Api\\Code\\Generator\\ExtensionAttributesGenerator', - - extension_attributes.xml - - extends \Magento\Framework\Api\AbstractSimpleObject - - implements {name}\ExtensionInterface - - for every custom attribute, getters and setters - 'remote' => '\\Magento\\Framework\\MessageQueue\\Code\\Generator\\RemoteServiceGenerator', -) -``` - -``` -Magento\Framework\App\ResourceConnection\Proxy -> type Proxy, name Magento\Framework\App\ResourceConnection -Magento\Framework\Code\Generator::shouldSkipGeneration - type not detected, or class exists -\Magento\Framework\Code\Generator::createGeneratorInstance -- new for every file -``` - -### \Magento\Framework\Code\Generator\EntityAbstract - code generation -``` -\Magento\Framework\Code\Generator\EntityAbstract::generate -\Magento\Framework\Code\Generator\EntityAbstract::_validateData - class not existing etc. -\Magento\Framework\Code\Generator\EntityAbstract::_generateCode - - \Magento\Framework\Code\Generator\ClassGenerator extends \Zend\Code\Generator\ClassGenerator -- \Magento\Framework\Code\Generator\EntityAbstract::_getDefaultConstructorDefinition -- \Magento\Framework\Code\Generator\EntityAbstract::_getClassProperties -- \Magento\Framework\Code\Generator\EntityAbstract::_getClassMethods -``` - - -## 1.6 Configure event observers and scheduled jobs - -### Demonstrate how to configure observers -- can define observer in global area, then disable in specific area - -Observer sortOrder: - -- before sortOrder=10, before sortOrder=20, before sortOrder=30 ... -- before and around (first half) called together for same plugin! -- around (second half) and after called together for same plugin! - -Example: - -- pluginA.beforeMethod, pluginA.aroundMethod first half -- pluginB.beforeMethod, pluginB.aroundMethod first half -- pluginC.beforeMethod, `____________________ -- ____________________, pluginD.aroundMethod first half -- method() -- ____________________, pluginD.aroundMethod second half -- pluginC.afterMethod , ________________________________ -- pluginB.aroundMethod second half, pluginB.afterMethod -- pluginA.aroundMethod second half, pluginA.afterMethod - -### Demonstrate how to configure a scheduled job - -cron_groups.xml - store view scope: - -- default (no separate process) -- index - mview, targetrule -- catalog_event - catalog_event_status_checker - mark event open/closed -- consumers - consumers_runner if configured to run by cron. `bin/magento queue:consumers:start`. PID file var/{$consumer}.pid -- staging - staging_apply_version, staging_remove_updates, staging_synchronize_entities_period -- ddg_automation (dotmailer) - -``` - - - some/config/path - - - * * * * * - - -``` - -run: - -- magento cron:run [–group=”"] -- pub/cron.php?[group=] in a web browser, protect with basic auth - -``` -\Magento\Cron\Console\Command\CronCommand::execute -\Magento\Framework\App\Cron::launch -`default` event -\Magento\Cron\Observer\ProcessCronQueueObserver -check for specific group -cleanup -generate -check for standalone process -``` - -`\Magento\Cron\Model\Config\Data` extends `\Magento\Framework\Config\Data` -- merges \Magento\Cron\Model\Config\Reader\Db::get from Database - - Sample DB structure: -``` -default/crontab/GROUP/jobs/JOB/schedule/cron_expr = '* * * * *' -default/crontab/GROUP/jobs/JOB/schedule/config_path = 'some/config/path' -- try to read schedule from this config, store view scope -default/crontab/GROUP/jobs/JOB/run/model = 'class::method' -``` - - -`bin/magento cron:install` example: -``` -#~ MAGENTO START 4d557a63fe1eac8a2827a4eca020c6bb -* * * * * /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 -* * * * * /usr/bin/php7.0 /var/www/m22ee/update/cron.php >> /var/www/m22ee/var/log/update.cron.log -* * * * * /usr/bin/php7.0 /var/www/m22ee/bin/magento setup:cron:run >> /var/www/m22ee/var/log/setup.cron.log -#~ MAGENTO END 4d557a63fe1eac8a2827a4eca020c6bb -``` - -how is separate process ran? -`bin/magento cron:run --group=NAME --bootstrap=standaloneProcessStarted=1` - -what is update/cron.php? -TODO: find out - -what is setup:cron:run? -TODO: find out - - -### Identify the function and proper use of automatically available events - -Model events \Magento\Framework\Model\AbstractModel: - -- `model_load_before`, `{$_eventPrefix}_load_before` -- `model_load_after`, `{$_eventPrefix}_load_after` -- `model_save_commit_after`, `{$_eventPrefix}_save_commit_after` -- `model_save_before`, `{$_eventPrefix}_save_before` -- `model_save_after`, `{$_eventPrefix}_save_after` -- `model_delete_before`, `{$_eventPrefix}_delete_before` -- `model_delete_after`, `{$_eventPrefix}_delete_after` -- `model_delete_commit_after`, `{$_eventPrefix}_delete_commit_after` - -Flat collection events \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection: - -- `core_collection_abstract_load_before`, `{_eventPrefix}_load_before` -- `core_collection_abstract_load_after`, `{_eventPrefix}_load_after` - -only if `_eventPrefix` and `_eventObject` defined: `{prefix}_load_before`, `{prefix}_load_after` - -EAV collection events \Magento\Eav\Model\Entity\Collection\AbstractCollection: - -- eav_collection_abstract_load_before - -\Magento\Framework\Model\AbstractModel: - -- _eventObject = 'object' -- _eventPrefix = 'core_abstract', e.g. 'catalog_category' -- _getEventData() - 'data_object' + $_eventObject -- `model_load_before` (object, field=null, value=ID) -- `{_eventPrefix}_load_before`, e.g. `catalog_category_load_before` (object, field, value, data_object, category) - -## 1.7 Utilize the CLI -### Describe the usage of bin/magento commands in the development cycle. - -### Demonstrate an ability to create a deployment process. -Modes: *default*, *developer*, *production*. MAGE-MODE env variable - -Commands: - -- `bin/magento deploy:mode:show` -- `magento deploy:mode:set {mode} [-s|--skip-compilation]` -- skip compilation when changing to production - -cannot switch to default mode, only developer or production - -Default: - -- errors logged in var/report, not displayed -- static created dynamically - copied! changes not visible. cached - -Developer: - -- exceptions displayed, not logged -- exception thrown if bad event subscriber. -- var/report detailed -- static created dynamically - symlinked???, changes visible immediately -- error handler - throws exception instead of logging (notice etc.) - -Production - max speed, no errors, no file generation: - -- admin can't enable/disable cache types -- errors logged, not displayed -- static not created dynamically, must be deployed -- not need for www-data to write, pub/static can be read-only - -## 1.8 Demonstrate the ability to manage the cache - -### Describe cache types and the tools used to manage caches. - -- config -- layout -- block_html -- collections -- db_ddl -- eav -- full_page -- reflection -- translate -- config_integration -- config_integration_api -- config_webservice - -Commands: - -- `magento setup:db-schema:upgrade` -- `magento cache:status`, `magento cache:enable`, `magento cache:disable` -- `magento cache:clean`, `magento cache:flush` - -### Init: - -frontend: -``` -\Magento\Framework\App\ObjectManager\ConfigLoader::load -cacheType = config, frontend = default -\Magento\Framework\App\Cache\Frontend\Pool::_initialize -\Magento\Framework\App\Cache\Frontend\Factory::create -\Zend_Cache::_makeFrontend -\Zend_Cache_Core::__construct -``` - -backend: - -- \Zend_Cache_Backend -- \Zend_Cache_Backend_File -- \Magento\Framework\Cache\Backend\Database - -How do you add dynamic content to pages served from the full page cache? - -1. Mark any block `cacheable="false"` in layout xml - whole page is uncacheable. Example -checkout -1. Disable caching in controller using *headers*: -`$page->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0', true);` -1. Marking block property isScopePrivate - will be loaded via AJAX - deprecated -1. ESI when Varnish enabled, set TTL - Example - menu block -1. Configure page variations - extend *http context*, more cached versions of same page - store view, customer group, language, currency, is logged in -`\Magento\Framework\App\Http\Context::getVaryString` - -Only _GET_ and _HEAD_ are cached - -Clear cache -\Magento\Framework\DataObject\IdentityInterface - -### when giving product page, must somehow send varnish tags -Any block can implement IdentityInterface. After rendering layout and before sending output, -all blocks are examined for implementing this interface. Cache tags are collected as merge of all blocks -getIdentities() tags. - -``` -\Magento\PageCache\Model\Layout\LayoutPlugin::afterGetOutput - X-Magento-Tags = merge(all blocks.getIdentities) -``` - -block ListProduct: - -- every product[].getIdentities - - cat_p_{productId} - - *if changed categories* - cat_p_c_{categoryId} - - *if changed status* - every category[] cat_p_c_{categoryId} - - *if frontend* - 'cat_p' -- cat_c_p_{categoryId} - -block product/view: - -- \Magento\Catalog\Model\Product::getIdentities: - - cat_p_{productId} - - *if changed categories* - cat_p_c_{categoryId} - - *if changed status* - every category[] cat_p_c_{categoryId} - - *if frontend* - 'cat_p' -- *if current_category* - cat_c_{categoryId} - -### after reindex, must somehow clean cache - -- any indexer.execute -- by MView -- any indexer.executeFull -- \Magento\Framework\Indexer\CacheContext::registerTags - -plugin \Magento\Indexer\Model\Processor: - -\Magento\Indexer\Model\Processor\CleanCache::afterUpdateMview - -- event `clean_cache_after_reindex` -- clean cache cacheContext->getIdentities() - -\Magento\Indexer\Model\Processor\CleanCache::afterReindexAllInvalid - -- event `clean_cache_by_tags` -- clean cache cacheContext->getIdentities() - -module-cache-invalidate observer `clean_cache_after_reindex` -\Magento\CacheInvalidate\Observer\InvalidateVarnishObserver::execute -\Magento\CacheInvalidate\Model\PurgeCache::sendPurgeRequest - -### Describe how to operate with cache clearing. - -How would you clean the cache? In which case would you refresh cache/flash cache storage? - - -### Describe how to clear the cache programmatically. - -What mechanisms are available for clearing all or part of the cache? diff --git a/1.Magento-Architecture-Customisation-Techniques.md b/1.Magento-Architecture-Customisation-Techniques.md new file mode 100755 index 0000000..e619ac4 --- /dev/null +++ b/1.Magento-Architecture-Customisation-Techniques.md @@ -0,0 +1,1566 @@ + +1 - Magento Architecture & Customisation Techniques +=================================================== + +1.0 What is a module? +--------------------- + +### Magento module overview + +> 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. +> +> 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. +> +> 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 2’s module-based architecture keeps that module’s files in one folder. This makes discovery of the functionality pertaining to that module easier.Modules live in one of two places: +> +> • `app/code/MyVendor/MyModule` +> +> • `vendor/vendor-name/module-name` + + +1.1 Describe Magento's module-based architecture +------------------------------------------------ + + + +Magento 2 modules are realised with MVVM architecture: + + + +![](./images/Magento-2-Certified-Professional-Developer-Guide-Screenshot-MVC.jpg) + + +MVVM has three layers: + +1. Model - The Model contains the + application's business logic and depends on an associated class—the + ResourceModel – for database access. Models depend on service contracts to + disclose their functionality to other application layers. +2. View - The View is both + structure and layout of what is seen on a screen – the actual HTML. This is + achieved in the PHTML files distributed with modules. Such files are associated with + each ViewModel in the Layout XML files, sometimes referred to as binders. The layout + files can as well assign JavaScript files to be used on the final page. +3. ViewModel -The ViewModel works + together with the Model layer and exposes only necessary information to the View layer. + In Magento 2, this is handled by the module's Block classes. Note that this was + usually part of the Controller role of an MVC system. On MVVM, the controller is only + responsible for handling the user flow, meaning that it receives requests and either + tells the system to render a view or to redirect the user to another route. + + + + + +![](./images/Magento-2-Certified-Professional-Developer-Guide-Persistence-Layer.jpg) + + + +Magento 2 architecture consists of 4 layers: + +1. Presentation Layer - Presentation Layer is the top layer that contains view elements + (layouts, blocks, templates) and controllers. +Presentation Layer usually calls the + service layer using service contracts. But, depending on the implementation, it may + cross over with the business logic. +2. Service Layer - Service layer is the layer between presentation and domain layers. + It executes service contracts, which are implemented as PHP interfaces. Service + contracts allow you to add or change the business logic resource model using the + dependency injection file (di.xml). The service layer is also used to provide API access + (REST / SOAP or other modules). The service interface is declared in the / API namespace + of the module. +Data (entity) interface is declared inside / Api / Data. Data + entities are the structures of data passed to and returned from service + interfaces. +3. Domain Layer - The domain layer is responsible for business logic that does not + contain information about resources and the database. Also, the domain layer may include + the implementation of service contracts. Each data model at the domain layer level + depends on the resource model, which is responsible for the database access. +4. Persistence Layer - The persistence layer describes a resource model that is responsible + for retrieving and modifying data in a database using CRUD requests. +It also + implements additional features of business logic, such as data validation and the + implementation of database functions. + + + + +![Magento Question](./images/icon-question.png)**Describe Module Registration** + +![Magento Answer Section - Chevron](./images/icon-chevron.png)How to register a Module - registration.php + + +```php +\', + __DIR__ +); +``` + + +![Magento Answer Section - Chevron](./images/icon-chevron.png)How to register a Language Package - registration.php + + +```php +\', + __DIR__ +); + +``` + + +![Magento Answer Section - Chevron](./images/icon-chevron.png)How to register a Theme - registration.php + +```php +//', + __DIR__ +); +``` + + +![Magento Answer Section - Chevron](./images/icon-chevron.png)Register using composer.json autoload/files[] = "registration.php" + + +```json +{ + "name": "/", + "autoload": { + "psr-4": { + "MY\MODULE\": "" + }, + "files": [ + "registration.php" + ] + } +} +``` + +![Magento Answer Section - Chevron](./images/icon-chevron.png)registration.php search paths + +Magento searches the following paths for registration: + +* app/code/*/*/cli_commands.php,registration.php +* app/design/*/*/*/registration.php +* app/i18n/*/*/registration.php +* lib/internal/*/*/registration.php +* lib/internal/*/*/*/registration.php + +![Magento Question](./images/icon-question.png)**What stage does registration occur?** + +![Magento Answer Section - Chevron](./images/icon-chevron.png)In composer - vendor/autoload.php: + + +```json +"autoload": { + "files": [ + "app/etc/NonComposerComponentRegistration.php" + ], +} +``` + +![Magento Answer Section - Chevron](./images/icon-chevron.png)Not in composer - app/etc/NonComposerComponentRegistration.php: + + +```php +/Data` directory which would house your Data Interfaces. + +![Magento Exam Notes - More information](./images/icon-info.png)Data Interfaces define functions that return information about data entities such as search results, set validation rules and return validation results. You must define the data interfaces for a service contract in the Api/Data subdirectory of a module e.g. Customer module are in the app/code/Magento/Customer/Api/Data subdirectory. + +[More info here](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/service-contracts/design-patterns.html&sa=D&ust=1609223264435000&usg=AOvVaw2RT5AqHcc9eKc9fF2vhFDt). + +![Magento Answer Section - Chevron](./images/icon-chevron.png)Area Scope + + + +One of the important parts of module interaction is a Magento area. + All components operate with the system and other components in the scope of default areas. + + + +Magento has 5 areas types: + +| Area code | Entry point | Directory path |Details +| --- | --- | --- | --- | +| `adminhtml` | `index.php` or `pub/index.php` | `/app/design/adminhtml` | Code for components you'll see while working in the Admin panel | +| `frontend` | `index.php` or `pub/index.php` | `/app/design/frontend` | The storefront (or frontend) contains template and layout files that define the appearance of your storefront | +| `base` | `index.php` or `pub/index.php` | `/app/design/base` OR `/Magento_Checkout/layout/override/base/` | Used as a fallback for files absent in adminhtml and frontend areas | +| `crontab` | `cron.php` | `cron.php` | CRON schedule tasks | +| `webapi_rest` | `index.php` or `pub/index.php` | `https:///rest/V1/products/:sku` | The REST area has a front controller that understands how to do URL lookups for REST-based URLs | +| `webapi_soap` | `index.php` or `pub/index.php` | WSDL: `https:///soap/?wsdl&services=,`| The XML SOAP area for API returning SOAP format. | +| `graphql` | `index.php` or `pub/index.php` | `https:///graphql` | An alternative to REST and SOAP web APIs for frontend development Mainly used for B2C use cases including: Reorders, Inventory Management store pickups, Order history for logged in customers, Replace product-specific mutations that add, products to a cart with a single mutation that can handle all product types, Gift wrapping and messages, Saved payment methods | + +![Magento Answer Section - Chevron](./images/icon-chevron.png)Module dependencies + + + +Modules can contain dependencies upon these software components: + +* Other Magento modules. +* PHP extensions. +* Third party libraries. + + + + + +1.2 Describe Magento's directory structure +------------------------------------------ + +![Magento Question](./images/icon-question.png)**Determine how to locate different types of files in Magento. Where are the files containing JavaScript, HTML, and PHP located?** + +* `view/{area}/web/js` +* `view/{area}/web/template` +* `view/{area}/web/css` +* `view/{area}/requirejs-config.js` +* `view/{area}/layout` +* `view/{area}/templates` +* `lib/` - Some supporting JS and CSS files are found here. + +> **Where** +> +> * `{area}` = Application Area code e.g. `frontend` or `adminhtml` + + +![Magento Question](./images/icon-question.png)**How do you find the files responsible for certain functionality?** + +Common directories of a module: + + + +* `Api` - The /Api folder stores the contracts for modules that contain specific actions that can be reliably utilized from various places in the app. + * `Api/Data` - This folder contains interfaces that represent data. Examples of this would be a Product interface, a Category interface or a Customer interface. The concrete implementations of these interfaces usually do little more than provide getters and setters for data (a.k.a Data Transfer Objects). +* `Block` - PHP view + classes as part of Model View Controller(MVC) vertical implementation of module + logic. +* `Console` - Console CLI + commands +* `Controller` - PHP + controller classes as part of MVC vertical implementation of module logic. + * `Controller/Adminhtml` - Admin + controllersCron - Cron job + classes +* `Cron` - This folder is the standard place for storing actions that are executed on a schedule (cron job). +* `etc` - Configuration + files; in particular, `module.xml`, which is required. +* `Helper` - Helpers +* `i18n` - Localization files in CSV format (languages) +* `Model` - PHP model classes as part of MVC + vertical implementation of module logic. + * `Model/ResourceModel` - Database + interactionsObserver - Event + listeners +* `Observer` - Event Listeners. When Magento fires an event, listeners that are attached to it are called. This decouples the system. +* `Plugin` - Contains any + needed plug-ins. +* `Setup` - + Classes for module database structure and data setup which are invoked when installing or + upgrading. +* `Test` - Unit Tests + tests +* `Ui` - UI component + classes, forms, grid etc +* `view` - View files, including static view files, design templates, email + templates, and layout files: + * `view/{area}/email` + * `view/{area}/layout` + * `view/{area}/templates` + * `view/{area}/ui_component` + * `view/{area}/ui_component/templates` + * `view/{area}/web` + * `view/{area}/web/template` + * `view/{area}/requirejs-config.js` + + + + +![Magento Exam Notes - More information](./images/icon-info.png)You do not have to memorise all this, the important thing to understand is that when you are produced a file or directory name, you understand what it's purpose is for. + +* Magento root app directory location `/app`: + +* Modules - `app/code`. + +* Storefront themes - `app/design/frontend`. + +* Admin themes - `app/design/adminhtml`. + +* Language packages - use `app/i18n`. + +* Or (via composer): `/vendor` + + +![Magento Question](./images/icon-question.png)**What files are required when creating a module?** + + + +* `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. + +* `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. + +* `composer.json`: Specifies component dependencies and other metadata. For more + information, see Composer integration. + + + + +1.3 Utilize configuration XML and variables scope +------------------------------------------------- + +![Magento Question](./images/icon-question.png)**Determine how to use configuration files in Magento. Which configuration files correspond to different features and functionality?** + + + +* [`acl.xml`](#h.gk6g5dbgvggm) - Restricting menu + actions, resource title, sortadminhtml/rules/payment_{country}.xml - + PayPaladdress_formats.xml +address_types.xml - format code and title only[cache.xml](#h.d3c5w1uvsp70) - Create custom cache type, name, instance - e.g. full_page=Page + Cachecatalog_attributes.xml - Contains the list of attributes that serve different purposes. + catalog_category, catalog_product, unassignable, used_in_autogeneration, quote_item + *[communication.xml](#h.c0x3qack8ych) - Queue Consumer +* [`config.xml`](#h.glpavwxvxuhd) - Adminhtml menus default values +* [`crontab.xml`](#h.nzp3v0np709e) - CRON tasks group[], job instance, method, schedule +* [`cron_groups.xml`](#h.nzp3v0np709e) - Grouping + / split processes +* [`di.xml`](#h.9ofwnl5q9fnf) - preferences, + plugins, virtual types + + + +* [`db_schema.xml`](#h.iqvl9uln4170) - Declarative setup declares what the database structure should + be. +* [`eav_attributes.xml`](#h.wm8oeat9wvdw) - EAV + attribute configuration: locked entity attributes (global, unique etc.) +* email_templates.xml - Specifies email templates that are used in Magento. The template id is the concatenated XML-style path to where in system configuration template is specified. +* [`events.xml`](#h.y2lezqicnwxl) - + Define an Observers: shared, disabled *export.xml - Add new column to exports +* [`extension_attributes.xml`](#h.15bsya8yrb45) - + Extension attributes, for, attribute code, attribute typefieldset.xml - Magento UI Object copying, e.g. + customer address copy firstname lastname with +* `import.xml` - same as export but + import +* [`indexer.xml`](#h.xa0vtkhng9ut) - Create a new + indexer class, view_id, title, description +* [integration.xml](#h.t252zlddzc3r) + + * [`integration/api.xml`](#h.85qnykcohlui) - + Defines which API resources the integration has access to. + * [`integration/config.xml`](#h.pbacdg8dooea) - An + Integrations pre-configured with default values. +* [`menu.xml`](#h.gk6g5dbgvggm) - Admin main left menu +* [`module.xml`](#h.n1343l3gk3km) - (required) Defines a module: version, sequence +* [`mview.xml`](#h.2m5sw7piaq5j) - Indexes, React to MySQLtriggers - scheduled updates, subscribe to + table changes, indexer model +* `page_types.xml` +* [`payment.xml`](#h.ctho0c6bcdkk) - Declaring a + new Payment Method: groups, method allow_multiple_addresspdf.xml - renders by type (invoice, + shipment, credit memo) and product typeproduct_types.xml - Create a new product + type. label, model instance, index priority, (?) custom attributes, (!) composable + typesproduct_options.xml + + + +* [`queue.xml`](https://devdocs.magento.com/guides/v2.4/extension-dev-guide/message-queues/queue-migration.html&sa=D&ust=1609223264462000&usg=AOvVaw3OB1YP_nd35xQiu2i7KxN9) - (deprecated) Old way of defining a new Queue consumer [see + this article](https://devdocs.magento.com/guides/v2.4/extension-dev-guide/message-queues/queue-migration.html&sa=D&ust=1609223264463000&usg=AOvVaw3PMNONHbowdtWoeZa0GGUF) on how to upgrade this. + + + +* [`queue_consumer.xml`](#h.bokyb17e6arz) - Where we define the consumer parameters. It's also possible to declare a + handler class in this file. + + + +* [`queue_topology.xml`](#h.qakz6upsjogv) - Defines the message routing rules and declares queues and exchanges. + + + +* [`queue_publisher.xml`](#h.yx5eensvx7ac) - Send a message to a message bus without knowing who is interested in the + message. +* `resources.xml` +* [`routes.xml`](#h.39aby0sbkm32) - Map a Controller to a + Route. Creating a new Router + (when module/action/method doesn't fit). +* [`sales.xml`](#h.6bmvxvofcj95) - Define totals for entities (quote, order, invoice, credit + memo)search_engine.xml - +search_request.xml - Elastic search + related: index, dimensions, queries, filters, aggregations, bucketssections.xml - action route placeholder + -> invalidate customer sections +* [`system.xml`](#h.op69p944prs) - adminhtml menu + system configvalidation.xml - Regex validation entity, rules, constraints -> + classview.xml - vars by + module +* `view.xml` - Similar to `config.xml` but used for specifying default values for design configuration. +* [`webapi.xml`](#h.2lcvcwr56aq) - Create new API + route, method, service class and method, resourceswidget.xml - class, email compatible, + image, ttl (?), label, description, parameterszip_codes.xml +* `widget.xml` - Configures widgets to be used in products, CMS pages, and CMS blocks. + + + + +1.4 Demonstrate how to use dependency injection +----------------------------------------------- + +![Magento Question](./images/icon-question.png)**Describe Magento's dependency injection approach and architecture.** + + +> Dependency Injection is a design pattern that allows an object A +> to declare its dependencies to an external object B that supplies those dependencies. The +> dependencies declared by A are usually class interfaces and the dependencies B provides are +> concrete implementations for those interfaces. This allows for loose coupling of code +> because object A no longer needs to be concerned with initializing its own dependencies. +> Object B decides which implementations to provide to object A based on a configuration or +> desired behavior. + + + + +![Magento Exam Notes - More information](./images/icon-info.png)This is probably very useful to memorise as this question is more general programming related but also a big part of core Magento 2 + +![Magento Question](./images/icon-question.png)**Identify how to use DI configuration files for customizing Magento?** + +> Magento loads `di.xml` files and merges them all together from the + following stages: +* Initial master file (`/app/etc/di.xml`) +* Global (`/etc/di.xml`) +* Area-specific (`/etc/{area}/di.xml`) + +![Magento Answer Section - Chevron](./images/icon-chevron.png)Plugins +> Plugins allow you to wrap another class’ public functions, add a before method to modify the input arguments, or add an after method to modify the output. These will be discussed more in the next section.Example: `vendor/magento/module-catalog/etc/di.xml` (search for “plugins”) + +![Magento Answer Section - Chevron](./images/icon-chevron.png)Preferences +> Preferences are used to substitute entire classes. They can also be used to specify a concrete class for an interface. If you create a service contract for a repository in your /Api folder and a concrete class in /Model, you can create a preference like: + +```xml + +``` + +![Magento Answer Section - Chevron](./images/icon-chevron.png)Virtual Types +> A virtual type allows the developer to create an instance of an existing class that has custom constructor arguments. This is useful in cases where you need a “new” class only because the constructor arguments need to be changed.This is used frequently in Magento to reduce redundant PHP classes. + +Argument Preferences / Constructor Arguments +> It is possible to modify what objects are injected into specific classes by targeting the name of the argument to associate it with the new class.Now, with Magento 2, you can inject your custom class into any other classes constructor in di.xmlExample: + +```xml + + + + sort_order + + + +``` + +![Magento Question](./images/icon-question.png)**How are objects realized in Magento?** + + + +> They are instantiated via the: + +* Object manager +* Arguments via Dependency Injection + + + + + +![Magento Question](./images/icon-question.png)**Where would you set sensitive settings?** + + + +* Shared config: `app/etc/config.php` +* Sensitive config: + `app/etc/env.php` + + + + +![Magento Question](./images/icon-question.png)**Why is it important to have a centralized process creating object instances?** + +> Through the Dependency inversion principle, Using interfaces in +> your code reduces the risk of incompatibility bugs when Magento changes the underlying +> implementation of those interfaces. It also avoids the inheritance, which means that +> applications become more flexible. In this, you don't need to think about the child +> classes when changing the parent one. + + + + +![Magento Exam Notes - More information](./images/icon-info.png)This is probably very useful to memorise as this question is more general programming related but also a big part of Magento 2 + +![Magento Question](./images/icon-question.png)**How can you override a native class, inject your class into another object, and use other techniques available in di.xml (such as virtualTypes)?** + + + +Using a DI preference from a custom module: + +Preference `di.xml` +```xml + +``` + +> For `` Inject your class into another object: use a `` entry with a `\Path\To\Your\Class` entry in the `` node. + +![Magento Exam Notes - More information](./images/icon-info.png)Preferences in the di.xml are very commonly used. + +1.5 Demonstrate ability to use plugins +-------------------------------------- + +![Magento Question](./images/icon-question.png)**Demonstrate how to design complex solutions using the plugin's life cycle.** + +> There are three types of plugins in magento: **around, before and after interceptors**. Classes, abstract classes and interfaces +> that are implementations of or inherit from classes that have plugins will also inherit plugins from the parent class. For example, if you create a plugin for `Magento\Catalog\Block\Product\AbstractProduct`, plugin methods will be called for all child classes, such as `Magento\Catalog\Block\Product\View`,`Magento\Catalog\Block\Product\ProductList\Upsell` and so on... + + +![Magento Exam Notes - More information](./images/icon-info.png)The important thing to remember here is that plugins' life cycles are based before, around and after the overriding method. + +![Magento Exam Notes - More information](./images/icon-info.png)An around plugin should only be when the execution of the before and after plugins must be suppressed. + +![Magento Question](./images/icon-question.png)**How do multiple plugins interact, and how can their execution order be controlled?** + +> The `sortOrder` property controls how your plugin interacts with other plugins on the same class. + + +![Magento Question](./images/icon-question.png)**How do you debug a plugin if it doesn't work?** + + + +> Magento automatically generates an Interceptor class for the +> plugin target and store it in the generated\code directory. If it's not working then +> this will not have been created in which you may need to compile DI or check the logs for +> errors. + + + + +![Magento Question](./images/icon-question.png)**Identify strengths and weaknesses of plugins. What are the limitations of using plugins for customization? In which cases should plugins be avoided?** + +> Plugins only work on public methods for any classes or interfaces. So plugins CANNOT be used on following: + +1. Final methods +2. Final classes +3. Non-public methods +4. Class methods (such as static methods) +5. _construct +6. Virtual types +7. Objects that are instantiated before Magento\Framework\Interception is bootstrapped + + +> Plugins are useful to modify the input, output, or execution of +> an existing method. Plugins are also best to be avoided in situations where an event +> observer will work. Events work well when the flow of data does not have to be +> modified. +> Around-plugins SHOULD only be used when behavior of an original method is +> supposed to be substituted in certain scenarios, they increase stack traces and affect +> performance. Used improperly, an around plugin can prevent the system from functioning. They +> can also make understanding what is going on by reading source code hard (spooky action at a +> distance). + + + + +![Magento Exam Notes - More information](./images/icon-info.png)Important and useful. + +1.6 Configure event observers and scheduled jobs +------------------------------------------------ + +### Observers + +Events are dispatched by modules when certain actions are triggered. In addition to its own events, Magento allows you to create your own events that can be dispatched in your code. When an event is dispatched, it can pass data to any observers configured to watch that event. + +![Magento Question](./images/icon-question.png)**Demonstrate how to configure observers. How do you make your observer only active on the frontend or backend?** + +> Observers can be configured in the events.xml file. It can be global and specific to either front-end or back-end. The structure goes as follows within your Module: +* `/etc` +* `/etc/frontend` +* `/etc/adminhtml` + +![Magento Exam Notes - More information](./images/icon-info.png)I've seen many questions about events.xml and area codes appear before in the past exam questions + + + +Observers have the following properties: + +* name (required) - Name of the observer for the event + definition +* instance (required) - Class name of the observer +* disabled - Is observer active or not (Default: false) + +* shared - Class lifestyle (Default: false) + + +Events dispatch by Magento\Framework\Event\Manager class that implement Magento\Framework\Event\ManagerInterface interface: +```php +$this->eventManager->dispatch('cms_page_prepare_save', ['page' => $model, 'request' => $this->getRequest()]); +``` + + +### CRONtab + +![Magento Question](./images/icon-question.png)**Demonstrate how to configure a scheduled job. Which parameters are used in configuration, and how can configuration interact with server configuration?** + +![Magento Answer Section - Chevron](./images/icon-chevron.png)CRON group + + + +> A cron group is a logical group that enables you to easily run cron +> for more than one process at a time. Most Magento modules use the default cron group. Each +> group is run on a separate PHP process. +> To declare new group and specify settings, create `/etc/cron_groups.xml` file (store view scope): + +```xml + + + + + 15 + 20 + 15 + 10 + 10080 + 10080 + 0 + + +``` + +![Magento Answer Section - Chevron](./images/icon-chevron.png)CRON tab + +> `crontab.xml` file is used to execute an action on schedule. This file always located in the /etc/ + +Parameters used: + +```xml + + + + + + 0 * * * * + + + +``` + +![Magento Exam Notes - More information](./images/icon-info.png)You won't be expected to write this off-by-heart but it will pay to understand it's required parameters + +* `update/cron.php` - NOT used any more since 2.1 +* `bin/magento setup:cron:run` - only used during installation +* `bin/magento cron:run` [–-group=] - the main cron run command +* `pub/cron.php` [--group=] in a web browser, protect with basic auth +* You can also override the schedule times of CRON groups via the admin in: __Admin > Stores > Configuration > Advanced > System > Cron (Scheduled * Tasks)__ + + +![Magento Exam Notes - More information](./images/icon-info.png)All the times are in minutes. + +![Magento Question](./images/icon-question.png)**Identify the function and proper use of automatically available events, for example *_load_after, etc.** + + + +Load, Save, Delete on Model, Flat Collections & EAV Collections before + after: +``` +model_load_before, +{$_eventPrefix}_load_before +model_load_after, +{$_eventPrefix}_load_after +model_save_commit_after, +{$_eventPrefix}_save_commit_after +model_save_before, +{$_eventPrefix}_save_before +model_save_after, +{$_eventPrefix}_save_after +model_delete_before, +{$_eventPrefix}_delete_before +model_delete_after, +{$_eventPrefix}_delete_after +model_delete_commit_after, +{$_eventPrefix}_delete_commit_after +core_collection_abstract_load_before, +{_eventPrefix}_load_before +core_collection_abstract_load_after, +{_eventPrefix}_load_after +eav_collection_abstract_load_before +``` + + + +![Magento Exam Notes - More information](./images/icon-info.png) Just remember that core events such as the above will feature _before and _after suffixes + +1.7 Utilize the CLI +------------------- + +![Magento Question](./images/icon-question.png)**Describe the usage of bin/magento commands in the development cycle. Which commands are available?** + +`php bin/magento` +``` +Magento CLI 2.4.2-p1 + +Usage: + command [options] [arguments] + +Options: + -h, --help Display this help message + -q, --quiet Do not output any message + -V, --version Display this application version + --ansi Force ANSI output + --no-ansi Disable ANSI output + -n, --no-interaction Do not ask any interactive question + -v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug + +Available commands: + help Display help for a command + list List commands + admin + admin:user:create Creates an administrator + admin:user:unlock Unlock Admin Account + app + app:config:dump Create dump of application + app:config:import Import data from shared configuration files to appropriate data storage + app:config:status Checks if config propagation requires update + braintree + braintree:migrate Migrate stored cards from a Magento 1 database + cache + cache:clean Cleans cache type(s) + cache:disable Disables cache type(s) + cache:enable Enables cache type(s) + cache:flush Flushes cache storage used by cache type(s) + cache:status Checks cache status + catalog + catalog:images:resize Creates resized product images + catalog:product:attributes:cleanup Removes unused product attributes. + cms + cms:wysiwyg:restrict Set whether to enforce user HTML content validation or show a warning instead + config + config:sensitive:set Set sensitive configuration values + config:set Change system configuration + config:show Shows configuration value for given path. If path is not specified, all saved values will be shown + cron + cron:install Generates and installs crontab for current user + cron:remove Removes tasks from crontab + cron:run Runs jobs by schedule + customer + customer:hash:upgrade Upgrade customer's hash according to the latest algorithm + deploy + deploy:mode:set Set application mode. + deploy:mode:show Displays current application mode. + dev + dev:di:info Provides information on Dependency Injection configuration for the Command. + dev:profiler:disable Disable the profiler. + dev:profiler:enable Enable the profiler. + dev:query-log:disable Disable DB query logging + dev:query-log:enable Enable DB query logging + dev:source-theme:deploy Collects and publishes source files for theme. + dev:template-hints:disable Disable frontend template hints. A cache flush might be required. + dev:template-hints:enable Enable frontend template hints. A cache flush might be required. + dev:template-hints:status Show frontend template hints status. + dev:tests:run Runs tests + dev:urn-catalog:generate Generates the catalog of URNs to *.xsd mappings for the IDE to highlight xml. + dev:xml:convert Converts XML file using XSL style sheets + dotdigital + dotdigital:connector:automap Auto-map data fields + dotdigital:connector:enable Add Dotdigital API credentials and enable the connector + dotdigital:migrate Migrate data into email_ tables to sync with Engagement Cloud + dotdigital:sync Run syncs to populate email_ tables before importing to Engagement Cloud + dotdigital:task Run dotdigital module tasks on demand + downloadable + downloadable:domains:add Add domains to the downloadable domains whitelist + downloadable:domains:remove Remove domains from the downloadable domains whitelist + downloadable:domains:show Display downloadable domains whitelist + encryption + encryption:payment-data:update Re-encrypts encrypted credit card data with latest encryption cipher. + i18n + i18n:collect-phrases Discovers phrases in the codebase + i18n:pack Saves language package + i18n:uninstall Uninstalls language packages + indexer + indexer:info Shows allowed Indexers + indexer:reindex Reindexes Data + indexer:reset Resets indexer status to invalid + indexer:set-dimensions-mode Set Indexer Dimensions Mode + indexer:set-mode Sets index mode type + indexer:show-dimensions-mode Shows Indexer Dimension Mode + indexer:show-mode Shows Index Mode + indexer:status Shows status of Indexer + info + info:adminuri Displays the Magento Admin URI + info:backups:list Prints list of available backup files + info:currency:list Displays the list of available currencies + info:dependencies:show-framework Shows number of dependencies on Magento framework + info:dependencies:show-modules Shows number of dependencies between modules + info:dependencies:show-modules-circular Shows number of circular dependencies between modules + info:language:list Displays the list of available language locales + info:timezone:list Displays the list of available timezones + info:unirgy:list-modules List installed licenses + inventory + inventory:reservation:create-compensations Create reservations by provided compensation arguments + inventory:reservation:list-inconsistencies Show all orders and products with salable quantity inconsistencies + inventory-geonames + inventory-geonames:import Download and import geo names for source selection algorithm + maintenance + maintenance:allow-ips Sets maintenance mode exempt IPs + maintenance:disable Disables maintenance mode + maintenance:enable Enables maintenance mode + maintenance:status Displays maintenance mode status + media-content + media-content:sync Synchronize content with assets + media-gallery + media-gallery:sync Synchronize media storage and media assets in the database + module + module:config:status Checks the modules configuration in the 'app/etc/config.php' file and reports if they are up to date or not + module:disable Disables specified modules + module:enable Enables specified modules + module:status Displays status of modules + module:uninstall Uninstalls modules installed by composer + newrelic + newrelic:create:deploy-marker Check the deploy queue for entries and create an appropriate deploy marker. + queue + queue:consumers:list List of MessageQueue consumers + queue:consumers:start Start MessageQueue consumer + remote-storage + remote-storage:sync Synchronize media files with remote storage. + sampledata + sampledata:deploy Deploy sample data modules for composer-based Magento installations + sampledata:remove Remove all sample data packages from composer.json + sampledata:reset Reset all sample data modules for re-installation + security + security:recaptcha:disable-for-user-forgot-password Disable reCAPTCHA for admin user forgot password form + security:recaptcha:disable-for-user-login Disable reCAPTCHA for admin user login form + setup + setup:backup Takes backup of Magento Application code base, media and database + setup:config:set Creates or modifies the deployment configuration + setup:db-data:upgrade Installs and upgrades data in the DB + setup:db-declaration:generate-patch Generate patch and put it in specific folder. + setup:db-declaration:generate-whitelist Generate whitelist of tables and columns that are allowed to be edited by declaration installer + setup:db-schema:upgrade Installs and upgrades the DB schema + setup:db:status Checks if DB schema or data requires upgrade + setup:di:compile Generates DI configuration and all missing classes that can be auto-generated + setup:install Installs the Magento application + setup:performance:generate-fixtures Generates fixtures + setup:rollback Rolls back Magento Application codebase, media and database + setup:static-content:deploy Deploys static view files + setup:store-config:set Installs the store configuration. Deprecated since 2.2.0. Use config:set instead + setup:uninstall Uninstalls the Magento application + setup:unirgy:add-license Add Unirgy license + setup:unirgy:check-updates Check for module updates + setup:unirgy:update-reinstall Update - reinstall unirgy modules + setup:upgrade Upgrades the Magento application, DB data, and schema + store + store:list Displays the list of stores + store:website:list Displays the list of websites + theme + theme:uninstall Uninstalls theme + varnish + varnish:vcl:generate Generates Varnish VCL and echos it to the command line + yotpo + yotpo:reset Reset Yotpo sync flags &/or configurations + yotpo:sync Sync Yotpo manually (reviews module) + yotpo:update-metadata Manually send platform metadata to Yotpo +``` + +> Modes: default, developer, production. MAGE-MODE env variable + +```bash +bin/magento deploy:mode:show +bin/magento deploy:mode:set {mode} [-s|--skip-compilation] +``` + + +![Magento Exam Notes - More information](./images/icon-info.png)Ambiguous question, there are many commands in bin/magento - this answer assumes the command `bin/magento deploy:mode:show` + +![Magento Question](./images/icon-question.png)**How are commands used in the development cycle?** + + + +> During DI compilation `bin/magento setup:di:compile`. Code compilation includes the following (in no particular order): + +* Application code generation (factories, proxies) +* Area configuration aggregation (optimized dependency + injection configurations per area) see generated/metadata +* Interceptor generation (optimized code generation of + interceptors) +* Interception cache generation +* Repositories code generation (generated code for + APIs) +* Service data attributes generation (generated extension + classes for data objects) +* Running setup:di:compile places the app into a special + mode + +![Magento Question](./images/icon-question.png)**Demonstrate an ability to create a deployment process. How does the application behave in different deployment modes, and how do these behaviors impact the deployment approach for PHP code, frontend assets, etc?** + +![Magento Answer Section - Chevron](./images/icon-chevron.png)Default mode + +* Symlinks are established in the `pub/static` folder. These are linked from files in the `app/code` or `app/design` folders. +* Exceptions are not displayed to the user (making development very difficult). They are logged in `var/log`. +* Static files are generated on the fly and are symlinked into the `var/view_preprocessed` folder. + +![Magento Answer Section - Chevron](./images/icon-chevron.png)Developer mode + +* Symlinks are established in the `pub/static` folder. These are linked from files in the `app/code` or `app/design` folders. +* Errors are shown to the user and logging is verbose. Caution: debug logging is disabled by default even in developer mode. +* Magento automatically builds code for plugins (interceptors), factories, etc. as it does in the other modes. +* Slow performance. + +![Magento Answer Section - Chevron](./images/icon-chevron.png)Production mode + +* Max speed, no errors, no file generation: admin can't enable/disable cache types +* Exceptions are not displayed to the user (making development very difficult). They are logged in `var/log`. +* Static assets not created dynamically, must be deployed via CLI such like: `php bin/magento setup:static-content:deploy -f en_AU en_US` + + +![](./images/Magento2-Certification-Exam-Notes-Detailed-Insight-Deploy-Modes.png) + +![Magento Exam Notes - More information](./images/icon-info.png)I think it's important to remember the pros and cons of each mode, this image displays it nicely. + +1.8 Demonstrate the ability to manage the cache +----------------------------------------------- + +![Magento Question](./images/icon-question.png)**Describe cache types and the tools used to manage caches.** + + + +| Cache type "friendly" name | Cache type code name | Description | +| --- | --- | --- | +| Configuration | `configuration` | Magento collects configuration from all modules, merges it, and saves the merged result to the cache. This cache also contains store-specific settings stored in the file system and database. Clean or flush this cache type after modifying configuration files. | +| Layout | `layout` | Compiled page layouts (that is, the layout components from all components).Clean or flush this cache type after modifying layout files. | +| Block HTML output | `block_html` | HTML page fragments per block. Clean or flush this cache type after modifying the view layer. +| Collections data | `collections` | Results of database queries. If necessary, Magento cleans up this cache automatically, but third-party developers can put any data in any segment of the cache. Clean or flush this cache type if your custom module uses logic that results in cache entries that Magento cannot clean. | +| DDL | `db_ddl` | Database schema. If necessary, Magento cleans up this cache automatically, but third-party developers can put any data in any segment of the cache. Clean or flush this cache type after you make custom changes to the database schema. (In other words, updates that Magento does not make itself.) One way to update the database schema automatically is using the [`bin/magento setup:db-schema:upgrade`](https://devdocs.magento.com/guides/v2.2/install-gde/install/cli/install-cli-subcommands-db.html&sa=D&ust=1609223264497000&usg=AOvVaw11ISXilTr222oUPOaex6ti) command. | +| Entity attribute value (EAV) | `eav` | Metadata related to EAV attributes (for example, store labels, links to related PHP code, attribute rendering, search settings, and so on). You should not typically need to clean or flush this cache type. | +| Page cache | `full_page` | Generated HTML pages. If necessary, Magento cleans up this cache automatically, but third-party developers can put any data in any segment of the cache. Clean or flush this cache type after modifying code level that affects HTML output. It's recommended to keep this cache enabled because caching HTML improves performance significantly. | +| Reflection | `reflection` | Removes a dependency between the Webapi module and the Customer module. | +| Translations | `translate` | Merged translations from all modules. | +| Integration configuration | `config_integration` | Compiled integrations. Clean or flush this cache after changing or adding integrations. | +| Integration API configuration | `config_integration_api` | Compiled integration APIs. | +| Web services configuration | `config_webservice` | Web API structure. | + + + + ![Magento Exam Notes - More information](./images/icon-info.png)I wouldn't think you'd be expected to remember this, these cache types are available in the admin but it might pay to understand roughly what each cache type does when presented with the "friendly" name. Just try to remember: Store module configuration / Layout declaration / Block template HTML output / Translations / Full Page Cache (FPC) / Database Queries. + +![Magento Question](./images/icon-question.png)**How do you add dynamic content to pages served from the full page cache?** + +* Mark any block cacheable="false" in layout.xml - renders whole page uncacheable. Example = + checkout. +* Disable caching in controller using headers: + +```php +$page->setHeader( + 'Cache-Control', + 'no-store, no-cache, must-revalidate, max-age=0', + true +); +``` + +* ![Magento Exam Note Warning](images/icon-warning.png)NOTE: deprecated Marking block property isScopePrivate + - will be loaded via AJAX +* ESI when Varnish enabled, set TTL. For example: Megamenu + blocks from common extensions. +* Configure page variations - extend http context, more + cached versions of same page - store view, customer group, language, currency, is logged + in Magento\Framework\App\Http\Context::getVaryString - So when you are not logged in, + all guests receive a cached version of the page. When you add a product to cart the + query string changes which renders another variation. +* Magento 2 [JSComponents](#h.kg229a1fn1nm) for FPC + hole-punching. +* Magento 2 [Dynamic Blocks](#h.db4onextldi8). + + +![Magento Question](./images/icon-question.png)**Describe how to operate with cache clearing. How would you clean the cache?** + +![Magento Answer Section - Chevron](./images/icon-chevron.png)Via CLI + + +``` bash +bin/magento setup:upgrade +bin/magento setup:db-schema:upgrade +bin/magento cache:disable +bin/magento cache:clean +bin/magento cache:clean full_page +bin/magento cache:flush +bin/magento cache:flush full_page +bin/magento index:reindex +``` + +![Magento Answer Section - Chevron](./images/icon-chevron.png)Via Admin + + + +> **Admin > System > Tools > Cache Management** + + +![Magento Question](./images/icon-question.png)**In which case would you refresh (clean) cache vs flush cache storage?** + +> When you clean cache, it deletes all items from enabled Magento cache types only. Disabled cache types are not cleaned. +> When you flush cache, you remove all cache records in storage. It might affect other processes applications that are using the same storage. +> Re-indexing also clears cache. +> Performing a `bin/magento setup:upgrade` also clears cache. + + +![Magento Exam Notes - More information](./images/icon-info.png)Important to know the difference between cache clean and cache flush. + +![Magento Question](./images/icon-question.png)**Describe how to clear the cache programmatically.** + + + +Remove cached data by identifier: +```php +Magento\Framework\App\CacheInterface::remove(string $identifier); +``` + +Clean cached data by specific tag: +```php +Magento\Framework\App\CacheInterface::clean(array $tags = []); +``` + +![Magento Exam Notes - More information](./images/icon-info.png) Rather than remembering this class name. Remember 2 things: + +1. Cache is part of Magento_Framework module so it's very core. +2. Magento likes you to inject interface dependencies thanks to it's automatic type hint allowing the system to search for a class. + +Thus "Magento" "Framework" "Cache" "Interface". + +![Magento Question](./images/icon-question.png)**What mechanisms are available for clearing all or part of the cache?** + + +Admin +> **Admin > System > Tools > Cache Management** + + +CLI +```bash +bin/magento cache:clean +bin/magento cache:clean full_page +bin/magento cache:flush +bin/magento cache:flush full_page +``` + +Events + +> Dispatch a "clean_cache_by_tags" event with +> parameters of the object you want to clear from the cache. Example: +> Magento\Framework\Model\AbstractModel (afterSave, afterDelete methods) + + + +```php +cleanModelCache(); + $this->_eventManager->dispatch('model_save_after', ['object' => $this]); + $this->_eventManager->dispatch('clean_cache_by_tags', ['object' => $this]); + $this->_eventManager->dispatch($this->_eventPrefix . '_save_after', $this->_getEventData()); + $this->updateStoredData(); + return $this; +} + +public function cleanModelCache() +{ + $tags = $this->getCacheTags(); + if ($tags !== false) { + /** @var Magento\Framework\App\CacheInterface */ + $this->_cacheManager->clean($tags); + } + return $this; +} + +``` + +![Magento Question](./images/icon-question.png)**Describe Cache Types. How would you create your own Cache Type?** + +> Caching is the storage of data for future retrieval / usage. It's designed to speed up future requests. Or is it a way to mask poorly optimised code... + + +Magento uses caching to quickly display Categories, Products, and CMS + pages. Full-page caching in particular improves response time and reduces the load on the + server. Without caching, each page might need to run blocks of code and retrieve information + from the database. However, with full-page caching enabled, a fully-generated page can be + read directly from the cache. + +Magento recommends that Varnish Cache be used as the + caching applications and only in a production environment. + +Cached content can be used + to process the requests from similar types of visits. As a result, pages shown to a casual + visitor might differ from those shown to a customer. For the purposes of caching, each visit + is one of three types: + + + +* Non-sessioned - During a non-sessioned visit, a shopper views pages, but does not interact with the store. The + system caches the content of each page viewed and serves them to other non-sessioned + shoppers. +* Sessioned - During a sessioned + visit, shoppers who interact with the store -- through activities such as comparing + products or adding products to the shopping cart -- are assigned a session ID. Cached + pages that are generated during the session are used only by that shopper during the + session. +* Customer - Customer sessions are + created for those who have registered for an account with your store and shop while + logged in to their accounts. During the session, customers can be presented with special + offers, promotions, and prices that are based on their assigned customer group. + + +Creating your own cache type + +#### cache.xml + +> A cache type enables you to specify what is cached and enables +> merchants to clear that cache type using the Cache Management page in the Magento Admin. See +> above for all available cache types. The following is an example for demonstration purposes only + + +`/etc/cache.xml` +```xml + + + + + + Example of how to create a cache type using declarative schema + + +``` + +Cache Type example: + +`MyVendor\MyModule\Model\Cache\Type\Example` +```php + System > Cache Management > Cache type: "Example Cache Type" */ +class Example extends TagScope +{ + /** @var string Cache type code unique among all cache types */ + const TYPE_IDENTIFIER = 'example'; + + /** @var string The tag name that limits the cache cleaning scope within a particular tag */ + const CACHE_TAG = 'example'; + + /** @var FullPageCache */ + private $fullPageCache; + + /** @var TypeListInterface */ + protected $typeList; + + /** + * @param FrontendPool $cacheFrontendPool + * @param TypeListInterface $typeList + */ + public function _construct( + FrontendPool $cacheFrontendPool, + TypeListInterface $typeList + + ) { + parent::_construct( + $cacheFrontendPool->get(self::TYPE_IDENTIFIER), + self::CACHE_TAG + ); + $this->typeList = $typeList; + } + + /** + * WARNING: Use this method lightly as the whole site can be affected here without hole-punching. + * @return FullPageCache + */ + + private function getFullPageCache(): FullPageCache + { + if (!$this->fullPageCache) { + $this->fullPageCache = ObjectManager::getInstance()->get(FullPageCache::class); + } + return $this->fullPageCache; + } + + public function cleanByTags(array $postRelatedProductIds): bool + { + $tags = []; + foreach ($postRelatedProductIds as $postRelatedProductId) { + $tags[] = static::CACHE_TAG . '_' . $postRelatedProductId; + } + return $this->clean(Zend_Cache::CLEANING_MODE_MATCHING_TAG, $tags); + } + + public function cleanTag(): bool + { + return $this->clean(Zend_Cache::CLEANING_MODE_MATCHING_TAG, [static::CACHE_TAG]); + } +} + +``` + + +> Finally, this can be outputted somewhere in any block class that +> implements Magento\Framework\DataObject\IdentityInterface this way +> you will see such tags (ex, ex_1) in the request headers of any page that renders this block +> such as: + +```bash +curl -I -k https://example.localhost/my-example-page/ + +HTTP/2 200 +x-magento-tags: store,cms_b,cms_b_footer-top-content,cms_b_footer-middle-logo,cms_b_footer-middle-newsletter,cms_b_footer-middle-link,cms_b_footer-payment-logo,cms_b_footer-bottom-copyright,cms_b_footer-bottom-braintrees,cms_b_header-top-wrapper,aw_blog_category_sidebar,cms_b_0,ex,ex_1 +``` + +> When you clean caches by tag (i.e. `MyVendor\MyModule\Model\Cache\Type\Example::cleanTag`) then any page matching these tag types will be purged. + + + + +> Block class example implementing Magento\Framework\DataObject\IdentityInterface + +```php +getSomeIds() as $id) { + $identities[] = Example::CACHE_TAG . '_' . $id; + } + return $identities; + } +} + +``` + +![Magento Question](./images/icon-question.png)**What is the difference between full_page Cache (FPC) and the other cache types?** + + + +There are 3 common terms: +1. Cold Cache - When there's nothing stored for + retrieval. +2. Warm Cache - When content is available for + retrieval. +3. Full Page Cache (FPC) - Caching of the entire + page. + + + + + +![Magento Question](./images/icon-question.png)**How would you hole-punch FPC?** + +### ![Magento Answer Section - Chevron](./images/icon-chevron.png)JavaScript Component + + +> A JSComponent with AJAX request would be one effective way to by-pass information from being cached and keep output dynamic. +> A JSComponent is a RequireJS module. +> Magento will load these based on JSON given in custom script tags or data-* attributes. The crucial difference is that this module has to return a function. This function will be called with two arguments: the configuration object passed via the JSON, and the element (singular, non-jQuery object) that the component is called on. It is called once per matched element. + + + +First we define RequireJs configuration: + +`requirejs-config.js` +```js +var config = { + map: { + '*': { + exampleJsComponent: 'MyVendor_MyModule/js/model/example' + } + } +}; +``` + +>Next we declare our JSComponent using XML Layout declarative schema. +>For the purpose of this example, the product view page has been chosen as this would be an +> example of a cached page (unlike checkout). + +`catalog_product_view.xml` +```xml + + + + + + + + + + exampleJsComponent + + 'MyVendor_MyModule/view/example + + + + + + + + + +``` + + +> Next we specify custom script tags with JSON of which to load this RequireJs Module. + + + + +`MyVendor_MyModule::product/view/example.phtml` +```html +getProduct(); +?> + + + +
+ + +
+``` + +> Now for the meaty part, the JSComponent itself with the AJAX Request. + + +`MyVendor_MyModule/js/model/example` + +```js +define( + [ + 'ko', + 'jquery', + 'uiComponent', + 'mage/cookies' + ], + function ( + ko, + $, + Component + ) { + 'use strict'; + + const PRODUCT_DATA = window.exampleData; + + return Component.extend({ + + /** + * @var {Object} + */ + description: ko.observable(PRODUCT_DATA.description), + + /** + * @var {Object} + */ + title: ko.observable(PRODUCT_DATA.title), + + /** + * @var {Object} + */ + + defaults: { + template: 'MyVendor_Module/view/example' + }, + + /** + * Constructor + */ + + initialize: function () { + + this._super(); + $(document).ready(() => this.ready()); + + return this; + }, + + /** + * DOM Loaded + */ + ready: function () { + + this.ajaxRequest( + {}, + PRODUCT_DATA.uri + ).then( + (response) => { + this.description(response); + } + ).catch( + (err) => { + console.error(err); + } + ); + }, + + /** + * @param {Object|JSON} data + * @param {String} uri + * @return {Promise} + */ + ajaxRequest: function (data, uri) { + + data['form_key'] = $.mage.cookies.get('form_key'); + const contentType = (data instanceof Object) ? "application/x-www-form-urlencoded; charset=UTF-8" : "application/json; charset=UTF-8"; + return new Promise((resolve, reject) => { + + $.ajax({ + url: uri, + type: 'POST', + dataType: "json", + contentType: contentType, + showLoader: true, + data: data, + timeout: 30000, + success: (data, textStatus, jQxhr) => { + resolve(data); + }, + error: (jqXhr) => { + reject(jqXhr); + } + }); + }); + } + }); + } +); +``` + +> Finally, the template file which was defined in both the XML Layout and JSComponent + +`MyVendor_Module/view/example` +```html + +

+``` + +### ![Magento Answer Section - Chevron](./images/icon-chevron.png)Magento Dynamic Blocks (Magento enterprise/commerce only). + + + +> Dynamic Blocks are rich, interactive content that is driven by logic from [Price Rules](#h.5wkclkkw15ho) and [Customer Segments](https://docs.magento.com/user-guide/marketing/customer-segments.html&sa=D&ust=1609223264553000&usg=AOvVaw1wyY9x_gmS7G01s_Yx-eAH). + + +They can be created via the Admin: +> **Admin > Content > Elements > Dynamic Blocks** + + +![Magento Exam Notes - More information](./images/icon-info.png)[More information on Dynamic Blocks here](https://docs.magento.com/user-guide/cms/dynamic-blocks.html&sa=D&ust=1609223264554000&usg=AOvVaw30Kq-Ec3OfDFsYQ7ld07Tg) \ No newline at end of file diff --git a/10. Customer Management/10.1 Demonstrate ability to customize My Account.md b/10. Customer Management/10.1 Demonstrate ability to customize My Account.md deleted file mode 100644 index 1edfc15..0000000 --- a/10. Customer Management/10.1 Demonstrate ability to customize My Account.md +++ /dev/null @@ -1,34 +0,0 @@ -## 10.1 Demonstrate ability to customize My Account - -Describe how to customize the “My Account” section. - -*How do you add a menu item?* -- Create A theme or use an existing one -- Create a folder in the theme Magento_Customer -- Create a folder inside the theme Magento_Customer called layout -- Create a file inside the theme/Magento_Customer/layout/customer_account.xml -- Add similar xml -```xml - - - - - - - - Russell Special - special/link - 165 - - - - - -``` - -*How would you customize the “Order History” page?* diff --git a/10. Customer Management/10.2 Demonstrate ability to customize customer functionality.md b/10. Customer Management/10.2 Demonstrate ability to customize customer functionality.md deleted file mode 100644 index 212ca01..0000000 --- a/10. Customer Management/10.2 Demonstrate ability to customize customer functionality.md +++ /dev/null @@ -1,21 +0,0 @@ -## 10.2 Demonstrate ability to customize customer functionality - -Describe how to add or modify customer attributes. - -Describe how to extend the customer entity. - -*How would you extend the customer entity using the extension attributes mechanism?* - -Describe how to customize the customer address. - -*How would you add another field into the customer address?* - -Describe customer groups and their role in different business processes. - -*What is the role of customer groups?* - -*What functionality do they affect?* - -Describe Magento functionality related to VAT. - -*How do you customize VAT functionality?* diff --git a/10.Magento-Customer-Management.md b/10.Magento-Customer-Management.md new file mode 100755 index 0000000..a842bfa --- /dev/null +++ b/10.Magento-Customer-Management.md @@ -0,0 +1,433 @@ +10 - Customer Management +======================== + +10.1 Demonstrate ability to customize My Account +------------------------------------------------ + +![Magento Exam Question](./images/icon-question.png)**Describe how to customize the "My Account" section.** + +> Magento_Customer module is responsible for the user account functioning. If you need to modify in some way the user account (for instance, add new elements, delete the existing ones or change the position of blocks), you need to modify the Magento_Customer module components. +> +> We can do this via the following ways: + +* [Create A Storefront Theme](https://devdocs.magento.com/guides/v2.4/frontend-dev-guide/themes/theme-create.html&sa=D&ust=1609223266250000&usg=AOvVaw2t16x04YpZnZUGweZfJOP7) then [apply & configure it](https://devdocs.magento.com/guides/v2.4/frontend-dev-guide/themes/theme-apply.html&sa=D&ust=1609223266251000&usg=AOvVaw1QcmFf_qO5ciuHbJGsNtDR). Alternatively you can use an existing one theme (Magento/blank for example) Or even [Create a Module](#h.8r6t4fakpxnh). +* Create a folder in the theme `Magento_Customer/layout`, or (if using a custom Module): `/view/frontend/layout` +* The following layout files are available for customisation: + + * `customer_account_confirmation.xml` + * `customer_account_createpassword.xml` + * `customer_account_create.xml` + * `customer_account_edit.xml` + * `customer_account_forgotpassword.xml` + * `customer_account_index.xml` + * `customer_account_login.xml` + * `customer_account_logoutsuccess.xml` + * `customer_account.xml` + * `customer_address_form.xml` + * `customer_address_index.xml` + * `default.xml` (all pages) + +![Magento Exam Information](./images/icon-info.png)Main thing to remember here are: `customer_account_*.xml` layout update handles and default.xml Where these layouts go (Theme / Module) is more outta scope for this question. + +![Magento Exam Question](./images/icon-question.png)**How do you add a menu item?** + +> To add a new menu item, add the following instruction into customer_account.xml file specifically. For example: + +```xml + + + + My awesome menu item + path/i/need + 100 + + + +``` + +> We add a new block of the interface instance `Magento\Customer\Block\Account\SortLinkInterface` (or a concrete class that implements it) class and pass the menu arguments: + +* `label` (name) +* `page` (link) +* `sortOrder` (position) + +> As a result, a new menu item will appear in the user menu: + +![](./images/Magento-2-Certified-Professional-Developer-Guide-Screenshot-79.jpg) + +> To delete the existing menu item, apply the following instruction in your customer_account.xml: + +```xml + +``` + +> Finally, when we need to change the block order, we can modify the sortOrder argument value. For example, let us make the Wishlist section the first menu item: + +```xml + + + 500 + + +``` +![Magento Exam Information](./images/icon-info.png)Following the rule above here of just remembering the layout name: customer_account_*.xml you apply common sense and if presented with a customer_account.xml option in the exam - this looks correct to edit the Customer Account page. + +![Magento Exam Question](./images/icon-question.png)**How would you customize the "Order History" page?** + +> To customize My Orders page, modify the layout-file sales_order_history.xml of the Magento_Sales module. For this, create a sales_order_history.xml file and place it in our theme at the following path: Magento_Sales/layout/. Then, apply any instructions, provided in Magento 2 for layout customization. For example, let us add a text block before the orders list: + +```xml + + + + Hey! I have been added to demonstrate the ability to adding the new blocks! + + + +``` + +> As a result, on the My Orders page in the user account we will see the following text: + +![](./images/Magento-2-Certified-Professional-Developer-Guide-Screenshot-80.jpg) + +> To modify the order table contents (for example, add a new column), we need to override the `vendor/magento/module-sales/view/frontend/templates/order/history.phtml` template. + +![Magento Exam Information](./images/icon-info.png)This one is a tad mis-leading as it's actually within the `Magento_Sales` sales module rather than `Magento_Customer` - as long as you remember this, then `sales_order_history.xml` / `order/history.phtml` layout / templates would appear to make sense. + +10.2 Demonstrate ability to customize customer functionality +------------------------------------------------------------ + +![Magento Exam Question](./images/icon-question.png)**Describe the customer entity.** + +### ![Magento Section Chevron](./images/icon-chevron.png)Customers + +> A Customer is its own Magento Entity within the Magento_Customer module. +> Like all Modules, Magento_Customer uses the [Service Contracts](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/service-contracts/service-contracts.html&sa=D&ust=1609223266259000&usg=AOvVaw2KGjEGSyVnYNDGQI08Xl8Z) design pattern to manage (C.R.U.D actions) on the Customers Entity. Here are a few (notable) Repositories: + +> Main Customer CRUD interface + +```php +Magento\Customer\Api\CustomerRepositoryInterface::save( + \Magento\Customer\Api\Data\CustomerInterface $customer, + $passwordHash = null +); +``` + +> Interface for managing customers accounts + +```php +Magento\Customer\Api\AccountManagementInterface::createAccount( + \Magento\Customer\Api\Data\CustomerInterface $customer, + $password = null, + $redirectUrl = '' +); +``` + +> Customer Address CRUD interface +```php +Magento\Customer\Api\AddressRepositoryInterface::save( + \Magento\Customer\Api\Data\AddressInterface $address +); +``` + +> Main Customer Address Data Interface + +```php +Magento\Customer\Api\Data\AddressInterface::getCountryId(); +``` + +> Interface For Managing Customer Groups. + +```php +Magento\Customer\Api\GroupRepositoryInterface::save( + \Magento\Customer\Api\Data\GroupInterface $group +); +``` + +> Customer Group Data Interface + +```php +Magento\Customer\Api\Data\GroupInterface::getTaxClassId(); +``` + +> Interface For Managing Customer Groups + +```php +Magento\Customer\Api\GroupManagementInterface::getDefaultGroup( + $storeId = null +); +``` + +> Interface For System Configuration Operations For Customer Groups + +```php +Magento\Customer\Api\CustomerGroupConfigInterface::setDefaultCustomerGroup( + int $id +); +``` + +> Interface For Getting Attributes Metadata (Note that this interface should not be used directly, use its children). + +```php +Magento\Customer\Api\MetadataInterface::getAttributes(); +``` + +> Customer Attribute Metadata Interface + +```php +Magento\Customer\Api\Data\AttributeMetadataInterface::getIsUsedInGrid(); +``` + +![Magento Exam Information](./images/icon-info.png)Main thing to remember here is that the Magento_Customer module is responsible for the Customer Entity. Within this module are repositories that conform to the [Service Contract](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/service-contracts/service-contracts.html&sa=D&ust=1609223266265000&usg=AOvVaw2QEerRAv1hfxHcgYE6cZC6) design pattern which create injectable repositories for the following entities: Customer, Customer Address, Customer Groups. + +| Database Table: `customer_entity` | Notable Columns | Description | +| --- | --- | --- | +| Core Customer Properties | `entity_id` - int(10), `website_id` - smallint(5), `created_at` - timestamp, `updated_at` - timestamp, email - varchar(255), `group_id` - smallint(5), `increment_id` - varchar(50), `store_id` - smallint(5), `is_active` - decimal(12,4), `disable_auto_group_change` - smallint(5), `created_in` - varchar(255) | `entity_id`: used all over the code and is the global identifier. `email`: The customer's email address. `is_active`: Used for status. `increment_id`: Changing the initial customer increment id (if you want it different from default). `group_id`: The ID of the customer group where the customer is assigned. `disable_auto_group_change` Determines if customer groups can be dynamically assigned during [](https://docs.magento.com/user-guide/tax/vat-validation.html&sa=D&ust=1609223266269000&usg=AOvVaw1LhR3NMIt-SJ1xCmzH32MB) [VAT ID validation](https://docs.magento.com/user-guide/tax/vat-validation.html&sa=D&ust=1609223266269000&usg=AOvVaw1LhR3NMIt-SJ1xCmzH32MB). `created_in`: The store view where the account was created. | +| Customer Properties | `prefix` - varchar(40), `firstname` - varchar(255), `middlename` - varchar(255), `lastname` - varchar(255), `suffix` - varchar(40), `dob` - `date`, `taxvat` - varchar(50), `confirmation` - varchar(64), `default_billing` - int(10), `default_shipping` - int(10), `failures_num` - smallint(6), `first_failure` - timestamp, `lock_expires` - timestamp | `prefix`: Any prefix that is used with the customer name.(Mr., Ms., Mrs., Dr., etc.). `firstname`: The first name of the customer. `middlename`: The middle name or middle initial of the customer. `lastname`: The last name of the customer. `suffix`: Any suffix that is used with the customer name. (Jr., Sr., Esquire, etc.). `dob`: The customer's date of birth. `taxvat`: The [Value Added Tax (VAT)](https://docs.magento.com/user-guide/tax/vat.html&sa=D&ust=1609223266275000&usg=`AOvVaw2IrzV3sr_zb0qGTszTbjrV`) ID that is assigned to the customer. The default label of this attribute is "VAT Number". The VAT number field is always present in all shipping and billing customer addresses when viewed from the Admin, but is not a required field. `gender`: The customer gender. `default_billing`: The user selected default Billing Address ID `default_shipping`: The user selected default Shipping Address ID | + +![Magento Exam Information](./images/icon-info.png)[More information on Customers here](https://docs.magento.com/user-guide/stores/attributes-customer.html&sa=D&ust=1609223266278000&usg=AOvVaw2oivunhnbBQMDCY90HW5Pb) + +![Magento Exam Information](./images/icon-info.png)(Magento commerce/enterprise only) It is worth mentioning that in Magento Commerce you are able to add/edit Customer Attributes via Admin > Stores > Attributes > Customer + +### ![Magento Section Chevron](./images/icon-chevron.png)Customer Address Attributes + +| Database Table: `customer_address_entity` | Notable Columns | Description | +| --- | --- | --- | +| Core Customer Address Properties | `entity_id` - int(10), `parent_id` - int(10), `created_at` - timestamp, `updated_at` - timestamp, `is_active` - smallint(5) | `entity_id`: used all over the code and is the global identifier. `is_active`: Used for status. If value = 0, then it won't appear in the checkout for the customer. `increment_id`: Changing the initial customer increment id (if you want it different from default). `parent_id`: The ID of the customer. +| Customer Address Properties | `city` - varchar(255), `company` - varchar(255), `country_id` - varchar(255), `fax` - varchar(255), `firstname` - varchar(255), `lastname` - varchar(255), `middlename` - varchar(255), `postcode` - varchar(255), `prefix` - varchar(40), `region` - varchar(255), `region_id` - int(10), `street` - text, `suffix` - varchar(40), `telephone` - varchar(255), `vat_id` - varchar(255), `vat_is_valid` - int(10), `vat_request_date` - varchar(255), `vat_request_id` - varchar(255), `vat_request_success` - int(10) | `country_id`: Two-letter country code in ISO_3166-2 format. `region`: e.g. Queensland (part of Magento\Customer\Api\Data\RegionInterface class). `region_id`: The middle name or middle initial of the customer. `postcode`: Zipcode. `vat_id`: The [Value Added Tax (VAT)](https://docs.magento.com/user-guide/tax/vat.html&sa=D&ust=1609223266285000&usg=AOvVaw0coHcdWFT_M-1U70VZ94Is) ID that is assigned to the customer. The default label of this attribute is "VAT Number". The VAT number field is always present in all shipping and billing customer addresses when viewed from the Admin, but is not a required field. | +| Unused columns | `increment_id` - varchar(50) | | + +![Magento Exam Information](./images/icon-info.png)[More information on Customer Address here](https://docs.magento.com/user-guide/stores/attributes-customer-address.html&sa=D&ust=1609223266286000&usg=AOvVaw0yclXlWq1FZq0qgRmknLgp) & [here](https://devdocs.magento.com/compliance/privacy/pi-data-reference-m2.html&sa=D&ust=1609223266286000&usg=AOvVaw3twE6Mmh_Rcjp8-c-Vy6Ho). + +![Magento Exam Information](./images/icon-info.png)It's worth noting that much information is duplicated here (customer information) as well as address information being duplicated in the sales_order_address table. This follows the data de-normalisation approach and is used because each serves different purposes. For example: if a customer removes and address then it should not then be removed from any previous orders. + +![Magento Exam Information](./images/icon-info.png)(Magento commerce/enterprise only) It is also worth mentioning that in Magento Commerce you are able to add/edit Customer Address Attributes via Admin > Stores > Attributes > Customer Address + +![Magento Exam Question](./images/icon-question.png)**Describe how to add or modify customer attributes.** + +> To create a new customer attribute, we can use Data Setup script in the form of a Create a Customer [Data Patch](#h.fb7s5a4nvxv9): + +```php +moduleDataSetup = $moduleDataSetup; + $this->customerSetupFactory = $customerSetupFactory; + $this->attributeSetFactory = $attributeSetFactory; + $this->attributeRepository = $attributeRepository; + } + + /** + * {@inheritdoc} + */ + public function apply() + { + $setup = $this->moduleDataSetup->getConnection()->startSetup(); + $customerSetup = $this->customerSetupFactory->create(['setup' => $setup]); + try { + $attribute = $this->attributeRepository->get(Customer::ENTITY, 'my_new_attribute'); + } catch (NoSuchEntityException $e) { + // Good, it doesn't exist. Create it. + $customerSetup->addAttribute(Customer::ENTITY, 'my_new_attribute', [ + 'type' => 'varchar', + 'label' => 'My new attribute', + 'input' => 'text', + 'required' => false, + 'visible' => true, + 'user_defined' => true, + 'system' => false, + 'used_in_forms' => [ + 'adminhtml_customer', + 'adminhtml_checkout', + 'checkout_register', + 'customer_account_create', + 'customer_account_edit', + ] + ]); + } + + $this->moduleDataSetup->getConnection()->endSetup(); + } + + /** + * {@inheritdoc} + */ + public static function getDependencies() + { + return []; + } + + public function revert() + { + // Revert operations here + $customerSetup = $this->customerSetupFactory->create(['setup' => $setup]); + try { + $attribute = $this->attributeRepository->get(Customer::ENTITY, 'my_new_attribute'); + if ($attribute) { + $customerSetup->removeAttribute(Customer::ENTITY, $attribute->getAttributeCode()); + } + } catch (NoSuchEntityException $e) { + // Good, it doesn't exist. No need to revert. + } + } + + /** + * {@inheritdoc} + */ + public function getAliases() + { + return []; + } +} +``` + +> Customer attributes can be displayed in different forms, which are set in the used_in_forms array parameter. +> +> The list of forms can be found in customer_form_attribute table list available are: + +* `adminhtml_checkout` +* `adminhtml_customer` +* `adminhtml_customer_address` +* `checkout_register` +* `customer_account_create` +* `customer_account_edit` +* `customer_address_edit` +* `customer_register_address` + +> To modify the attribute, use `Magento\Customer\Setup\CustomerSetupFactory` or `Magento\Eav\Api\AttributeRepositoryInterface` if you want to get it by Attribute Code: + +`Magento\Customer\Setup\CustomerSetupFactory` +```php +$customerSetup = $this->customerSetupFactory->create(['setup' => $setup]); +$customerSetup->updateAttribute(\Magento\Customer\Model\Customer::ENTITY, 'new_attribute', 'visible', false); +``` + +`Magento\Eav\Api\AttributeRepositoryInterface` +```php +$attribute = $this->attributeRepository->get(Customer::ENTITY, 'my_new_attribute'); +$attribute->setData('used_in_forms', ['adminhtml_customer']); +$this->attributeRepository->save($attribute); +``` + +![Magento Exam Question](./images/icon-question.png)**How would you extend the customer entity using the extension attributes mechanism?** + +> Module developers can not modify API Data interfaces, described in core Magento 2, but the majority of the modules have the [Extension Attributes](#h.aqe3c923mrz4) mechanism. [Extension Attributes](#h.aqe3c923mrz4) are set and stored separately from the data object of the initial class, so everything, connected with data storage and extraction, must be realized by the developer himself. + +![Magento Exam Information](./images/icon-info.png)Please refer to the [Extension Attributes](#h.aqe3c923mrz4) section for more information. + +![Magento Exam Question](./images/icon-question.png)**Describe how to customize the customer address. How would you add another field into the customer address?** + +> Magento has a standard set of attributes for the Customer Address entity, stored in customer_address_entity table ([see above](#h.l7dt83731myl) for the main columns of this table). Other attributes of the address need to be added as EAV attributes. They will work via Custom Attributes. +> +> To add new fields into the Customer Address entity, please do the following: + +1. Create an EAV attribute using `Magento\Customer\Setup\CustomerSetupFactory`. [Please see the Data Patch in the previous question above](#h.l7dt83731myl) for more information on how to achieve this. + +![Magento Exam Question](./images/icon-question.png)**Describe customer groups and their role in different business processes. What is the role of customer groups? What functionality do they affect?** + +![Magento Section Chevron](./images/icon-chevron.png)Customer Groups + +> User/Customer Groups allow you to categorise Users and provide certain functionality based on their groups, these functions include: + +* Modify taxes based on Customer Groups. +* Different catalog product pricing based on Customer Groups (tiered prices for example). +* Create [Catalog Price Rules](#h.y3a3xjg08db) specific to certain Customer Groups +* Create [Cart/Sales Price Rules](#h.9it0avmbiyyv) specific to certain Customer Groups +* Create [Cart/Sales Price Rules](#h.9it0avmbiyyv) based on [Customer Segments](https://docs.magento.com/user-guide/marketing/customer-segments.html&sa=D&ust=1609223266306000&usg=AOvVaw14L718FqVeQ1ioscj0DJU_) (Magento Enterprise/Commerce only feature). +* Separate their rights/permissions at the storefront side such as disabling guest checkout in Stores > Settings > Configuration > Sales > Checkout > Checkout Options +* Filter grid by Customer Groups in the Admin. + +> There are the following default user groups: + +* NOT LOGGED IN +* General +* Wholesale +* Retailer + +> NOT LOGGED IN is the only user group that you can not delete (same as you can not delete the default registered users group, but, in contrast to NOT LOGGED IN, it is not static, which means it can be modified). NOT LOGGED IN is assigned to all the visitors without a session and determines the type of shopping cart (guest cart). General is the default group for the newly registered users. + +![Magento Exam Information](./images/icon-info.png)[Customer Segments](https://docs.magento.com/user-guide/marketing/customer-segments.html&sa=D&ust=1609223266309000&usg=AOvVaw2LmfpaspHl9m-GdIwLJ3OG) allow you to dynamically display content and promotions to specific customers, based on properties such as customer address, order history, shopping cart contents, and so on. You can optimize marketing initiatives based on targeted segments with shopping cart price rules. You can also generate reports and export the list of targeted customers. Because customer segment information is constantly refreshed, customers can become associated and dissociated from a segment as they shop in your store. NOTE: This is not specific to Customer Groups but a useful feature to add here. + +![Magento Section Chevron](./images/icon-chevron.png)Company Accounts (Magento B2B feature only) + +> On top of **Customer Groups** is also **Company Accounts** +> +> A **Company Account** can be set up to reflect the structure of the business. Initially, the company structure includes only the company administrator, but can be expanded to include teams of users. The users can be associated with teams or organized within a hierarchy of divisions and subdivisions within the company. +> +> +> **Company Accounts** can be set up from the storefront OR from the Admin via: Admin > Customers > Companies. +> +> By default, the ability to create company accounts from the storefront is enabled. If allowed in the configuration, a visitor to the store can request to open a company account. +> +> After the company account is approved, the company administrator can set up the company structure and users with various levels of permission. + +![Magento Exam Information](./images/icon-info.png)NOTE: This is not specific to Company Accounts but a useful feature to add here. + +![Magento Exam Question](./images/icon-question.png)**Describe Magento functionality related to VAT.** + +> Magento is a built-in functionality for working with VAT. VAT depends on the seller's country and buyer's address. When a downloadable product is purchased, VAT depends solely on the delivery destination. + +![Magento Exam Question](./images/icon-question.png)**How do you customize VAT functionality?** + +> To configure [Value Added Tax](https://docs.magento.com/user-guide/tax/vat.html&sa=D&ust=1609223266314000&usg=AOvVaw2m0N3wThO3aMxl1R9vcFsf) (VAT), you can navigate to the following path: +> +> **Admin > Stores > Configuration > General > General > Store Information** +> +> VAT Number: this is where you set the seller's VAT number. + +![](./images/Magento2-Certification-Exam-Notes-Detailed-Insight-VAT.jpg) + +> **Admin > Customers > All Customers > Edit: Customer** +> +> Account Information +> +> Tax/VAT Number: If applicable, the tax number or value-added tax number that is assigned to the customer +> +> Addresses +> +> VAT Number: If applicable, the value-added tax number that is associated with a specific billing or shipping address of the customer. For the sale of [](https://docs.magento.com/user-guide/tax/eu-place-of-supply.html&sa=D&ust=1609223266317000&usg=AOvVaw1Wn5M7ae1vk1XUadd6LszX) [digital goods](https://docs.magento.com/user-guide/tax/eu-place-of-supply.html&sa=D&ust=1609223266317000&usg=AOvVaw1Wn5M7ae1vk1XUadd6LszX) within the EU, the amount of the VAT is based on shipping destination. +> +> **Admin > Stores > Configuration > Customers > Customer Configuration** +> +> +> Show VAT Number on Storefront: Determines if the customer VAT Number field is included in the Address Book that is available in the customer account. +> +> Default Value for Disable Automatic Group Changes Based on VAT ID: +> +> VAT ID is an internal identifier for the VAT Number of the customer when used in VAT Validation. During [](https://docs.magento.com/user-guide/tax/vat-validation.html&sa=D&ust=1609223266318000&usg=AOvVaw34MEAbLuB_e7HrNUfU3J56) [VAT Validation](https://docs.magento.com/user-guide/tax/vat-validation.html&sa=D&ust=1609223266318000&usg=AOvVaw34MEAbLuB_e7HrNUfU3J56), Magento confirms that the number matches the [](http://ec.europa.eu/taxation_customs/vies/&sa=D&ust=1609223266319000&usg=AOvVaw2526GcJFWHdE9agT_j1ZrO) [European Commission](http://ec.europa.eu/taxation_customs/vies/&sa=D&ust=1609223266319000&usg=AOvVaw2526GcJFWHdE9agT_j1ZrO) database. Customers can be automatically assigned to one of the four default customer groups based on the validation results. +> +> For the correct functioning of VAT in Magento, you should set the Customer Groups and create Tax rules and rates. Tax can be applied separately to Products and Customers with Product Tax Classes and Customer Tax Classes (Admin > Stores > Taxes > Tax Rules); you can also create taxes for certain areas with Tax Zones and Rates (Stores > Taxes > Tax Zones and Rates). +> +> To modify Tax, apply `sales_quote_collect_totals_before` event that calculates the order's total cost. + +![Magento Exam Information](./images/icon-info.png)In short, VAT is customised via the Admin on both Store/Website and a Customer level configuration. \ No newline at end of file diff --git a/11.Magento-Multi-Store-Multi-Website-Scopes.md b/11.Magento-Multi-Store-Multi-Website-Scopes.md new file mode 100755 index 0000000..30c30d5 --- /dev/null +++ b/11.Magento-Multi-Store-Multi-Website-Scopes.md @@ -0,0 +1,202 @@ +11 - Multi-store & Multi-website Scopes +======================================= + +11.1 Describe Multi-store & Multi-website functionality +------------------------------------------------------- + +![Magento Exam Question](./images/icon-question.png)**Outline all the available Magento scopes, what are the scopes available in a standard Magento install?** + +> Magento 2 has a 4-level hierarchy: Global, Website, Store (Store Group) & Store View. + +![Magento Exam Question](./images/icon-question.png)**What is the difference between Magento 2 website, store, and store view?** + +![Magento Section Chevron](./images/icon-chevron.png)Global + +> This is the highest level in the Magento pyramid. Here you can set the only 3 options that will be the same for all stores: + +1. Stock - configure the main product settings. +2. Price - define the same price for the products in all stores. +3. Buyers - Merge all store customer data into one big database for all websites. + +![Magento Section Chevron](./images/icon-chevron.png)Global + +> Global values are values that are out-of-the-box if the user has not specified. These values are usually defined within a modules `etc/config.xml`. +> +> By default, a vanilla Magento install features: + +1. System configurations (dependent on their scope in declarative schema `showInDefault`, `showInWebsite`, `showInStore`) available in *Admin > Stores > Configuration*. +2. No Products +3. No Customers +4. 1 Root Category + 1 Default Category without any products +5. 1 Tax Rule: "Taxable goods", 3 Tax Zones / Rates: US-CA. A Non-taxable goods class is also available. +6. 1 Website, 1 Store (Store group), 1 Store View. + +![Magento Section Chevron](./images/icon-chevron.png)Website + +> With one Magento base, you can design various websites, for example, hats.com and pants.com. The following can be configured per Website: + +1. Separate Payment methods. +2. Separate Shipping methods. +3. A totally separate Product base - products are required to be assigned to websites individually. They can have different prices / currencies / attribute values etc. +4. Separate tax classes. +5. Separate (base) currencies. +6. Separate Customer base - It's up to you whether your customers can log in to all shops with the same credentials. +7. System configurations (dependent on their scope in declarative schema `showInDefault`, `showInWebsite`, `showInStore`) available in *Admin > Stores > Configuration*. Mostly all configurations are configurable at this level. + +> For each website, you can create multiple stores, but all the information will be gathered in one admin panel. + +![Magento Section Chevron](./images/icon-chevron.png)Store (Store Group) + +> It's possible to create several stores on one Magento 2 website. The following can be configured per Store: + +1. Different Root Categories which allows for different products to be assigned. + +> The following *CANNOT* be configured per Store: + +1. All the stores within one website share the same customer accounts. +2. All stores share Shipping Methods. +3. All stores share Tax Rates / Zones. +4. All stores share Product stock. +5. All stores share Product prices. +6. All currencies are identical for all the stores. +7. System configurations available in *Admin > Stores > Configuration*. +8. EAV attributes across entities Customer (including Customer Address), Products, Categories cannot be configured on a Store Group level. + + +![Magento Section Chevron](./images/icon-chevron.png)Store View + +> And finally, for every store, you can create several store views. The following can be configured per Store View: + +1. Different languages. +2. Different currencies. +3. Different design themes +4. Certain Product EAV attributes can be different such as name, or tax class (dependent on their `is_global` / `scope` / `is_user_defined` properties). +5. Different Category EAV attributes (such as name, or URL key). +6. System configurations (dependent on their scope in declarative schema `showInDefault`, `showInWebsite`, `showInStore`) available in *Admin > Stores > Configuration*. + +> The following CANNOT be configured per Store View: + +1. All store views within one website share the same customer accounts. +2. All store views share Shipping Methods. +3. All store views share Tax Rates / Zones. +4. All store views share Product stock. +5. All store views share Product prices. +6. All store views share the same Root Category. +7. All currencies are identical for all the store views. + +![Magento Exam Notes - More information](./images/icon-info.png)[Here is more information on the subject with infographics!](https://docs.magento.com/user-guide/configuration/scope.html) + +![Magento Exam Notes - More information](./images/icon-info.png)[Here is some information regarding Scope System Configuration](https://devdocs.magento.com/guides/v2.4/config-guide/prod/config-reference-systemxml.html) + + +![Magento Exam Question](./images/icon-question.png)**How do you create a new Website, Store & Store View?** + +> **Admin > Stores > Settings > All Stores** + +![](./images/magento2-all-stores.png) + +![Magento Exam Question](./images/icon-question.png)**How do you set up a new Website?** + +![Magento Exam Information](./images/icon-info.png)For the purpose of this question we shall assume you are running an Nginx Webserver with PHP-FPM and will specifically focus on the multi-website aspect of the configuration. + +```perl +server { + listen 80; + server_name mydomain.com; + set $MAGE_ROOT ; + index index.php; + root $MAGE_ROOT/pub; + set $MAGE_CODE default; + location / { + try_files $uri $uri/ /index.php$is_args$args; + } + include /etc/nginx/defaults/magento2.conf; +} +server { + listen 80; + server_name myotherdomain.com; + set $MAGE_ROOT ; + index index.php; + root $MAGE_ROOT/pub; + set $MAGE_CODE other; + location / { + try_files $uri $uri/ /index.php$is_args$args; + } + include /etc/nginx/defaults/magento2.conf; +} +``` + +![Magento Exam Question](./images/icon-question.png)**How do you set up a new subfolder Website?** + +![Magento Exam Information](./images/icon-info.png)For the purpose of this question we shall assume you are running an Nginx Webserver with PHP-FPM and will specifically focus on the multi-website aspect of the configuration. + +```perl +server { + listen 80; + server_name mydomain.com; + set $MAGE_ROOT ; + index index.php; + root $MAGE_ROOT/pub; + + location /cn/ { + set $code my_project_cn_store; + rewrite / /cn/index.php; + try_files $uri $uri/ /cn/index.php$is_args$args; + } + + location /us/ { + set $code my_project_us_store; + rewrite / /us/index.php; + try_files $uri $uri/ /us/index.php$is_args$args; + } + + location / { + set $code default; + try_files $uri $uri/ /index.php$is_args$args; + } + + include /etc/nginx/defaults/magento2.conf; +} +``` + +`/pub/us/index.php` +```php + +
+

Autoload error

+
+

{$e->getMessage()}

+ +HTML; + exit(1); +} +$params = $_SERVER; +$params[\Magento\Store\Model\StoreManager::PARAM_RUN_CODE] = 'my_project_us_website'; +$params[\Magento\Store\Model\StoreManager::PARAM_RUN_TYPE] = 'website'; +$params[Bootstrap::INIT_PARAM_FILESYSTEM_DIR_PATHS] = [ + DirectoryList::PUB => [DirectoryList::URL_PATH => ''], + DirectoryList::MEDIA => [DirectoryList::URL_PATH => 'media'], + DirectoryList::STATIC_VIEW => [DirectoryList::URL_PATH => 'static'], + DirectoryList::UPLOAD => [DirectoryList::URL_PATH => 'media/upload'], +]; +$bootstrap = Magento\Framework\App\Bootstrap::create(BP, $params); + +/** @var Magento\Framework\App\Http $app */ +$app = $bootstrap->createApplication(Magento\Framework\App\Http::class); +$bootstrap->run($app); + +``` + +![Magento Exam Information](./images/icon-info.png)'`my_project_us_website`'; must match the Website code in Admin > Stores > All Stores + +![Magento Exam Information](./images/icon-info.png)'`my_project_us_store`'; must match the Store code in Admin > Stores > All Stores \ No newline at end of file diff --git a/12.Magento-Index-Management.md b/12.Magento-Index-Management.md new file mode 100755 index 0000000..bb1872f --- /dev/null +++ b/12.Magento-Index-Management.md @@ -0,0 +1,1160 @@ +12 - Index Management +===================== + +![Magento Exam Question](./images/icon-question.png)**Describe Index Management. How would you create your own Indexer?** + +> An index collects, parses and stores data to facilitate fast and accurate information retrieval which saves repeatedly making complex calculations. Magento utilities indexers due to its wide range of features and complex/large data sets. +> +> By default Magento indexes the following: + +* Product Prices +* Product Inventory +* Product Attribute Data +* Product Category Associations + +![Magento Exam Information](./images/icon-info.png)Indexer could be considered a form of [caching](#h.s3rfn71t3q6i) but, where data is transformed during the process. + +![Magento Exam Information](./images/icon-info.png)[Excellent Indexer Guide Here](https://docs.google.com/presentation/d/e/2PACX-1vRQioOtIvVz8WGa5XTOGmWsu0t6rjUhDQdUGdmss62YZuTJSEV1lwkefBRKGKKxSFTA5k2P5oki_A2t/pub%23slide%3Did.g587a946c8a_2_51&sa=D&ust=1609223266338000&usg=AOvVaw0ceVngmq-G5XpF5QnfZXbi) + +> Creating an indexer + +> The structure of an indexer might look something like: + +![](./images/Magento-2-Exam-Notes-Example-Indexer.png) + +> Only the necessaries will be covered here: + +#### `indexer.xml` + +```xml + + + + An Example Indexer + An example indexer for the purpose of the Magento exam notes + + +``` + +| Attribute | Required | Description | +| --- | --- | --- | +| `id` | Yes | A unique indexer ID | +| `class` | No | The class that processes indexer methods (executeFull, executeList, executeRow) | +| `primary` | No | The source provider | +| `shared_index` | No | Use this option to improve performance if your indexer is related to another indexer. In this example, if the Catalog Product Price index needs to be reindexed | +| `view_id` | No | The ID of the view element that is defined in the mview.xml configuration file. | + + +> An indexer process can also have the following optional parameters: + +| Parameter | Description | +| --- | --- | +| `description` | The description of indexer to be displayed on the System > Tools > Index Management page. | +| `fieldset` | Describes the fields, source, and data provider of the flat index table. | +| `saveHandler` | An extension point. The class for processing (deleting, saving, updating) items when indexing. | +| `structure` | The class that processes (creates, removes) flat index tables. | +| `title` | The title of indexer to be displayed on the System > Tools > Index Management page. | + +#### `mview.xml` + +> Add the `mview.xml` configuration file in the etc module directory, where you declare the following: + +* Indexer view ID +* Indexer class +* The database tables the indexer tracks +* What column data is sent to the indexer + + +> All Mview declarations related to a module should be declared in one file. + +```xml + + + + + +
+
+
+
+ + + +``` + +| Attribute | Required | Description | +| `view` | Yes | The new materialized view to be declared of which have attributes: `id` = the ID of this new view. `class` = The model class object to call. `group` = Which CRON group this falls under (usually indexer).| +| `subscriptions` | No | This places database triggers for your new view when the following tables are updated. `` take: `
` - `name` = The name of the table, `entity_column` = that table's entity ID column to use. | + +> `MyVendor\MyModule\Model\Indexer\Example\Index` + +> The main indexer class implements the following interfaces: + +* `Magento\Framework\Mview\ActionInterface` +* `Magento\Framework\Indexer\ActionInterface` + +> And therefore must have the following methods: + +* `execute` - Used by mview, allows process indexer in the "Update on schedule" mode +* `executeFull` - Will take all of the data and reindex Will run when reindex via command line or [Update on Save](#h.wndwlsqudpgx). Should take into account all entities in the system. +* `executeList` - Works with a set of entity changed (may be massaction) +* `executeRow` - Works in runtime for a single entity using plugins ([Update On Schedule](#h.n9r64sdphflm)). + +![Magento Exam Information](./images/icon-info.png)Only need to remember this, the following is for demonstration purposes only. + +Example: +```php +fullActionFactory = $fullActionFactory; + $this->rowsActionFactory = $rowsActionFactory; + $this->indexerRegistry = $indexerRegistry; + } + + /** + * Used by mview, allows process indexer in the "Update on schedule" mode + */ + public function execute($ids) + { + // Used by mview, allows you to process multiple blog posts in the "Update on schedule" mode + $this->executeAction($ids); + $this->registerEntities($ids); + } + + /** + * Will take all of the data and reindex + * Will run when reindex via command line + * Should take into account all blog posts in the system + */ + public function executeFull() + { + /** @var Example\Action\Full $action */ + $action = $this->fullActionFactory->create(); + $action->execute(); + $this->registerTags(); + } + + /** + * Works with a set of entity changed (may be massaction) + */ + public function executeList(array $ids) + { + // Works with a set of blog posts (mass actions and so on) + $this->executeAction($ids); + } + + /** + * Works in runtime for a single entity using plugins + */ + public function executeRow($id) + { + // Works in runtime for a single blog post using plugins + $this->executeAction([$id]); + } + + /** + * Add entities to cache context + * @param int[] $ids + * @return void + * @since 1.0.1 + */ + protected function registerEntities($ids) + { + $this->getCacheContext()->registerEntities(ExampleInterface::CACHE_TAG, $ids); + } + + /** + * Add tags to cache context + * @return void + * @since 1.0.1 + */ + protected function registerTags() + { + $this->getCacheContext()->registerTags([ExampleInterface::CACHE_TAG]); + } + + /** + * Execute action for single entity or list of entities + * @param int[] $ids + * @return $this + */ + protected function executeAction($ids) + { + $ids = array_unique($ids); + $indexer = $this->indexerRegistry->get(static::INDEXER_ID); + + /** @var Example\Action\Rows $action */ + $action = $this->rowsActionFactory->create(); + if ($indexer->isWorking()) { + $action->execute($ids, true); + } + $action->execute($ids); + + return $this; + } + + /** + * Get cache context + * @return CacheContext + * @since 1.0.1 + */ + protected function getCacheContext(): CacheContext + { + if (!($this->cacheContext instanceof CacheContext)) { + return ObjectManager::getInstance()->get(CacheContext::class); + } else { + return $this->cacheContext; + } + } +} +``` + +> In this example, Full reindexes and Row indexes have been separated into their own object + + +`MyVendor\MyModule\Model\Indexer\Example\Action\Full` +```php + + + +batchRowsCount = $batchRowsCount; + $this->batchProvider = $batchProvider ?: $objectManager->get(BatchProviderInterface::class); + } + + /** {@inheritdoc} */ + public function execute(array $entityIds = [], bool $useTmpTables = false): AbstractAction + { + if ($this->isEnabled()) { + $userFunctions = []; + // $this->createTables(); + foreach ($this->storeManager->getStores() as $store) { + $userFunctions[$store->getId()] = function () use ($store) { + if ($this->getIndexTable((int) $store->getId()) !== $this->resource->getMainTable()) { + $batchCount = ($this->batchRowsCount && is_array($this->batchRowsCount) && isset($this->batchRowsCount["configurable"])) + ? (int) $this->batchRowsCount["configurable"] + : static::RANGE_PRODUCT_STEP; + + $batchQueries = $this->prepareSelectsByRange( + $this->getAllProducts($store), + ExampleInterface::ENTITY_COLUMN_PRODUCT_ID, + $batchCount + ); + + /** @var Select $select */ + foreach ($batchQueries as $select) { + if (!empty($entityIds)) { + $select->where( + $this->connection->prepareSqlCondition( + 'aw.' . ExampleInterface::ENTITY_COLUMN_PRODUCT_ID, + ['in' => $entityIds] + ) + ); + } + $products = $this->connection->fetchAll($select); + if ($products && is_array($products) && !empty($products)) { + foreach ($products as $product) { + $this->reindex($store, $product); + } + } + } + } else { + print(__("Performing full re-index as queues are empty... Please wait a while." . PHP_EOL)); + $products = $this->connection->fetchAll($this->getAllProducts($store)); + if ($products && is_array($products) && !empty($products)) { + foreach ($products as $product) { + $this->reindex($store, $product); + } + } + } + }; + } + $this->processManager->execute($userFunctions); + } else { + $this->logger->warning( + __("[%1] WARNING: will not reindex, module is disabled", static::class) + ); + } + + return $this; + } + + /** {@inheritdoc} */ + protected function reindex(Store $store, array $postExampleData): void + { + $action = 'update/insert'; + $rowsAffected = 0; + if ( + !isset($postExampleData[ExampleInterface::ENTITY_COLUMN_PRODUCT_ID]) + || !isset($postExampleData[ExampleInterface::ENTITY_COLUMN_BLOG_POST_ID]) + || !isset($postExampleData[ExampleInterface::ENTITY_COLUMN_STORE_ID]) + ) { + return; + } + + /** @var Product $product */ + $product = $this->productRepository->getById($postExampleData[ExampleInterface::ENTITY_COLUMN_PRODUCT_ID]); + $postExampleData[ExampleInterface::ENTITY_COLUMN_PRICE] = $this->calculateProductPrice($product); + $postExampleData[ExampleInterface::ENTITY_COLUMN_URL] = $product->getProductUrl(); + $postExampleData[ExampleInterface::ENTITY_COLUMN_IS_AVAILABLE] = $product->isAvailable(); + $postExampleData[ExampleInterface::ENTITY_COLUMN_IMAGE_URL] = $this->getProductImageFullUrl($product); + + $select = $this->resource->getConnection() + ->select() + ->from($this->resource->getMainTable()) + ->where( + $this->resource->getConnection()->prepareSqlCondition( + ExampleInterface::ENTITY_COLUMN_PRODUCT_ID, + ['eq' => $postExampleData[ExampleInterface::ENTITY_COLUMN_PRODUCT_ID]] + ) + )->where( + $this->resource->getConnection()->prepareSqlCondition( + ExampleInterface::ENTITY_COLUMN_BLOG_POST_ID, + ['eq' => $postExampleData[ExampleInterface::ENTITY_COLUMN_BLOG_POST_ID]] + ) + )->where( + $this->resource->getConnection()->prepareSqlCondition( + ExampleInterface::ENTITY_COLUMN_STORE_ID, + ['eq' => (int)$postExampleData[ExampleInterface::ENTITY_COLUMN_STORE_ID]] + ) + ); + + $postExample = $this->resource->getConnection()->fetchRow($select); + if ($postExample) { + $action = 'update'; + $rowsAffected = (int)$this->resource->getConnection()->update( + $this->resource->getMainTable(), + $postExampleData, + [ + ExampleInterface::ENTITY_COLUMN_PRODUCT_ID . ' = ?' => (int)$postExampleData[ExampleInterface::ENTITY_COLUMN_PRODUCT_ID], + ExampleInterface::ENTITY_COLUMN_BLOG_POST_ID . ' = ?' => (int)$postExampleData[ExampleInterface::ENTITY_COLUMN_BLOG_POST_ID], + ExampleInterface::ENTITY_COLUMN_STORE_ID . ' = ?' => (int)$postExampleData[ExampleInterface::ENTITY_COLUMN_STORE_ID] + + ] + ); + $rowsAffected = ($rowsAffected === 0) ? 1 : $rowsAffected; // Updates don't always change rows if the data is the same, for by-pass CouldNotSaveException here. + } else { + $action = 'insert'; + $rowsAffected = (int)$this->resource->getConnection()->insert( + $this->resource->getMainTable(), + $postExampleData + ); + } + + if ($rowsAffected === 0) { + throw new CouldNotSaveException( + __( + "[%1] ERROR: Could not %2 Post Related Product %3 for Post %4 of Store %5", + static::class, + $action, + (int)$postExampleData[ExampleInterface::ENTITY_COLUMN_PRODUCT_ID], + (int)$postExampleData[ExampleInterface::ENTITY_COLUMN_BLOG_POST_ID], + (int)$postExampleData[ExampleInterface::ENTITY_COLUMN_STORE_ID] + ) + ); + } else if ( + $this->removeIndexEntry( + (int)$postExampleData[ExampleInterface::ENTITY_COLUMN_STORE_ID], + (int)$postExampleData[ExampleInterface::ENTITY_COLUMN_BLOG_POST_ID], + (int)$postExampleData[ExampleInterface::ENTITY_COLUMN_PRODUCT_ID] + ) === 0 + ) { + $this->logger->warning( + __( + "[%1] WARNING: Could not remove Post Related Product %2 for Post %3 of Store %4", + static::class, + (int)$postExampleData[ExampleInterface::ENTITY_COLUMN_PRODUCT_ID], + (int)$postExampleData[ExampleInterface::ENTITY_COLUMN_BLOG_POST_ID], + (int)$postExampleData[ExampleInterface::ENTITY_COLUMN_STORE_ID] + ) + ); + } + } + + /** + * Create the store tables + * @todo add tableMaintainer functionality for dimension + * @return void + */ + private function createTables(): void + { + foreach ($this->storeManager->getStores() as $store) { + $this->tableMaintainer->createTablesForStore((int)$store->getId()); + } + } +} +``` + +`MyVendor\MyModule\Model\Indexer\Example\Action\Rows` +```php +isEnabled()) { + $userFunctions = []; + foreach ($this->storeManager->getStores() as $store) { + $userFunctions[$store->getId()] = function () use ($store, $entityIds) { + + $select = $this->getAllProducts($store); + $select->where( + $this->connection->prepareSqlCondition( + 'aw.' . ExampleInterface::ENTITY_COLUMN_PRODUCT_ID, + ['in' => $entityIds] + ) + ); + $products = $this->connection->fetchAll($select); + if ($products && is_array($products) && !empty($products)) { + foreach ($products as $product) { + $this->reindex($store, $product); + } + } + }; + } + + $this->processManager->execute($userFunctions); + } else { + $this->logger->warning( + __("[%1] WARNING: will not reindex, module is disabled", static::class) + ); + } + return $this; + } + + /** {@inheritdoc} */ + protected function reindex(Store $store, array $postExampleData): void + { + $action = 'update/insert'; + $rowsAffected = 0; + if ( + !isset($postExampleData[ExampleInterface::ENTITY_COLUMN_PRODUCT_ID]) + || !isset($postExampleData[ExampleInterface::ENTITY_COLUMN_BLOG_POST_ID]) + || !isset($postExampleData[ExampleInterface::ENTITY_COLUMN_STORE_ID]) + ) { + return; + } + + /** @var Product $product */ + $product = $this->productRepository->getById($postExampleData[ExampleInterface::ENTITY_COLUMN_PRODUCT_ID]); + $postExampleData[ExampleInterface::ENTITY_COLUMN_PRICE] = $this->calculateProductPrice($product); + $postExampleData[ExampleInterface::ENTITY_COLUMN_URL] = $product->getProductUrl(); + $postExampleData[ExampleInterface::ENTITY_COLUMN_IS_AVAILABLE] = $product->isAvailable(); + $postExampleData[ExampleInterface::ENTITY_COLUMN_IMAGE_URL] = $this->getProductImageFullUrl($product); + + $select = $this->resource->getConnection() + ->select() + ->from($this->resource->getMainTable()) + ->where( + $this->resource->getConnection()->prepareSqlCondition( + ExampleInterface::ENTITY_COLUMN_PRODUCT_ID, + ['eq' => $postExampleData[ExampleInterface::ENTITY_COLUMN_PRODUCT_ID]] + ) + )->where( + $this->resource->getConnection()->prepareSqlCondition( + ExampleInterface::ENTITY_COLUMN_BLOG_POST_ID, + ['eq' => $postExampleData[ExampleInterface::ENTITY_COLUMN_BLOG_POST_ID]] + ) + )->where( + $this->resource->getConnection()->prepareSqlCondition( + ExampleInterface::ENTITY_COLUMN_STORE_ID, + ['eq' => (int)$postExampleData[ExampleInterface::ENTITY_COLUMN_STORE_ID]] + ) + ); + + $postExample = $this->resource->getConnection()->fetchRow($select); + if ($postExample) { + $action = 'update'; + $rowsAffected = (int)$this->resource->getConnection()->update( + $this->resource->getMainTable(), + $postExampleData, + [ + ExampleInterface::ENTITY_COLUMN_PRODUCT_ID . ' = ?' => (int)$postExampleData[ExampleInterface::ENTITY_COLUMN_PRODUCT_ID], + ExampleInterface::ENTITY_COLUMN_BLOG_POST_ID . ' = ?' => (int)$postExampleData[ExampleInterface::ENTITY_COLUMN_BLOG_POST_ID], + ExampleInterface::ENTITY_COLUMN_STORE_ID . ' = ?' => (int)$postExampleData[ExampleInterface::ENTITY_COLUMN_STORE_ID] + + ] + ); + $rowsAffected = ($rowsAffected === 0) ? 1 : $rowsAffected; // Updates don't always change rows if the data is the same, for by-pass CouldNotSaveException here. + } else { + $action = 'insert'; + $rowsAffected = (int)$this->resource->getConnection()->insert( + $this->resource->getMainTable(), + $postExampleData + ); + } + + if ($rowsAffected === 0) { + throw new CouldNotSaveException( + __( + "[%1] ERROR: Could not %2 Post Related Product %3 for Post %4 of Store %5", + static::class, + $action, + (int)$postExampleData[ExampleInterface::ENTITY_COLUMN_PRODUCT_ID], + (int)$postExampleData[ExampleInterface::ENTITY_COLUMN_BLOG_POST_ID], + (int)$postExampleData[ExampleInterface::ENTITY_COLUMN_STORE_ID] + ) + ); + } else if ( + $this->removeIndexEntry( + (int)$postExampleData[ExampleInterface::ENTITY_COLUMN_STORE_ID], + (int)$postExampleData[ExampleInterface::ENTITY_COLUMN_BLOG_POST_ID], + (int)$postExampleData[ExampleInterface::ENTITY_COLUMN_PRODUCT_ID] + ) === 0 + ) { + throw new CouldNotDeleteException( + __( + "[%1] ERROR: Could not remove Post Related Product %2 for Post %3 of Store %4", + static::class, + (int)$postExampleData[ExampleInterface::ENTITY_COLUMN_PRODUCT_ID], + (int)$postExampleData[ExampleInterface::ENTITY_COLUMN_BLOG_POST_ID], + (int)$postExampleData[ExampleInterface::ENTITY_COLUMN_STORE_ID] + ) + ); + } + } + + /** {@inheritdoc} */ + protected function isRangingNeeded(): bool + { + return false; + } +} +``` + +`MyVendor\MyModule\Model\Indexer\Example\AbstractAction` +```php +config = $config; + $this->helper = $helper; + $this->resource = $resource; + $this->tableMaintainer = $tableMaintainer; + $this->logger = $this->helper->getLogger(); + $this->connection = $resource->getConnection(); + $this->storeManager = $this->helper->getStoreManager(); + $this->productRepository = $this->helper->createProductRepository(); + $this->queryGenerator = $queryGenerator ?: ObjectManager::getInstance()->get(QueryGenerator::class); + $this->metadataPool = $metadataPool ?: ObjectManager::getInstance()->get(MetadataPool::class); + $this->processManager = $processManager ?: ObjectManager::getInstance()->get(ProcessManager::class); + } + + /** + * Run full reindex + * @param array $entityIds + * @param bool $useTmpTables + * @return $this + */ + abstract public function execute(array $entityIds = [], bool $useTmpTables = false): AbstractAction; + + /** + * Run reindexation + * @param Store $store + * @param array $postExampleData + * @return void + * @throws CouldNotSaveException + * @throws CouldNotDeleteException + */ + abstract protected function reindex(Store $store, array $postExampleData): void; + + /** + * Return validated table name + * @param string|string[] $table + * @return string + */ + protected function getTable($table) + { + return $this->resource->getTable($table); + } + + /** + * Return the relvent index table name to work from. Since we are re-writing the current blog indexers, + * this depends on a number of factors: if the old index table still has rows then use this, else use the + * new. If new is empty and we are fully re-indexing, get main entity table. + * @todo Temp tables, Table maintainer + * @param int $storeId + * @return string + * @since 1.0.1 + */ + protected function getIndexTable(int $storeId): string + { + $conn = $this->connection; + $t = $conn->getTableName(static::MAIN_INDEX_TABLE); + if ($this->areTempTablesNeeded()) { + $t = $this->tableMaintainer->getMainReplicaTable($storeId); + } else if ($this->isTableMaintainerNeeded()) { + $t = $this->tableMaintainer->getMainTable($storeId); + } + return $t; + } + + /** @return bool */ + protected function isEnabled(): bool + { + return $this->helper->isEnabled(); + } + + /** @return mixed */ + protected function getConfigValue(string $configField, string $scope = null) + { + return $this->helper->getConfigValue($configField, $scope); + } + + /** + * Check whether select ranging is needed + * @return bool + */ + protected function isRangingNeeded(): bool + { + return true; + } + + /** + * Check whether select ranging is needed + * @todo add functionality to handle this + * @return bool + */ + protected function areTempTablesNeeded(): bool + { + return false; + } + + /** + * Check whether multiple tables for multiple stores is needed + * @todo add functionality to handle this + * @return bool + */ + protected function isTableMaintainerNeeded(): bool + { + return false; + } + + /** + * Return selects cut by min and max + * @param Select $select + * @param string $field + * @param int $range + * @return Select[] + */ + protected function prepareSelectsByRange( + Select $select, + string $field, + int $range = self::RANGE_PRODUCT_STEP + ) { + if ($this->isRangingNeeded()) { + $iterator = $this->queryGenerator->generate( + $field, + $select, + $range, + BatchIteratorInterface::NON_UNIQUE_FIELD_ITERATOR + ); + + $queries = []; + foreach ($iterator as $query) { + $queries[] = $query; + } + return $queries; + } + return [$select]; + } + + /** + * Get select for all products + * @param Store $store + * @return Select + * @throws Exception when metadata not found for ProductInterface + */ + protected function getAllProducts(Store $store) + { + if (!isset($this->productsSelects[$store->getId()])) { + $table = $this->getIndexTable($store->getId()); + $statusAttributeId = $this->config->getAttribute(Product::ENTITY, 'status')->getId(); + $visibilityAttributeId = $this->config->getAttribute(Product::ENTITY, 'visibility')->getId(); + $nameAttributeId = $this->config->getAttribute(Product::ENTITY, 'name')->getId(); + $priceAttributeId = $this->config->getAttribute(Product::ENTITY, 'price')->getId(); + $urlAttributeId = $this->config->getAttribute(Product::ENTITY, 'url_key')->getId(); + $imgAttributeId = $this->config->getAttribute(Product::ENTITY, 'image')->getId(); + $metadata = $this->metadataPool->getMetadata(ProductInterface::class); + $linkField = $metadata->getLinkField(); + $blogPostColumn = ($table === $this->resource->getMainTable()) + ? ExampleInterface::ENTITY_COLUMN_BLOG_POST_ID + : static::COLUMN_POST_ID; + + $select = $this->connection + ->select() + ->from( + ['aw' => $table], + [] + )->joinLeft( + ['cp' => $this->getTable('catalog_product_entity')], + 'cp.entity_id = aw.' . static::COLUMN_PRODUCT_ID, + [] + )->joinInner( + ['cpw' => $this->getTable('catalog_product_website')], + 'cpw.product_id = cp.entity_id', + [] + )->joinInner( + ['cpsd' => $this->getTable('catalog_product_entity_int')], + 'cpsd.' . $linkField . ' = cp.' . $linkField . ' AND cpsd.store_id = 0' . ' AND cpsd.attribute_id = ' . $statusAttributeId, // Default store + [] + )->joinLeft( + ['cpss' => $this->getTable('catalog_product_entity_int')], + 'cpss.' . $linkField . ' = cp.' . $linkField . ' AND cpss.attribute_id = cpsd.attribute_id' . ' AND cpss.store_id = ' . $store->getId(), // Store specific + [] + )->joinInner( + ['cpvd' => $this->getTable('catalog_product_entity_int')], + 'cpvd.' . $linkField . ' = cp.' . $linkField . ' AND cpvd.store_id = 0' . ' AND cpvd.attribute_id = ' . $visibilityAttributeId, // Default store + [] + )->joinLeft( + ['cpvs' => $this->getTable('catalog_product_entity_int')], + 'cpvs.' . $linkField . ' = cp.' . $linkField . ' AND cpvs.attribute_id = cpvd.attribute_id ' . ' AND cpvs.store_id = ' . $store->getId(), // Store specific + [] + )->joinLeft( + ['cptn' => $this->getTable('catalog_product_entity_varchar')], + 'cptn.' . $linkField . ' = cp.' . $linkField . ' AND cptn.attribute_id = ' . $nameAttributeId . ' AND cptn.store_id = 0', // Default store + [] + )->joinLeft( + ['cptns' => $this->getTable('catalog_product_entity_varchar')], + 'cptns.' . $linkField . ' = cp.' . $linkField . ' AND cptns.attribute_id = cptn.attribute_id AND cptns.store_id = ' . $store->getId(), // Store specific + [] + )->joinLeft( + ['cpdp' => $this->getTable('catalog_product_entity_decimal')], + 'cpdp.' . $linkField . ' = cp.' . $linkField . ' AND cpdp.attribute_id = ' . $priceAttributeId . ' AND cpdp.store_id = 0', // Default store + [] + )->joinLeft( + ['cpdps' => $this->getTable('catalog_product_entity_decimal')], + 'cpdps.' . $linkField . ' = cp.' . $linkField . ' AND cpdps.attribute_id = cpdp.attribute_id AND cpdps.store_id = ' . $store->getId(), // Store specific + [] + )->joinLeft( + ['cpvu' => $this->getTable('catalog_product_entity_varchar')], + 'cpvu.' . $linkField . ' = cp.' . $linkField . ' AND cpvu.attribute_id = ' . $urlAttributeId . ' AND cpvu.store_id = 0', // Default store + [] + )->joinLeft( + ['cpvus' => $this->getTable('catalog_product_entity_varchar')], + 'cpvus.' . $linkField . ' = cp.' . $linkField . ' AND cpvus.attribute_id = cpvu.attribute_id AND cpvus.store_id = ' . $store->getId(), // Store specific + [] + )->joinLeft( + ['cpvi' => $this->getTable('catalog_product_entity_varchar')], + 'cpvi.' . $linkField . ' = cp.' . $linkField . ' AND cpvi.attribute_id = ' . $imgAttributeId . ' AND cpvi.store_id = 0', // Default store + [] + )->joinLeft( + ['cpvis' => $this->getTable('catalog_product_entity_varchar')], + 'cpvis.' . $linkField . ' = cp.' . $linkField . ' AND cpvis.attribute_id = cpvi.attribute_id AND cpvis.store_id = ' . $store->getId(), // Store specific + [] + )->where( + 'cpw.website_id = ?', + $store->getWebsiteId() + )->where( + $this->connection->getIfNullSql('cpss.value', 'cpsd.value') . ' = ?', + Status::STATUS_ENABLED + )->where( + $this->connection->getIfNullSql('cpvs.value', 'cpvd.value') . ' IN (?)', + [ + Visibility::VISIBILITY_IN_CATALOG, + Visibility::VISIBILITY_IN_SEARCH, + Visibility::VISIBILITY_BOTH + ] + )->where( + $this->connection->prepareSqlCondition( + 'aw.' . static::COLUMN_STORE_ID, + ['in' => [0, $store->getId()]] + ) + ) + ->group( + 'cp.entity_id' + )->columns( + [ + ExampleInterface::ENTITY_COLUMN_SKU => 'cp.sku', + ExampleInterface::ENTITY_COLUMN_BLOG_POST_ID => 'aw.' . $blogPostColumn, + ExampleInterface::ENTITY_COLUMN_PRODUCT_ID => 'aw.' . static::COLUMN_PRODUCT_ID, + ExampleInterface::ENTITY_COLUMN_STORE_ID => new Zend_Db_Expr( + $this->connection->getIfNullSql('aw.' . static::COLUMN_STORE_ID, 0) + ), + ExampleInterface::ENTITY_COLUMN_NAME => new Zend_Db_Expr( + $this->connection->getIfNullSql('cptns.value', 'cptn.value') + ), + ExampleInterface::ENTITY_COLUMN_PRICE => new Zend_Db_Expr( + $this->connection->getIfNullSql('cpdps.value', 'cpdp.value') + ), + ExampleInterface::ENTITY_COLUMN_URL => new Zend_Db_Expr( + $this->connection->getIfNullSql('cpvus.value', 'cpvu.value') + ), + ExampleInterface::ENTITY_COLUMN_IMAGE_URL => new Zend_Db_Expr( + $this->connection->getIfNullSql('cpvis.value', 'cpvi.value') + ) + ] + ); + + $this->productsSelects[$store->getId()] = $select; + } + + return $this->productsSelects[$store->getId()]; + } + + /** + * Remove entry from index table once data has been reindexed + * @return int + */ + protected function removeIndexEntry(int $storeId, int $postId, int $productId): int + { + $r = 1; + $t = $this->getIndexTable($storeId); + if ($t !== $this->resource->getMainTable()) { + $where = [ + $this->connection->prepareSqlCondition(static::COLUMN_POST_ID, ['eq' => $postId]), + $this->connection->prepareSqlCondition(static::COLUMN_PRODUCT_ID, ['eq' => $productId]), + $this->connection->prepareSqlCondition(static::COLUMN_STORE_ID, ['eq' => $storeId]) + ]; + $r = (int)$this->connection->delete($this->getIndexTable($storeId), implode(' AND ', $where)); + } + return $r; + } + + /** + * @param ProductInterface $product + * @return string + */ + protected function getProductImageFullUrl(ProductInterface $product): string + { + return $this->helper->getProductImageFullUrl($product); + } + + /** + * @param Product $product + * @return float + */ + protected function calculateProductPrice(ProductInterface $product): float + { + $price = (float) $product->getPrice(); + switch ($product->getTypeId()) { + case Grouped::TYPE_CODE: + + /** @var \Magento\GroupedProduct\Pricing\Price\FinalPrice */ + $priceInfo = $product->getPriceInfo()->getPrice('final_price'); + $price = $priceInfo->getMinProduct()->getFinalPrice(); + case Configurable::TYPE_CODE: + + /** @var \Magento\ConfigurableProduct\Pricing\Price\ConfigurableRegularPrice */ + $priceInfo = $product->getPriceInfo()->getPrice('regular_price'); + $price = $priceInfo->getMinRegularAmount()->getValue(); + break; + case Bundle::TYPE_CODE: + + /** @var \Magento\Bundle\Pricing\Price\BundleRegularPrice */ + $priceInfo = $product->getPriceInfo()->getPrice('final_price'); + $price = $priceInfo->getMinimalPrice()->getValue(); + break; + default: + return (float) $product->getFinalPrice(); + } + return $price; + } +} +``` + +![Magento Exam Information](./images/icon-info.png)TODO + +* Table Maintainers +* Temporary Tables +* Flat Tables + +![Magento Exam Question](./images/icon-question.png)**Describe the Materialized View pattern?** + +> **MView** stands for **Materialized View** which is a snapshot of the database at a point in time. +> +> **Materialized View (mview)** Allows tracking database changes for a certain entity (product, category and so on) and running change handler. +> +> Mview emulates the materialized view technology for **MySQL using triggers** and separate materialization process (provides executing PHP code instead of SQL queries, which allows materializing multiple queries).`Indexers are costly to run, especially when there is traffic on category pages, customers place orders whilst admins save products etc. +> +> *Example:* +> On product save the cache gets invalidated (off topic). In case of stock indexer, before it ends the execution, it sends the entity ids affected as cache tags to be cleaned (full page cache type). +> +> Product **creates a changelog** in the change handler which creates a backlog in indexers such as catalog_category_product, catalog_product_category, catalog_product_attribute, catalog_product_price etc. This informs the indexers that updates to the [EAV](#h.915l1avejvsh) need to be made for specific products. +> +> The state of the mview can be seen in the mview_state database table which holds different current versions of each indexer. + +![Magento Exam Question](./images/icon-question.png)**What is the difference between Index On Save and Index On Schedule?** + +### ![Magento Section Chevron](./images/icon-chevron.png)Update On Save + +> Indexers are run immediately after entities are saved. E.g. when a product is saved in the admin. This sounds fine but... +> When you save a product Magento recalculates every single product on the storefront. Worse still, refreshing the index will clear the Full Page Cache (FPC) which affects all pages. + +### ![Magento Section Chevron](./images/icon-chevron.png)Update On Schedule + +> Indexers are run via a scheduled task (CRON) which by default runs every minute. When a product is saved in the admin it will run the indexing in the background. It aims to remedy the problems with Update On Save by jsut indexing the data that has changed + +![Magento Exam Information](./images/icon-info.png)You should almost always have indexers set to Update On Schedule if you don't wish to affect FPC and therefore your site's performance. \ No newline at end of file diff --git a/2. Request Flow Processing/1. Utilize modes and application initialization.md b/2. Request Flow Processing/1. Utilize modes and application initialization.md deleted file mode 100644 index 4875813..0000000 --- a/2. Request Flow Processing/1. Utilize modes and application initialization.md +++ /dev/null @@ -1,157 +0,0 @@ -# Utilize modes and application initialization - -## Identify the steps for application initialization. -[app/bootstrap.php](https://github.com/magento/magento2/blob/2.3/app/bootstrap.php): - -- composer autoloader, functions, umask, timezone UTC, php precision - -[\Magento\Framework\App\Bootstrap](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/App/Bootstrap.php)::*create* - -- configure autoloader - PSR-4 prepend generation\Magento - -[\Magento\Framework\App\Bootstrap](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/App/Bootstrap.php)::*createApplication* - -- just call object manager->create - -[\Magento\Framework\App\Bootstrap](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/App/Bootstrap.php)::*run* - -- set error handler -- assert maintenance -- assert installed -- response = application->*launch*() -- response->*sendResponse*() -- on error: application->catchException -- on missed error: - - dev mode: print exception trace - - normal mode: log, message "An error has happened during application run. See exception log for details." - -### Application class - -[bootstrap->createApplication()](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/App/Bootstrap.php#L230) - - - [\Magento\Framework\App\Http](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/App/Http.php) - index.php, pub/index.php - load config area by front name - front controller->dispatch - event `controller_front_send_response_before` - - - [\Magento\Framework\App\Cron](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/App/Cron.php) - pub/cron.php - config area `crontab` - load translations - dispatch event `default` - - - [\Magento\MediaStorage\App\Media](https://github.com/magento/magento2/blob/2.3/app/code/Magento/MediaStorage/App/Media.php) - pub/get.php - access /media/* when using DB image storage and physical file doesn't exist - - - [\Magento\Framework\App\StaticResource](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/App/StaticResource.php) - pub/static.php - 404 in production - /$area/$resource/$file/... params, load config by params - sends file in response - assetRepo->createAsset - \Magento\Framework\View\Asset\File - assetPublisher->publish - materialize (copy/symlink) file if doesn't exist - - - [\Magento\Indexer\App\Indexer](https://github.com/magento/magento2/blob/2.3/app/code/Magento/Indexer/App/Indexer.php) - module-indexer, unused? - - [\Magento\Backend\App\UserConfig](https://github.com/magento/magento2/blob/2.3/app/code/Magento/Backend/App/UserConfig.php) - module-backend, unused? - -Notes: - -- Responsibility - launch() and return response -- Roughly different application class matches different physical file entry points (index.php, media.php, get.php, static.php, cron.php) -- Front controller exists only in Http application -- There's no CLI application, Symfony command is used - -### HTTP application - -[\Magento\Framework\App\Http::launch](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/App/Http.php#L128) -1. detect config area by front name - -```php -_areaList->getCodeByFrontName($this->_request->getFrontName()); -$this->_state->setAreaCode($areaCode); -$this->_objectManager->configure($this->_configLoader->load($areaCode)); -``` - -`\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)) - -- frontend = [frontname null, router "standard"] --- *default when nothing matched* -- adminhtml - [frontNameResolver=..., router "admin"] - [\Magento\Backend\App\Area\FrontNameResolver::getFrontName(checkhost)](https://github.com/magento/magento2/blob/2.3/app/code/Magento/Backend/App/Area/FrontNameResolver.php#L83) - system config `admin/url/use_custom`, `admin/url/custom` -- crontab = null -- webapi_rest = [frontName `/rest`] -- webapi_soap = [frontname `/soap`] - -1. [ObjectManagerInterface->configure()](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/ObjectManager/ObjectManager.php#L82) - selected area code -1. result = FrontControllerInterface->dispatch() -1. [ResultInterface->renderResult()](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Controller/AbstractResult.php#L122) into response object -1. event `controller_front_send_response_before` (request, response) - - -### How would you design a customization that should act on every request and capture output data regardless of the controller? -- event `controller_front_send_response_before` - - -## Describe front controller responsibilities - -*Front controller* exists only in Http application (pub/index.php) - -- Same entry point, how to execute different logic? - Via different DI preference depending on detected config area (areaList->getCodeByFrontName) -- *Default* global preference app/etc/di.xml - Magento\Framework\App\FrontController -- "frontend", "adminhtml", "crontab" area code - no preference, use default *App\FrontController* -- "webapi_rest (frontName `/rest`) - preference module-webapi/etc/webapi_rest/di.xml - *\Magento\Webapi\Controller\Rest* -- "webapi_soap" (frontname `/soap`) - preference module-webapi/etc/webapi_soap/di.xml - *\Magento\Webapi\Controller\Soap* - -### [\Magento\Framework\App\FrontController](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/App/FrontController.php): - -- routerList -- action = router[].match -- result = action.dispatch() or action.execute() -- noroute action fallback - -### Router match - action can be: - -- generic \Magento\Framework\App\ActionInterface::execute - not used? -- [\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 - -### Dispatch/execute action - result can be: - -- [\Magento\Framework\Controller\ResultInterface](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Controller/ResultInterface.php) - renderResult, setHttpResponseCode, setHeader - - Implementations: - - [Result\Raw](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Controller/Result/Raw.php) -> Result\AbstractResult - - [Result\Json](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Controller/Result/Json.php) -> Result\AbstractResult - - [Result\Forward](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Controller/Result/Forward.php) -> Result\AbstractResult - - [\Magento\Framework\View\Result\Layout](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/View/Result/Layout.php) -> Result\AbstractResult - - [\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 - - [Result\Redirect](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Controller/Result/Redirect.php) -> Result\AbstractResult - -- [\Magento\Framework\App\ResponseInterface](https://github.com/magento/magento2/blob/2.3/lib/internal//Magento/Framework/App/ResponseInterface.php) - sendResponse - - Implementations: - - [Console\Response](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/App/Console/Response.php) - - [\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 - - [\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 - - [\Magento\Framework\Webapi\Response](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Webapi/Response.php) -> \Magento\Framework\HTTP\PhpEnvironment\Response - - [\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 - -### [\Magento\Webapi\Controller\Rest](https://github.com/magento/magento2/blob/2.3/app/code/Magento/Webapi/Controller/Rest.php) -> \Magento\Framework\App\FrontControllerInterface: - -- 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) -- process path [/$store]/... - specific store, [/all]/... - admin store (0), /... - default store -- a. process schema request /schema -- b. or process api request (resolve route, invoke route -> service class with params) - -### [\Magento\Webapi\Controller\Soap](https://github.com/magento/magento2/blob/2.3/app/code/Magento/Webapi/Controller/Soap.php) -> \Magento\Framework\App\FrontControllerInterface: - -- process path (same as REST) -- a. generate WSDL ?wsdl -- b. or generate WSDL service list ?wsdl_list -- c. or handle SOAP (native PHP) - - -### In which situations will the front controller be involved in execution, and how can it be used in the scope of customizations? - -- Front controller is only used in HTTP application - pub/index.php -- involved in `frontend` and `adminhtml` (HTTP\App\FrontController), `webapi_rest` (Controller\Rest), `webapi_soap` (Controller\Soap) areas -- HTTP customization - register router and place custom code in match() method diff --git a/2. Request Flow Processing/2. Demonstrate ability to process URLs in Magento.md b/2. Request Flow Processing/2. Demonstrate ability to process URLs in Magento.md deleted file mode 100644 index fc642cf..0000000 --- a/2. Request Flow Processing/2. Demonstrate ability to process URLs in Magento.md +++ /dev/null @@ -1,187 +0,0 @@ -# Demonstrate ability to process URLs in Magento -## Describe how Magento processes a given URL. - -urlBuilder - [\Magento\Framework\UrlInterface](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/UrlInterface.php): getUrl - -Instances: - -- [\Magento\Framework\Url](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Url.php) -- [\Magento\Backend\Model\Url](https://github.com/magento/magento2/blob/2.3/app/code/Magento/Backend/Model/Url.php) - -1. preprocess route params - - \Magento\Framework\Url\*RouteParamsPreprocessorInterface*::execute - - composite - delegates to many instances - - staging - Preview\RouteParamsPreprocessor - * if frontend and preview, adds `?___version` - * if store code in URL, adds `?___store` - -1. Disable caching if object found in route params -1. *createUrl* (may be cached in memory) - - - stash all parameters `_fragment`, `_escape`, `_escape_params`, `_query`, `_nosid` - - \Magento\Framework\Url::*getRouteUrl* - without any system params - - then compile ?query and #fragment params - -1. run modifier - - \Magento\Framework\Url\ModifierInterface::execute - mode entire/base - - composite - delegates to many instances - - staging - Url\BaseUrlModifier - only base mode - In frontend and preview, replaces host part with `$_SERVER[HTTP_HOST]` - -*getRouterUrl*: - -- `_direct` option = baseUrl + `_direct` -- $routeName/$controllerName/$actionName/$param1/$value1/... - -### How do you identify which module and controller corresponds to a given URL? - -- first part is route name. Search routes.xml for route with matching *ID* -- modules for this ID are sorted with "before" and "after" -- controller/action classes is searched in matched modules - -### What is necessary to create a custom URL structure? - -- register custom router, e.g. [Magento\Robots\Controller\Router](https://github.com/magento/magento2/blob/2.3/app/code/Magento/Robots/Controller/Router.php) -- create rewrite record for specific URL - - -## Describe the URL rewrite process and its role in creating user-friendly URLs. - -Router `urlrewrite `: -- `?____from_store` param, redirect to new URL if necessary. - - Example: - - on English store category page /shoes switching to Norwegian store - - _/no/shoes?____from_store=1_ - - find new rewrite for norwegian store - - 302 redirect to _/no/sko_ - -- find rewrite by request path -- redirect if necessary -- return forward action - mark request not dispatched, force continue router loop - - -### How is getUrl('catalog/product/view/id/1') replaced with rewrite? - -- Product->getProductUrl -- Product\Url->getProductUrl -- Product\Url->getUrl -- UrlFinderInterface->findOneByData -- new Url->getUrl -- `_direct` if rewrite found = baseUrl . requestPath - -Rewrite is not used with regular getUrl, only when module uses explicitly (catalog, CMS). - -### CMS URL rewrite - -- On event `cms_page_save_after`, if identifier or store changed, deletes and creates new rewrites. -- Doesn't create redirect rewrite for renamed redirects. -- CMS page opens with UrlRewrite router (priority 20), not CMS router (priority 60). - -### How are user-friendly URLs established, and how are they customized? - -Module UrlRewrite: -- \Magento\UrlRewrite\Model\UrlPersistInterface::deleteByData -- \Magento\UrlRewrite\Model\UrlPersistInterface::replace - -Product: - -- event `catalog_product_save_before` - generate URL key by product name (if url key wasn't provided) - * [ProductUrlKeyAutogeneratorObserver](https://github.com/magento/magento2/blob/2.3/app/code/Magento/CatalogUrlRewrite/Observer/ProductUrlKeyAutogeneratorObserver.php) - * [\Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator::getUrlKey](https://github.com/magento/magento2/blob/2.3/app/code/Magento/CatalogUrlRewrite/Model/ProductUrlPathGenerator.php#L125) - -- event `catalog_product_save_after` - generate and replace URL rewrites (when changed url_key, categories, websites or visibility) - * [ProductProcessUrlRewriteSavingObserver](https://github.com/magento/magento2/blob/2.3/app/code/Magento/CatalogUrlRewrite/Observer/ProductProcessUrlRewriteSavingObserver.php) - * [\Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator::generate](https://github.com/magento/magento2/blob/2.3/app/code/Magento/CatalogUrlRewrite/Model/ProductUrlRewriteGenerator.php#L128) - * deleteByData, replace - -Category: - -- event `catalog_category_save_before` - generate URL key, update child categories - * [CategoryUrlPathAutogeneratorObserver](https://github.com/magento/magento2/blob/2.3/app/code/Magento/CatalogUrlRewrite/Observer/CategoryUrlPathAutogeneratorObserver.php) - * \Magento\CatalogUrlRewrite\Observer\CategoryUrlPathAutogeneratorObserver::updateUrlPathForChildren - * \Magento\CatalogUrlRewrite\Observer\CategoryUrlPathAutogeneratorObserver::updateUrlPathForCategory - * \Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator::getUrlPath - * child category.url_path - -- event `catalog_category_save_after` - when changed (key, anchor, products) - * [CategoryProcessUrlRewriteSavingObserver](https://github.com/magento/magento2/blob/2.3/app/code/Magento/CatalogUrlRewrite/Observer/CategoryProcessUrlRewriteSavingObserver.php#L90) - * [\Magento\CatalogUrlRewrite\Observer\UrlRewriteHandler::generateProductUrlRewrites](https://github.com/magento/magento2/blob/2.3/app/code//Magento/CatalogUrlRewrite/Observer/UrlRewriteHandler.php#L124) - * ... lots of logic - - -## Describe how action controllers and results function. - -[App\Action\Action::dispatch](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/App/Action/Action.php#L91): - -- event `controller_action_predispatch` -- event `controller_action_predispatch_$routeName`, e.g. `..._checkout` -- event `controller_action_predispatch_$fullActionName`, e.g. `..._checkout_cart_index` -- stop if FLAG_NO_DISPATCH -- *execute* - all action controllers implement this -- stop if FLAG_NO_POST_DISPATCH -- event `controller_action_postdispatch_$fullActionName` -- event `controller_action_postdispatch_$routeName` -- event `controller_action_postdispatch` -- if action doesn't return result, response object is returned -- action can just modify response object - - -### How do controllers interact with another? - -- Controller\Response\Forward - changes request params, marks request not dispatched, so front controller - will match again and new controller will be executed -- Controller\Response\Redirect - another controller URL - -### How are different response types generated? -[\Magento\Framework\Controller\ResultInterface](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Controller/ResultInterface.php): -- renderResult -- setHttpResponseCode -- setHeader - -[Controller\AbstractResult](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Controller/AbstractResult.php): - -- *renderResult* - required by interface - applies headers and calls *render*. children must implement this -- setHttpResponseCode -- setHeader -- setStatusHeader - -[Controller\Result\Raw](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Controller/Result/Raw.php): - -- setContents -- *render* - set response body - -[Controller\Result\Json](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Controller/Result/Json.php): - -- setData - array -- setJsonData - string -- *render* - processes inline translations, sets application/json header and response body json string - -[Controller\Result\Forward](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Controller/Result/Forward.php): - -- setModule, setController, setParams -- *forward* - does the trick, modifies request object, marks request not dispatched -- *render* - does nothing, forward must be called manually - -[Controller\Result\Redirect](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Controller/Result/Redirect.php): - -- setUrl, setPath - custom address -- setRefererUrl, setRefererOrBaseUrl - go back function - -[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.) - -- *renderResult* - * event `layout_render_before` - * event `layout_render_before_$fullActionName`, e.g. `..._checkout_cart_index` - * render - -- *render* - layout->getOutput, translate inline, set response body -- addDefaultHandle = $fullActionName, e.g. `checkout_cart_index` -- addHandle, addUpdate - -[View\Result\Page](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/View/Result/Page.php): - wraps layout into page layout -- same events as above -- *render* - renders layout, assigns vars and renders outer page template -- assign - values into viewVars property. default - Default: requireJs, headContent, headAdditional, htmlAttributes, headAttributes, bodyAttributes, loaderIcon, layoutContent -- addDefaultHandle = $fullActionName + `default` diff --git a/2. Request Flow Processing/3. Demonstrate ability to customize request routing.md b/2. Request Flow Processing/3. Demonstrate ability to customize request routing.md deleted file mode 100644 index 0a36f95..0000000 --- a/2. Request Flow Processing/3. Demonstrate ability to customize request routing.md +++ /dev/null @@ -1,57 +0,0 @@ -# 2.3 Demonstrate ability to customize request routing - -## Describe request routing and flow in Magento. - -[Frontend routers](https://devdocs.magento.com/guides/v2.2/extension-dev-guide/routing.html): - -- robots (10) -- urlrewrite (20) -- standard (30) - module/controller/action - * get modules by front name registered in routes.xml - * find action class - * if action not found in all modules, searches "noroute" action in *last* module - -- cms (60) -- default (100) - noRouteActionList - 'default' noroute handler - -Adminhtml routers: - -- admin (10) - extends base router - module/controller/action, controller path prefix "adminhtml" -- default (100) - noRouteActionList - 'backend' noroute handler - -Default router (frontend and backend): - -- noRouteHandlerList. process - + backend (10) - *Default admin 404 page* "adminhtml/noroute/index" when requested path starts with admin route. - - + default (100) - *Default frontend 404 page* "cms/noroute/index" - admin config option `web/default/no_route`. - -- always returns forward action - just mark request not dispatched - this will continue match loop - - -### When is it necessary to create a new router or to customize existing routers? - -Create new router when URL structure doesn't fit into module/controller/action template, -e.g. fixed robots.txt or dynamic arbitraty rewrites from DB. - -If you want to replace controller action, you can register custom module in controller lookup sequence - -reference route by ID and add own module "before" original module. - -### How do you handle custom 404 pages? - -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. - E.g. catalog/product/view/id/1 throws NotFoundException. catalog/product/noroute is checked. - -1. If standard router recognizes front name but can't find controller, it tries to find *"noroute"* - action from last checked module. - E.g. catalog/brand/info controller doesn't exist, so catalog/brand/noroute will be checked. - - [\Magento\Framework\App\Router\Base::getNotFoundAction](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/App/Router/Base.php#L237) - -1. If all routers didn't match, default controller provides two opportunities: - - 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)) - - register custom handler in [noRouteHandlerList](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/App/Router/NoRouteHandlerList.php): - * 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` - * default (sortOrder: 100) [Magento\Framework\App\Router\NoRouteHandler](https://github.com/magento/magento2/blob/2.3/lib/internal/Magento/Framework/App/Router/NoRouteHandler.php) diff --git a/2. Request Flow Processing/4. Determine the layout initialization process.md b/2. Request Flow Processing/4. Determine the layout initialization process.md deleted file mode 100644 index 6223c03..0000000 --- a/2. Request Flow Processing/4. Determine the layout initialization process.md +++ /dev/null @@ -1,369 +0,0 @@ -# Determine the layout initialization process - -## Determine how layout is compiled. - -View\Layout::*build* = View\Layout\Builder::*build* - once - -1. loadLayoutUpdates - reads layout xml files and DB updates by *current handles*, result in `_updates` array - - * event `layout_load_before` - -1. generateLayoutXml - joins `_updates` into XML string, loads XML object, initiailzes `_elements` = [] - - * layout.generateXml - * no events - -1. generateLayoutBlocks - layout.generateElements - - * event `layout_generate_blocks_before` - * readerPool->interpret - every reader checks for matching xml node name (attribute, body, head etc.), prepares page structure - * generatorPool->process - * add top-level element as outputElement. default "root" - * event `layout_generate_blocks_after` - -``` -builder.generateLayoutBlocks -> layout.generateElements -readerPool.*interpret* -- schedules - nodeReaders[type].interpret each element -- Layout\ReaderInterface - - html, move - - body - own readerPool.interpret, but without 'body' reader - - head - css,script,link,remove,meta,title,attribute - - 'container', 'referenceContainer' --> Layout\Reader\Container - - 'block', 'referenceBlock' --> Layout\Reader\Block - - uiComponent - View\Layout\ReaderInterface::interpret -- html, body, head, ui component, reader pool, container, move, block - -View\Layout\GeneratorPool.process -- generates blocks - buildStructure - generator[].*process* - - head, body - - block - creates blocks, sets layout, event `core_layout_block_create_after`, block 'actions' - - container - sets tag, id, class, label, display - - uiComponent - creates wrapper element, prepareComponent recursively etc. - -getOutput -- renderElement(root) - - renderNonCachedElement(root) -- hardcoded switch - - is uiComponent -> toHtml - - is block -> toHtml - - is container -> renderElement(child[]) - - event `core_layout_render_element` -``` - -### How would you debug your layout.xml files and verify that the right layout instructions are used? - -When XML object is created from string XML updates, this is a good place to examine resuls. -View\Layout\Builder.generateLayoutXml or View\Layout.generateXml is a good place to dump structure. - -## Determine how HTML output is rendered. - -First external code calls layout.addOutputElement(name) to register top level elements for output generation. -When layout.*getOutput* is called, it renders each outputElement. - -- View\Layout::getOutput -- for every registered `_output` element -- View\Layout::renderElement - -When layout is build initially, it finds top level container and registers it as addOutputElement - default "root". - -### How does Magento flush output, and what mechanisms exist to access and customize output? - -Render response: - -- action controller returns ResponseInterface object, e.g. View\Response\Page -- front controller returns this response -- App\Http.launch application renders result, calling response.renderResult -- controller response renders contents and assigns to response.body -- App\Http event `controller_front_send_response_before` allows to modify response before returning -- App\Bootstrap object gets response object from App\Http -- response.sendResponse - Zend implementation - send headers, send content - -Customize: - -1. modify any response in event `controller_front_send_response_before` -1. View\Element\AbstractBlock::toHtml event `view_block_abstract_to_html_after` - -## Determine module layout XML schema. - -Module layouts: - -- module/view/base/layout/ -- module/view/frontend/layout/ -- module/view/adminhtml/layout/ - -### layout_generic.xsd - -Generic layout - useful for returning Ajax response. Doesn't have body, head, css etc., only pure structure: - -```xml - - - - - - - - - -``` - -### page_configuration.xsd - -Same as layout plus: - -- top level layout selection -- structure is wrapped in body node -- additional nodes - html attribute, head title, attribute and scripts - -```xml - - - - - - Title - - - - - ``` - Element selector syntax is good for passing container element, so module can bind event listeners only - within given element instead of querying DOM directly. Makes code tidier. - - ```html - - ``` - -- action - set some data, call AJAX - * Magento_Checkout/js/action/select-payment-method.js - * Magento_Customer/js/action/login.js - when called, calls AJAX, when done executes callbacks and adds messages - -- shared model for data access - ko.observable properties - * Magento_Customer/js/model/customer - * Magento_Checkout/js/model/quote - * some state - is loading etc. Magento_Checkout/js/model/shipping-address/form-popup-state.js - * validation rules Magento_Ui/js/lib/validation/rules.js - -- view - ui component - * form Magento_Ui/js/form/form - * grid Magento_Ui/js/grid/listing - * minicart Magento_Checkout/js/view/minicart - -- jQuery widget - - ```JS - { - $.widget('mage.shippingCart', { - ... // this.config, this.element - }) - return $.mage.shippingCart; - } - $(element).shippingCart(...) - ``` - - -## Describe UI components. - -Base class hierarchy: - -- uiClass -- uiElement + uiEvents + links -- uiCollection - -### *uiClass* - Magento_Ui/js/lib/core/class - -[About the uiClass library](http://devdocs.magento.com/guides/v2.2/ui_comp_guide/concepts/ui_comp_uiclass_concept.html) - -The uiClass is an abstract class from which all components are extended. The uiClass is -a low-level class and is rarely used as direct parent for UI components’ classes. - -Adds support for extending classes `extend()` and calling parent methods `_super()`. - -methods: -- initialize, initConfig - -properties: -- contructor.defaults - - -### *uiElement* - Magento_Ui/js/lib/core/element/element.js - -[About the uiElement class](http://devdocs.magento.com/guides/v2.2/ui_comp_guide/concepts/ui_comp_uielement_concept.html) - -Powers most of UI components internals. Adds support for features: -- automatic data linking between models -- automatic module loading -- working with observable properties -- stateful properties - stored automatically -- events - on/off/trigger - -methods: -- initialize, initObservable, initModules -properties: -- tracks, modules, source, provider, - -initLinks: -- imports -- exports -- links == same value in imports and exports - -``` -imports: { - totalRecords: '${ $.provider }:data.totalRecords' -} -``` -same as -``` -imports: { - totalRecords: 'example.example_data_source:data.totalRecords' -} -``` - -``` -exports: {'visible': '${ $.provider }:visibility'} -imports: {'visible': '${ $.provider }:visibility'} -links: {'visible': '${ $.provider }:visibility'} -links: {value: '${ $.provider }:${ $.dataScope }'} -``` - -``` -listens: {'${ $.namespace }.${ $.namespace }:responseData': 'setParsed'} -listens: {'${ $.provider }:data.overload': 'overload reset validate'} -- call multiple callbacks -listens: - 'index=create_category:responseData' => 'setParsed', - 'newOption' => 'toggleOptionSelected' -``` - -$.someProperty - property of the UI component in the scope of this component - -conditions in string literals: - -'${ $.provider }:${ $.customScope ? $.customScope + "." : ""}data.validate': 'validate' - - -### initObservable -- observe([{Boolean} isTracked,] {String|Array|Object} listOfProperties) -- observe('var1 var2 var3') -- observe(['var1', 'var2', 'var3']) -- observe(true, 'var1 var2 var3') -- isTracked, use property accessors -- observe(false, 'var1 var2 var3') -- not tracked, use observable properties -- observe({var1: 'value', var2: true}) -- initial values from object - -### initModules -```JS -defaults: { - modules: { - '%myProperty%': '%linkToTheComponent%', - externalSource: '${ $.externalProvider }', - street: '${ $.parentName }.street', - city: '${ $.parentName }.city', - country: '${ $.parentName }.country_id', - productForm: 'product_form.product_form' - } -} -``` - - - -## uiCollection - Magento_Ui/js/lib/core/collection.js - -[About the uiCollection class](http://devdocs.magento.com/guides/v2.2/ui_comp_guide/concepts/ui_comp_uicollection_concept.html) - -Enables components to have nested child components: -- `elems` observable property -- initElement -- getChild, insertChild, removeChild, destroyChildren -- regions - - -## Module_Ui/js/core/app, uiLayout - Module_Ui/js/core/renderer/layout - -[The uiLayout service object](http://devdocs.magento.com/guides/v2.2/ui_comp_guide/concepts/ui_comp_uilayout_concept.html) - -Renders UI collection components structure, instantiates classes, sets children etc. - -- app -> layout() -- run -- iterator -- process -- addChild - current node to parent -- manipulate - appendTo, prependTo, insertTo -- initComponent - * loadDeps - * loadSource (source='uiComponent') - * initComponent - + long async -> global function var component = new Constr(_.omit(node, 'children')); - - -## In which situation would you use UiComponent versus a regular JavaScript module? - -[Overview of UI components](http://devdocs.magento.com/guides/v2.2/ui_comp_guide/bk-ui_comps.html) - -UI components work well together: they communicate with each other via the uiRegistry service that -tracks their asynchronous initialization. - -Therefore, if we need to extend something that has already been implemented as a hierarchy -of UI components or add a new feature that should interact with other UI components, -it’s easier and more effective to use a UI component. - - -## Describe the use of requirejs-config.js, x-magento-init, and data-mage-init. - -### requirejs-config.js - -We are interested in: -- map as alias - same as defining virtual type preference in DI -```js -var config = { - map: { - '*': { - 'Magento_Swatches/js/swatch-renderer' : 'Custom_Module/js/swatch-renderer' - } - } -}; -``` -- map as preference - same as preference in DI, replace one class with another -```js -var config = { - map: { - '*': { - uiElement: 'Magento_Ui/js/lib/core/element/element', - } - } -}; -``` -- mixins - same as around plugins in DI. This is Magento customization over requireJS -```js -var config = { - config: { - mixins: { - 'Magento_Checkout/js/action/place-order': { - 'Magento_CheckoutAgreements/js/model/place-order-mixin': true - } - } - } -}; -``` - -Use `wrapper` model to implement around functionality for functions. - -Place order mixin body: -``` -define([ - 'jquery', - 'mage/utils/wrapper', - 'Magento_CheckoutAgreements/js/model/agreements-assigner' -], function ($, wrapper, agreementsAssigner) { - 'use strict'; - - return function (placeOrderAction) { - - /** Override default place order action and add agreement_ids to request */ - return wrapper.wrap(placeOrderAction, function (originalAction, paymentData, messageContainer) { - agreementsAssigner(paymentData); - - return originalAction(paymentData, messageContainer); - }); - }; -}); - -``` - -Plain requirejs objects can be extended directly. - -Example - mixin over Magento_Checkout/js/model/quote: - -``` -define([ - 'jquery', - 'ko' -], function($, ko) { - 'use strict'; - - var checkoutMethod = ko.observable(null); - - return function(quote) { - return $.extend(quote, { - checkoutMethod: checkoutMethod - }); - }; -}); -``` - -### Text/x-magento-init and Data-mage-init - -[Alan Storm - Javascript Init Scripts](http://alanstorm.com/magento_2_javascript_init_scripts) - -Following 3 snippets are completely equivalent: -```html -
-``` - -```html -
- -``` - -```html -
- -``` - - -How Magento executes scripts: -- mage/bootstrap.js -- lib/web/mage/apply/scripts.js - * parses init scripts `document.querySelectorall('script[type="text/x-magento-init"]')` - * converts to data-attribute format on according element, e.g. `
` - * scripts with `*` selector (virtuals, no node to assign to) are collected separately -- mage/apply/main.js - * parses data-attribute init scripts `document.querySelectorAll('[data-mage-init]')` - including converted ` - - -``` - -Transforms into following: -```html -
-
- -``` - -```JS -element = $('[data-gallery-role=gallery-placeholder]'); -config = {magnifierOpts:..., data:..., options:...} - -require(['mage/gallery/gallery'], function(gallery) { - require(["magnifier/magnify"], function(magnify) { - var result = magnify(config, element); // call for each mixin. Magnify will use config.magnifierOpts - extend(config, result); // magnify returns same config, config not changed - }) - gallery(config, element); -}); -``` diff --git a/3.Magento-Customising-The-UI.md b/3.Magento-Customising-The-UI.md new file mode 100755 index 0000000..16691d6 --- /dev/null +++ b/3.Magento-Customising-The-UI.md @@ -0,0 +1,618 @@ +3 - Customizing the Magento UI +================================== + +3.1 Demonstrate ability to utilize themes and the template structure +-------------------------------------------------------------------- + +![Magento Exam Question](./images/icon-question.png)**Demonstrate the ability to customize the Magento UI using themes.** + +![Magento Section Chevron](./images/icon-chevron.png)Theme directory + +app/design/frontend/Vendor/themename/ +``` +- composer.json +- registration.php +- theme.xml - name, parent, logo +- etc/view.xml - currently catalog images configuration +- i18n/ +- media/ - pub logo here +- Magento_Checkout/ - extend specific module +---- layout/ - normally layouts are extended +------- override/base/ - special case: completely replace base layout for module +------- override/theme/Magento/luma/ - special case: completely replace specific theme layout +---- templates/ - replace module files +---- web/ - replace module files +- web/ - replace theme files +---- js/ +---- css/ +------- source/ +---- fonts/ +---- images/ +---- i18n/en_US/ - locale-specific file replacement (without module) +---- i18n/en_US/Magento_Checkout/ - locale-specific module file replacement +``` + +![Magento Exam Information](./images/icon-info.png)In multiple choice if you are presented with something around theme structure you should know the basic structure having worked with this daily + +![Magento Section Chevron](./images/icon-chevron.png)Layout + +> You can customise layouts / templates / html / css & js by placing overrides in the theme directory. +> +> Normally layouts are merged, extending parent ones. +> +> Full override +> +> To completely replace original module layout file, e.g. to replace vendor/magento/module-checkout/view/frontend/layout/default.xml, place new file in Magento_Checkout/layout/override/base/default.xml + +| Theme layout | Override | +| --- | --- | +| Base `vendor/magento/module-checkout/view/frontend/layout/default.xml` | `/Magento_Checkout/layout/override/base/default.xml` | +| Luma (parent theme) `vendor/magento/theme-frontend-luma/Magento_Checkout/layout/default.xml` | `/Magento_Checkout/layout/override/Magento/luma/default.xml` | + +![Magento Exam Information](./images/icon-info.png)I'm personally not aware of this "override trick" / placing into the override directory within the teme to completely override a layout so this could be worth learning. In case this isn't true however, a quick answer would be to say that you can override module/vendor files by placing them in the theme. + +![Magento Exam Information](./images/icon-info.png)It is not possible to use the "override trick" inside a module, only in theme. You cannot create a new module app/code/MyVendor/MyModule/view/frontend/layout/override/... and replace layout files for example. + +![Magento Section Chevron](./images/icon-chevron.png)Locale + +> You can replace static files with locale-specific - JS, logo, font etc. You have 2 places: + +* Non-module specific, e.g. theme asset - `web/i18n/en_US/` +* From some module - `//web/i18n/en_US/Magento_Checkout/` + +![Magento Exam Question](./images/icon-question.png)**How is the theme loaded?** + +> Module Magento_Store is responsible for loading themes in it's etc/di.xml file. Theme is loaded during [Dependency Injection](#h.9ofwnl5q9fnf) (DI) compilation. Detailed process include: +``` +App\Action\AbstractAction.beforeDispatch + +App\Area::_initDesign + +get/setDesignTheme = theme + +get/setArea = frontend + +getLocale + +getConfigurationDesignTheme - Load config "design/theme/theme_id" + +setDefaultDesignTheme - area, theme model, is. loads theme model from DB +``` + +> The theme is set via the admin in the ‘design/theme/theme_id' configuration. + +![Magento Section Chevron](./images/icon-chevron.png)Theme types + +Physical themes +> Physical refers to the fact that those themes are defined by files. For example, the blank and luma theme are physically defined under app/design/frontend/ + +Virtual themes +> This is yet unclear but I think virtual themes refer to themes you can create in the backend which extends existing physical themes but it seems like it's not fully implemented yet. Unused. + +Staging +> Something to do with EE campaigns. Unused in CE. + +![Magento Exam Question](./images/icon-question.png)**When would you create a new theme?** + +* When you want to edit the look of your storefront you can create one: + * From scratch, if your design is totally different to default. + * Inherited from a parent (blank / luma) to add smaller customizations - move, hide, reorder elements, change block arguments, html attributes. +* Theme can apply dynamically based on browser user agent as an exception - enter regexp in Content > Design > Implementation > [Edit] > Design Rule > User Agent Rules Full page cache and design exception. + +![Magento Exam Question](./images/icon-question.png)**How do you define theme hierarchy for your project?** +``` +/theme.xml: + + tag +``` + +![Magento Exam Question](./images/icon-question.png)**Demonstrate the ability to customize/debug templates using the template fallback process. How do you identify which exact theme file is used in different situations?** + +> The first step in customizing a template is to locate it. This is done by either: + +* finding the path in layout XML, using the debugger, +* Or by enabling template hints (`bin/magento dev:template-hints:enable`). +* Another way is to find a unique string in the HTML (translation, tag or class). Searching the entire project directory often yields the location of the file. Be aware that there are not only `.phtml` templates, but also `.html` knockout.js templates or `.xhtml` template files. + * Once you find the file, create a folder in your theme for the module that you copied it from. For example, if you are modifying a template from the `Magento_Catalog` module, create a `Magento_Catalog` folder inside your theme directory. + + +![Magento Exam Question](./images/icon-question.png)**How can you override native files?** + +File Override Fallback Rule + +| Path example | Priority order | Type | +| --- | --- | --- | +| `layout/override/base/*.xml` | 1b | Theme module e.g. `app/design/frontend/Vendor/themename/Magento_Checkout` | +| `layout/override/Magento/luma/*.xml` | 1a - This will override base for all themes inherited from luma | Theme module e.g. app/design/frontend/Vendor/themename/Magento_Checkout | +| `/Magento_Checkout/web/js/view/minicart.js` | 2b | Static asset for module within theme | +| `/web/i18n/en_US/Magento_Checkout/` | 2a - if you create a locale then all stores deployed with en_US will prioritise this | Static asset for specific locale within theme | + +![Magento Exam Information](./images/icon-info.png)Again is this "override trick" - may need to confirm this is still in use. + +3.2 Determine how to use blocks +------------------------------- + +![Magento Exam Question](./images/icon-question.png)**Demonstrate an understanding of block architecture and its use in development.** + +![Magento Section Chevron](./images/icon-chevron.png)Blocks + +> Blocks satisfy the [ViewModel part of MVVM architecture](#h.i0qq8aqgijv7). Blocks are PHP classes that provide data for template files (.phtml) and implement rendering. Also, blocks can cache the results of template rendering. All block classes extend the Magento\Framework\View\Element\AbstractBlock abstract class which implements Magento\Framework\View\Element\BlockInterface and provides the following functionality: + +* Data automatically assigns to _data, can access data arguments later, e.g. in `_construct` +* `jsLayout` data argument +* Render - `toHtml` + * Event view_block_abstract_to_html_before is dispatched. + * Disable module output still works. + * If not in cache: `_beforeToHtml`, `_toHtml`, save cache + * `_afterToHtml` - always, even when cached +* Event: `view_block_abstract_to_html_after` + +![Magento Section Chevron](./images/icon-chevron.png)Templates + +> Template files are responsible for loading the template file (.phtml) they inherit the class Magento\Framework\View\Element\Template which is an extension of the Magento\Framework\View\Element\AbstractBlock class from above. Specifically this class is responsible for: + +* Template data arguments +* `_viewVars` property, assign() +* `_toHtml` renders template when defined + * `getTemplateFile` = `Magento\Framework\View\Element\Template\File\Resolver::getTemplateFileName` + `fetchView` + * `Magento\Framework\View\Element\Template\File\Validator::isValid` - checks allowed directory (view_preprocessed/module/theme) or symlink + * If its a bad file, log and throw error in DEV mode + * `Magento\Framework\View\TemplateEnginePool::get()` - by file extension phtml, xhtml. + * `Magento\Developer\Model\TemplateEngine\Decorator\DebugHints` + * engine.render(block, template, viewVars) +* default getCacheKeyInfo - store code, template, base URL. + +![Magento Exam Information](./images/icon-info.png)FYI: Admin configuration `dev/template/allow_symlink`, default = false [following an exploit](https://maxchadwick.xyz/blog/what-allow-symlinks-actually-does&sa=D&ust=1609223264702000&usg=AOvVaw05eFXzMZwapN_T8Hm98fHU). + +![Magento Exam Information](./images/icon-info.png)Just remember: Blocks are PHP classes that provide data for template files (.phtml) and implement rendering. Also, blocks can cache the results of template rendering. + +![Magento Exam Question](./images/icon-question.png)**Which objects are accessible from the block?** + +Within a template file (.phtml) the following objects are accessible: + +* `$this` - Instance of class `Magento\Framework\View\TemplateEngine\Php` +* `$block` - Instance of the custom block class defined in layout of which is calling this template. +* `$this->helper()` - Gets the singleton class of AbstractHelper which calls upon the Object Manager to generate other Object Instances this template may need access to. + +![Magento Exam Information](./images/icon-info.png)`$this` is proxied to $block via magic __call function so $this->property = $block->property + +![Magento Exam Note Warning](images/icon-warning.png)Using `$this` & `$this->helper()` [considered an anti-pattern & is now deprecated](https://magento.stackexchange.com/questions/325189/is-it-okay-to-use-this-helper-in-phtml-files&sa=D&ust=1609223264703000&usg=AOvVaw1gTPUq_b4GUeO4NjTu-Vdj). `$block` should be used in its place + +![Magento Exam Question](./images/icon-question.png)**What is the typical block's role?** + +> Blocks satisfy the [ViewModel part of MVVM architecture](#h.i0qq8aqgijv7). As much business logic as possible should be moved out of template, and blocks provide access to processed data for templates. Actual data crunching can (and should) be proxied further to Models. + +![Magento Exam Information](./images/icon-info.png)Blocks can be thought of as data containers for a template, which represents a piece of the HTML on the page. In layouts XML, you can manage blocks on page and add new ones, set templates to move and delete. + +![Magento Exam Question](./images/icon-question.png)**Identify the stages in the lifecycle of a block.** + +The block life cycle consists of two phases: + +1. Generating blocks: + * `Magento\Framework\View\Page\Config::publicBuild()` + * `Magento\Framework\View\Page\Config::build()` + * `Magento\Framework\View\Layout\Builder::build()` + * `Magento\Framework\View\Layout\Builder::generateLayoutBlocks()` + * `Magento\Framework\View\Layout::generateElements()` + * `Magento\Framework\View\Layout\GeneratorPool::process()` + * GeneratePool - goes through all generators and generates all scheduled elements. It has generators with the following elements: + * `Magento\Framework\View\Page\Config\Generator\Head` + * `Magento\Framework\View\Page\Config\Generator\Body` + * `Magento\Framework\View\Layout\Generator\Block` + * `Magento\Framework\View\Layout\Generator\Container` + * `Magento\Framework\View\Layout\Generator\UiComponent` + +2. Rendering blocks: + * `Magento\Framework\View\Result/Page::render()` + * `Magento\Framework\View\Layout::getOutput()` + +> Blocks are instantiated at the moment the layout is created. They are not executed at that time, just instantiated. Also during this phase, the structure is built, the children of blocks are set, and for each block, the prepareLayout() method is called. However, rendering occurs in the later rendering phase. + +![Magento Exam Information](./images/icon-info.png)Just remember: Blocks are generated, then rendered - that is the main 2 stages in its life lifecycle. + +![Magento Exam Question](./images/icon-question.png)**Describe block arguments** + +| Attribute | Description | Values | Required +| --- | --- | --- | --- | +| `class` | Name of a class that implements rendering of a particular block. An object of this class is responsible for actual rendering of block output. | A fully-qualified class name, such as Vendor\Module\Block\Class. Defaults to Magento\Framework\View\Element\Template. | No | +| `name` | Name that can be used to address the block to which this attribute is assigned. The name must be unique per generated page. If not specified, an automatic name will be assigned in the format ANONYMOUS_n | | 0-9, A-Z, a-z, underscore (_), period (.), dash (-). Should start with a letter. Case-sensitive. | No | +| `before` | Used to position the block before an element under the same parent. The element name or alias name is specified in the value. Use dash (-) to position the block before all other elements of its level of nesting. See [](https://devdocs.magento.com/guides/v2.3/frontend-dev-guide/layouts/xml-instructions.html%23fedg_xml-instrux_before-after&sa=D&ust=1609223264710000&usg=AOvVaw2vzXxub4ZE1fZ6x1aB2o2W) [before and after attributes](https://devdocs.magento.com/guides/v2.3/frontend-dev-guide/layouts/xml-instructions.html%23fedg_xml-instrux_before-after&sa=D&ust=1609223264711000&usg=AOvVaw10SW-i3aOGPkEKkgi21z_z) for details. | Element name or dash (-) | No | +| `after` | Used to position the block after an element under the same parent. The element name or alias name is specified in the value. Use dash (-) to position the block after all other elements of its level of nesting. See [](https://devdocs.magento.com/guides/v2.3/frontend-dev-guide/layouts/xml-instructions.html%23fedg_xml-instrux_before-after&sa=D&ust=1609223264712000&usg=AOvVaw0_MSqb4TUWSfxLWn8bnHCi) [before and after attributes](https://devdocs.magento.com/guides/v2.3/frontend-dev-guide/layouts/xml-instructions.html%23fedg_xml-instrux_before-after&sa=D&ust=1609223264712000&usg=AOvVaw0_MSqb4TUWSfxLWn8bnHCi) for details. | Element name or dash (-) | No | +| `template` | A template that represents the functionality of the block to which this attribute is assigned. `Vendor_Module::path/to/template.phtml` (Scope is already in the templates directory of the module) | No | +| `as` | An alias name that serves as identifier in the scope of the parent element | 0-9, A-Z, a-z, underscore (_), period (.), dash (-). Case-sensitive. | No | +| `cacheable` | Defines whether a block element is cacheable. This can be used for development purposes and to make needed elements of the page dynamic. ![Magento Exam Note Warning](images/icon-warning.png)This attribute renders the whole page uncached. | true or false | No | +| `ifconfig` | Makes the block's visibility dependent on a system configuration field | XPath to the system configuration field. E.g. contact/contact/enabled | No | + +Example: +```xml + + + My\Module\ViewModel\Custom + + +``` + +![Magento Exam Question](./images/icon-question.png)**In what cases would you put your code in the _prepareLayout(), _beforeToHtml(), and _toHtml() methods?** + +![Magento Section Chevron](./images/icon-chevron.png)`_prepareLayout()` + +Most commonly used in adminhtml + +* Set page config title +* Edit head block, e.g. add RSS +* Add breadcrumbs +* Create new block, set as child - e.g. grid + +![Magento Section Chevron](./images/icon-chevron.png)`_beforeToHtml()` + +* Adding extra child blocks +* Assign additional template values to block +* Delay computation to the latest point until render. If is not rendered we save computation + +![Magento Section Chevron](./images/icon-chevron.png)`_toHtml()` + +Using this method, you can manipulate block rendering, complement the condition, make wrappers for html , change the template for the block, block without template, put custom rendering here, suppress template output if should not render by condition such as returning an empty string. + +![Magento Exam Question](./images/icon-question.png)**How would you use events fired in the abstract block?** + +![Magento Section Chevron](./images/icon-chevron.png)`view_block_abstract_to_html_before`: + +* Edit cache params - lifetime, ttl, tags +* Add column to grid +* Edit template params - e.g. set disable edit flag + +![Magento Section Chevron](./images/icon-chevron.png)`view_block_abstract_to_html_after`: + +* Edit html - replace, add content, add wrappers + +![Magento Exam Question](./images/icon-question.png)**Describe how blocks are rendered and cached.** + +> The most important method for rendering a block is `Magento\Framework\View\Element\AbstractBlock::toHtml()`. +> +> First, it runs _loadCache(), and if the cache is missing, then it runs `_beforeToHtml()` after the block is rendered by the method `_toHtml()`. Afterward, the cache is saved `_saveCache($html)` and run `_afterToHtml($html)`. + +![Magento Exam Information](./images/icon-info.png)Remember all blocks have a `Magento\Framework\View\Element\AbstractBlock::toHtml()` In which cache is called/created. + +> Method `_loadCache()` uses cache_key return by `getCacheKey()` and `_saveCache($html)` – cache_tags obtained by the method `getCacheTags()`. Cache_key each block is added up like: +> +> `BLOCK_CLASS::CACHE_KEY_PREFIX . $cache_key` , +> +> If this property is not defined, create it: +> +> `BLOCK_CLASS::CACHE_KEY_PREFIX . sha1(implode('|', $this->getCacheKeyInfo()))` +> +> Cache_tags is an array and consists of a property `$cache_tags`, if it is defined in block and if the block instance of `Magento\Framework\DataObject\IdentityInterface` values returned by the method `getIdentities()` are added to the array. We can manage `$cache_lifetime` variable. Value will be in seconds, if you want to disable cache, you can set value to `0` or not set the value at all, because all blocks are non-cacheable by default. + +![Magento Exam Question](./images/icon-question.png)**Identify the uses of different types of blocks.** + +| Block | Class | Description | +| --- | --- | --- | +| Abstract Block | `Magento\Framework\View\Element\AbstractBlock` | Block without template. Parent block for all custom blocks. Your custom block would extend this. | +| Template | `Magento\Framework\View\Element\Template` | Block with template | +| Text | `Magento\Framework\View\Element\Text` | Just rendering text | +| Form Key | `Magento\Framework\View\Element\FormKey` | Return hidden input with form key | +| Message | `Magento\Framework\View\Element\Messages` | Rendering notification message by type | + +![Magento Exam Question](./images/icon-question.png)**When would you use non-template block types?** + +> Applying non-template block types is wise when we use simple renderers. Another reason for using such block types is when block content is dynamically generated or stored in a database or in containers. An Edge Sides Includes (ESI) block for something like GeoIP Redirect might be an example here. + +![Magento Exam Information](./images/icon-info.png)Something like CMS page - admin controls block content and layout. Another example - dynamic robots.txt whose content is stored in DB. + +![Magento Exam Question](./images/icon-question.png)**In what situation should you use a template block or other block types?** + +> Use template block whenever possible to allow for theme markup customization. + +3.3 Demonstrate ability to use layout and XML schema +---------------------------------------------------- + +![Magento Exam Question](./images/icon-question.png)**Describe the elements of the Magento layout XML schema, including the major XML directives.** + +| Directive | Description | +| --- | --- | +| `` | Html reader is used for collecting attributes of html in to the scheduled page structure. | +| `` | Sets the HTML DOM head property | +| `` | Sets the HTML DOM body property | +| `` | Move allows changing elements' order and parent. All moves are processed in View\Layout\GeneratorPool before all other generators.You can move either a container or a block. | +| `` | Containers represent the placeholders within that web page structure I.e the wireframe and automatically render it's children | +| `` | `View\Layout\Generator\Block::process`: - creates block instance, evaluates arguments. Blocks represent the UI controls or components within the container placeholders. Does not automatically render it's children | +| `` | Magento UI components are used to represent distinct UI elements, such as tables, buttons, dialogs, and others such as Knockout JS components. [See example here about how this is laid out in an XML Layout.](https://devdocs.magento.com/guides/v2.3/ui_comp_guide/bk-ui_comps.html%23ui-component-used-in-the-frontend-design-area&sa=D&ust=1609223264732000&usg=AOvVaw0HFsDsU9yTt4xlajgamTns) They are designed for simple and flexible user interface (UI) rendering. Components are responsible for rendering result page fragments and providing/supporting further interactions of JavaScript components and servers. | + +![Magento Exam Information](./images/icon-info.png)The purpose of a page layout is to create a structured, common set of layout instructions to render pages. Most pages on a website can be categorized as fitting into a 1, 2, or 3-column container system. These page layouts can be selected in the admin panel to provide a specific layout per page. Look at a vendor copy of [checkout_index_index.xml](https://github.com/magento/magento2/blob/2.3/app/code/Magento/Checkout/view/frontend/layout/checkout_index_index.xml&sa=D&ust=1609223264733000&usg=AOvVaw3bttzFkzTA5SUapsscd1sP) if you want a nice overview of how such a file is laid out. + +![Magento Exam Question](./images/icon-question.png)**How do you use layout XML directives in your customizations?** + +* `` - Move elements +* `` - change attributes, add children +* `` - change template, position before/after, add arguments, add children, set no display +* `` - Add HEAD scripts/styles + +![Magento Exam Question](./images/icon-question.png)**Describe how to create a new layout XML file.** + +![Magento Section Chevron](./images/icon-chevron.png)Place layout in one of directories: + +* ``/view/base/layout/ +* ``/view/frontend/layout/ +* ``/Magento_Checkout/layout/ + +![Magento Exam Information](./images/icon-info.png)When extending page layout, don't copy-paste layout="..." attribute if not intended. + +![Magento Section Chevron](./images/icon-chevron.png)Use standard XML namespaces +```xml + + + + ... + + +``` +```xml + + + ... + +``` + +![Magento Exam Question](./images/icon-question.png)**Describe how to pass variables from layout to block.** + +![Magento Section Chevron](./images/icon-chevron.png)Block arguments: +```xml + + + header links + + +``` +![Magento Exam Information](./images/icon-info.png)[More in Magento DevDocs on use of block arguments](https://devdocs.magento.com/guides/v2.3/frontend-dev-guide/layouts/xml-instructions.html%23argument&sa=D&ust=1609223264738000&usg=AOvVaw3C9W6M5H3sMIX71o3XcsOG) + +![Magento Section Chevron](./images/icon-chevron.png)Substitute dynamic argument value as result of helper execution: +```xml + + + VENDOR_MODULE::path/to/template.phtml + + +``` +![Magento Exam Information](./images/icon-info.png)I didn't know this! [Magento DevDocs on use of helper params](https://devdocs.magento.com/guides/v2.3/frontend-dev-guide/layouts/xml-instructions.html%23argument&sa=D&ust=1609223264740000&usg=AOvVaw3VC8ljhpCLd1XlwFgmh44Q) The helper can use only public methods. In this example the getReviewButtonTemplate() method should be public. The argument with helper type can contain param items which can be passed as a helper method parameters. + +3.4 Utilize JavaScript in Magento +--------------------------------- + +![Magento Exam Question](./images/icon-question.png)**Describe different types and uses of JavaScript modules.** + +![Magento Section Chevron](./images/icon-chevron.png)A "Plain RequireJS" module: +```html + +``` +![Magento Exam Information](./images/icon-info.png)Good for executing regular JS code, maybe some legacy code that doesn't require active interactions with other existing JS components on the page. + +![Magento Section Chevron](./images/icon-chevron.png)An "Element Selector" requirejs module: +```html + +``` +![Magento Exam Information](./images/icon-info.png)Element selector syntax is good for passing container element, so a module can bind event listeners only on a given element instead of querying DOM directly. Makes code tidier. + +![Magento Section Chevron](./images/icon-chevron.png)An "Action" module: + +* `Magento_Checkout/js/action/select-payment-method` +* `Magento_Customer/js/action/login` - when called, calls AJAX, when done executes callbacks and adds messages + +![Magento Exam Information](./images/icon-info.png)Set some data, call AJAX + +![Magento Section Chevron](./images/icon-chevron.png)A "Shared Model" in a module, e.g. + +* `Magento_Customer/js/model/customer` +* `Magento_Checkout/js/model/quote` +* Some state - is loading etc. +* `Magento_Checkout/js/model/shipping-address/form-popup-state` validation rules `Magento_Ui/js/lib/validation/rules` + +![Magento Exam Information](./images/icon-info.png)Shared model for data access - ko.observable properties + +![Magento Section Chevron](./images/icon-chevron.png)View - UI Component, e.g. + +* Forms - `Magento_Ui/js/form/form` +* Grid - `Magento_Ui/js/grid/listing` +* Minicart - `Magento_Checkout/js/view/minicart` + +![Magento Section Chevron](./images/icon-chevron.png)Finally, a jQuery widget: +```js +{ + .widget('mage.shippingCart', { + ... // this.config, this.element + }) + return $.mage.shippingCart; +} +$(element).shippingCart(...) +``` +![Magento Exam Question](./images/icon-question.png)**Which JavaScript modules are suited for which tasks?** + +* Plain RequireJS - Supplying functionality to all elements e.g. including standard JS or 3rd party library +* Element Selector - For applying contained functionality within the scope of an element. +* Shared Model - For data access & using ko.observable properties +* Action - AJAX Requests, when done executes callbacks and adds messages +* View - UI Component - Forms, Minicart, Grid +* jQuery widget - Modals, Accordion, Pop-ups + +![Magento Exam Question](./images/icon-question.png)**Describe UI components?** + +> Magento UI components are used to represent distinct UI elements, such as tables, buttons, dialogs, and others. +> +> They are designed for simple and flexible user interface (UI) rendering. Components are responsible for rendering result page fragments and providing/supporting further interactions of JavaScript components and servers. +> +> Magento UI components are implemented as a standard module named [Magento_UI](https://devdocs.magento.com/guides/v2.3/mrg/ce/Ui.html&sa=D&ust=1609223264748000&usg=AOvVaw3ffMUMWMVTAqn41vsS8vxc). + +![Magento Section Chevron](./images/icon-chevron.png)`uiClass` + +The `uiClass` is an abstract class from which all components are extended. It's low-level and is rarely used as direct parent for UI components' classes. + + +![Magento Section Chevron](./images/icon-chevron.png)`uiElement` + `uiEvents` + `links` + +A direct successor of the uiClass library. When creating a new component, use the `uiElement` class as a direct parent, if your component will be the last in the components hierarchy chain. + +Methods available + +* initialize +* initObservable +* initModules + +Properties available + +* tracks +* modules +* source +* provider + +![Magento Section Chevron](./images/icon-chevron.png)`uiElement` + +> UI Elements establish data links between other models via the "defaults" property, automatic data linking between models, working with observable & stateful properties and events (on/off/trigger) with uiEvents. + +Example of a data link: +```json +{ + "defaults": { + "imports": { + "visible": "${ $.provider }:visibility" + }, + "exports": { + "items": "checkout.sidebar.summary.cart_items:items" + }, + "links": { + "visible": "${ $.provider }:visibility" + } + } +} +``` + +Example of using links in a component's configuration .xml file: + +```xml + + + + sample_config.sample_provider:visibility + + + +``` + +![Magento Exam Information](./images/icon-info.png)`uiElement` is the main class you usually inherit from. Just remember that `uiElement`'s make use of the "defaults" property which can be used in XML. Possesses observable & stateful properties and events of UIEvent class. + + + +![Magento Section Chevron](./images/icon-chevron.png)`uiCollection` + +The `uiCollection` library class should be used as a base class by any components that contain a collection of child UI components. `uiCollection` inherits from the `uiElement` class. + +Notable Methods: + +* `getChild` +* `insertChild` +* `removeChild` +* `destroyChildren` +* `destroy` +* `remove` +* `initElement` + +Notable Properties: + +* `elems` +* `regions` + +![Magento Exam Information](./images/icon-info.png)`elems` - Enables components to have nested child components + +![Magento Exam Information](./images/icon-info.png)Essentially `uiCollection` is an array of uiElement's + +![Magento Section Chevron](./images/icon-chevron.png)`uiLayout` + +JavaScript function object used for initializing and configuring UI components. + +Notable methods: + +* The `run()` method is the class entry point represented by uiLayout and has the following signature: + +`uiLayout` Notable properties: + +| Property | Type | Description | +| --- | --- | --- | +| `name` | String | Name of the component. | +| `parent` | String | Full name of the component's parent element. | +| `template` | String | Path to the component's .html template. | +| `config` | Object | Configuration properties for the UI component. | +| `children` | Object | Configuration nodes for children components. | +| `isTemplate` | Boolean | Whether to save the configuration as a template. | +| `nodeTemplate` | String | The full name of a saved configuration template. | +| `provider` | String | The full name of the DataSource UI component. This property is inherited from the parent if skipped. | + +![Magento Exam Information](./images/icon-info.png)uiLayout would be used to programmatically add new layouts to the uiRegistry. Good in the situation where you might be dynamically creating layouts. + +![Magento Exam Question](./images/icon-question.png)**In which situation would you use UiComponent versus a regular JavaScript module?** + +* When wanting to create Magento UI components that represent distinct UI elements, such as tables, buttons, dialogs, and others. +* UI components work well together: they communicate with each other via the `uiRegistry` (service that tracks their asynchronous initialization). +* Extending something that has already been implemented as a hierarchy of UI components or adding a new feature that should interact with other UI components. + +![Magento Exam Information](./images/icon-info.png)Probably very important to know! + +![Magento Exam Question](./images/icon-question.png)**Describe the use of requirejs-config.js, x-magento-init, and data-mage-init.** + +![Magento Section Chevron](./images/icon-chevron.png)requirejs-config.js + +[map as alias](https://devdocs.magento.com/guides/v2.3/javascript-dev-guide/javascript/requirejs.html%23requirejs-config-map&sa=D&ust=1609223264770000&usg=AOvVaw0NvsZ5OZdZigpO3YTD2Wwu) - same as defining virtual type [Preference in DI](#h.9ofwnl5q9fnf) +[map as preference](https://devdocs.magento.com/guides/v2.3/javascript-dev-guide/javascript/custom_js.html%23js_replace&sa=D&ust=1609223264771000&usg=AOvVaw0LO9SubTYlre9LQF4pX05h) - same as [Preference in DI](#h.9ofwnl5q9fnf), replace one class with another +[mixins](https://devdocs.magento.com/guides/v2.3/javascript-dev-guide/javascript/js_mixins.html%23example&sa=D&ust=1609223264771000&usg=AOvVaw3x_iq9sXJSrvSjQbncUy3L) - same as around [Plugins Interceptors in DI](#h.nc1ow1wmf2km). This is Magento customization over requireJS + +![Magento Exam Information](./images/icon-info.png)requirejs-config.js is essentially the di.xml of JS UiComponents + +![Magento Section Chevron](./images/icon-chevron.png)x-magento-init & data-mage-init + +* Javascript Init Methods provide a way to invoke a stand alone [RequireJS module](#h.h647tzfdps5a) (defined with define) as a program. +* It discourages directly embedding JavaScript into a page. +* They provide a way to pass that program a server side generated JSON object. +* They provide a way to tell that program which (if any) DOM nodes it should operate on. + +`x-magento-init` is the most basic way a RequireJS module can be initialised. Accepting only JSON such like: +```html + +``` +data-mage-init is another way to invoke similar functionality on a specific DOM node, and that's with the data-mage-init attribute: +```html +
+ A single div +
+``` \ No newline at end of file diff --git a/4. Working with Databases in Magento/1. Demonstrate ability to use data-related classes.md b/4. Working with Databases in Magento/1. Demonstrate ability to use data-related classes.md deleted file mode 100644 index 3593d32..0000000 --- a/4. Working with Databases in Magento/1. Demonstrate ability to use data-related classes.md +++ /dev/null @@ -1,127 +0,0 @@ -# Demonstrate ability to use data-related classes - -## Describe repositories and data API classes. - -[Magento - Searching with Repositories](http://devdocs.magento.com/guides/v2.2/extension-dev-guide/searching-with-repositories.html) - -Magento 2.2 changed repository getList approach. Before you applied filters, sorts and pagination manually -right in the `getList` method itself. - -Magento 2.2 takes this boilerplate routine off developer's shoulders. Just call `collectionProcessor.process()`. - -[Api\SearchCriteria\CollectionProcessorInterface](https://github.com/magento/magento2/blob/2.2-develop/lib/internal/Magento/Framework/Api/SearchCriteria/CollectionProcessorInterface.php) -[Api\SearchCriteria\CollectionProcessor](https://github.com/magento/magento2/blob/2.2-develop/lib/internal/Magento/Framework/Api/SearchCriteria/CollectionProcessor.php) -- this is a composite and default preference - -Default collection processors - always applied: -- [FilterProcessor](https://github.com/magento/magento2/blob/2.2-develop/lib/internal/Magento/Framework/Api/SearchCriteria/CollectionProcessor/FilterProcessor.php#L45): - * DI configurable *custom filters* by field name. E.g. store_id - * *field mapping* - * addFilterGroupToCollection when no custom filter defined - like before -- [SortingProcessor](https://github.com/magento/magento2/blob/2.2-develop/lib/internal/Magento/Framework/Api/SearchCriteria/CollectionProcessor/SortingProcessor.php#L45): - * DI configurable *default orders* - * *field mapping* -- [PaginationProcessor](https://github.com/magento/magento2/blob/2.2-develop/lib/internal/Magento/Framework/Api/SearchCriteria/CollectionProcessor/PaginationProcessor.php): - * setCurPage, setPageSize - -Additional processors - add them via DI for your EAV repository: -- EAV [FilterProcessor](https://github.com/magento/magento2/blob/2.2-develop/app/code/Magento/Eav/Model/Api/SearchCriteria/CollectionProcessor/FilterProcessor.php#L45) - * same as normal filter processor, slightly different condition apply - * *field mapping* - * *custom filters* -- [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 - -For EAV, inject virtual collection processor `Magento\Eav\Model\Api\SearchCriteria\CollectionProcessor` - -## How do you obtain an object or set of objects from the database using a repository? - -*Loading single object* - -While model->load() is deprecated, your repository is the place to work with resource model. -- create empty model object with factory -- call `resourceModel.load($object)` just like `model->load()` would. - -Example: -```xml -public function getById($blockId) -{ - $block = $this->blockFactory->create(); - $this->resource->load($block, $blockId); - if (!$block->getId()) { - throw new NoSuchEntityException(__('CMS Block with id "%1" does not exist.', $blockId)); - } - return $block; -} -``` - - -## How do you configure and create a SearchCriteria instance using the builder? - -- Api\SearchCriteriaBuilder -- Api\SearchCriteria - -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. - - -## How do you use Data/Api classes? - -Api/: -- repository interfaces -- put operational interfaces - helpers etc. business logic API -- implementation lies in models -- usually available via WebAPI - -Api/Data - entity container interfaces, usually extend AbstractSimpleObject - -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. - -Example of converting collection models to data objects in customer repository: -```xml -$customers = []; -/** @var \Magento\Customer\Model\Customer $customerModel */ -foreach ($collection as $customerModel) { - $customers[] = $customerModel->getDataModel(); -} -$searchResults->setItems($customers); -``` - -`getDataModel` is not a standard method, you implement it yourself. - -```xml -public function getDataModel() -{ - // ... - $customerDataObject = $this->customerDataFactory->create(); - $this->dataObjectHelper->populateWithArray( - $customerDataObject, - $customerData, // $customer->getData() - \Magento\Customer\Api\Data\CustomerInterface::class - ); - // ... - return $customerDataObject; -} -``` - -## populateWithArray helper - -Based on interface, calls all SETTERS with given data to fill data object - -[Api\DataObjectHelper::populateWithArray](https://github.com/magento/magento2/blob/2.2-develop/lib/internal/Magento/Framework/Api/DataObjectHelper.php#L80) -- calls data object SETTERS `set*`, `setIs*` with given raw data -- handles `custom_attributes` - data object.setCustomAttribute - - -## buildOutputDataArray - -Based on given interface, calls all GETTERS to make resulting data array. - -[Reflection\DataObjectProcessor::buildOutputDataArray](https://github.com/magento/magento2/blob/2.2-develop/lib/internal/Magento/Framework/Reflection/DataObjectProcessor.php#L72): -- \Magento\Framework\Reflection\MethodsMap::getMethodsMap - method name and getter return types -- filter only getters: is..., has..., get... -- get data using interface getter, e.g. $object->getSku() - based on Interface definition -- deduce field name by getter name, e.g. 'sku' -- process custom_attributes \Magento\Framework\Reflection\CustomAttributesProcessor::buildOutputDataArray -- process extension_attributes \Magento\Framework\Reflection\ExtensionAttributesProcessor::buildOutputDataArray -- process return object: build return value objects with their return type annotation -- process return array: cast each element to type, e.g. int[] => (int) each value -- cast element to type diff --git a/4.Magento-Working-With-Databases.md b/4.Magento-Working-With-Databases.md new file mode 100755 index 0000000..660cf5d --- /dev/null +++ b/4.Magento-Working-With-Databases.md @@ -0,0 +1,981 @@ +4 - Working with Databases in Magento +===================================== + +4.1 Demonstrate ability to use data-related classes +--------------------------------------------------- + +> Repositories can be applied for working with Collections in Magento 2. It realises the Repository pattern that allows it to work with collections and entities regardless of their storage place (storing or caching are the implementation of details). +> +> A Repository should be stateless after instantiation. This means that every method call should not rely on previous calls nor should it affect later method calls. Any field contained in the repository class must also be stateless. + +![Magento Exam Question](./images/icon-question.png)**What mechanisms are available to extend existing classes, for example by adding a new attribute, a new field in the database, or a new related entity?** + +* Custom EAV-Attributes - More info below +* Extension Attributes - More info below +* Declarative Schema - More info below +* Declarative Data - More info below +* Schema Patches - More info below +* Data Patches - More info below +* Custom Repositories and API Classes - More info below + +![Magento Exam Question](./images/icon-question.png)**Describe Repositories and data API classes.** + +> Repositories give service requesters the ability to perform create, read, update, and delete (CRUD) operations on entities or a list of entities. A repository is an example of a [service contract](https://devdocs.magento.com/guides/v2.2/extension-dev-guide/service-contracts/design-patterns.html&sa=D&ust=1609223264777000&usg=AOvVaw1W0IBLhVtdLevoBCn-pXDX), and its implementation is part of the [domain layer](https://devdocs.magento.com/guides/v2.3/architecture/archi_perspectives/domain_layer.html&sa=D&ust=1609223264778000&usg=AOvVaw36Pi0TnWV7_9eOsx8vcB9l). +> +> Repositories open your entity for the Magento 2 REST API (also requires data model interfaces). +> +> Repositories usually lie within the `/Api/` & `/Api/Data/` Directories. + +![Magento Exam Question](./images/icon-question.png)**How do you obtain an object or set of objects from the database using a repository?** + +In Magento 2, five basic repository functions are realized using Resource Models. They are + +* `save($entity)` +* `getById($id)` +* `getList(SearchCriteria $searchCriteria)` +* `delete($entity)` +* `deleteById($id)` + +![Magento Exam Note Warning](images/icon-warning.png)You used to be able to save using model->save() - but this is now deprecated, your Resource Model is the place to work with data storage. + +![Magento Exam Question](./images/icon-question.png)**How do you configure and create a SearchCriteria instance using the builder? Describe how to filter, sort, and specify the selected values for collections and repositories.** + +A Search Criteria is an implementation of the [SearchCriteriaInterface class](https://github.com/magento/magento2/blob/2.2/lib/internal/Magento/Framework/Api/SearchCriteriaInterface.php&sa=D&ust=1609223264781000&usg=AOvVaw2grCpVdTjMrz79L1pEePCH) that allows you to build custom requests with different conditions. Repositories use this class to retrieve entities based on a matching criteria. + +![Magento Section Chevron](./images/icon-chevron.png) Filter + +> The Filter class is the smallest part of a Search Criteria. It allows you to add a custom field, value, and condition type to the criteria. Example of how to define a Filter: + +```php +$filter + ->setField("url") + ->setValue("%magento.com") + ->setConditionType("like"); +``` + +![Magento Section Chevron](./images/icon-chevron.png)Filter Group + +> A collection of Filters that apply one or more criteria to a search. Example: + +```php +$filter1 + ->setField("url") + ->setValue("%magento.com") + ->setConditionType("like"); + +$filter2 + ->setField("store_id") + ->setValue("1") + ->setConditionType("eq"); + +$filterGroup1->setFilters([$filter1, $filter2]); + +$filter3 + ->setField("url_type") + ->setValue(1) + ->setConditionType("eq"); + +$filterGroup2->setFilters([$filter3]); + +$searchCriteria->setFilterGroups([$filterGroup1, $filterGroup2]); +``` + +![Magento Section Chevron](./images/icon-chevron.png)Sorting + +> The example below defines a Sort Order object that will sort the customer email in ascending order: + +```php +$sortOrder + ->setField("email") + ->setDirection("ASC"); + +$searchCriteria->setSortOrders([$sortOrder]); +``` + +![Magento Section Chevron](./images/icon-chevron.png)Pagination + +> The setPageSize function paginates the Search Criteria by limiting the amount of entities it retrieves: + +```php +$searchCriteria->setPageSize(20); //retrieve 20 or less entities +``` + +![Magento Section Chevron](./images/icon-chevron.png)Search Result + +> The getList(SearchCriteria $searchCriteria) method defined in your repository should return a Search Result object. This object is an instance of a class that implements the interface [SearchResultsInterface](https://github.com/magento/magento2/blob/2.2/lib/internal/Magento/Framework/Api/SearchResultsInterface.php&sa=D&ust=1609223264787000&usg=AOvVaw0IMC3MEGEPbw8kXlNd6Y6N). + +![Magento Exam Question](./images/icon-question.png)**How do you use Data/Api classes?** + +> Api in Magento 2 is commonly used to describe Repository interfaces, further implemented by Models and Data Models. Repository API interfaces can be applied for WebAPI requests (when they are described in `webapi.xml`). Api directory is located in modules roots, similarly to etc, Model and other directories. +> +> Unlike Api, `Api/Data` directory contains interfaces for the data, for example, store data or customer data. +> +> An excellent example for explaining the difference between Api and `Api/Data` is the implementation of `Magento\Catalog\Api\ProductRepositoryInterface` and `Magento\Catalog\Api\Data\ProductInterface`. +> +> ProductRepository implements a get method that loads a Product object (using ProductFactory) that implements ProductInterface for working with data. + +![Magento Exam Information](./images/icon-info.png)Just remember: API Interfaces where Service Contract / Repositories are kept which keep in mind WebAPI - Concerned only with how to retrieve information of a specific entity/model. `Magento\Catalog\Api\ProductRepositoryInterface` for example has methods `get()` and `save()` . API/Data on the other hand are a set of interfaces specifically for the data (which will be implemented by a Model class). `Magento\Catalog\Api\Data\ProductInterface` has model related methods like `getSku()` + +![Magento Exam Question](./images/icon-question.png)**Describe how to create and register new entities.** + +In Magento 2, entities are unique objects that contain a number of various attributes and/or parameters. Products, Orders & Customers for example. Entities are subsets of [Entity-Attribute-Values (EAV).](https://blog.magestore.com/entity-attribute-value-in-magento/&sa=D&ust=1609223264790000&usg=AOvVaw0Haxczn-QByjK23DXB7e6l) + +![Magento Exam Information](./images/icon-info.png)Think EAV, think catalog_product_entity ➞ eav_attribute table ➞ (`catalog_product_entity_varchar`, `catalog_product_decimal`) etc + +### ![Magento Section Chevron](./images/icon-chevron.png)Step 1 - Create A Model + +Create a new [Module](#h.8r6t4fakpxnh) with [registration](#h.i0qq8aqgijv7), entities can only be created within a module. + +The Model usually lies within the `/Model` directory and is the sole entity class. Models are where your main business logic should be handled and is a single instance of an object. The Model will use the Resource Model to talk to the database and get/set data for The Resource Model to `save()` and `load()` / `get()`. Models will extend the class `Magento\Framework\Model\AbstractExtensibleModel`. + +Example Model Concrete Class: +```php +_init(ResourceModel\ExampleResourceModel::class); + } + + /** {@inheritdoc} */ + public function getId(): int + { + return (int) $this->getData(ExampleModelInterface::ENTITY_COLUMN_TABLE_ID); + } + + /** {@inheritdoc} */ + public function getEntityId(): int + { + return $this->getId(); + } + + /** {@inheritdoc} */ + public function getResource(): AbstractDb + { + return $this->_getResource(); + } +} +``` +### ![Magento Section Chevron](./images/icon-chevron.png)Step 2 - Create Resource Model + +> Map an object to one or more database rows. A Resource Model is responsible for performing functions such as: + +* Executing all CRUD (create, read, update, delete) requests. The resource model contains the SQL code for completing these requests. +* Performing additional business logic. For example, a resource model could perform data validation, start processes before or after data is saved, or perform other database operations. + +> Resource models usually lie in the `/Model/ResourceModel` directory. It extends the `Magento\Framework\Model\ResourceModel\Db\AbstractDb` class which handles most C.R.U.D methods. You just need to specify the "table" and "entity_id" within the Magento `_construct()` method: +```php +_init('my_table', 'entity_id'); + } +} +``` +![Magento Exam Information](./images/icon-info.png)Resource Models and Repositories are very alike, but Resource Model represents a lower level persistence mechanism that should be used by Repositories. It is the only recommended persistence mechanism by Magento right now. Repositories are more concerned with creating access to your custom entity via the REST API. + +![Magento Exam Information](./images/icon-info.png)Magento uses an [active record pattern strategy for persistence](https://devdocs.magento.com/guides/v2.3/architecture/archi_perspectives/persist_layer.html&sa=D&ust=1609223264794000&usg=AOvVaw3iHooG-vMX_8WIs2944qx3). Resource Models are an important part of this pattern. Just Remember CRUD here. + +### ![Magento Section Chevron](./images/icon-chevron.png)Step 3 - Create Collection + +> Collections encapsulate sets of models and related functionality, such as filtering, sorting, and pagination. Collections usually reside in `\Model\ResourceModel\ExampleModel\Collection` +> +> The collection extends `Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection` - which provide +> +> you MySQL-like commands as methods, Popular examples methods are: + +* `getConnection()` +* `getSelect()` +* `setPageSize()` +* `addAttributeToSelect()` +* `getMainTable()` +* `addFieldToSelect()` +* `addFieldToFilter()` +* `join()` + +> Collections are great if you're working with entities that have a lot of attributes, filters, or possibly a future large dataset, a good practice for debugging would be to use SQL logging to record actual SQL queries hitting the database. +> +> Example Collection: +```php +_init(Vendor\MyModule\Model\ExampleModel, Vendor\MyModule\Model\ResourceModel\ExampleModel); + } +} +``` +![Magento Exam Information](./images/icon-info.png)Remember that Collections are a good use for filtering, sorting, and pagination. They are not 100% necessary when creating new entities but are recommended for scalability. + +![Magento Exam Information](./images/icon-info.png)Collections can help us spot possible performance bottlenecks and react on time, either by adding more limiting values to setPageSize or addAttributeToSelect, or both. + +### ![Magento Section Chevron](./images/icon-chevron.png)Step 4 - Schema / Data Installation & Upgrade + +> Declarative setup +> +> Declarative setup is based on database structure declarations. Schema files declare what the database structure should be, and Magento determines the differences between the current table structure and what it should be. These differences can be represented with atomic SQL operations. +> +> Declarative Schema is defined in [/etc/db_schema.xml](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/declarative-schema/db-schema.html&sa=D&ust=1609223264799000&usg=AOvVaw3DX2NFuBBuc4PtRaiH0XE-) +> +> More on Declarative Setup in [Section 4.2](#h.8i0etxv2dw9i) + +### ![Magento Section Chevron](./images/icon-chevron.png)Step 5 - Create A Repository + +> Repositories adhere to Magento 2 Service Contracts defined by data interfaces (but extensible by third-party modules). Repositories are a form of Service Contract keep in mind the WebAPI and is located in the /Api & /Api/Data directories then implemented in /Model & /Model/Data. Create a Interfaces in: + +* `/Api/ExampleRepositoryInterface` - containing all the necessary Resource Model CRUD operations. +* `/Api/Data/ExampleModelInterface` - containing all the necessary getter / setter methods for your Data model. +* `/Api/DataExampleModelSearchResultInterface` - which extends the `Magento\Framework\Api\SearchResultsInterface` where you can add specific search. + +Implementing your Repository as a concrete class: +```php +exampleFactory = $exampleFactory; + $this->exampleCollectionFactory = $exampleCollectionFactory; + $this->searchResultFactory = $exampleSearchResultInterfaceFactory; + $this->collectionProcessor = $collectionProcessor; + } + public function getById($id) + { + $example = $this->exampleFactory->create(); + $example->getResource()->load($example, $id); + if (!$example->getId()) { + throw new NoSuchEntityException(__('Unable to find example with ID "%1"', $id)); + } + return $example; + } + public function save(MY\MODULE\Api\Data\ExampleModelInterface $example) + { + $example->getResource()->save($example); + return $example; + } + public function delete(MY\MODULE\Api\Data\ExampleModelInterface $example) + { + $example->getResource()->delete($example); + } + public function getList(Magento\Framework\Api\SearchCriteriaInterface $searchCriteria) + { + $collection = $this->collectionFactory->create(); + $this->collectionProcessor->process($searchCriteria, $collection); + $searchResults = $this->searchResultFactory->create(); + $searchResults->setSearchCriteria($searchCriteria); + $searchResults->setItems($collection->getItems()); + return $searchResults; + } +} + +``` +![Magento Exam Information](./images/icon-info.png)For smaller operations like getById() & save() The repository will obtain the Resource Model directly to carry out database operations. + +![Magento Exam Information](./images/icon-info.png)As good practice, More complex methods such as getList() in the Repository will talk to the Collection which will use the Resource Model to filter and retrieve data from the database more efficiently. + +![Magento Exam Information](./images/icon-info.png)Factory classes are used when creating new instantiations of the Model, more on this in the next subsection. + +> We must tell Magento 2 to implement these interfaces by setting a preference in the your modules `di.xml` +```xml + + + + + + +``` + +![Magento Exam Information](./images/icon-info.png)Magento requires this definition so that when you inject Interfaces as dependencies it knows which class implementation to load. Very useful for future extension. + +![Magento Exam Information](./images/icon-info.png)Usually `Magento\Framework\Api\SearchResults` is nearly always a good enough implementation of `\\Api\Data\ExampleSearchResultInterface`. + +### ![Magento Section Chevron](./images/icon-chevron.png)Step 6 - Create Factory Class + +> Factories are used in Object Oriented Programming as a way to instantiate an object. +> +> The Factory class name is the same as the Model class but with the word 'Factory' appended. Factories are injected as a dependency into other classes that you wish to use them within. During DI Compilation, it will automatically generate the Factory class in the var/generation folder if the class does not already exist. You will see the factory class in +> +> `var/generation///Model/ClassFactory.php` + +![Magento Exam Information](./images/icon-info.png)There is no other setup required to create Factories, Magento will generate them on `setup:di:compile` + +![Magento Exam Information](./images/icon-info.png)Factories are used when you want to create a new instance of a Model e.g. a new Product `Magento\Catalog\Model\Product` will be instantiated by the factory class `Magento\Catalog\Api\Data\ProductInterfaceFactory` . + +![Magento Exam Information](./images/icon-info.png)Think of a literal factory production line which just keeps churning out new products of the same design. Use factories to instantiate a new edition of the class. + +![Magento Exam Question](./images/icon-question.png)**How do you add a new table to the database?** + +![Magento Section Chevron](./images/icon-chevron.png)Pre-Magento 2.3 - Install Scripts + +Before Magento 2.3, extension developers were required to write code (PHP scripts) to change the database data / schema. The following types of scripts existed: + +* [InstallSchema](https://devdocs.magento.com/videos/fundamentals/add-a-new-table-to-database/%23step-2-create-an-installschema-script&sa=D&ust=1609223264808000&usg=AOvVaw1tQ8oggkW-gSRmeG9F0RAe) / [InstallData](https://devdocs.magento.com/videos/fundamentals/add-a-new-table-to-database/%23step-3-create-an-installdata-script&sa=D&ust=1609223264809000&usg=AOvVaw0-iTq7k-aZDXDydN6iEOZn) scripts, which are executed the first time a module is installed. +* [UpgradeSchema](https://devdocs.magento.com/videos/fundamentals/add-a-new-table-to-database/%23step-5--create-an-upgradeschema-script&sa=D&ust=1609223264809000&usg=AOvVaw2fK09ff9UnGv__-ytpfT5W) / [UpgradeData](https://devdocs.magento.com/videos/fundamentals/add-a-new-table-to-database/%23step-6-create-the-upgradedata-script&sa=D&ust=1609223264810000&usg=AOvVaw0j9Fk1N0YdBcBqeKQbXzQj) incremental scripts, which supplement an existing module schema. +* [Recurring schema](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/prepare/lifecycle.html%23recurring-schema-event&sa=D&ust=1609223264810000&usg=AOvVaw3XrG9_7RqUQQBBriD9p7BF) event scripts, which are executed each time you install or upgrade Magento. + +![Magento Exam Information](./images/icon-info.png)The main disadvantage of this approach is that Magento applies changes blindly. For example, in one version a new database column might be introduced, only to be removed in the next. Declarative setup eliminates this type of unnecessary work. + +![Magento Section Chevron](./images/icon-chevron.png)Post-Magento 2.3 - Declarative setup + +> Declarative setup is based on database structure declarations. Schema files declare what the database structure should be, and Magento determines the differences between the current table structure and what it should be. These differences can be represented with atomic SQL operations. +> +> Declarative Schema is defined in [/etc/db_schema.xml](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/declarative-schema/db-schema.html&sa=D&ust=1609223264811000&usg=AOvVaw2NGT8LwDUsD4QgWZcMQjCs) + +![Magento Exam Question](./images/icon-question.png)**Describe the entity load and save process.** + +![Magento Section Chevron](./images/icon-chevron.png)Service Contracts & Repositories + +> The Resource Model is responsible for CRUD and Validation operations for the entity database operations including loading and saving. Magento 2's modular flexibility, however, comes at a price. Business logic tends to leak across the layers of the Magento system, which manifests as duplicated and inconsistent code. To address these issues, the Magento system introduces [Service Contracts](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/service-contracts/service-contracts.html&sa=D&ust=1609223264812000&usg=AOvVaw39YIqPi26jMy3hgXfyTNw4), which hide business logic details from service requesters such as controllers, web services, and other modules under [Data Interfaces](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/service-contracts/design-patterns.html%23data-interfaces&sa=D&ust=1609223264813000&usg=AOvVaw2_qDAZ5piTdHK8ci_5x_pU) AKA Repositories. + +![](./images/Magento-2-Certified-Professional-Developer-Guide-Request-Flow-Processing.jpg) + +![Magento Section Chevron](./images/icon-chevron.png)Using the Repository pattern in custom code + +> Repositories are designed so that we shouldn't need to inject anything else for CRUD operations in our custom modules. Products for example give you the Interface: \Magento\Catalog\Api\ProductRepositoryInterface to create products programmatically. You can tell which classes are specifically made available for custom code as they will be marked with the @api tag. + +As per item [8.5 of the Technical Guidelines](https://devdocs.magento.com/guides/v2.3/coding-standards/technical-guidelines.html%238-modularity&sa=D&ust=1609223264814000&usg=AOvVaw31O8OYDMRSW5GnN2CFBk7h) given by Magento: + +> "Only the @api code of any module can be referenced by other modules". +> +> We should strive to only use @api code in our custom code. + +![Magento Exam Question](./images/icon-question.png)**Describe how to extend existing entities.** + +> Schema is used to create/update DB tables and its structure. While Data is used to insert/add data into tables + +### ![Magento Section Chevron](./images/icon-chevron.png)Custom (EAV) Attributes + +> Custom Attributes are defined as a subset of Entity-Attribute-Values (EAV). EAV attributes typically store values in several MySQL tables. For Products, a merchant can add new attributes via the admin or programmatically. For Orders & Customers this requires extending Magento programmatically. +> +> The Catalog module has several attributes that are defined as EAV attributes, but are treated as built-in attributes. These attributes include: + +* `name` +* `price` +* `sku` +* etc... +* See the [Simple Product section below](#h.cbgifdse6zd5) for more information on how the other default attributes. + +> To check if an entity supports Extension Attributes, check the Api/ interface of the entity in mind for methods: + +* `getCustomAttributes()` +* `setCustomAttributes()` + +> This will determine if they are available for that entity. +> +> You can also check if your entity extends interface `Magento\Framework\Api\CustomAttributesDataInterface` - to decipher if they are compatible with Custom (EAV) Attributes. `Magento\Framework\Api\CustomAttributesDataInterface` defines the methods that are called to get and set Custom (EAV) Attributes. +> +> Custom (EAV) Attributes are created using: + +* [Data Patches](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/declarative-schema/data-patches.html&sa=D&ust=1609223264818000&usg=AOvVaw30oojC9eaLdkq5AtiOLmpA). Using a Setup Factory e.g. Magento\Customer\Setup\CustomerSetupFactory +* Creating a [Service Contract](#h.i0qq8aqgijv7) which implements interface `Magento\Framework\Api\CustomAttributesDataInterface`. +* Declarative Schema: /etc/eav_attributes.xml + +`eav_attributes.xml` provides the EAV attributes configuration. + +For example, in `vendor/magento/module-catalog/etc/eav_attributes.xml` you can see many attributes like sku, status, etc and the configuration says about those attributes like if the attribute is global or it is searchable or filterable. Similar things that we do while creating a custom attribute using the Data Patch. + +![Magento Exam Information](./images/icon-info.png)Think EAV, think `catalog_product_entity` ➞ `eav_attribute` table ➞ (`catalog_product_entity_varchar`, `catalog_product_decimal`) etc + +#### eav_attributes.xml + +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +Getter/Setter +```php +Magento\Customer\Api\Data\AddressInterface::getCustomAttribute( + $attributeCode +); // Get the Custom (EAV) Attribute + +Magento\Customer\Api\Data\AddressInterface::setCustomAttribute( + $attributeCode, + $attributeValue +); // Set the Custom (EAV) Attribute + +Magento\Customer\Api\AddressRepositoryInterface::save( + Magento\Customer\Api\Data\AddressInterface $address +); // Save the entity + ``` + +### ![Magento Section Chevron](./images/icon-chevron.png)Extension Attributes + +> When adding new attributes to entities, We can't change the interface each time we want to add a new attribute as we would lose backwards compatibility. Our logic has to be in line with the [Service Contracts](#h.i0qq8aqgijv7) design pattern (mentioned earlier). Third-party developers cannot change the API Data interfaces defined in the Magento Core code. However, most of these entities have a feature called Extension Attributes. +> +> To check if an entity supports Extension Attributes, check the Api/ interface of the entity in mind for methods: + +* `getExtensionAttributes()` +* `setExtensionAttributes()` + +> This will determine if they are available for that entity. +> +> You can also check if your entity extends interface `Magento\Framework\Api\ExtensibleDataInterface` - to decipher if they are compatible with Extension Attributes +> +> Extension Attributes are new in Magento 2. They are used to extend functionality and often use more complex data types than [Custom Attributes](#h.k3zjo176fdlt) (above). +> +> Features +> +> Extension Attributes do not appear in the Magento Admin. +> +> Extension Attributes are created using: + +* [Data Patches](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/declarative-schema/data-patches.html&sa=D&ust=1609223264830000&usg=AOvVaw13nYcuIOQjAQiYrtmRsgiJ). Using a Setup Factory e.g. Magento\Customer\Setup\CustomerSetupFactory +* Creating a [Service Contract](#h.i0qq8aqgijv7) which implements interface Magento\Framework\Api\ExtensibleDataInterface. +* Declarative Schema: `/etc/extension_attributes.xml` + +> Extension Attributes have the advantage of declarative schema and are defined in `/etc/extension_attributes.xml` +> +> Extension Attributes are used to extend functionality and often use more complex data types than custom or eav attributes. These attributes do not appear in the Magento Admin. +> +> extension_attributes.xml is used for declaring the extension attributes for the module. + +#### extension_attributes.xml + +```xml + + + + + + + + fieldname + + + + +``` + +| Properties | Field | Description | Example | +| --- | --- | --- | --- | +| `` | Attributes: `for` - The fully-qualified type name with the namespace that processes the extensions. The value must be a type that implements `ExtensibleDataInterface`. The interface can be in a different module. Elements: Field: `` | `Magento\Quote\Api\Data\TotalsInterface` | +| `` | Attributes: `code` - The name of the attribute. The attribute name should be in snake case (the first letter in each word should be in lowercase, with each word separated by an underscore). `type` - The data type. This can be a simple data type, such as string or integer, or complex type, such as an interface. Elements - Field: ``, Field: `` | `code` : `gift_cards_amount_used`, `type` - `float`, `Magento\CatalogInventory\Api\Data\StockItemInterface` | +| `` | Elements: Field: `` | | +| `` | Attributes: `ref` - Optional. Restricts access to the extension attribute to users with the specified permission. Elements: Field - `` | `Magento_CatalogInventory::cataloginventory` | +| `` | Attributes: `reference_table` - The table involved in a join operation. [See Searching Extension Attributes](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/attributes.html%23search&sa=D&ust=1609223264844000&usg=AOvVaw1LwiBr2j-FftDHXe8-7zbF) for details. `reference_field`: Column in the reference_table. `join_on_field`: The column of the table associated with the interface specified in the for keyword that will be used in the join operation. Elements: Field - `` | `reference_table`: `admin_user`, `reference_field`: `user_id`, `join_on_field`: `store_id`. [Here is a good article](https://fishchenko.com/blog/magento-2-join-table-in-orderrepositorygetlist-extension-attributes/&sa=D&ust=1609223264847000&usg=AOvVaw1axgrEfJhYCZBW4s8b9AlM) on how to access the Join Processor from the extended class. | +| `` | One or more fields present in the interface specified in the type keyword. You can specify the `column=""` keyword to define the column in the `reference_table` to use. The field value specifies the property on the interface which should be set. | `code` | + + +> In your concrete class implementation of the above data interface will look something like: +```php +namespace \\Model\Data; + +class ExampleAttribute implements \\Api\Data\getData(self::VALUE); + } + + /** + * {@inheritdoc} + */ + public function setValue($value) + { + return $this->setData(self::VALUE, $value); + } +} +``` +![Magento Exam Information](./images/icon-info.png)[Extension Attributes](#h.aqe3c923mrz4) are not magically saved to the database or populated when the quote or order, etc is loaded from the database. + +![Magento Exam Information](./images/icon-info.png) `type=""` Can be scalar / non-scalar, meaning either: string or `\\Api\Data\` or `string[]` + +![Magento Exam Information](./images/icon-info.png)This means that we are extending our Sales Order Interface with an attribute. It's accessible via `$order->getExtensionAttributes()->getNameOfAttribute()`. + +![Magento Exam Information](./images/icon-info.png)[Here is a good article](https://fishchenko.com/blog/magento-2-join-table-in-orderrepositorygetlist-extension-attributes/&sa=D&ust=1609223264850000&usg=AOvVaw3BeT6r7k6d3f8Db4JZBUHE) on how to access the Join Processor from the extended class + +![Magento Exam Information](./images/icon-info.png)[Here's a great guide](https://store.fooman.co.nz/blog/an-introduction-to-extension-attributes.html&sa=D&ust=1609223264851000&usg=AOvVaw3fKCSQfPQRXzfs69ukWnJy) on what Extension Attributes. + +![Magento Exam Information](./images/icon-info.png)Extension Attributes Advantages = declarative schema, can involve more complex data types, they are in-line with Service Contract design pattern, are better for backward compatibility rather than changing core interfaces. + +Saving and retrieving + +Saving [Extension Attributes](#h.aqe3c923mrz4) will require a custom persistence layer for storage. Therefore, the rest of the [Service Contracts](#h.i0qq8aqgijv7) will need to be created i.e. a custom: + +* [Repository](#h.5y2r6c8djm3h) +* [Resource Model](#h.h64lcrmfa6jp) +* [Collection](#h.ovkq1s5o8zg) +* [Search Criteria](#h.5y2r6c8djm3h) +* [Declarative Schema](#h.iqvl9uln4170) + +will be required. + +[Extension Attributes](#h.aqe3c923mrz4) are not magically saved to the database or populated when the quote or order, etc is loaded from the database. Therefore you will need to set them using a [Plugin Interceptor](#h.nc1ow1wmf2km) or an [Observer](#h.y2lezqicnwxl). Here is an example of a Plugin: +`etc/di.xml` +```xml + + + + + + + + +``` +```php +saveExampleAttribute($resultOrder); + return $resultOrder; + } + + private function saveExampleAttribute(\Magento\Sales\Api\Data\OrderInterface $order) + { + $extensionAttributes = $order->getExtensionAttributes(); + if ( + null !== $extensionAttributes && + null !== $extensionAttributes->getExampleAttribute() + ) { + + $exampleAttributeValue = $extensionAttributes->getExampleAttribute()->getValue(); + + try { + + // The actual implementation of the repository is omitted + // but it is where you would save to the database (or any other persistent storage) + $this->exampleExampleRepository->save($order->getEntityId(), $exampleAttributeValue); + } catch (\Exception $e) { + + throw new CouldNotSaveException( + __('Could not add attribute to order: "%1"', $e->getMessage()), + $e + ); + } + } + + return $order; + } +} + +``` +![Magento Exam Question](./images/icon-question.png)**What is the difference between Custom Attributes and Extension Attributes** + +> [Custom Attributes](#h.k3zjo176fdlt) are the attributes added to describe an entity, such as product attributes, customer attributes etc. These are a subset of EAV attributes. +> +> +> [Extension Attributes](#h.aqe3c923mrz4) on the other hand are generally used for more complex data types such as adding additional complex data into an entity from a custom external table. +> Simply put, custom attributes conform to EAV standards whereas extension attributes are used for more complex data which custom attributes cannot handle. + +![Magento Exam Question](./images/icon-question.png)**How do you select a subset of records from the database?** + +> Use `addFieldToSelect` and `addAttributeToSelect` methods of a Collection to specify the fields for selection. +> +> For example: +> +> `$productCollection->addFieldToSelect("custom_field");` +> +> To apply filters to collections, use +> +> `addAttributeToFilter($field, $condition)` +> +> `addFieldToFilter($field, $condition)` + +Conditions can be one of the following: + +### Where Query Operators + +| Magento Associative Array Condition | MySQL Condition | +| --- | --- | +| `"eq"` | Equals Value | +| `"neq"` | Not Equal Value | +| `"like"` | Like Value | +| `"nlike"` | Not Like Value | +| `"is"` | Is Value | +| `"in"` | In Values | +| `"nin"` | Not In Values | +| `"notnull"` | Value Is Not Null | +| `"null"` | Value Is Null, example: `$c->prepareSqlCondition('finished_at', ['null' => NULL])` | +| `"moreq"` | More Or Equal Value +| `"gt"` | Greater Value | +| `"lt"` | Less Value | +| `"gteq"` | Greater Or Equal Value | +| `"lteq"` | Less Or Equal Value | +| `"finset"` | Value In Set | +| `"from" => "value", "to" => "value"` | In Between "from" & "to" Values | + + +> Example: +> +> `$productCollection->addFieldToFilter('entity_id', array('in' => [1,2,3])` +> +> `setOrder` method is used for sorting and processes both filter and direction fields. For instance: +> +> `$productCollection>setOrder('position','ASC');` +> +> `searchCriteria` is used for applying filters in repositories. More on that above. + +![Magento Exam Question](./images/icon-question.png)**Describe the Database Abstraction Layer for Magento.** + +> The Database Abstraction Layer performs generic database operations like connections, commands, parameters insulating you from vendor specific data libraries and providing one high level api for accessing data regardless of whether you use MySQL, Microsoft SQL Server, Oracle, DB2, etc... + +![Magento Exam Information](./images/icon-info.png)The Resource Model of each Entity acts as the Database Abstraction Layer. + +![Magento Exam Information](./images/icon-info.png)This gives Magento 2 the ability to better plug-in and out different DB management systems such as Postgres SQL - Which they might start diving into in the near future. + +![Magento Exam Question](./images/icon-question.png)**What type of exceptions does the database layer throw?** + +> Database layer can put out exceptions depending on its realization. For example, +> PDO/Mysql can put out the following exceptions: + +* Zend_Db_Adapter_Exception +* Zend_Db_Statement_Exception +* Zend_Db_Exception +* Zend_Db_Statement_Pdo +* PDOException +* LocalizedException +* InvalidArgumentException +* Exception +* DuplicateException + +![Magento Exam Information](./images/icon-info.png)Not certain how important this is to learn really. + +![Magento Exam Question](./images/icon-question.png)**What additional functionality does Magento provide over Zend_Adapter?** + +> The following additional methods are realized atop Zend_Adapter: + +* addUniqueField +* unserializeFields +* serializeFields +* hasDataChanged +* prepareDataForUpdate +* isObjectNotNew +* saveNewObject +* afterSave +* beforeSave +* isModified +* afterLoad +* beforeDelete +* afterDelete + +![Magento Exam Information](./images/icon-info.png)Not certain how important this is to learn really. + +4.2 Demonstrate an ability to use declarative schema +---------------------------------------------------- + +![Magento Exam Question](./images/icon-question.png)**Demonstrate use of schema.** + +> Schema setup scripts change database schema, they create or change needed database tables. + +![Magento Exam Information](./images/icon-info.png)i.e Create a new table and its fields with its structure. + +![Magento Exam Question](./images/icon-question.png)**How to manipulate columns and keys using declarative schema?** + +![Magento Section Chevron](./images/icon-chevron.png) `db_schema.xml` + +> Magento prioritizes the declarative schema approach and executes updates from the [db_schema.xml](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/declarative-schema/db-schema.html&sa=D&ust=1609223264887000&usg=AOvVaw3s2_fjZNEGX96-L5Vm0X92) before the data and schema patches. + +![Magento Exam Information](./images/icon-info.png)All database related extension or manipulation all belong in the `/Setup` directory. A good example would be the [Magento_Catalog](https://github.com/magento/magento2/blob/2.3/app/code/Magento/Catalog/etc/db_schema.xml&sa=D&ust=1609223264888000&usg=AOvVaw3m3468kc66q7g7KAms8WeZ) + +![Magento Exam Information](./images/icon-info.png)Schema is used to create/update DB tables and its structure. While Data is used to insert/add data into tables. + +Example `db_schema.xml`: +```xml + + +
+ + + + + + + + + +
+ +``` +| Field | Description | +| --- | --- | +| `` | Attributes: `name` - Name of the table. `comment` - Comment for the table. `engine` - SQL engine. This value must be innodb or memory. `resource` - The database shard on which to install the table. This value must be "default", "checkout", or "sales". Elements `` | +| `` | Attributes: `xsi:type` - Column types include: blob (includes blob, mediumblob, longblob), boolean, date, datetime, decimal, float, int (includes smallint, bigint, tinyint), real (includes decimal, float, double, real), smallint, text (includes text, mediumtext, longtext), timestamp, varbinary, varchar. `name` - Column name. `padding` - The size of an integer column. `usigned` - For numeric data types, specifies whether the column can contain positive and negative values or only positive values. `nullable` - Indicates whether the column can be nullable. `identity` - Indicates whether a column is auto-incremented. `comment` - Comment for the column. | +| `` | Attributes: `type` - One of "primary", "unique", or "foreign". `referenceId` - A custom identifier that is used only for relation mapping in the scope of `db_schema.xml` files. The real entity in the database has a system-generated name. The most convenient way to set the value of this attribute is to use the value that is written in the module's `db_schema_whitelist.json` file when you run the generate-whitelist command. **NOTE:** ensure your foreign key constraint matches the same type as the referencing column, otherwise you risk the misleading "MySQL: Foreign key constraint is incorrectly formed" error. | +| `` | Attributes: `indexType` - The value must be "btree", "fulltext", or "hash". `referenceId` - A custom identifier that is used only for relation mapping in the scope of db_schema.xml files. The real entity in the database has a system-generated name. The most convenient way to set the value of this attribute is to use the value that is written in the module's `db_schema_whitelist.json` file when you run the generate-whitelist command. | + +![Magento Exam Question](./images/icon-question.png)**What is the purpose of whitelisting?** + +> The db_schema_whitelist.json file is a way of telling Magento which tables and columns it can safely alter using the db_schema.xml It was added to preserve backward compatibility. Since Backward compatibility must be maintained, declarative schema does not automatically delete database tables, columns or keys that are not defined in a db_schema.xml as they can be declared somewhere else such as Setup/UpgradeSchema.php +> +> The Magento 2.3+ version of the command to generate a /etc/db_schema_whitelist.json file: +> +> `php bin/magento declaration:generate:whitelist` + +![Magento Exam Note Warning](images/icon-warning.png)`db_schema_whitelist.json` is a temporary solution. It will be removed in the future, when upgrade scripts are no longer supported. + +![Magento Exam Note Warning](images/icon-warning.png)Whitelists cannot be generated on Magento instances that use a database prefix. + +Example of a `db_schema_whitelist.json` file: +```json +{ + "my_table_1": { + "column": { + "column_1": true, + "column_2": true, + "column_3": true + }, + "index": { + "MY_TABLE_1_COLUMN_1": true + }, + "constraint": { + "PRIMARY": true + } + }, + "my_table_2": { + …… + } +} +``` +![Magento Exam Question](./images/icon-question.png)**How to use Data and Schema patches?** + +### ![Magento Section Chevron](./images/icon-chevron.png) Data Patches + +> A Data Patch is a Setup class that contains data modification instructions. +> +> Data Patches and belong in a `/Setup/Patch/Data/.php` directory of a custom [Module](#h.8r6t4fakpxnh). + +```php +moduleDataSetup = $moduleDataSetup; + } + + /** + * {@inheritdoc} + */ + public function apply() + { + $this->moduleDataSetup->getConnection()->startSetup(); + // The code that you want apply in the patch + $this->moduleDataSetup->getConnection()->endSetup(); + } + + /** + * {@inheritdoc} + */ + + public static function getDependencies() + { + return [ + SomeDependency::class + ]; + } + + public function revert() + { + $this->moduleDataSetup->getConnection()->startSetup(); + // Revert operations here + $this->moduleDataSetup->getConnection()->endSetup(); + } + + /** + * {@inheritdoc} + */ + public function getAliases() + { + return []; + } +} + +``` +> `apply()` - The code that you want to apply in the patch goes here. Please note, that one patch is responsible only for one setup version. So one UpgradeData can consist of a few data patches.getAliases() - This internal Magento method, that means that some patches with time can change their names, but changing name should not affect installation process, that's why if we will change name of the patch we will add alias here +> +> `revert()` - Mentioned below +> +> `getDependencies()` - Mentioned below + +![Magento Exam Information](./images/icon-info.png)Data setup is executed after Schema setup, they function in a similar fashion. + +![Magento Exam Information](./images/icon-info.png)A list of applied patches is stored in the `patch_list` database table. + +![Magento Exam Information](./images/icon-info.png)Just remember that Patches contain data modification instructions. They implement `DataPatchInterface` & `PatchRevertableInterface` and are located in `/Setup/Patch/Data/.php` + +![Magento Section Chevron](./images/icon-chevron.png) Reverting Data Patches + +> Magento does not allow you to revert a particular module data patch. However, you can revert all installed data patches of a single module.Run the following command to revert all composer installed data patches: +> +> `bin/magento module:uninstall Vendor_ModuleName` +> +> Run the following command to revert all non-composer installed data patches: +> +> `bin/magento module:uninstall --non-composer Vendor_ModuleName` +> +> This will run the public function `revert()` method of your modules `Magento\Framework\Setup\Patch\PatchRevertableInterface` implementation. +> +> `revert()` - Here should go code that will revert all operations from apply() method. Please note that some operations, like removing data from columns, that is in the role of foreign key reference is dangerous, because it can trigger an `ON DELETE` statement. + +### ![Magento Section Chevron](./images/icon-chevron.png) Schema Patches + +> A Schema Patch is a Setup class that contains custom schema (database) modification instructions. +> +> These modifications can be complex. It is defined in a `/Setup/Patch/Schema/.php` file and implements `Magento\Framework\Setup\Patch\SchemaPatchInterface`. + +![Magento Exam Information](./images/icon-info.png)Unlike the declarative schema approach, patches will only be applied once. A list of applied patches is stored in the patch_list database table. An unapplied patch will be applied when running the setup:upgrade from the Magento CLI. + +> Schema Patches and belong in a `/Setup/Patch/Schema/.php` directory of a custom Module. + +```php +moduleDataSetup = $moduleDataSetup; + } + + /** + * {@inheritdoc} + */ + public function apply() + { + $this->moduleDataSetup->getConnection()->startSetup(); + // The code that you want apply in the patch + $this->moduleDataSetup->getConnection()->endSetup(); + } + + /** + * {@inheritdoc} + */ + public static function getDependencies() + { + return [ + SomeDependency::class + ]; + } + + public function revert() + { + $this->moduleDataSetup->getConnection()->startSetup(); + // Revert operations here + $this->moduleDataSetup->getConnection()->endSetup(); + } + + /** + * {@inheritdoc} + */ + public function getAliases() + { + return []; + } +} + +``` + +> `apply()` - Mentioned above +> +> `getAliases()` - Mentioned above +> +> `revert()` - Mentioned above +> +> `getDependencies()` - Mentioned below + +![Magento Exam Question](./images/icon-question.png)**How to manage dependencies between patch files?** + +> `getDependencies()` - defines dependencies to another patch. Each dependency should be applied first. One patch can have few dependencies. Patches do not have versions, so if in the old approach with Install/Upgrade data scripts you used versions, right now you need to point from patch with higher version to patch with lower version But please, note, that some of your patches can be independent and can be installed in any sequence. So use dependencies only if this is important for you. \ No newline at end of file diff --git a/5. Using the Entity-Attribute-Value -EAV- Model/2. Demonstrate ability to use EAV entity load and save.md b/5. Using the Entity-Attribute-Value -EAV- Model/2. Demonstrate ability to use EAV entity load and save.md deleted file mode 100644 index a09f9e0..0000000 --- a/5. Using the Entity-Attribute-Value -EAV- Model/2. Demonstrate ability to use EAV entity load and save.md +++ /dev/null @@ -1,158 +0,0 @@ -# Demonstrate ability to use EAV entity load and save - -Types of resource models: -- flat resource - `Magento\Framework\Model\ResourceModel\AbstractResource` -- EAV entity resource - `Magento\Eav\Model\Entity\AbstractEntity` -- version controll aware EAV entity resource - `Magento\Eav\Model\Entity\VersionControl\AbstractEntity`. - Used only by customer and customer address. - -New Entity Manager stands aside, you don't need to extend your resource model from it, -but rather manually call its methods in your resource - load, save, delete. -\Magento\Framework\EntityManager\EntityManager is suitable for both flat and EAV tables. - -## Entity Manager - -Entity manager is a new way of saving both flat tables and EAV entities. -Separates individual operations into own class - read, create, update, delete. -This give a lot of freedom for extensions. - -Each operation still has a lot to do - save EAV data and run extensions. Default operations -split these activities into smaller actions: -- save main table - both for flat and EAV -- save EAV attributes - only for EAV entities. see *attribute pool* -- run extensions - see *extension pool* - -Entity manager is concept separate from regular flat and EAV resource models saving, it doesn't -call them under the hood. It does the same things but in own fashion with extensibility in mind. - -This means that you can still build your code on regular resource models: -- flat resource Magento\Framework\Model\ResourceModel\Db\AbstractDb -- EAV entity resource Magento\Eav\Model\Entity\AbstractEntity - -Entity manager is currently used by: -- bundle option -- bundle selection -- catalog rule -- product -- category -- cms block -- cms page -- sales rule -- gift card amount -- and some others (staging etc.) - -Terms: -- *entity manager* - calls appropiate operations. Holds type resolver and operations pool -- *metadata pool* - DI injectable. Register Api Data interface and [`entityTableName`, `identifierField`] - ```xml - - cms_page - page_id - - - - - - ``` - -- *operation pool* - `checkIfExists`/`read`/`create`/`update`/`delete` operations per entity type. Operaton does ALL work. - *Default operations - can extend in DI:* - - checkIfExists - Magento\Framework\EntityManager\Operation\CheckIfExists - decides if entity is new or updated - - read - Magento\Framework\EntityManager\Operation\Read - - create - Magento\Framework\EntityManager\Operation\Create - - update - Magento\Framework\EntityManager\Operation\Update - - delete - Magento\Framework\EntityManager\Operation\Delete - -- *attribute pool* - read/create/update attributes in separate tables per entity type. DI injectable, has default, register only to override. - ```xml - - Magento\Catalog\Model\ResourceModel\CreateHandler - Magento\Catalog\Model\ResourceModel\UpdateHandler - - ``` - *Default attribute pool actions - Module_Eav/etc/di.xml:* - - read - Magento\Eav\Model\ResourceModel\ReadHandler - - create - Magento\Eav\Model\ResourceModel\CreateHandler - - update - Magento\Eav\Model\ResourceModel\UpdateHandler - -- *extension pool* - custom modifications on entity read/create/update. Put your extensions here, good for extension attributes - + product gallery read/create - + product option read/save - + product website read/save - + configurable product links read/save - + category link read/save - + bundle product read/save - set options - + downloadable link create/delete/read/update - + gift card amount read/save - + cms block, cms page - store read/save - - -### *Entity manager.save:* -- resolve entity type - implementing data interface `\Api\Data` when possible, or original class -- check `has(entity)` - detect create new or update - * operation pool.getOperation(`checkIfExists`) -- if new, operation pool.getOperation(`create`).execute -- if existing, operation pool.getOperation(`update`).execute -- run callbacks - -### `create` operation -- event `entity_manager_save_before` -- event `{$lower_case_entity_type}_save_before` -- apply sequence - \Magento\Framework\EntityManager\Sequence\SequenceApplier::apply - * entity[id] = sequence.getNextValue -- *create main* - * EntityManager\Db\CreateRow::execute - insert into main table by existing column -- *create attributes* - * attribute pool actions `create`[].execute - * default action handler `Magento\Eav\Model\ResourceModel\CreateHandler`: - + only if entity is EAV type, insert all attributes - - \Magento\Eav\Model\ResourceModel\AttributePersistor::registerInsert - - \Magento\Eav\Model\ResourceModel\AttributePersistor::flush -- *create extensions* - \Magento\Framework\EntityManager\Operation\Create\CreateExtensions - * extension pool actions `create`[].execute - save many-to-many records, custom columns etc. -- event `{$lower_case_entity_type}_save_after` -- event `entity_manager_save_after` - - -## Object converters -\Magento\Framework\Reflection\DataObjectProcessor::buildOutputDataArray(object, interface) -\Magento\Framework\Reflection\CustomAttributesProcessor::buildOutputDataArray -\Magento\Framework\Reflection\ExtensionAttributesProcessor::buildOutputDataArray - - -\Magento\Framework\Api\\`SimpleDataObjectConverter`: -- toFlatArray: - * data object processor.buildOutputDataArray - * ConvertArray::toFlatArray -- convertKeysToCamelCase - used by Soap API, supports `custom_attributes` - -\Magento\Framework\Api\\`ExtensibleDataObjectConverter`: -- toNestedArray(entity, skip custom attributes list, interface) - used by entity repositories.save: - * data object processor.buildOutputDataArray - * add `custom_attributes` - * add `extension_attributes` -- toFlatArray: - * toNestedArray - * ConvertArray::toFlatArray -- (static) convertCustomAttributesToSequentialArray - - -\Magento\Framework\Reflection\DataObjectProcessor::buildOutputDataArray: -- \Magento\Framework\Reflection\MethodsMap::getMethodsMap - method name and getter return types -- filter only getters: is..., has..., get... -- get data using interface getter, e.g. $object->getSku() - based on Interface definition -- deduce field name by getter name, e.g. 'sku' -- process custom_attributes \Magento\Framework\Reflection\CustomAttributesProcessor::buildOutputDataArray -- process extension_attributes \Magento\Framework\Reflection\ExtensionAttributesProcessor::buildOutputDataArray -- process return object: build return value objects with their return type annotation -- process return array: cast each element to type, e.g. int[] => (int) each value -- cast element to type - -Product.save: -- buildOutputDataArray -- product data = call all getters + original data -- product = initializeProductData. create new product/get existing by SKU. set product data -- process links, unless product data `ignore_links_flag` -- abstract entity.validate -- product resource.save -- entity manager.save diff --git a/5.Magento-Using-Entity-Attribute-Value-Model.md b/5.Magento-Using-Entity-Attribute-Value-Model.md new file mode 100755 index 0000000..30502b9 --- /dev/null +++ b/5.Magento-Using-Entity-Attribute-Value-Model.md @@ -0,0 +1,823 @@ +5 - Using the Entity-Attribute-Value (EAV) Model +================================================ + +5.1 Demonstrate ability to use EAV model concepts +------------------------------------------------- + +![Magento Exam Question](./images/icon-question.png)**Describe how EAV data storage works in Magento. Describe the EAV hierarchy structure.** + +> EAV (Entity-attribute-value) is a model for storing entity attribute values ​​in a certain storage place. For storage, Magento 2 supports MySQL-compatible databases. +> +> The table of the classic EAV model has 3 columns: + +1. Entity (object to which the attribute value must be set) +2. Attribute +3. Value + +> Whereas Magento has 5: + +1. A `backend_type` e.g. varchar, int, text etc is assigned for each attribute. +2. Table eav_entity_type stores each custom and system EAV entity type separately (e.g. `catalog_product_entity`, `customer_address_entity`, sales_order etc). It's purpose is to name the tables where the entities are stored via the entity_table column. See [Section 5.3 EAV Attribute Features](#h.v4fzn8rafjf4) for available types. +3. Table eav_entity holds system EAV entities - those that are available out-of-the-box +4. Attributes with a `backend_type` as "static", are stored in the same table as entities. These attributes have global scope. +5. Each `backend_type` of each entity type has its own table e.g. "`catalog_product_entity_int`", "`catalog_product_entity_decimal`", in which attribute values are stored. The name of this table is crafted according to the template {entity_table} _ {`backend_type`}, where entity_table is the name of the entity table and `backend_type` is the backend type of the attribute. These tables include the following columns: + * `value_id` (int) + * `attribute_id` (int) + * `store_id` (int) + * `entity_id` (int) + * `value` (type depends on the backend type). + +![Magento Exam Question](./images/icon-question.png)**What happens when a new attribute is added to the system? (what frontend input's are available when adding a new attribute to the system).** + +![Magento Section Chevron](./images/icon-chevron.png) Text Attributes + +1. A new record in eav_attribute table is made. +2. One entry per store view is created in the eav_attribute_label table with the Label attribute. +3. A new record in the catalog_eav_attribute table is created. + +![Magento Section Chevron](./images/icon-chevron.png) Dropdown (dynamic) Attributes + +For attributes with varying options, there are a few additional differences to the above, chiefly: + +1. eav_attribute.frontend_input = "select" +2. eav_attribute.source_model = "Magento\Eav\Model\Entity\Attribute\Source\Table" +3. eav_attribute.`backend_type` = "int" +4. eav_attribute.default_value = eav_attribute_option.option_id by default +5. Each option are added to table eav_attribute_option + +![Magento Section Chevron](./images/icon-chevron.png) Price Attributes + +Differences compared to Text Field: + +1. eav_attribute.frontend_input = "price" +2. eav_attribute.backend_model = "Magento\Catalog\Model\Product\Attribute\Backend\Price" +3. eav_attribute.`backend_type` = "decimal" + +![Magento Section Chevron](./images/icon-chevron.png)Media Image + +Differences compared to Text Field: + +1. eav_attribute.frontend_input = "media_image" + +![Magento Section Chevron](./images/icon-chevron.png)Text swatch + +Differences compared to Dropdown: + +1. catalog_eav_attribute.additional_data is in JSON output: {"swatch_input_type":"text","update_product_preview_image":"0″,"use_product_image_for_swatch":0} +2. Each swatch option is added to eav_attribute_option_swatch + +![Magento Exam Information](./images/icon-info.png)See Section [5.3 EAV Attribute Features](#h.v4fzn8rafjf4) for available types. + +![Magento Exam Question](./images/icon-question.png)**What is the role of attribute sets and attribute groups?** + +> Attribute Set's are applied to display different attributes for various products (eg. shoes and clothes). For example, if we have "shoe_size" and "clothing_size" attributes, and we need to display the first one for shoes, and the second - for clothes. An Attribute Set allows us to hide the unnecessary attributes, display the needed ones, modify the sorting and group. +> +> Groups simplify the process of filling product information by the administrator, but have no effect on product loading / storing logic. +> +> Attributes in attribute set settings: +> +> **Admin > Stores > Attributes > Attribute Sets** + +![Magento Exam Question](./images/icon-question.png)**How are Attributes presented in the admin?** + +> Product Attributes consist of a Name (label) and a Field (value) of which the administrator can change. +> +> The following attribute properties affect the display: + +* `frontend_model` - a class that describes the field display in the frontend section of a site. Inherited from `Magento\Eav\Model\Entity\Attribute\Frontend\AbstractFrontend` and overrides the `getValue()` method to change the displayed attribute values +* `frontend_input` (Catalog Input Type for Store Owner) - The form element that is displayed in the admin section of the site. This can only be selected once per attribute. +* `frontend_label` (Default Label) - the name of the attribute, displayed in the admin section of the site. It is also displayed in the site frontend, unless otherwise specified in the `eav_attribute_label` table (in the attribute settings it is changed in the "Manage Labels" tab) +* `frontend_class` (Input Validation for Store Owner) - used to validate the attribute value in the admin section of the site. In the attribute settings, the property is called Input Validation for Store Owner. For email validation, the `frontend_class` property is set to "`validate-email`". + +![Magento Exam Information](./images/icon-info.png)See Section [5.3 EAV Attribute Features](#h.v4fzn8rafjf4) for available types, models & classes. + +![Magento Exam Question](./images/icon-question.png)**Which additional options do you have when saving EAV entities?** + +![Magento Section Chevron](./images/icon-chevron.png)The EAV entity modification page offer the following capabilities: + +> Default Attribute Values. Allows to set the value that will be inserted into the attribute field if it is not filled in. This simplifies the process of adding and modifying the entities. These are the potential default attribute values for the product: + +* Text Field - any one-line text +* Textarea - any multi-line text +* Date - any date +* Yes/No - yes or no +* Multiple Select - any number of options +* Dropdown - one option +* Price - no default value +* Media Image - no default value +* Visual Swatch - one option +* Text Swatch - one option +* Fixed Product Tax - no default value + +![Magento Section Chevron](./images/icon-chevron.png)Scope selection. + +> Scope allows to set different attribute values for different websites / stores / views. Below is a list of the possible attribute scope for the product: + +* Text Field - store view / website / global +* Textarea - store view / website / global +* Date - store view / website / global +* Yes/No - store view / website / global +* Multiple Select - store view / website / global +* Dropdown - store view / website / global +* Price - website / global (configured in Stores > Configuration > Catalog > Catalog > Price > Catalog Price Scope) +* Media Image - store view / website / global +* Visual Swatch - store view / website / global +* Text Swatch - store view / website / global +* Fixed Product Tax - global + +![Magento Exam Information](./images/icon-info.png)I think the important thing here to remember is that all default attributes are configurable down to a store view level except Fixed Product Tax and Price (price is a per website thing). + +![Magento Section Chevron](./images/icon-chevron.png) Attribute Set Selection (products only). + +> Allows to regroup or hide the attributes that do not suit the current product type. See above for more info on Attribute Sets. + +![Magento Section Chevron](./images/icon-chevron.png)EAV Entity Validation. + +> Before saving an EAV entity at the client side, we have the following features: + +* Prohibition to save entities, if there are empty attribute fields with the is_required = 1 feature. It allows us to define what attributes of the entity are required to be filled in. +* Validation of the attribute fields values by the algorithm, set in frontend_class. The following frontend classes are supported by default it Magento: +* `validate-number`: Decimal Number +* `validate-digits`: Integer Number +* `validate-email`: Email +* `validate-url`: URL +* `validate-alpha`: Letters +* `validate-alphanum`: Letters (a-z, A-Z) or Numbers (0-9) + + +Before saving an EAV entity at the server side, we have the following features: + +* Check attribute fields with `is_required = 1` for fullness. Magento checks the required fields on both the client and server sides. +* Uniqueness check of the attribute fields with `is_unique = 1`. +* Perform operations in `backend_model`: +* Validation (validate method). Allows to realize additional server check before saving. +* Operation before saving (`beforeSave` method). +* Operation after saving (`afterSave` method). + +![Magento Exam Information](./images/icon-info.png)Basically there is frontend and backend validation, before and after methods and a backend_model for those more complex attributes. + +![Magento Section Chevron](./images/icon-chevron.png) Additional Attribute Parameters + +> Module catalog has an additional catalog_eav_attribute table for attributes, where the following parameters are stored: + +* Frontend Input Renderer +* Is Global +* Is Visible +* Is Searchable +* Is Filterable +* Is Comparable +* Is Visible On Front +* Is HTML Allowed On Front +* Is Used For Price Rules +* Is Filterable In Search +* Is Used In Product Listing +* Is Used For Sorting +* Is Visible In Advanced Search +* Is WYSIWYG Enabled +* Is Used For Promo Rules +* Is Required In Admin Store +* Is Used in Grid +* Is Visible in Grid +* Is Filterable in Grid + +> These parameters allow us to perform a more sophisticated attribute configuration. + +![Magento Exam Information](./images/icon-info.png)These are all available parameters a user can select when creating attributes in the admin. Just gotta think back and remember them! + +![Magento Exam Question](./images/icon-question.png)**How do you create customizations based on changes to attribute values?** + +> To customize an attribute, modify the following attribute features: + +* `backend_model` - The model is used for processing and validating the attribute values. By default, `Magento\Eav\Model\Entity\Attribute\Backend\DefaultBackend` is used. More info on this in the above "EAV Entity Validation" subsection. +* `source_model` - The model is used for providing the list of the attribute values. By default, `Magento\Eav\Model\Entity\Attribute\Source\Config` is used. You can choose to extend it to leverage the `getAllOptions()` method. +* `attribute_model` - The model allows us to perform a more sophisticated attribute setting. By default, `Magento\Eav\Model\Entity\Attribute` is used, and a model, different from the default one, is rarely applied. +* `frontend_model` - Frontend model is used for displaying the frontend part of the website. By default, `Magento\Eav\Model\Entity\Attribute\Frontend\DefaultFrontend` is applied, but you can extend it to leverage the `getValue()` method. + +![Magento Exam Question](./images/icon-question.png)**Describe the key differences between EAV and flat table collections.** + +> Magento typically stores catalog data in multiple tables, based on the Entity-Attribute-Value (EAV) model. Because product attributes are stored in many tables, SQL queries are sometimes long and complex. +> +> In contrast, a flat catalog creates new tables on the fly, where each row contains all the necessary data about a product or category. A flat catalog is updated automatically - either every minute, or according to your cron job. Flat catalog indexing can also speed up the processing of Catalog and [Cart Price Rules](#h.x0kvcqk03fp8). A catalog with as many as 500,000 SKUs can be indexed quickly as a flat catalog. +> +> Flat Catalog is configured in: +> +> **Admin > Stores > Configuration > Catalog > Catalog > Use Flat Catalog Category** +> +> **Admin > Stores > Configuration > Catalog > Catalog > Use Use Flat Catalog Product** + +![Magento Exam Note Warning](images/icon-warning.png)Magento no longer recommends the use of a flat catalog as a best practice. Continued use of this feature is known to cause performance degradation and other indexing issues in Magento 2.1 and below. + +![Magento Exam Question](./images/icon-question.png)**In which situations would you use EAV for a new entity?** + +Using EAV for a new entity is advisable in case at least one of the following conditions is true: + +* There is a scope. +* Admins and modules have the ability to add attributes into an entity or modify an attribute's `backend_type`. +* Potentially, the number of columns in the Flat model can exceed the maximum column per table limit (1017 with InnoDB). +* Potentially, the required amount of indexes, like INDEX in the Flat model, can exceed the maximum number of indexes per table (64 in InnoDB) or reach the amount when the entities' adding / modifying / deletion will be slow. + +![Magento Exam Information](./images/icon-info.png)Good to remember in general! + +![Magento Exam Question](./images/icon-question.png)**What are the pros and cons of EAV architecture?** + +![Magento Section Chevron](./images/icon-chevron.png) Advantages + +1. The implementation of SCOPE in the Flat model stores a lot of unnecessary information. For example, if you want to redefine one attribute in another scope, then both EAV and Flat will create one line each. But in EAV, the number of columns is always fixed, while in Flat it can reach 1000. Additionally, there is a problem when the value is inherited from the parent scope. You can mark such values ​​as NULL, but then you must prevent the attributes from being NULL if they are not inherited. +2. Quickly add a new attribute. Adding a new attribute does not change the EAV table in any way. In Flat, you need to add a new column. The ALTER TABLE operation is also slow. This is especially noticeable in large tables.* +3. Changing `backend_type` attribute is faster. To change the attribute type in EAV, you need to move this attribute data from one table to another. For Flat, you need to perform ALTER TABLE.* +4. EAV allows us to separate attribute values ​​from entity field values. +5. The following InnoDB restrictions on the table restrict Flat: + + * The table may contain no more than 1017 columns + * A table can contain a maximum of 64 indexes of type INDEX + +> `*` As for EAV, when adding / changing an attribute type, an ALTER TABLE is performed if the attribute's backend type is static. + +![Magento Section Chevron](./images/icon-chevron.png) Disadvantages + +1. Getting entity attribute values ​​in EAV is slower than in Flat * +2. Search by attribute value in EAV is slower than in Flat * +3. In Flat, you can create an index on several attributes to speed up the search * + +* To speed up operations, some attributes can be set the `backend_type` as "static" and, if necessary, create indexes on the column with the attribute in the database, but then the visibility of the attribute will only be global. Another option is to use a flat table as an index if the data can be out of date. + +5.2 Demonstrate ability to use EAV entity load and save +------------------------------------------------------- + +![Magento Exam Note Warning](images/icon-warning.png)Entity Manager + +> The `EntityManager` was introduced in Magento 2.1 . Since version 2.2 however, its use is not recommended by Magento anymore. +> +> Here is what the framework says about the class "`Magento\Framework\EntityManager\EntityManager`": +> +> "It is not recommended to use the EntityManager and its infrastructure for the Persistence Entities. In the next version a new "`PersistenceEntityManagerInterface`" will be created in order to cover the needs of the persistence with the API requests for the reading of the datas." +> +> Instead it is recommended to use the "`ResourceModel`" by inheriting from `Magento\Framework\Model\ResourceModel\Db\AbstractDb` or `Magento\Eav\Model\Entity\AbstractEntity` if it is an EAV model. For the collections and filters, it is recommended to use `Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection` + +![Magento Section Chevron](./images/icon-chevron.png)Working Via EntityManager + +> If you want to work with `EntityManager` despite deprecation, you must provide information about the entity interface in `di.xml` for `MetadataPool` and for `HydratorPool`. +> +> Magento\Framework\EntityManager\OperationPool class contains operations array that call EntityManager for loading, saving, existence check and object deletion (C.R.U.D + check). The operation is performed by execute method. + +![Magento Exam Information](./images/icon-info.png)Remember Entity Manager uses C.R.U.D + a check methodology. +```xml + + + + + myvendor_mymodule_myentity_entity + myvendor_mymodule_myentity + entity_id + + Magento\Store\Model\StoreScopeProvider + + + + + + + + + + Magento\Framework\EntityManager\AbstractModelHydrator + + + +``` + +| Action | Class | Sub-operation | +| --- | --- | --- | +| `checkIfExists` | `Magento\Framework\EntityManager\Operation\CheckIfExists` | By default, CheckIfExists operation checks for the existence of an entry in the main table with a direct SQL query. | +| `read` | `Magento\Framework\EntityManager\Operation\Read` | `ReadMain`, `ReadAttributes`, `ReadExtensions` | +| `create` | `Magento\Framework\EntityManager\Operation\Create` | `CreateMain`, `CreateAttributes`, `CreateExtensions` | +| `update` | `Magento\Framework\EntityManager\Operation\Update` | `UpdateMain`, `UpdateAttributes`, `UpdateExtensions` | +| `delete` | `Magento\Framework\EntityManager\Operation\Delete` | `DeleteExtensions`, `DeleteAttributes`, `DeleteMain` | + +![Magento Exam Information](./images/icon-info.png)May not be necessary to remember since it's now deprecated. Again just remember Entity Manager uses C.R.U.D + a check methodology. + +![Magento Section Chevron](./images/icon-chevron.png)Attribute Operations + +> The EntityManager class relies on the OperationPool class Magento\Framework\EntityManager\OperationPool performing C.R.U.D operations on the entity itself. For instance, writing changes for an existing entity to the database (i.e. an update) is done using an operation subclass Magento\Framework\EntityManager\Operation\Update. +> +> Attribute Operations is responsible for performing C.R.U.D operations on attribute values into the new entities. Attribute Operations are located in the class Magento\Framework\EntityManager\Operation\AttributePool. +> +> This is defined in XML, and other modules can extend operations. + +![Magento Exam Information](./images/icon-info.png)The general `app/etc/di.xml` gives clues on how this works: Whenever OperationPool is instantiated, a type definition tells the Object Manager to instantiate the OperationPool with a list of operations. + +![Magento Section Chevron](./images/icon-chevron.png)Extensions Operations + +> Extensions Operations can be used to perform additional persistent operations on objects, like creating many-to-many relations, an example of this in 2.1 is the CMS module - to link the entity to the store. +> +> Extensions Operations are located in the class `Magento\Framework\EntityManager\Operation\ExtensionPool`. + +> Using overrides of Attributes Operations, EAV applies the following Handlers for operations: + +* `Magento\Eav\Model\ResourceModel\CreateHandler` - Writes attribute values into the new entities. Creates, if the attribute is absent in Snapshot and the new value is not empty or the attribute allows empty values. +* `Magento\Eav\Model\ResourceModel\UpdateHandler` - takes a snapshot thanks to ReadSnapshot (which is based on ReadHandler). Then, UpdateHandler: Changes the value, if the attribute is modified relative to Snapshot and the new value is not empty or the attribute allows empty values. UpdateHandler Deletes, if the attribute is changed relative to Snapshot and the new value is empty and the attribute does not allow empty values. +* `Magento\Eav\Model\ResourceModel\ReadHandler` - ReadHandler performs reading by: Getting all attribute tables for a specific entity_type For each table, subquery attribute values for each scope then perform a UNION ALL of all subqueries writing resulting values are written into the $entityData array. + +![Magento Exam Question](./images/icon-question.png)**Describe the EAV load and save process and differences from the flat table load and save process.** + +![Magento Exam Information](./images/icon-info.png)Debatable whether or not the following question is still relevant any more since Flat catalog / categories are now not recommended. + +![Magento Section Chevron](./images/icon-chevron.png)Collections + +> EAV Collections derive from `Magento\Eav\Model\Entity\Collection\AbstractCollection` whereas Flat Collections derive from `Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection` + +![Magento Section Chevron](./images/icon-chevron.png)Resource Models + +> EAV Resource Models extend `Magento\Eav\Model\Entity\AbstractEntity` and must implement the `getEntityType()` method, whereas Flat Resource Models extend `Magento\Framework\Model\ResourceModel\Db\AbstractDb` and initialize in the Magento constructor `_construct()` method. + +![Magento Section Chevron](./images/icon-chevron.png)Models + +> Both EAV Models extend `Magento\Framework\Model\AbstractModel` but EAV Models must establish the ENTITY constant whereas Flat Models do not. + +![Magento Section Chevron](./images/icon-chevron.png)Loading + +* `addAttributeToSelect()` converts attribute into its code and calls `addFieldToSelect()` +* `addAttributeToFilter()` converts attribute into its code and calls `addFieldToFilter()` +* `addAttributeToSort()` converts attribute into its code and calls `addOrder()` + +![Magento Section Chevron](./images/icon-chevron.png)Saving/Updating/Deleting + +* `Magento\Eav\Model\ResourceModel\CreateHandler` - To create values in new entities. +* `Magento\Eav\Model\ResourceModel\UpdateHandler` - To modify / delete / add values into the existing entities. +* `Magento\Eav\Model\ResourceModel\ReadHandler` - To get the values. + +![Magento Section Chevron](./images/icon-chevron.png)How to check for Flat Products + +`Magento\Catalog\Model\ResourceModel\Product\Collection`: + +```php +if ($this->isEnabledFlat()) { + ... +} else { + ... +} +``` + +![Magento Section Chevron](./images/icon-chevron.png)How to check for Flat Categories + +`Magento\Catalog\Model\Category` model is initialized the following way: +```php +protected function _construct() +{ + // If Flat Index enabled then use it but only on frontend + if ($this->flatState->isAvailable()) { + $this->_init(Magento\Catalog\Model\ResourceModel\Category\Flat::class); + $this->_useFlatResource = true; + } else { + $this->_init(Magento\Catalog\Model\ResourceModel\Category::class); + } +} +``` +![Magento Exam Question](./images/icon-question.png)**What happens when an EAV entity has too many attributes?** + +> The advantage of EAV, compared to Flat, is that the Flat table in InnoDb can not contain more than 1017 columns. In EAV, the number of attributes is limited by the maximum size of InnoDb tables - which is far greater (4 billion tables). +> +> Too many tables on disk will have no impact on MySQL (unless you have a filesystem that can't rapidly open a filename from a very full directory. As far as active tables however, those being used frequently in queries there are effects from: + +* What queries you're running - although you aren't likely to be querying all tables within a single query. +* table_open_cache & innodb_open_files limits the number of tables cached. Exceeding these cache limits will cause tables to close and open and need to be re-examined adding overhead to queries. +* The overall query load on the system at any given point in time. + +![Magento Exam Question](./images/icon-question.png)**How does the number of websites/stores affect the EAV load/save process?** + +> The number of websites/stores impacts the entity loading / saving. +> In Magento, scope in EAV is realized due to the "store_id" column in the EAV attribute value tables. + +* store_id = 0, if scope global +* store_id = X of the selected Store View, if not global + +> Queries are loaded the following way: + +```sql +SELECT XXX FROM _<`BACKEND_TYPE`> WHERE store_id IN () + +``` + +> Where STORE_IDS are Store View identificators of the current context. +> +> The context is calculated recursively: In the STORE_IDS array, the identificator of the current scope is added. In case the current scope has Fallback scope, then an operation is repeated for Fallback. + +![Magento Exam Information](./images/icon-info.png)The more Store Views are in the Website, the more entries in the database you need to create or modify, and therefore the slower the loading/saving. + +![Magento Exam Information](./images/icon-info.png)It turns out that the number of websites / stores does not directly affect the upload speed, because when StoreScopeProvider is used during loading, the context contains no more than two store_id, i.e. does not depend on the number of websites / stores. + +![Magento Exam Question](./images/icon-question.png)**How would you customize the load and save process for an EAV entity in the situations described here?** + +![Magento Section Chevron](./images/icon-chevron.png)Overriding the operations + +`di.xml`: +```xml + + + + + MY_NAMESPACE\CheckIsExists + MY_NAMESPACE\Read + MY_NAMESPACE\Create + MY_NAMESPACE\Update + MY_NAMESPACE\Delete + + + + +``` + +![Magento Exam Information](./images/icon-info.png)You can partially override, for example, a single "read" operation, then the default operations will be used for the rest of the operations. + +![Magento Section Chevron](./images/icon-chevron.png)Overriding the extensionActions + +`di.xml`: +```xml + + + + + + MY_NAMESPACE\ReadHandler + + + MY_NAMESPACE\CreateHandler + + + MY_NAMESPACE\UpdateHandler + + + + + +``` + +5.3 Demonstrate ability to manage attributes +-------------------------------------------- + +![Magento Exam Question](./images/icon-question.png)**Describe EAV attributes, including the frontend/source/backend structure.** + +> Backend Model: Performs loading, saving, deletion and validation of the attribute. Different validations are used based on the eav_attribute.`backend_type`. +> +> Source Model (`eav_attribute`.`source_model`): Provides a list of values for the attributes, which is later used for dropdown/multiselect attributes. +> +> Frontend (`eav_attribute`.`frontend_input`): How the Model displays the attribute at the frontend side e.g. "`select`". + +![Magento Exam Information](./images/icon-info.png)Similar things were mentioned in [Section 5.1](#h.7mu91scg3uyf) to help you remember. + +![Magento Exam Information](./images/icon-info.png)See Section [5.3 EAV Attribute Features](#h.v4fzn8rafjf4) for available types. + +![Magento Exam Question](./images/icon-question.png)**How would you add dropdown/multiselect attributes?** + +![Magento Section Chevron](./images/icon-chevron.png) Install Data Scripts + +> You can create attributes through the setup script (`Vendor\Module\Setup\InstallData` or `Vendor\Module\Setup\UpgradeData`). + +![Magento Section Chevron](./images/icon-chevron.png) Data patches + +![Magento Section Chevron](./images/icon-chevron.png)Admin + +> Via the admin panel (for products only). + +![Magento Section Chevron](./images/icon-chevron.png)Create new Extension Attribute + +Define a new [Extension Attribute](#h.2lcvcwr56aq) in [`/etc/extension_attributes.xml`](https://store.fooman.co.nz/blog/an-introduction-to-extension-attributes.html&sa=D&ust=1609223264976000&usg=AOvVaw2D54qlN24sv9fdXKR-CbVq) In the `` field the `type=""` is your Backend Model and should point to your new Interface. Being a dropdown/multiselect this will need to be an array .i.e `ExampleAttributeInterface[]` - plural Interfaces. + +> Source Model (`eav_attribute`.`source_model`). +> +> Add a `backend_model`. +> +> Add a `frontend_model`. + +Example: + +```xml + + + +``` + +Interface (Service Contract): + +```php +getData(self::VALUE); + } + + /** + * {@inheritdoc} + */ + public function setValue($value) + { + return $this->setData(self::VALUE, $value); + } +} +``` + +Saving & Retrieving + +The use of plugins is the best approach here, in `di.xml`: +```xml + + + +``` + +Product Save +```php +saveExampleAttribute($resultProduct); + return $resultProduct; + } + + public function afterGet( + Magento\Catalog\Api\ProductRepositoryInterface $subject, + Magento\Catalog\Api\Data\ProductInterface $resultProduct + + ) { + + $resultProduct = $this->getExampleAttribute($resultProduct); + return $resultProduct; + } + + private function getExampleAttribute(Magento\Catalog\Api\Data\ProductInterface $product) + { + try { + // The actual implementation of the repository is omitted + // but it is where you would load your value from the database (or any other persistent storage) + $exampleAttributeValue = $this->exampleRepository->get($product->getEntityId()); + } catch (NoSuchEntityException $e) { + return $product; + } + + $extensionAttributes = $product->getExtensionAttributes(); + $productExtension = $extensionAttributes ? $extensionAttributes : $this->productExtensionFactory->create(); + $exampleAttribute = $this->exampleAttributeFactory->create(); + $exampleAttribute->setValue($exampleAttributeValue); + $productExtension->setExampleAttribute($exampleAttribute); + $product->setExtensionAttributes($productExtension); + + return $product; + } + + private function saveExampleAttribute(Magento\Sales\Api\Data\OrderInterface $product) + { + $extensionAttributes = $product->getExtensionAttributes(); + if ( + null !== $extensionAttributes && null !== $extensionAttributes->getExampleAttribute() + ) { + $exampleAttributeValue = $extensionAttributes->getExampleAttribute()->getValue(); + try { + // The actual implementation of the repository is omitted + // but it is where you would save to the database (or any other persistent storage) + $this->exampleRepository->save($product->getEntityId(), $exampleAttributeValue); + } catch (\Exception $e) { + throw new CouldNotSaveException( + __('Could not add attribute to order: "%1"', $e->getMessage()), + $e + ); + } + } + return $product; + } +} + +``` +![Magento Exam Information](./images/icon-info.png)More info in [Section 4.1](#h.2lcvcwr56aq) to help you remember. + +![Magento Exam Question](./images/icon-question.png)**What other possibilities do you have when adding an attribute (to a product, for example)?** + +### ![Magento Section Chevron](./images/icon-chevron.png)EAV Attribute Features + +> EAV attribute has several features that we can specify. Class `Magento\Eav\Model\Entity\Setup\PropertyMapper` class contains the conversion of property names in the setup script into EAV attribute property in the database. + +| Feature in setup script | Feature in the database | Default value | Available Core Values | +| --- | --- | --- | --- | +| `attribute_model` | `attribute_model` | `null` | `Magento\Catalog\Model\ResourceModel\Eav\Attribute` | +| `backend` | `backend_model` | `null` | `Magento\Catalog\Model\Attribute\Backend\Customlayoutupdate`, `Magento\Catalog\Model\Attribute\Backend\Startdate`, `Magento\Catalog\Model\Category\Attribute\Backend\Image`, `Magento\Catalog\Model\Category\Attribute\Backend\Sortby`, `Magento\Catalog\Model\Product\Attribute\Backend\Boolean`, `Magento\Catalog\Model\Product\Attribute\Backend\Category`, `Magento\Catalog\Model\Product\Attribute\Backend\Price`, `Magento\Catalog\Model\Product\Attribute\Backend\Sku`, `Magento\Catalog\Model\Product\Attribute\Backend\Stock`, `Magento\Catalog\Model\Product\Attribute\Backend\Tierprice`, `Magento\Catalog\Model\Product\Attribute\Backend\Weight`, `Magento\Customer\Model\Attribute\Backend\Data\Boolean`, `Magento\Customer\Model\Customer\Attribute\Backend\Billing`, `Magento\Customer\Model\Customer\Attribute\Backend\Password`, `Magento\Customer\Model\Customer\Attribute\Backend\Shipping`, `Magento\Customer\Model\Customer\Attribute\Backend\Store`, `Magento\Customer\Model\Customer\Attribute\Backend\Website`, `Magento\Customer\Model\ResourceModel\Address\Attribute\Backend\Region`, `Magento\Eav\Model\Entity\Attribute\Backend\ArrayBackend`, `Magento\Eav\Model\Entity\Attribute\Backend\Datetime`, `Magento\Eav\Model\Entity\Attribute\Backend\DefaultBackend` | +| `type` | `backend_type` | `varchar` | `static`, `varchar`, `int`, `text`, `datetime`, `decimal` | +| `table` | `backend_table` | `null` | `null` | +| `frontend` | `frontend_model` | `null` | `Magento\Catalog\Model\Product\Attribute\Frontend\Image`, `Magento\Eav\Model\Entity\Attribute\Frontend\Datetime` | +| `input` | `frontend_input` | `text` | `boolean`, `select`, `text`, `image`, `media_image`, `price`, `date`, `textarea`, `gallery`, `multiselect`, `hidden`, `multiline` | +| `label` | `frontend_label` | `null` | Any varchar up to 255 characters long | +| `frontend_class` | `frontend_class` | `null` | `null` | +| `source` | `source_model` | `null` | `Magento\Config\Model\Config\Source\Locale\Currency\All`, `Magento\Config\Model\Config\Source\Locale\Country`, `Magento\Config\Model\Config\Source\Locale\Timezone`, `Magento\Config\Model\Config\Source\Locale\Weekdaycodes`, `Magento\Config\Model\Config\Source\Locale\Weekdays`, `Magento\Config\Model\Config\Source\Locale\Currency`, `Magento\Config\Model\Config\Source\Email\Identity`, `Magento\Config\Model\Config\Source\Email\Method`, `Magento\Config\Model\Config\Source\Email\Template`, `Magento\Config\Model\Config\Source\Email\Smtpauth`, `Magento\Config\Model\Config\Source\Reports\Scope`, `Magento\Config\Model\Config\Source\Store`, `Magento\Config\Model\Config\Source\Dev\Dbautoup`, `Magento\Config\Model\Config\Source\Design\Robots`, `Magento\Config\Model\Config\Source\Yesnocustom`, `Magento\Config\Model\Config\Source\Web\Protocol`, `Magento\Config\Model\Config\Source\Web\Redirect`, `Magento\Config\Model\Config\Source\Website\AdminOptionHash`, `Magento\Config\Model\Config\Source\Website\OptionHash`, `Magento\Config\Model\Config\Source\Locale`, `Magento\Config\Model\Config\Source\Enabledisable`, `Magento\Config\Model\Config\Source\Yesno`, `Magento\Config\Model\Config\Source\Date\Short`, `Magento\Config\Model\Config\Source\Nooptreq`, `Magento\Config\Model\Config\Source\Image\Adapter`, `Magento\Config\Model\Config\Source\Admin\Page`, `Magento\Config\Model\Config\Source\Website` | +| `required` | `is_required` | `1` | `1` & `0` | +| `user_defined` | `is_user_defined` | `0` | `1` & `0` | +| `default` | `default_value` | `null` | `null` | +| `unique` | `is_unique` | `0` | `1` & `0` | +| `note` | `note` | `null` | `null` | +| `global` | `is_global` | `0` | `Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface::SCOPE_STORE` (0), `Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface::SCOPE_GLOBAL` (1), `Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface::SCOPE_WEBSITE` (2) | +| `sort_order` | `sort_order` | `null` | `null` | +| `group` | `group` | `General` | e.g. `General`, `Search Engine Optimization`, `Display Settings` | + +![Magento Exam Information](./images/icon-info.png)You can also specify: + +* `option` – the list of values for dropdown/multiselect attributes + +![Magento Exam Information](./images/icon-info.png)You can find `backend_type`, type, frontend_input and input values from your database's eav_attribute table. You can check for `backend_type` value for your type case and frontend_input for your input value for all attributes from this table. + +![Magento Section Chevron](./images/icon-chevron.png)EAV Attribute Magento/Catalog Additional Features + +Additionally, Magento/Catalog has the following properties according to Magento\Catalog\Model\ResourceModel\Setup\PropertyMapper: + +| Feature in setup script | Feature in the database | Default value | +| --- | --- | --- | +| `input_renderer` | `frontend_input_renderer` | `null` | +| `visible` | `is_visible` | `1` | +| `searchable` | `is_searchable` | `0` | +| `filterable` | `is_filterable` | `0` | +| `comparable` | `is_comparable` | `0` | +| `visible_on_front` | `is_visible_on_front` | `0` | +| `wysiwyg_enabled` | `is_wysiwyg_enabled` | `0` | +| `is_html_allowed_on_front` | `is_html_allowed_on_front` | `0` | +| `visible_in_advanced_search` | `is_visible_in_advanced_search` | `0` | +| `filterable_in_search` | `is_filterable_in_search` | `0` | +| `used_in_product_listing` | `used_in_product_listing` | `0` | +| `used_for_sort_by` | `used_for_sort_by` | `0` | +| `apply_to` | `apply_to` | `null` | +| `position` | `position` | `0` | +| `used_for_promo_rules` | `is_used_for_promo_rules` | `0` | +| `is_used_in_grid` | `is_used_in_grid` | `0` | +| `is_visible_in_grid` | `is_visible_in_grid` | `0` | +| `is_filterable_in_grid` | `is_filterable_in_grid` | `0` | +| `attribute_set` | `attribute_set` | `null` (Accepts strings such as "Default") | + +![Magento Exam Question](./images/icon-question.png)**Describe how to implement the interface for attribute frontend models. What is the purpose of this interface? How can you render your attribute value on the frontend?** + +![Magento Section Chevron](./images/icon-chevron.png)How + +> To create Frontend model, create a class, inherited from `Magento\Eav\Model\Entity\Attribute\Frontend\AbstractFrontend`, and override getValue method.Then, set frontend_model as the name of the newly created class. +```PHP +class TestFrontend extends \Magento\Eav\Model\Entity\Attribute\Frontend\AbstractFrontend +{ + public function getValue(Magento\Framework\DataObject $object) + { + $attribute_code = $this->getAttribute()->getAttributeCode(); + $value = $object->getData($attribute_code); + return nl2br(htmlspecialchars($value)); + } +} +``` + +![Magento Section Chevron](./images/icon-chevron.png)Why (purpose). + +> The purpose of the interface is to decrease the code dependency (dependency inversion principle). + +![Magento Exam Question](./images/icon-question.png)**Identify the purpose and describe how to implement the interface for attribute source models.** + +> The model is used for providing a list of attribute values for dropdown/multiselect attributes. Source model realization example: + +```PHP +class TestSource extends \Magento\Eav\Model\Entity\Attribute\Source\AbstractSource +{ + public function getAllOptions() + { + if (!$this->_options) { + $this->_options = [ + ['label' => __('Label 1'), 'value' => 'value 1'], + ['label' => __('Label 2'), 'value' => 'value 2'], + ['label' => __('Label 3'), 'value' => 'value 3'], + ['label' => __('Label 4'), 'value' => 'value 4'] + ]; + } + + return $this->_options; + } +} +``` +![Magento Exam Question](./images/icon-question.png)**For a given dropdown/multiselect attribute, how can you specify and manipulate its list of options?** + +> The values are stored in the eav_attribute_option_value table. If the source_model is the `Magento\Eav\Model\Entity\Attribute\Source\Table` class or is inherited from it. The values can be added when creating an attribute in the option property, or you can call the `Magento\Eav\Setup\EavSetup->addAttributeOption($option)` method. +> +> If the source model is not inherited from `Magento\Eav\Model\Entity\Attribute\Source\Table`, then, to change the list of values, you can: + +* Create a plugin on the source model on the `getAllOptions` method +* Create a new source model, inherited from the mutable class, change the behavior of the `getAllOptions` method and specify the new `source_model` in the attributes + +![Magento Exam Information](./images/icon-info.png)Just remember that The values are stored in the eav_attribute_option_value table and that the `source_model` column should reference a class inherited from `Magento\Eav\Model\Entity\Attribute\Source\Table` which allows you to override the `addAttributeOption` method. + +![Magento Exam Question](./images/icon-question.png)**Identify the purpose and describe how to implement the interface for attribute backend models.** + +> Backend modules are created with a purpose to upload / save / delete / validate attribute values. + +![Magento Section Chevron](./images/icon-chevron.png)How + +`etc\adminhtml\system.xml` +```xml + + + my_module/section/config_example + Vendor\MyModule\Model\Config\Backend\TestBackend + 1 + +``` + +```php +class TestBackend implements Magento\Framework\App\Config\ValueInterface +{ + public function isValueChanged($object) + { + } + + Public function getOldValue() + { + } + + public function getFieldsetDataValue($key) + { + } +} +``` + +![Magento Section Chevron](./images/icon-chevron.png)How (and why) would you create a backend model for an attribute? + +> Backend modules are created with a purpose to upload / save / delete / validate attribute values. + +![Magento Exam Question](./images/icon-question.png)**Describe how to create and customize attributes. How would you add a new attribute to the product, category, or customer entities? What is the difference between adding a new attribute and modifying an existing one?** + +![Magento Section Chevron](./images/icon-chevron.png)Adding a new attribute + +Data patches are used for creating a new attribute using the injected method: + +```php +Magento\Eav\Setup\EavSetupFactory->create()->addAttribute(string $entityType, string $attributeCode, array $properties) +``` + +* For Products use entity code `Magento\Catalog\Model\Product::ENTITY` +* For Inventory use entity code `Magento\CatalogInventory\Model\Stock::ENTITY` +* For Stock Items use entity code `Magento\CatalogInventory\Model\Stock\Item::ENTITY` +* For Categories use entity code `Magento\Catalog\Model\Category::ENTITY` +* For Customers use entity code `Magento\Customer\Model\Customer::ENTITY` +* For Customer Groups use entity code `Magento\Customer\ModelGroup::ENTITY` +* For Customer Address use entity code `Magento\Customer\Model\Indexer\Address\AttributeProvider::ENTITY` + +[More info on Data Patches in Section 4.2](#h.8i0etxv2dw9i) + +![Magento Exam Information](./images/icon-info.png)For products you can also [manually create attributes via the admin](https://docs.magento.com/user-guide/stores/attributes-product.html&sa=D&ust=1609223265035000&usg=AOvVaw0p75AzVbovNy8DnDVQaMh8) in Stores > Attributes > Product + +![Magento Exam Information](./images/icon-info.png)For customers you can also [manually create attributes via the admin](https://docs.magento.com/user-guide/stores/attributes-customer.html&sa=D&ust=1609223265035000&usg=AOvVaw31283qtMpVdnNfLYT466ui) in Stores > Attributes > Customer (Magento Enterprise Commerce feature only). + +![Magento Exam Note Warning](images/icon-warning.png)Setup scripts are applied to create or modify an attributes, however this has become deprecated as of Magento 2.3 + +![Magento Section Chevron](./images/icon-chevron.png)Modifying an existing attribute + +> Much like creating an attribute (above), use a Data Patch with the same injected class: + +```php +Magento\Eav\Setup\EavSetupFactory->create()->updateAttribute(string $entityType, string $attributeCode, string $attributeField, mixed $value = null, int $sortOrder = null); +``` +[More info on Data Patches in Section 4.2](#h.8i0etxv2dw9i) + +![Magento Exam Information](./images/icon-info.png)For customers you can also [manually modify most attributes via the admin](https://docs.magento.com/user-guide/stores/attributes-customer.html&sa=D&ust=1609223265037000&usg=AOvVaw3nuq9nltxOteADYAxh-H1F) in Stores > Attributes > Customer (Magento Enterprise Commerce feature only). However, properties such as: frontend_class & attribute_code ARE NOT editable after creation. + +![Magento Exam Information](./images/icon-info.png)For products you can [manually modfiy most attributes via the admin](https://docs.magento.com/user-guide/stores/attributes-product.html&sa=D&ust=1609223265037000&usg=AOvVaw2yFkHEc2Lva4xKVBXLJ5xL) in Stores > Attributes > Product. However, properties such as: input_type, attribute_code, sort_order, used_in_forms ARE NOT editable after creation. + +![Magento Exam Note Warning](images/icon-warning.png)Setup scripts are applied to create or modify an attributes, however this has become deprecated as of Magento 2.3 \ No newline at end of file diff --git a/6. Developing with Adminhtml/1. Describe common structure architecture.md b/6. Developing with Adminhtml/1. Describe common structure architecture.md deleted file mode 100644 index a914d26..0000000 --- a/6. Developing with Adminhtml/1. Describe common structure architecture.md +++ /dev/null @@ -1,146 +0,0 @@ -# Describe common structure/architecture - -## Some URLs in backend start with "admin/admin/.." - 2 times admin, some only 1 - "admin/...". How? - -Sample router: -```xml - - - - - - - - -``` - -Sample menu items: -- menu add action="adminhtml/system_design_theme" - http://example.com/admin/admin/system_design_theme/index/key/$secret/ - \Magento\Theme\Controller\Adminhtml\System\Design\Theme\Index - * /admin/admin/system_design_theme/index - * App\AreaList.getCodeByFrontName('admin') - * \Magento\Backend\App\Area\FrontNameResolver::getFrontName - * default front name = deployment config `backend/frontName` - from env.php - * if config `admin/url/use_custom_path`, admin front name = config `admin/url/custom_path` - -- menu add action="theme/design_config" - http://example.com/admin/theme/design_config/index/key/$secret/ - \Magento\Theme\Controller\Adminhtml\Design\Config\Index - -Fun fact: same "Content > Design > Themes" link can open by 2 different URLs: -- http://example.com/admin/admin/system_design_theme/index/key/82c8...6bfa/ -- http://example.com/admin/theme/system_design_theme/index/key/0ed6...6c75/ - -First link "hides" behind Magento_Backend original route "adminhtml"="admin" frontName. -Second link has own frontName="theme" and is more beautiful. - - -Admin app flow: -- bootstrap.run(application) -- application.launch -- front controller.dispatch -- admin routerList: - * `admin` - * `default` -- Magento\Backend\App\Router.match - same as frontend, but admin parses *4* sections: - * parseRequest - + `_requiredParams = ['areaFrontName', 'moduleFrontName', 'actionPath', 'actionName']` - + "admin/admin/system_design_theme/index" - -`router id="admin"` - -Admin router - Base, same as frontend, BUT: -- `_requiredParams = ['areaFrontName', 'moduleFrontName', 'actionPath', 'actionName']` - *4* sections, rest is params. - Example: "kontrollpanel/theme/design_config/index/key/3dd89..7f6e/": - * moduleFrontName = "theme", actionPath = "design_config", actionName = "index", - * parameters = [key: 3dd89..7f6e] - * fromt name "theme" - search adminhtml/routers.xml for ....? - Example 2 "/kontrollpanel/admin/system_design_theme/index/key/4bd2...13/: - * moduleFrontName = "admin", actionPath = "system_design_theme", actionName = "index" - * parameters = [key: 4bd2...13] - -- `pathPrefix = 'adminhtml'` -- all controller classes are searched in `Module\Controller\Adminhtml\...` - -When in *adminhtml area*, ALL controllers will reside in Controller/Adminhtml/ modules path. - -Module_Backend registers router `` => ``. -This means that `getUrl("adminhtml/controller/action")`: -- Magento\Backend\Model\Url adds prefix `$areaFrontName/` in `_getActionPath` -- *route name* "adminhtml", frontName will always be "admin/" -- default controllers search module is *Magento_Backend* -- declaring other modules before "Magento_Backend" you risk overriding some system backend controllers: - + ajax/translate, auth/login, dashboard/index, index/index, system/store etc. - -You can in turn *register own router* `` => ``. -You will generate links with `getUrl("theme/controller/action")`: -- backend URL model._getActionPath adds prefix areaFrontName -- *route name* "theme", frontName "theme" -- controlles are searched *only in your module* Module_Alias, no interference with Magento_Backend controllers. - -### What does `router id="admin"`, `router id="standard"` mean in routes.xml? -```xml - - - - ... - - - - - ... - - - -``` -We know that frontend and admin areas main *router* is almost the same - \Magento\Framework\App\Router\Base. -It simply finds *routes* by matching `frontName`. - -Key difference is matched *area* in *area list* defined `router` name: -- `adminhtml` area - `admin` *router*. -- `frontend` area - `standard` *router*. -All routes and frontNames are read only from router by ID. - -This means that admin routes always lie in etc/adminhtml/routes.xml and have `router id="admin"`. -Frontend routes - etc/frontend/routes.xml and always have `router id="standard"`. -This looks like redundancy to me. - -*Admin controller load summary (one more time)*: -- match backend route name -- match adminhtml area, router = 'admin' -- load adminhtml configuration -- adminhtml/di.xml - routerList = [admin, default] -- admin router = extends frontend Base router -- parse params - 4 parts: [front area from env.php, frontName, controllerPath, actionName] -- search routes.xml/router id="admin"/route by frontName=$frontName - second parameter -- matched route has 1+ ordered module names, e.g. [Module_Theme, Module_Backend] -- search controller class in each Module_Name/Controller/Adminhtml/[Controller/Path]/[ActionName] - - -*URL generation*: - -In *frontend* URLs are generated with `\Magento\Framework\Url.getUrl`: -- getUrl() -> createUrl() = getRouterUrl + ?query + #fragment -- getRouterUrl() = getBaseUrl() + `_getRoutePath` -- getBaseUrl() example 'http://www.example.com/' -- `_getRoutePath` = `_getActionPath` + params pairs. For example: 'catalog/product/view/id/47' -- `_getActionPath` example: 'catalog/product/view' - -In *backend* use customized version `\Magento\Backend\Model\Url`: -- `_getActionPath` *prepends* area front name, e.g. 'kontrollpanel/catalog/product/view'. - Now all backend links have this prefix. -- getUrl() adds secret key /key/hash -- can disable secret key generation with turnOffSecretKey() and revert with turnOnSecretKey(). - Global admin config `admin/security/use_form_key`. - -*Secret key* = `hash($routeName . $controllerName . $actionName . $sessionFormKey)` - - - - -## Describe the difference between Adminhtml and frontend. What additional tools and requirements exist in the admin? -- ACL permissions - `_isAllowed`, change static const `ADMIN_RESOURCE` -- base controller Magento\Backend\App\Action = Magento\Backend\App\AbstractAction -- custom *URL model* - secret key `key` -- layout `acl` block attribute -- ... diff --git a/6. Developing with Adminhtml/2. Define form and grid widgets.md b/6. Developing with Adminhtml/2. Define form and grid widgets.md deleted file mode 100644 index a3a863b..0000000 --- a/6. Developing with Adminhtml/2. Define form and grid widgets.md +++ /dev/null @@ -1,493 +0,0 @@ -# Define form and grid widgets - -## UI Component generation - -UI component scheme - `urn:magento:module:Magento_Ui:etc/ui_configuration.xsd` -Magento_Ui, name just like page_configuration - -Generation flow: -- \Magento\Framework\View\Layout\Generator\UiComponent::generateComponent - create, prepare, wrap -- create component \Magento\Framework\View\Element\UiComponentFactory::create - * \Magento\Framework\View\Element\UiComponentFactory::mergeMetadata -- components definitions in array format + dataProvider.getMeta - + metadata = ['cert_form' => ['children' => dataProvider.getMeta]] - * for each child[], \Magento\Framework\View\Element\UiComponentFactory::createChildComponent recursively: - + create PHP class for component, e.g. `new Magento\Ui\Component\DataSource($components = [])`; - * create PHP class for component, e.g. `new Magento\Ui\Component\Form($components = [children...])`; -- prepare component recursively - * getChildComponents[].prepare - update data, js_config etc. -- wrap to *UiComponent\Container* as child 'component' - * toHtml = render -> renderEngine->render (template + '.xhtml') - -## AbstractComponent - -*component PHP class common arguments:* -- name -- template -> .xhtml -- layout: generic - default/tabs. DI config Magento\Framework\View\Layout\Pool -- config: - * value -- js_config: - * component - JS component class - * extends - by default context.getNamespace - top level UI component instance name, e.g. 'cms_block_form' - * provider - context.addComponentDefinition - adds to "types" -- actions - context.addActions. Broken? -- html_blocks - context.addHtmlBlocks -- buttons - context.addButtons -- observers - -*What is the difference between component data `config`, `js_config` and others?* -- looks like js_config is special - goes to `types` definition -- config - normal JS component config overriding `defaults`? -- other values - only for PHP component class usage? - -#### `provider`, `extends` -- PHP Component.getJsConfig - all components automatically get `data.js_config.extends = [top level ui component name]`. -- PHP Component.`prepare`: - * every component data.js_config is registered in `types` by constant type (_see below_). - Definitions for multiple same types (component `column` occurres many times) are merged. - * when data.js_config.`provider` is set: - + `extends` is removed. This is TOP LEVEL component. - + this TOP LEVEL compoment data.js_config is registered by personal name - e.g. "cms_block_form" instead of "form". - -This makes sense - ALL components inherit same top level `provider` via `extends`. - -Example: -``` -"Magento_Ui/js/core/app": { - "types": { - // [component constant type]: [data.js_config], - // TOP LEVEL specific js_config and required provider - cms_block_form: { - provider: 'cms_block_form_data_source' - }, - // below are generic types by constant type - form: { - extends: 'cms_block_form' - }, - fieldset: { - component: 'Magento_Ui/js/form/components/fieldset', - extends: 'cms_block_form' - }, - input: { - extends: 'cms_block_form' - } - - }, - "components": ... -} -``` - -## DataSource, DataProvider - -DataProvider interface is NOT in UI module, but in framework. -Magento\Framework\View\Element\Uicomponent\DataProvider\DataProviderInterface: -- getName - why??? -- getData - [items, totalItems] -- setConfig, getConfigData -- addFilter, addOrder, setLimit -- getPrimaryFieldName - used e.g. to delete row by ID -- getRequestFieldName ??? -- getMeta -- extends/overrides ui component structure - converted array. - Meta format example: - - ``` - [ - '' => [ - attributes => [ - class => 'Magento\Ui\Component\Form', - name => 'some_name', - ], - arguments => [ - data => [ - js_config => [ - component => 'Magento_Ui/js/form/form', - provider => 'cms_block_form.block_form_data_source', - ], - template => 'templates/form/collapsible', - ], - ], - children => [ ...nested components... ] - ] - ] - ``` -- getFieldMetaInfo, getFieldsMetaInfo, getFieldsetMetaInfo -- wtf? -- getSearchCriteria, getSearchResult ??? - -You don't have to implement data provider from scratch, choose from 2 implementations: -- in framework next to interface - `Magento\Framework\View\Element\UiComponent\DataProvider\DataProvider`. - This provider sets collects all input filters and sorts into *search criteria*. - Data is returned like `searchResult = $this->reporting->search($searchCriteria)`. Nice and tidy. - * static properties are passed in ui component XML in `listing/dataSource/dataProvider/settings`: - + name ??? - `[YourComponentName]_data_source` - + primaryFieldName, e.g. `entity_id` - + requestFieldName, e.g. `id` - * addFilter, addOrder, setLimit - proxy to search criteria builder - - But who will process our searchCriteria? - * `\Magento\Framework\View\Element\UiComponent\DataProvider\Reporting` is responsible for - returning SearchResult by SearchCriteria. - * `\Magento\Framework\View\Element\UiComponent\DataProvider\CollectionFactory` creates collection - instance by *data provider name*. - * You register your collection as DI arguments for CollectionFactory: - ```xml - - - - Magento\Sales\Model\ResourceModel\Order\Grid\Collection - - - - ``` - -- in Magento_Ui - `\Magento\Ui\DataProvider\AbstractDataProvider` - This provider works directly with *collection*, you define collection in constructor when extending. - * as usual, static properties are passed in ui component XML as arguments or settings: - + name - + primaryFieldName - + requestFieldName - + meta - + data - data['config'] for your own usage, you can pass some settings, preferences etc. - * addFilter = collection.addFieldToFilter - * addOrder = collection.addOrder - * etc... - * getData = collection.toArray - - -## Source of confusion - unknown UI component.xml structure, node names. `definition.map.xml` - -`definition.map.xml` maps what values come from where! This explains custom node names and attributes that are -magically inserted in resulting JS configuration. - -`Magento_Ui/view/base/ui_component/etc/definition.map.xml` - -E.g. component `dataSource` > argument `dataProvider` -> primaryFieldName = `xpath(dataProvider/settings/requestFieldName)` -Example from `definition.map.xml`: - -```xml - - - - - - settings/submitUrl - ... - dataProvider/settings/clientConfig - @provider - ... - - - settings/deps - - - settings/layout/type - settings/layout/navContainerName - - - dataProvider/@class - dataProvider/@name - ... -``` -And `definition.xml`: -```xml - -``` - -Using definition.map.xml we can deduce full `` definition and what parameter will end up where: -```xml - - - - - - - - - - - - - - - - - - - - - - - - - entity_id - id - - -create(`SomeDataProviderClass`, [ - 'name' => 'some_name', - 'primaryFieldName' => 'entity_id', - 'requestFieldName' => 'id', - 'data' => [ - 'config' => [ - 'submit_url' => '', - 'validate_url' => '', - 'update_url' => '', - 'filter_url_params' => '', - 'clientConfig' => '', - 'provider' => '', - 'component' => '', - 'template' => '', - 'sortOrder' => '', - 'displayArea' => '', - 'storageConfig' => '', - 'statefull' => '', - 'imports' => '', - 'exports' => '', - 'links' => '', - 'listens' => '', - 'ns' => '', - 'componentType' => '', - 'dataScope' => '', - 'aclResource' => '', - ], - 'js_config' => [ - 'deps' => [], - ], - 'layout' => [ - 'type' => '', - 'navContainerName' => '', - ], - ], -]); -$dataSource = $objectManager->create('Magento\Ui\Component\DataSource', [ - 'dataProvider' => $dataProvider, -]); -``` - -With this sacred knowledge we can add/override data provider parameters explicitly in addition to normal config: -```xml - - - - - - - - - - ... - - -``` - -Okay, ui component *data source* doesn't get any configuration, it all goes to *data provider* - 'submit_url' etc. -Data provider can only return data and modify meta, how does it pass these JS values for JS components? - -Here's how: -- View\Layout\Generic::build (initial page open) or - View\Element\UiComponent\ContentType\Json::render (load AJAX data) -- context.getDataSourceData -- merges - * component.getDataSourceData - * data provider.getConfigData -- returns data['config'] -- all the params we set as arguments - - -## UI Form - -*Caveats:* -- default form data.template is `templates/form/default`. But its spinner works only with `tabs` layout. - `
`. - -Magic unraveled: -- see definition.xml `` -- \Magento\Ui\Component\Form\Field::prepare - creates new child ui component `wrappedComponent` - with type = `$this->getData('config/formElement')`. -- how to set config/formElement? See definition.map.xml, search for `component name="field"`: - `@formElement`. - -`formElement` creates arbitrary ui component and inherits same data as main `field`. - -*Suitable `formElement` types:* -- input -- textarea -- fileUploader -- date -- email -- wysiwyg -- checkbox, prefer radio/toggle - ```xml - - - - - 0 - 1 - - toggle - - - - ``` -- select: - ```xml - - - - ``` -- multiselect - - -## UI Listing -`listing` structure: -- argument data.js_config.provider -- `dataSource` - * `dataProvider` -- `filterbar` - * `filters` - * `bookmark` - * `export` - * `massaction` -- `columns` - * `column` - - -- `listing` - just container, server template 'templates/listing/default.xhtml' -- `columns` - no server processing, all juice here: - * JS component Magento_Ui/js/grid/listing -- `column` - * JS component Magento_Ui/js/grid/columns/column - * don't forget to include config/dataType = settings/dataType - - -Implementing listing checklist: -- set settings/spinner = "columns" - * Listing template 'templates/listing/default.xhtml' has spiner `data-component="{{getName()}}.{{getName()}}.{{spinner}}"` - * `columns` js component - `Magento_Ui/js/grid/listnig` - hides data when loaded - `loader.get(this.name).hide()`. -- dataSource must specify provider in order to load data propertly - `` - This provider handles loading data via AJAX. -- set `dataSource/settings/updateUrl` = `mui/index/render`. This will return grid data in JSON format. - When this parameter is missing, AJAX is made to current URL - -```xml - - - - - -``` - -DataType: -- text - default - -Listing toolbar: -```xml - - - - - - - - -