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