Combined MD
Combined MD
md
Page 1 of 128
*How would you extend the customer entity using the extension attributes mechanism?*
*How would you add another field into the customer address?*
>
>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]([Link]
components/modules/mod_intro.html)
>
>The Magento Framework provides a set of core logic: PHP code, libraries, and the basic
functions that are inherited by the modules and other components. - [Magento DevDocs -
Module overview]([Link]
components/modules/mod_intro.html)
Modules can be installed with Composer allowing their version management. All modules
installed in such way are placed in the `vendor/` folder and have next base structure
`vendor/<vendor>/<type>-<module-name>`. In this case `<type>` can be:
1. `module` - Magento module
1. `theme` - admin or frontend themes
1. `language` - language packs
In a case when you have a very specific functionality or customisation which is related
to a specific project and there is no need to share it with other projects it should be
created in the `app/code/<vendor>/<type>-<module-name>` directory and the required
directories within it.
>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]([Link]
[Link])
>Each component must have a file called [Link] in its root directory. For
example, here is the [Link] file for Magento’s AdminNotification module.
Depending on the type of component, registration is performed through [Link] by
adding to it as follows: - [Magento DevDocs - Register your component](http://
[Link]/guides/v2.2/extension-dev-guide/build/[Link])
use \Magento\Framework\Component\ComponentRegistrar;
ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Magento_AdminNotification',
__DIR__);
```
use \Magento\Framework\Component\ComponentRegistrar;
ComponentRegistrar::register(ComponentRegistrar::LANGUAGE, '<VendorName>_<packageName>',
__DIR__);
```
use \Magento\Framework\Component\ComponentRegistrar;
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 3 of 128
\Magento\Framework\Component\ComponentRegistrar::register(
\Magento\Framework\Component\ComponentRegistrar::MODULE,
'Magento_Backend',
__DIR__
);
```
```
> Example: Module A declares a dependency upon Module B. Thus, in Module A’s [Link]
file, Module B is listed in the <sequence> list, so that B’s files are loaded before A’s.
Additionally, you must declare a dependency upon Module B in A’s [Link] file.
Furthermore, in the deployment configuration, Modules A and B must both be defined as
enabled. - [Magento DevDocs - Module dependencies]([Link]
architecture/archi_perspectives/components/modules/mod_depend.html)
>You can build dependencies between classes in the application layer, but these classes
must belong to the same module. Dependencies between the modules of the application layer
should be built only by the service contract or the service provider interface (SPI). -
[Magento DevDocs - Module dependency types]([Link]
architecture/archi_perspectives/components/modules/mod_depend.html)
>Modules define which resources are visible and accessible in an area, as well as an
area’s behavior. The same module can influence several areas. For instance, the RMA
module is represented partly in the adminhtml area and partly in the frontend area.
If your extension works in several different areas, ensure it has separate behavior and
view components for each area.
Each area declares itself within a module. All resources specific for an area are located
within the same module as well.
You can enable or disable an area within a module. If this module is enabled, it injects
an area’s routers into the general application’s routing process.
If this module is disabled, Magento will not load an area’s routers and, as a result, an
area’s resources and specific functionality are not available.
>
> -- [Magento DevDocs - Modules and areas]([Link]
architecture/archi_perspectives/components/modules/mod_and_areas.html)
>One of the first things you can do to get started with component development is to
understand and set up the file system. Each type of component has a different file
structure, although all components require certain files.
In addition, you can choose the component root directory to start development. The
following sections have more information. - [Magento DevDocs - About component file
structure]([Link]
prepare_file-[Link])
### Where are the files containing JavaScript, HTML, and PHP located?
- view/frontend/web/js
- view/frontend/[Link]
- view/frontend/layout
- view/frontend/templates
>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]([Link]
extension-dev-guide/build/[Link])
>This location is found in the alternative setups where the composer create-project
command was used to get a Magento 2 metapackage (which downloads the CE or EE code), or a
compressed Magento 2 archive was extracted in order to install Magento.
>
>Any third party components (and the Magento application itself) are downloaded and
stored under the vendor directory. If you are using Git to manage project, this directory
is typically added to the .gitignore file. Therefore, we recommend you do your
customization work in app/code, not vendor.
>
> -- [Magento DevDocs - Create your component file structure]([Link]
guides/v2.2/extension-dev-guide/build/[Link])
>`[Link]`: 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 <Magento root dir>/vendor directory. For more
information, see Component registration.
>
>`etc/[Link]`: 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.
>
>`[Link]`: Specifies component dependencies and other metadata. For more
information, see Composer integration.
>
> -- [Magento DevDocs - About component file structure]([Link]
guides/v2.2/extension-dev-guide/prepare/prepare_file-[Link])
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 7 of 128
Class [Magento\Framework\Module\ModuleList\Loader]([Link]
blob/2.3/lib/internal/Magento/Framework/Module/ModuleList/[Link]#L78) load `etc/
[Link]` files and sort modules by sequence.
The sequence use for sorting events, plugins, preferences and layouts.
> The service contract of a module is defined by the set of interfaces in the module’s `/
Api` directory.
>
> This directory contains:
>
> * Service interfaces in the `Api` namespace of the module ([Catalog API](https://
[Link]/magento/magento2/tree/2.0/app/code/Magento/Customer/Api)).
>
> * Data (or entity) interfaces in the `Api/Data` directory ([Catalog API/Data](https://
[Link]/magento/magento2/tree/2.0/app/code/Magento/Customer/Api/Data)). Data entities*
are data structures passed to and returned from service interfaces. Files in the data
directory contain get() and set() methods for entries in the entity table and extension
attributes.
>
> -- [Service contract anatomy]([Link]
archi_perspectives/service_layer.html#service-contract-anatomy)
├── [Link]
├── etc
│ └── [Link]
├── i18n
│ └── en_US.csv
├── LICENSE_AFL.txt
├── [Link]
├── media
│ └── [Link]
├── [Link]
└── web
├── css
│ ├── [Link]
│ ├── [Link]
│ ├── source
│ │ ├── _actions-[Link]
│ │ ├── _breadcrumbs.less
│ │ ├── _buttons.less
│ │ ├── components
│ │ │ └── _modals_extend.less
│ │ ├── _icons.less
│ │ ├── _layout.less
│ │ ├── _theme.less
│ │ ├── _tooltips.less
│ │ ├── _typography.less
│ │ └── _variables.less
│ ├── _styles.less
│ ├── [Link]
│ └── [Link]
├── images
│ └── [Link]
└── js
├── [Link]
├── [Link]
└── [Link]
```
#### [\Magento\Framework\Config\Reader\Filesystem]([Link]
blob/2.3/lib/internal/Magento/Framework/Config/Reader/[Link]) ->
[\Magento\Framework\Config\ReaderInterface]([Link]
lib/internal/Magento/Framework/Config/[Link])
Gets .xsd names from schema locator, gets full .xml file list from file resolver, merges
all files, validates, runs converter to get resulting array.
- read(scope)
+ `fileResolver->get(_filename)`
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 10 of 128
#### [\Magento\Framework\Config\ConverterInterface]([Link]
blob/2.3/lib/internal/Magento/Framework/Config/[Link])
Convert an array to any format
- `convert(\DOMDocument $source)`
#### [\Magento\Framework\Config\SchemaLocatorInterface]([Link]
magento2/blob/2.3/lib/internal/Magento/Framework/Config/[Link]) -
full path to .xsd
- `getPerFileSchema` - per file before merge
- `getSchema` - merged file
#### [\Magento\Framework\Config\ValidationStateInterface]([Link]
magento2/blob/2.3/lib/internal/Magento/Framework/Config/[Link])
[\Magento\Framework\App\Arguments\ValidationState]([Link]
blob/2.3/lib/internal/Magento/Framework/App/Arguments/[Link]) is default
implementation, that require validation only in developer mode.
#### [\Magento\Framework\Config\ScopeListInterface]([Link]
blob/2.3/lib/internal/Magento/Framework/Config/[Link])
#### [\Magento\Framework\Config\Data]([Link]
internal/Magento/Framework/Config/[Link]) -> [\Magento\Framework\Config\DataInterface]
([Link]
[Link])
>Nodes in configuration files are merged based on their fully qualified XPaths, which has
a special attribute defined in $idAttributes array declared as its identifier. This
identifier must be unique for all nodes nested under the same parent node.
>
> * If node identifiers are equal (or if there is no identifier defined), all underlying
content in the node (attributes, child nodes, and scalar content) is overridden.
> * If node identifiers are not equal, the node is a new child of the parent node.
> * If the original document has multiple nodes with the same identifier, an error is
triggered because the identifiers cannot be distinguished.
> * After configuration files are merged, the resulting document contains all nodes from
the original files.
>
> -- [Magento DevDocs - Module configuration files]([Link]
v2.2/config-guide/config/[Link])
This scope is a very huge part which includes a lot of things and there is a short list
of useful links
to the official Magento DevDocs documentation:
```xml
<type name="Magento\Config\Model\Config\TypePool">
<arguments>
<!-- sensitive config items -->
<argument name="sensitive" xsi:type="array">
<item name="payment/paypal_express/merchant_id" xsi:type="string">1</item>
<!-- keys, password, emails, personally identifiable information -->
</argument>
<!-- environment specific config items -->
<argument name="environment" xsi:type="array">
<item name="payment/paypal_express/debug" xsi:type="string">1</item>
<!-- URLs, IPs, hosts, modes sandbox/live, email recipients -->
</argument>
</arguments>
</type>
```
Sensitive info doesn't get exported with `bin/magento app:config:dump`. use env. params,
e.g.
`CONFIG__DEFAULT__PAYMENT__TEST__PASWORD` for `payment/test/password`
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 12 of 128
`bin/magento app:config:dump`:
`bin/magento config:sensitive:set`:
- writes to app/etc/[Link]
# Demonstrate how to use dependency injection
Magento 2 uses constructor injection, where all injected objects or factories are
provided when the object is constructed.
Magento loads [Link] files and merges them all together from the following stages:
* Initial (app/etc/[Link])
* Global ({moduleDir}/etc/[Link])
* Area-specific ({moduleDir}/etc/{area}/[Link])
[AbstractFactory]([Link]
Framework/ObjectManager/Factory/[Link]) >
[\Magento\Framework\ObjectManager\FactoryInterface]([Link]
blob/2.3/lib/internal/Magento/Framework/ObjectManager/[Link])
and their implementations use to resolve arguments and create new objects.
By default, all objects created via automatic constructor dependency injection are
singleton objects,
because they created via ObjectManager::get() method.
```
if ($isShared) {
$argument = $this->objectManager->get($argumentType);
} else {
$argument = $this->objectManager->create($argumentType);
}
```
[\Magento\Framework\ObjectManager\Factory::resolveArgument()]([Link]
magento2/blob/2.3/lib/internal/Magento/Framework/ObjectManager/Factory/
[Link]#L143-L147)
### Arguments
```
<argument xsi:type="object">{typeName}</argument>
<argument xsi:type="object" shared="{shared}">{typeName}</argument>
<argument xsi:type="string">{strValue}</argument>
<argument xsi:type="string" translate="true">{strValue}</argument>
<argument xsi:type="boolean">{boolValue}</argument>
<argument xsi:type="number">{numericValue}</argument>
<argument xsi:type="init_parameter">{Constant::NAME}</argument>
<argument xsi:type="const">{Constant::NAME}</argument>
<argument xsi:type="null"/>
<argument xsi:type="array">
<item name="someKey" xsi:type="<type>">someVal</item>
</argument>
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 13 of 128
```
###### Links
- [Dependency injection]([Link]
[Link])
- [The [Link] file]([Link]
[Link])
- [ObjectManager]([Link]
[Link])
- [Proxies]([Link]
- [Factories]([Link]
- [Alan Storm, The Magento 2 Object System]([Link]
#magento-2-object-system)
- [Alan Kent, Magento 2 Dependency injection]([Link]
dependency-injection-the-m2-way-to-replace-api-implementations/)
# Demonstrate ability to use plugins
There are three types of plugins in magento: around, before and after.
### Limitations
The `sortOrder` property controls how your plugin interacts with other plugins on the
same class.
guide/[Link]#prioritizing-plugins)
Plugin sortOrder:
Example:
> The greatest weakness is exploited in the hands of a developer who is either not
> experienced or not willing to take the time to evaluate the fallout. For example,
> used improperly, an around plugin can prevent the system from functioning.
> They can also make understanding what is going on by reading source code hard
> (spooky action at a distance).
>
> -- [Swiftotter Developer Study Guide]([Link]
magento-2-certified-developer-study-guide)
> Plugins are useful to modify the input, output, or execution of an existing method.
> Plugins are also best to be avoided in situations where an event observer will work.
> Events work well when the flow of data does not have to be modified.
>
> -- [Swiftotter Developer Study Guide]([Link]
magento-2-certified-developer-study-guide)
event observer. Instead, plugins SHOULD BE used for modifying the input or output of a
function.
>
> -- [Magento DevDocs - Technical guidelines]([Link]
coding-standards/[Link])
Also check:
- [Yireo - Magent 2 observer or Plugin?]([Link]
observer-or-plugin)
- [Magento DevDocs - Observers Best Practices]([Link]
ext-best-practices/extension-coding/[Link])
Magento automatically generates `Interceptor` class for the plugin target and store it in
the `generated\code` directory.
```
namespace \Notes;
[\Magento\Framework\Interception\Interceptor]([Link]
2.3/lib/internal/Magento/Framework/Interception/[Link]):
- $pluginList = [\Magento\Framework\Interception\PluginListInterface]([Link]
magento/magento2/blob/2.3/lib/internal/Magento/Framework/Interception/
[Link])
- $subjectType = 'MyBeautifulClass'
- `___init` - called in in constructor, pluginList = get from object manager, subjectType
= class name
- pluginList->getNext
- `___callPlugins`
- `___callParent`
\Magento\Framework\App\Bootstrap::create
\Magento\Framework\App\Bootstrap::__construct
\Magento\Framework\App\ObjectManagerFactory::create
\Magento\Framework\ObjectManager\DefinitionFactory::createClassDefinition
\Magento\Framework\ObjectManager\DefinitionFactory::getCodeGenerator
\Magento\Framework\Code\Generator\Io::__construct
\Magento\Framework\Code\Generator::__construct
spl_autoload_register([new \Magento\Framework\Code\Generator\Autoloader, 'load']);
\Magento\Framework\App\ObjectManagerFactory::create
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 16 of 128
\Magento\Framework\Code\Generator::setGeneratedEntities
\Magento\Framework\App\ObjectManager\Environment\Developer::configureObjectManager
\Magento\Framework\Code\Generator\Autoloader::load
\Magento\Framework\Code\Generator::generateClass
```
Magento\Framework\App\ResourceConnection\Proxy -> type Proxy, name
Magento\Framework\App\ResourceConnection
Magento\Framework\Code\Generator::shouldSkipGeneration - type not detected, or class
exists
\Magento\Framework\Code\Generator::createGeneratorInstance -- new for every file
```
###### Links
- [Magento DevDocs - Plugins (Interceptors)]([Link]
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 17 of 128
extension-dev-guide/[Link])
- [Alan Storm - Magento 2 Object Manager Plugin System]([Link]
magento_2_object_manager_plugin_system/)# Configure event observers and scheduled jobs
## Observers
Best practices:
- Make your observer efficient
- Do not include business logic
- Declare observer in the appropriate scope
- Avoid cyclical event loops
- Do not rely on invocation order
>
> `14. Events`
>
> 14.1. All values (including objects) passed to an event MUST NOT be modified in the
event observer. Instead, plugins SHOULD BE used for modifying the input or output of a
function.
>
> -- [Magento DevDocs - Technical guidelines]([Link]
coding-standards/[Link])
Properties:
- name (required) - Name of the observer for the event definition
- instance (required) - Class name of the observer
- disabled - Is observer active or not (Default: false)
- shared - Class lifestyle (Default: false)
Example:
```xml
<config xmlns:xsi="[Link]
xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/[Link]">
<event name="cms_block_save_before">
<observer name="myObserverName"
instance="MyCompany\MyModule\Observer\MyObserver" />
</event>
<event name="cms_block_save_commit_after">
<observer name="myObserverName2"
instance="MyCompany\MyModule\Observer\AnotherObserver" disabled="true" shared="true"/>
</event>
</config>
```
```php
<?php
Example:
> $this->eventManager->dispatch('cms_page_prepare_save', ['page' => $model, 'request' =>
$this->getRequest()]);
###### Links
- [Magento DevDocs - Events and observers]([Link]
extension-dev-guide/[Link])
- [Magento DevDocs - Observers Best Practices]([Link]
ext-best-practices/extension-coding/[Link])
## Scheduled jobs
A cron group is a logical group that enables you to easily run cron for more than one
process at a time.
Most Magento modules use the default cron group.
```xml
<config xmlns:xsi="[Link]
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Cron:etc/cron_groups.xsd">
<group id="default">
<schedule_generate_every>15</schedule_generate_every>
<schedule_ahead_for>20</schedule_ahead_for>
<schedule_lifetime>15</schedule_lifetime>
<history_cleanup_every>10</history_cleanup_every>
<history_success_lifetime>10080</history_success_lifetime>
<history_failure_lifetime>10080</history_failure_lifetime>
<use_separate_process>0</use_separate_process>
</group>
</config>
```
Existing groups:
- default (no separate process)
- index - mview, targetrule
- catalog_event - catalog_event_status_checker - mark event open/closed
- consumers - consumers_runner if configured to run by cron. `bin/magento
queue:consumers:start`. PID file var/{$consumer}.pid
- staging - staging_apply_version, staging_remove_updates,
staging_synchronize_entities_period
- ddg_automation (dotmailer)
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 19 of 128
##### [Link]
```xml
<config xmlns:xsi="[Link]
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Cron:etc/[Link]">
<group id="GROUP_NAME">
<job name="CRON_JOB_UNIQUE_ID" instance="CLASS" method="METHOD">
<config_path>some/config/path</config_path>
</job>
<job name="indexer_clean_all_changelogs"
instance="Magento\Indexer\Cron\ClearChangelog" method="execute">
<schedule>0 * * * *</schedule>
</job>
</group>
</config>
```
run:
Stack trace:
```
\Magento\Cron\Console\Command\CronCommand::execute
\Magento\Framework\App\Cron::launch
`default` event
\Magento\Cron\Observer\ProcessCronQueueObserver
check for specific group
cleanup
generate
check for standalone process
```
[\Magento\Cron\Model\Config\Data]([Link]
Magento/Cron/Model/Config/[Link]) extends [\Magento\Framework\Config\Data](https://
[Link]/magento/magento2/blob/2.3/lib/internal/Magento/Framework/Config/[Link])
- merges [\Magento\Cron\Model\Config\Reader\Db::get]([Link]
blob/2.3/app/code/Magento/Cron/Model/Config/Reader/[Link]#L51) from Database
Sample DB structure:
```
default/crontab/GROUP/jobs/JOB/schedule/cron_expr = '* * * * *'
default/crontab/GROUP/jobs/JOB/schedule/config_path = 'some/config/path' -- try to read
schedule from this config, store view scope
default/crontab/GROUP/jobs/JOB/run/model = 'class::method'
```
DB config usage example: [ProductAlert, [Link]]([Link]
blob/2.3/app/code/Magento/ProductAlert/etc/adminhtml/[Link]#L38:L45), [ProductAlert,
[Link]]([Link]
etc/[Link]), backend model: [Magento\Cron\Model\Config\Backend\Product\Alert]
([Link]
Product/[Link])
log/[Link]
#~ MAGENTO END 4d557a63fe1eac8a2827a4eca020c6bb
```
what is update/[Link]?
TODO: find out
what is setup:cron:run?
TODO: find out
###### Links
- [Magento DevDocs - Configure a custom cron job and cron group (tutorial)](https://
[Link]/guides/v2.2/config-guide/cron/[Link])
- [Magento DevDocs - Custom cron job and cron group reference](https://
[Link]/guides/v2.2/config-guide/cron/[Link])
### Identify the function and proper use of automatically available events
- `model_load_before`, `{$_eventPrefix}_load_before`
- `model_load_after`, `{$_eventPrefix}_load_after`
- `model_save_commit_after`, `{$_eventPrefix}_save_commit_after`
- `model_save_before`, `{$_eventPrefix}_save_before`
- `model_save_after`, `{$_eventPrefix}_save_after`
- `model_delete_before`, `{$_eventPrefix}_delete_before`
- `model_delete_after`, `{$_eventPrefix}_delete_after`
- `model_delete_commit_after`, `{$_eventPrefix}_delete_commit_after`
- `core_collection_abstract_load_before`, `{_eventPrefix}_load_before`
- `core_collection_abstract_load_after`, `{_eventPrefix}_load_after`
- eav_collection_abstract_load_before
[\Magento\Framework\Model\AbstractModel]([Link]
internal/Magento/Framework/Model/[Link]):
- _eventObject = 'object'
- _eventPrefix = 'core_abstract', e.g. 'catalog_category'
- _getEventData() - 'data_object' + $_eventObject
- `model_load_before` (object, field=null, value=ID)
- `{_eventPrefix}_load_before`, e.g. `catalog_category_load_before` (object, field,
value, data_object, category)
###### Links
- [Cyrill Schumacher's Blog - List of all dispatched events]([Link]
magento-2.2-list-of-all-dispatched-events/)
# Utilize the CLI
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 21 of 128
Commands:
- `bin/magento deploy:mode:show`
- `magento deploy:mode:set {mode} [-s|--skip-compilation]` -- skip compilation when
changing to production
Default:
Developer:
see [generated/metadata]([Link]
[Link]#codegen-om)
* Interceptor generation (optimized code generation of interceptors)
* Interception cache generation
* Repositories code generation (generated code for APIs)
* Service data attributes generation (generated extension classes for data objects)
* [Running setup:di:compile places the app into a special mode]([Link]
[Link]/2017/07/magento-2-run-setupdicompile/)
```php
<?php
namespace Vendor\Module\Console\Command;
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 22 of 128
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
// Positional argument
$this->addArgument(
'myargument',
InputArgument::REQUIRED,
'Positional required argument example'
);
parent::configure();
}
{module}/etc/[Link]:
```xml
<type name="Magento\Framework\Console\CommandListInterface">
<arguments>
<argument name="commands" xsi:type="array">
<item name="exampleHello"
xsi:type="object">Vendor\Module\Console\Command\ExampleCommand</item>
</argument>
</arguments>
</type>
```
###### Links
- [Magento DevDocs - How to add CLI commands]([Link]
extension-dev-guide/cli-cmds/[Link])
- [Magento DevDocs - Command naming guidelines]([Link]
extension-dev-guide/cli-cmds/[Link])
- [Symfony Documentation - The Console Component]([Link]
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 23 of 128
[Link])
- [Magento - sample-module-command]([Link]
master/sample-module-command)
# Demonstrate the ability to manage the cache
### Describe cache types and the tools used to manage caches.
- config
- layout
- block_html
- collections
- db_ddl
- eav
- full_page
- reflection
- translate
- config_integration
- config_integration_api
- config_webservice
Description for most cache type can be found on the [Magento DevDocs - Manage the cache]
([Link]
[Link]#config-cli-subcommands-cache-clean-over)
Commands:
- `magento setup:db-schema:upgrade`
- `magento cache:status`, `magento cache:enable`, `magento cache:disable`
- `magento cache:clean`, `magento cache:flush`
### Init:
frontend:
```
\Magento\Framework\App\ObjectManager\ConfigLoader::load
cacheType = config, frontend = default
\Magento\Framework\App\Cache\Frontend\Pool::_initialize
\Magento\Framework\App\Cache\Frontend\Factory::create
\Zend_Cache::_makeFrontend
\Zend_Cache_Core::__construct
```
backend:
- \Zend_Cache_Backend
- \Zend_Cache_Backend_File
- \Magento\Framework\Cache\Backend\Database
How do you add dynamic content to pages served from the full page cache?
1. Mark any block `cacheable="false"` in layout xml - whole page is uncacheable. Example -
checkout
1. Disable caching in controller using *headers*:
`$page->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0',
true);`
1. Marking block property isScopePrivate - will be loaded via AJAX - deprecated
1. ESI when Varnish enabled, set TTL - Example - menu block
1. Configure page variations - extend *http context*, more cached versions of same page -
store view, customer group, language, currency, is logged in
`\Magento\Framework\App\Http\Context::getVaryString`
Clear cache
\Magento\Framework\DataObject\IdentityInterface
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 24 of 128
### when giving product page, must somehow send varnish tags
Any block can implement IdentityInterface. After rendering layout and before sending
output,
all blocks are examined for implementing this interface. Cache tags are collected as
merge of all blocks
getIdentities() tags.
```
\Magento\PageCache\Model\Layout\LayoutPlugin::afterGetOutput
X-Magento-Tags = merge(all [Link])
```
block ListProduct:
- every product[].getIdentities
- cat_p_{productId}
- *if changed categories* - cat_p_c_{categoryId}
- *if changed status* - every category[] cat_p_c_{categoryId}
- *if frontend* - 'cat_p'
- cat_c_p_{categoryId}
block product/view:
- \Magento\Catalog\Model\Product::getIdentities:
- cat_p_{productId}
- *if changed categories* - cat_p_c_{categoryId}
- *if changed status* - every category[] cat_p_c_{categoryId}
- *if frontend* - 'cat_p'
- *if current_category* - cat_c_{categoryId}
plugin \Magento\Indexer\Model\Processor:
\Magento\Indexer\Model\Processor\CleanCache::afterUpdateMview
- event `clean_cache_after_reindex`
- clean cache cacheContext->getIdentities()
\Magento\Indexer\Model\Processor\CleanCache::afterReindexAllInvalid
- event `clean_cache_by_tags`
- clean cache cacheContext->getIdentities()
How would you clean the cache? In which case would you refresh cache/flash cache storage?
> To purge out-of-date items from the cache, you can clean or flush cache types:
>
> - Cleaning a cache type deletes all items from enabled Magento cache types only. In
other words, this option does not affect other processes or applications because it
cleans only the cache that Magento uses.
>
> Disabled cache types are not cleaned.
>
> - Flushing a cache type purges the cache storage, which might affect other processes
applications that are using the same storage.
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 25 of 128
>
> Flush cache types if you’ve already tried cleaning the cache and you’re still having
issues that you cannot isolate.
>
> -- [Magento DevDocs - Manage the cache]([Link]
guide/cli/[Link]#config-cli-subcommands-cache-clean-over)
Sessions and caching data should never be stored in one database in Redis. In that case
you'll not have problems with flushing the cache.
To clear the cache programmatically you neeed to call next the methods:
- [\Magento\Framework\App\CacheInterface::remove($identifier)]([Link]
magento2/blob/2.3/lib/internal/Magento/Framework/App/[Link]#L48) - remove
cached data by identifier
- [\Magento\Framework\App\CacheInterface::clean($tags = [])]([Link]
magento2/blob/2.3/lib/internal/Magento/Framework/App/[Link]#L56) - clean
cached data by specific tag
##### What mechanisms are available for clearing all or part of the cache?
Dispatch a `clean_cache_by_tags` event with parameter of the object you want to clear
from the cache.
Example: [\Magento\Framework\Model\AbstractModel]([Link]
blob/2.3/lib/internal/Magento/Framework/Model/[Link]#L817) (afterSave,
afterDelete methods)
```php
<?php
public function afterSave()
{
$this->cleanModelCache();
$this->_eventManager->dispatch('model_save_after', ['object' => $this]);
$this->_eventManager->dispatch('clean_cache_by_tags', ['object' => $this]);
$this->_eventManager->dispatch($this->_eventPrefix . '_save_after', $this-
>_getEventData());
$this->updateStoredData();
return $this;
}
###### Links
- [Magento DevDocs - Magento cache overview]([Link]
frontend-dev-guide/cache_for_frontdevs.html)
- [Magento DevDocs - Configure caching]([Link]
guide/[Link])
- [Magento DevDocs - Partial caching]([Link]
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 26 of 128
dev-guide/cache/[Link])
- [Magento DevDocs - Full Page caching]([Link]
dev-guide/cache/[Link])
- [Magento DevDocs - Private content]([Link]
dev-guide/cache/page-caching/[Link])
# 1-Magento Architecture and Customization Techniques
app/code/*/*/cli_commands.php, [Link]
app/design/*/*/*/[Link]
app/i18n/*/*/[Link]
lib/internal/*/*/[Link]
lib/internal/*/*/*/[Link]
pub/[Link]
app/[Link]
app/[Link]
vendor/[Link]
vendor/module[]/[Link] -- last step in Composer init
Magento\Framework\Component\ComponentRegistrar::register(type='module',
name='Prince_Productattach', path='/var/www/...')
- app/code/Vendor/Module
- app/design/frontend/VendorName/theme_name_lowercase
- app/i18n/VendorName/en_US
- vendor/vendor-name/module-theme-package/
Where are the files containing JavaScript, HTML, and PHP located?
- view/frontend/web/js
- view/frontend/[Link]
- view/frontend/layout
- view/frontend/templates
Determine how to use configuration files in Magento. Which configuration files correspond
to different features and
functionality?
- read(scope)
+ fileResolver->get(_filename)
+ merge and validate each file (if mode developer)
+ validate merged dom
+ converter->convert(dom) => array
- _idAttributes, _fileName, _schemaFile (from schemaLocator), _perFileSchema (from
schemaLocator), filename ([Link])
- schemaFile from schemaLocator
### \Magento\Catalog\Model\ProductTypes\Config\Converter,
\Magento\Framework\Config\ConverterInterface - array in any format
- convert(\DOMDocument $source)
```
<type name="Magento\Config\Model\Config\TypePool">
<arguments>
<!-- sensitive config items -->
<argument name="sensitive" xsi:type="array">
<item name="payment/paypal_express/merchant_id" xsi:type="string">1</item>
<!-- keys, password, emails, personally identifiable information -->
</argument>
<!-- environment specific config items -->
<argument name="environment" xsi:type="array">
<item name="payment/paypal_express/debug" xsi:type="string">1</item>
<!-- URLs, IPs, hosts, modes sandbox/live, email recipients -->
</argument>
</arguments>
</type>
```
sensitive info doesn't get exported with `bin/magento app:config:dump`. use env. params,
e.g.
`CONFIG__DEFAULT__PAYMENT__TEST__PASWORD` for `payment/test/password`
`bin/magento app:config:dump`:
`bin/magento config:sensitive:set`:
- writes to app/etc/[Link]
```
<argument xsi:type="object">{typeName}</argument>
<argument xsi:type="object" shared="{shared}">{typeName}</argument>
<argument xsi:type="string">{strValue}</argument>
<argument xsi:type="string" translate="true">{strValue}</argument>
```
* Initial (app/etc/[Link])
* Global (<moduleDir>/etc/[Link])
* Area-specific (<moduleDir>/etc/<area>/[Link])
```
class MyBeautifulClass
{
use \Magento\Framework\Interception\Interceptor;
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 29 of 128
Magento\Framework\Interception\Interceptor:
- $pluginList = \Magento\Framework\Interception\PluginListInterface
- $subjectType = 'MyBeautifulClass'
- `___init` - called in in constructor, pluginList = get from object manager, subjectType
= class name
- pluginList->getNext
- `___callPlugins`
- `___callParent`
\Magento\Framework\App\Bootstrap::create
\Magento\Framework\App\Bootstrap::__construct
\Magento\Framework\App\ObjectManagerFactory::create
\Magento\Framework\ObjectManager\DefinitionFactory::createClassDefinition
\Magento\Framework\ObjectManager\DefinitionFactory::getCodeGenerator
\Magento\Framework\Code\Generator\Io::__construct
\Magento\Framework\Code\Generator::__construct
spl_autoload_register([new \Magento\Framework\Code\Generator\Autoloader, 'load']);
\Magento\Framework\App\ObjectManagerFactory::create
\Magento\Framework\Code\Generator::setGeneratedEntities
\Magento\Framework\App\ObjectManager\Environment\Developer::configureObjectManager
\Magento\Framework\Code\Generator\Autoloader::load
\Magento\Framework\Code\Generator::generateClass
```
Magento\Framework\App\ResourceConnection\Proxy -> type Proxy, name
Magento\Framework\App\ResourceConnection
Magento\Framework\Code\Generator::shouldSkipGeneration - type not detected, or class
exists
\Magento\Framework\Code\Generator::createGeneratorInstance -- new for every file
```
Observer sortOrder:
Example:
```
<group id="NAME">
<job name="NAME" instance="CLASS" method="METHOD">
<config_path>some/config/path</config_path>
</job>
<job name="NAME" instance="CLASS" method="METHOD">
<schedule>* * * * *</config_path>
</job>
</group>
```
run:
```
\Magento\Cron\Console\Command\CronCommand::execute
\Magento\Framework\App\Cron::launch
`default` event
\Magento\Cron\Observer\ProcessCronQueueObserver
check for specific group
cleanup
generate
check for standalone process
```
Sample DB structure:
```
default/crontab/GROUP/jobs/JOB/schedule/cron_expr = '* * * * *'
default/crontab/GROUP/jobs/JOB/schedule/config_path = 'some/config/path' -- try to read
schedule from this config, store view scope
default/crontab/GROUP/jobs/JOB/run/model = 'class::method'
```
what is update/[Link]?
TODO: find out
what is setup:cron:run?
TODO: find out
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 32 of 128
### Identify the function and proper use of automatically available events
- `model_load_before`, `{$_eventPrefix}_load_before`
- `model_load_after`, `{$_eventPrefix}_load_after`
- `model_save_commit_after`, `{$_eventPrefix}_save_commit_after`
- `model_save_before`, `{$_eventPrefix}_save_before`
- `model_save_after`, `{$_eventPrefix}_save_after`
- `model_delete_before`, `{$_eventPrefix}_delete_before`
- `model_delete_after`, `{$_eventPrefix}_delete_after`
- `model_delete_commit_after`, `{$_eventPrefix}_delete_commit_after`
- `core_collection_abstract_load_before`, `{_eventPrefix}_load_before`
- `core_collection_abstract_load_after`, `{_eventPrefix}_load_after`
- eav_collection_abstract_load_before
\Magento\Framework\Model\AbstractModel:
- _eventObject = 'object'
- _eventPrefix = 'core_abstract', e.g. 'catalog_category'
- _getEventData() - 'data_object' + $_eventObject
- `model_load_before` (object, field=null, value=ID)
- `{_eventPrefix}_load_before`, e.g. `catalog_category_load_before` (object, field,
value, data_object, category)
Commands:
- `bin/magento deploy:mode:show`
- `magento deploy:mode:set {mode} [-s|--skip-compilation]` -- skip compilation when
changing to production
Default:
Developer:
### Describe cache types and the tools used to manage caches.
- config
- layout
- block_html
- collections
- db_ddl
- eav
- full_page
- reflection
- translate
- config_integration
- config_integration_api
- config_webservice
Commands:
- `magento setup:db-schema:upgrade`
- `magento cache:status`, `magento cache:enable`, `magento cache:disable`
- `magento cache:clean`, `magento cache:flush`
### Init:
frontend:
```
\Magento\Framework\App\ObjectManager\ConfigLoader::load
cacheType = config, frontend = default
\Magento\Framework\App\Cache\Frontend\Pool::_initialize
\Magento\Framework\App\Cache\Frontend\Factory::create
\Zend_Cache::_makeFrontend
\Zend_Cache_Core::__construct
```
backend:
- \Zend_Cache_Backend
- \Zend_Cache_Backend_File
- \Magento\Framework\Cache\Backend\Database
How do you add dynamic content to pages served from the full page cache?
1. Mark any block `cacheable="false"` in layout xml - whole page is uncacheable. Example -
checkout
1. Disable caching in controller using *headers*:
`$page->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0',
true);`
1. Marking block property isScopePrivate - will be loaded via AJAX - deprecated
1. ESI when Varnish enabled, set TTL - Example - menu block
1. Configure page variations - extend *http context*, more cached versions of same page -
store view, customer group, language, currency, is logged in
`\Magento\Framework\App\Http\Context::getVaryString`
Clear cache
\Magento\Framework\DataObject\IdentityInterface
### when giving product page, must somehow send varnish tags
Any block can implement IdentityInterface. After rendering layout and before sending
output,
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 34 of 128
all blocks are examined for implementing this interface. Cache tags are collected as
merge of all blocks
getIdentities() tags.
```
\Magento\PageCache\Model\Layout\LayoutPlugin::afterGetOutput
X-Magento-Tags = merge(all [Link])
```
block ListProduct:
- every product[].getIdentities
- cat_p_{productId}
- *if changed categories* - cat_p_c_{categoryId}
- *if changed status* - every category[] cat_p_c_{categoryId}
- *if frontend* - 'cat_p'
- cat_c_p_{categoryId}
block product/view:
- \Magento\Catalog\Model\Product::getIdentities:
- cat_p_{productId}
- *if changed categories* - cat_p_c_{categoryId}
- *if changed status* - every category[] cat_p_c_{categoryId}
- *if frontend* - 'cat_p'
- *if current_category* - cat_c_{categoryId}
plugin \Magento\Indexer\Model\Processor:
\Magento\Indexer\Model\Processor\CleanCache::afterUpdateMview
- event `clean_cache_after_reindex`
- clean cache cacheContext->getIdentities()
\Magento\Indexer\Model\Processor\CleanCache::afterReindexAllInvalid
- event `clean_cache_by_tags`
- clean cache cacheContext->getIdentities()
How would you clean the cache? In which case would you refresh cache/flash cache storage?
What mechanisms are available for clearing all or part of the cache?
# Utilize modes and application initialization
[\Magento\Framework\App\Bootstrap]([Link]
internal/Magento/Framework/App/[Link])::*create*
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 35 of 128
[\Magento\Framework\App\Bootstrap]([Link]
internal/Magento/Framework/App/[Link])::*createApplication*
[\Magento\Framework\App\Bootstrap]([Link]
internal/Magento/Framework/App/[Link])::*run*
[bootstrap->createApplication()]([Link]
internal/Magento/Framework/App/[Link]#L230)
- [\Magento\Framework\App\Http]([Link]
internal/Magento/Framework/App/[Link]) - [Link], pub/[Link]
load config area by front name
front controller->dispatch
event `controller_front_send_response_before`
- [\Magento\Framework\App\Cron]([Link]
internal/Magento/Framework/App/[Link]) - pub/[Link]
config area `crontab`
load translations
dispatch event `default`
- [\Magento\MediaStorage\App\Media]([Link]
code/Magento/MediaStorage/App/[Link]) - pub/[Link]
access /media/* when using DB image storage and physical file doesn't exist
- [\Magento\Framework\App\StaticResource]([Link]
lib/internal/Magento/Framework/App/[Link]) - pub/[Link]
404 in production
/$area/$resource/$file/... params, load config by params
sends file in response
assetRepo->createAsset - \Magento\Framework\View\Asset\File
assetPublisher->publish - materialize (copy/symlink) file if doesn't exist
- [\Magento\Indexer\App\Indexer]([Link]
Magento/Indexer/App/[Link]) - module-indexer, unused?
- [\Magento\Backend\App\UserConfig]([Link]
code/Magento/Backend/App/[Link]) - module-backend, unused?
Notes:
[\Magento\Framework\App\Http::launch]([Link]
internal/Magento/Framework/App/[Link]#L128)
1. detect config area by front name
```php
<?php
$areaCode = $this->_areaList->getCodeByFrontName($this->_request->getFrontName());
$this->_state->setAreaCode($areaCode);
$this->_objectManager->configure($this->_configLoader->load($areaCode));
```
- frontend = [frontname null, router "standard"] --- *default when nothing matched*
- adminhtml - [frontNameResolver=..., router "admin"]
[\Magento\Backend\App\Area\FrontNameResolver::getFrontName(checkhost)](https://
[Link]/magento/magento2/blob/2.3/app/code/Magento/Backend/App/Area/
[Link]#L83)
system config `admin/url/use_custom`, `admin/url/custom`
- crontab = null
- webapi_rest = [frontName `/rest`]
- webapi_soap = [frontname `/soap`]
1. [ObjectManagerInterface->configure()]([Link]
internal/Magento/Framework/ObjectManager/[Link]#L82) - selected area code
1. result = FrontControllerInterface->dispatch()
1. [ResultInterface->renderResult()]([Link]
internal/Magento/Framework/Controller/[Link]#L122) into response object
1. event `controller_front_send_response_before` (request, response)
### How would you design a customization that should act on every request and capture
output data regardless of the controller?
- event `controller_front_send_response_before`
### [\Magento\Framework\App\FrontController]([Link]
lib/internal/Magento/Framework/App/[Link]):
- routerList
- action = router[].match
- result = [Link]() or [Link]()
- noroute action fallback
- [\Magento\Framework\Controller\ResultInterface]([Link]
blob/2.3/lib/internal/Magento/Framework/Controller/[Link]) - renderResult,
setHttpResponseCode, setHeader
Implementations:
- [Result\Raw]([Link]
Framework/Controller/Result/[Link]) -> Result\AbstractResult
- [Result\Json]([Link]
Framework/Controller/Result/[Link]) -> Result\AbstractResult
- [Result\Forward]([Link]
Framework/Controller/Result/[Link]) -> Result\AbstractResult
- [\Magento\Framework\View\Result\Layout]([Link]
2.3/lib/internal/Magento/Framework/View/Result/[Link]) -> Result\AbstractResult
- [\Magento\Framework\View\Result\Page]([Link]
lib/internal/Magento/Framework/View/Result/[Link]) ->
\Magento\Framework\View\Result\Layout
- [Result\Redirect]([Link]
Framework/Controller/Result/[Link]) -> Result\AbstractResult
- [\Magento\Framework\App\ResponseInterface]([Link]
lib/internal//Magento/Framework/App/[Link]) - sendResponse
Implementations:
- [Console\Response]([Link]
Magento/Framework/App/Console/[Link])
- [\Magento\MediaStorage\Model\File\Storage\FileInterface]([Link]
magento2/blob/2.3/app/code/Magento/MediaStorage/Model/File/Storage/[Link]) ->
\Magento\Framework\App\Response\Http
- [\Magento\Framework\HTTP\PhpEnvironment\Response]([Link]
magento2/blob/2.3/lib/internal/Magento/Framework/HTTP/PhpEnvironment/[Link]) ->
\Zend\Http\PhpEnvironment\Response
- [\Magento\Framework\Webapi\Response]([Link]
lib/internal/Magento/Framework/Webapi/[Link]) ->
\Magento\Framework\HTTP\PhpEnvironment\Response
- [\Magento\Framework\Webapi\Rest\Response]([Link]
2.3/lib/internal/Magento/Framework/Webapi/Rest/[Link]) ->
\Magento\Framework\Webapi\Response
### [\Magento\Webapi\Controller\Rest]([Link]
code/Magento/Webapi/Controller/[Link]) ->
\Magento\Framework\App\FrontControllerInterface:
### [\Magento\Webapi\Controller\Soap]([Link]
code/Magento/Webapi/Controller/[Link]) ->
\Magento\Framework\App\FrontControllerInterface:
### In which situations will the front controller be involved in execution, and how can
it be used in the scope of customizations?
urlBuilder - [\Magento\Framework\UrlInterface]([Link]
2.3/lib/internal/Magento/Framework/[Link]): getUrl
Instances:
- [\Magento\Framework\Url]([Link]
Magento/Framework/[Link])
- [\Magento\Backend\Model\Url]([Link]
Magento/Backend/Model/[Link])
\Magento\Framework\Url\*RouteParamsPreprocessorInterface*::execute
- composite - delegates to many instances
- staging - Preview\RouteParamsPreprocessor
* if frontend and preview, adds `?___version`
* if store code in URL, adds `?___store`
1. run modifier
*getRouterUrl*:
### How do you identify which module and controller corresponds to a given URL?
- first part is route name. Search [Link] for route with matching *ID*
- modules for this ID are sorted with "before" and "after"
- controller/action classes is searched in matched modules
## Describe the URL rewrite process and its role in creating user-friendly URLs.
Router `urlrewrite `:
- `?____from_store` param, redirect to new URL if necessary.
Example:
- on English store category page /shoes switching to Norwegian store
- _/no/shoes?____from_store=1_
- find new rewrite for norwegian store
- 302 redirect to _/no/sko_
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 39 of 128
- Product->getProductUrl
- Product\Url->getProductUrl
- Product\Url->getUrl
- UrlFinderInterface->findOneByData
- new Url->getUrl -- `_direct` if rewrite found = baseUrl . requestPath
Rewrite is not used with regular getUrl, only when module uses explicitly (catalog, CMS).
### How are user-friendly URLs established, and how are they customized?
Module UrlRewrite:
- \Magento\UrlRewrite\Model\UrlPersistInterface::deleteByData
- \Magento\UrlRewrite\Model\UrlPersistInterface::replace
Product:
- event `catalog_product_save_before` - generate URL key by product name (if url key
wasn't provided)
* [ProductUrlKeyAutogeneratorObserver]([Link]
code/Magento/CatalogUrlRewrite/Observer/[Link])
* [\Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator::getUrlKey](https://
[Link]/magento/magento2/blob/2.3/app/code/Magento/CatalogUrlRewrite/Model/
[Link]#L125)
Category:
Observer/[Link]#L124)
* ... lots of logic
[App\Action\Action::dispatch]([Link]
Magento/Framework/App/Action/[Link]#L91):
- event `controller_action_predispatch`
- event `controller_action_predispatch_$routeName`, e.g. `..._checkout`
- event `controller_action_predispatch_$fullActionName`, e.g. `..._checkout_cart_index`
- stop if FLAG_NO_DISPATCH
- *execute* - all action controllers implement this
- stop if FLAG_NO_POST_DISPATCH
- event `controller_action_postdispatch_$fullActionName`
- event `controller_action_postdispatch_$routeName`
- event `controller_action_postdispatch`
- if action doesn't return result, response object is returned -- action can just modify
response object
[Controller\AbstractResult]([Link]
Magento/Framework/Controller/[Link]):
[Controller\Result\Raw]([Link]
Framework/Controller/Result/[Link]):
- setContents
- *render* - set response body
[Controller\Result\Json]([Link]
Magento/Framework/Controller/Result/[Link]):
- setData - array
- setJsonData - string
- *render* - processes inline translations, sets application/json header and response
body json string
[Controller\Result\Forward]([Link]
Magento/Framework/Controller/Result/[Link]):
[Controller\Result\Redirect]([Link]
Magento/Framework/Controller/Result/[Link]):
[View\Result\Layout]([Link]
Framework/View/Result/[Link]): - renders layout without `default` handle and page
layout (1-column etc.)
- *renderResult*
* event `layout_render_before`
* event `layout_render_before_$fullActionName`, e.g. `..._checkout_cart_index`
* render
[View\Result\Page]([Link]
Framework/View/Result/[Link]): - wraps layout into page layout
- same events as above
- *render* - renders layout, assigns vars and renders outer page template
- assign - values into viewVars property. default
Default: requireJs, headContent, headAdditional, htmlAttributes, headAttributes,
bodyAttributes, loaderIcon, layoutContent
- addDefaultHandle = $fullActionName + `default`
# 2.3 Demonstrate ability to customize request routing
[Frontend routers]([Link]
[Link]):
- robots (10)
- urlrewrite (20)
- standard (30) - module/controller/action
* get modules by front name registered in [Link]
* find action class
* if action not found in all modules, searches "noroute" action in *last* module
- cms (60)
- default (100) - noRouteActionList - 'default' noroute handler
Adminhtml routers:
- noRouteHandlerList. process
+ backend (10)
*Default admin 404 page* "adminhtml/noroute/index" when requested path starts with
admin route.
+ default (100)
*Default frontend 404 page* "cms/noroute/index" - admin config option `web/default/
no_route`.
- always returns forward action - just mark request not dispatched - this will continue
match loop
Create new router when URL structure doesn't fit into module/controller/action template,
e.g. fixed [Link] or dynamic arbitraty rewrites from DB.
If you want to replace controller action, you can register custom module in controller
lookup sequence -
reference route by ID and add own module "before" original module.
1. If [front controller]([Link]
Magento/Framework/App/[Link]#L61-L65) catches
[\Magento\Framework\Exception\NotFoundException]([Link]
2.3/lib/internal/Magento/Framework/Exception/[Link]), it changes action
name *"noroute"* and continues loop.
E.g. catalog/product/view/id/1 throws NotFoundException. catalog/product/noroute is
checked.
1. If standard router recognizes front name but can't find controller, it tries to find
*"noroute"*
action from last checked module.
E.g. catalog/brand/info controller doesn't exist, so catalog/brand/noroute will be
checked.
[\Magento\Framework\App\Router\Base::getNotFoundAction]([Link]
magento2/blob/2.3/lib/internal/Magento/Framework/App/Router/[Link]#L237)
1. loadLayoutUpdates - reads layout xml files and DB updates by *current handles*, result
in `_updates` array
* event `layout_load_before`
1. generateLayoutXml - joins `_updates` into XML string, loads XML object, initiailzes
`_elements` = []
* [Link]
* no events
1. generateLayoutBlocks - [Link]
* event `layout_generate_blocks_before`
* readerPool->interpret - every reader checks for matching xml node name (attribute,
body, head etc.), prepares page structure
* generatorPool->process
* add top-level element as outputElement. default "root"
* event `layout_generate_blocks_after`
```
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 43 of 128
getOutput
- renderElement(root)
- renderNonCachedElement(root) -- hardcoded switch
- is uiComponent -> toHtml
- is block -> toHtml
- is container -> renderElement(child[])
- event `core_layout_render_element`
```
### How would you debug your [Link] files and verify that the right layout
instructions are used?
When XML object is created from string XML updates, this is a good place to examine
resuls.
View\Layout\[Link] or View\[Link] is a good place to dump
structure.
- View\Layout::getOutput
- for every registered `_output` element
- View\Layout::renderElement
When layout is build initially, it finds top level container and registers it as
addOutputElement - default "root".
### How does Magento flush output, and what mechanisms exist to access and customize
output?
Render response:
Customize:
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 44 of 128
Module layouts:
- module/view/base/layout/
- module/view/frontend/layout/
- module/view/adminhtml/layout/
### layout_generic.xsd
Generic layout - useful for returning Ajax response. Doesn't have body, head, css etc.,
only pure structure:
```xml
<layout xmlns:xsi="[Link]
xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/
layout_generic.xsd">
<container
name="" as="" after="" before="" label=""
output=""
htmlTag="" htmlClass="" htmlId=""
/>
<referenceContainer
name="" label=""
htmlTag="" htmlClass="" htmlId=""
display="true/false"
remove="true/false"
/>
<block
class="" name="" as="" template="" before="" after="" group=""
acl="" aclResource="" ifconfig=""
output="" cacheable="bool" ttl="int"
/>
<referenceBlock name="" template="" display="" remove="" />
<update handle="name" />
<move element="" destination="" as="" after="" before="" />
<uiComponent
component="" name="" as="" before="" after="" group=""
aclResource="" ifconfig=""
output="" cacheable="" ttl=""
/>
</config>
```
### page_configuration.xsd
```xml
<page
layout="1column"
label=""
design_abstraction="custom / page_layout"
xmlns:xsi="[Link]
xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/
page_configuration.xsd"
>
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 45 of 128
<html>
<attribute name="" value="" />
</html>
<head>
<title>Title</title>
<css src defer ie_condition charset hreflang media rel rev sizes target type
src_type />
<link src defer ie_condition charset hreflang media rel rev sizes target type
src_type />
<meta content charset http-equiv name scheme />
<script src defer ie_condition async charset type src_type />
<remove src="" />
<attribute name="" value="" />
</head>
<update handle="" />
<body>
<attribute name="" value="" />
<!-- container/block structure same as in generic layout -->
</body>
</page>
```
- <module_dir>/view/<area_code>/[Link]
- <theme_dir>/<Namespace>_<Module>/[Link]
- base:
* empty
- frontend:
* 1column - extends empty
* 2columns-left - extends 1column
* 2columns-right - same as 2columns-left
* 3columns - same as 2columns-left
- adminhtml:
* admin-empty
* admin-1column
* admin-2columns-left
### page_layout.xsd
Only containers
```xml
<layout xmlns:xsi="[Link]
xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_layout.xsd">
<referenceContainer />
<container />
<update />
<move />
</layout>
```
- [Link]
- [Link]
- View\Layout\[Link]:
* loadLayoutUpdates
* generateLayoutXml
* generateLayoutBlocks
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 46 of 128
- View\Page\[Link] = build
- View\Page\[Link] - extends View\Layout\Builder, custom readPageLayout on step
generateLayoutBlocks
* (inherit) loadLayoutUpdates
* (inherit) generateLayoutXml
* generateLayoutBlocks - additionally *readPageLayout*
+ right before creating actual blocks, reads and merges page layout xml files
+ new instance of View\Model\Layout\Merge::load handles = '1column' -- uses
subDir 'page_layout'
+ *interprets* found page nodes - schedules blocks. page layout instructions
(root page template) are interpreted before others
+ original generateLayoutBlocks *interprets*, then runs *generators*
* \Magento\PageCache\Model\Layout\MergePlugin::beforeValidateUpdate - entity-specific
layout handles must not contain TTL
View\Layout\[Link]
- loadLayoutUpdates
- generateLayoutXml
- generateLayoutBlocks
View\Page\[Link]
- extend generateLayoutBlocks
- readPageLayout
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 47 of 128
- View\File\CollectorInterface
- View\File\Collector\Decorator\ModuleDependency - Decorator that sorts view files
according to dependencies between modules they belong to
- View\File\Collector\Decorator\ModuleOutput - Decorator that filters out view files that
belong to modules, output of which is prohibited
- View\File\Collector\Base - Source of base files introduced by modules
- view/base/, view/{$area}/
- theme/{$namespace}_{$module}/
- theme/
- theme/{$namespace}_{$module}/{$themeVendor}/{$themeName}/
- lib_web/
- each inherited theme[]: theme/web/
### How do you add new elements to the pages introduced by a given module?
```
<block class="module\block\class" template="new module template" ... />
```
## Demonstrate the ability to use layout fallback for customizations and debugging.
- View\Element\Template::getTemplateFile
- View\Element\Template\File\Resolver::getTemplateFileName(template, [module, area]) --
just caches in memory
- View\FileSystem::getTemplateFileName -- detects module by Module_Name::[Link],
adds default params
* View\Asset\Repository::updateDesignParams - adds default missing params [object
themeModel, locale]
- View\Design\FileResolution\Fallback\Resolver\Simple::resolveFile(fileRule)
- search file in each directory
### How do you identify which exact [Link] file is processed in a given scope?
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 48 of 128
## How does Magento treat layout XML files with the same names in different modules?
They are merged in module sequence order. Additionally, modules marked as "disable module
output"
are skipped (though deprecated).
- customized layout builder and page builders - automatically init messages block
- custom parent \Magento\Backend\Block\AbstractBlock
* auto inject AuthorizationInterface
- customized "block" layout reader (runs interpret) -- reads "acl" block attribute
- customzied "block" layout generator - default block
"class=Magento\Backend\Block\Template"
- controller result object with additional methods:
* setActiveMenu
* addBreadcrumb
* addContent(block) - moves to 'content'
* addLeft(block)
* addJs
* moveBlockToContainer
### What differences exist for layout initialization for the admin scope?
*Custom layout and page builders* - automatically initializes message block from message
manager:
\Magento\Backend\Model\View\Layout\Builder and \Magento\Backend\Model\View\Page\Builder:
- afterGenerateBlock - called in the end of generateLayoutBlocks, the end of generation -
`layout->initMessages()`
- getBlock('messages') or create new one
- addMessages from Message\ManagerInterface
- addStorageType('default')
# Demonstrate ability to utilize themes and the template structure
app/design/frontend/Vendor/themename/
```
- [Link]
- [Link]
- [Link] - name, parent, logo
- etc/[Link] - currently catalog images configuration
- i18n/
- media/ - pub logo here
- Magento_Checkout/ - extend specific module
---- layout/ - normally layouts are extended
------- override/base/ - special case: completely replace base layout for module
------- override/theme/Magento/luma/ - special case: completely replace specific theme
layout
---- templates/ - replace module files
---- web/ - replace module files
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 49 of 128
## Layouts:
It is *not possible* to use override trick inside *module*, only in theme. You cannot
create
new module app/code/MyVendor/MyModule/view/frontend/layout/override/... and replace
layout files.
## Locale:
You can replace static files with locale-specific - JS, logo, font etc. You have 2 places:
- non-module specific, e.g. theme asset - web/i18n/en_US/
- from some module - web/i18n/en_US/Magento_Checkout/
_Physical themes_
Physical refers to the fact that those themes are defined by files.
For example, the blank and luma theme are physically defined under app/design/frontend/
_Virtual themes_
This is yet unclear but I think virtual themes refer to themes you can create in the
backend which extends existing physical themes but it seems like it's not fully
implemented yet.
You can see that there's two tabs available in the edit section only for virtual themes
which let
you provides custom CSS and JS for a virtual theme.
I reckon a virtual theme would be something you setup temporarily (like Christmas theme)
for a short
period of theme and which requires only few design changes compared to the physical theme
it extends.
_Staging_
- Loads ALL layout update files using file name as layout handle name - as XML object.
- Selects only currently relevant handles (e.g. 'default', 'checkout_cart_index').
- Finds <update handle=name> instructions, merges them recursively.
- Selected file contents as added to `_updates[]` array as pieces of string - they will
be converted to XML object later in generateLayoutXml.
Trace:
View\Layout\Builder::loadLayoutUpdates
* event `layout_load_before`
* View\Model\Layout\Merge::load
+ Collects loaded layout files contents into `updates[]` array.
+ Loads from cache when possible, XML contents (e.g. '<block
name="someBlock" ....><referenceBlock....>')
and separately pageLayout (e.g. '1column')
[Link](handle)
- merge._merge(handle)
- merge._validateMergedLayout
* View\Model\Layout\Update\Validator::isValid with `urn:magento:framework:View/Layout/
etc/layout_merged.xsd`
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 51 of 128
*_fetchPackageLayoutUpdates*
* [Link] - loads ALL layouts regardless of handle
* merge._loadFileLayoutUpdatesXml
- get physical theme, checking parents until found
- update files = theme file [Link] + page layout file [Link]
- read files one by one
- merge._substitutePlaceholders - replaces in raw XML string - {{baseUrl}} and
{{baseSecureUrl}}
- adds file contents to result as one of:
```xml
<layout id="{$fileName}" {$attributes}>{$fileContents}</layout>
<handle id="{$fileName}" {$attributes}>{$fileContents}</handle>
```
```xml
<layouts xmlns:xsi="[Link]
<layout id="default" {$attributes}>{$fileContents}</layout>
<handle id="checkout_cart_index" {$attributes}>{$fileContents}</handle>
<handle id="checkout_cart_index">{$fileContents}</handle>
...
</layouts>
```
* finds all <layout> and <handle> with ID *matching handle* that is being loaded
* merge._fetchRecursiveUpdates - finds xpath <update handle="any">, calls _merge
recursively
* [Link]
plugin \Magento\PageCache\Model\Layout\MergePlugin::beforeValidateUpdate checks
that entity-specific
update handles don't have `ttl` declarations
* [Link] -- main goal, adds content to `updates[]`
*_fetchDbLayoutUpdates:*
* does nothing itself, but there's a plugin
\Magento\Widget\Model\ResourceModel\Layout\Plugin::aroundGetDbUpdateString - loads
updates from DB by theme/store
* merge._fetchRecursiveUpdates - finds xpath <update handle="any">, calls _merge
recursively
* [Link]
* [Link]
merge.*fileSource*:
View\Layout\File\Collector\Aggregated\Proxy - Source of layout files aggregated from a
theme
and its parents according to merging and overriding conventions
* View\File\Collector\Decorator\ModuleDependency as layoutFileSourceBaseSorted
* View\File\Collector\Decorator\ModuleOutput as layoutFileSourceBaseFiltered
* *View\File\Collector\Base* as layoutFileSourceBaseFiltered, subDir = 'layout'
* Component\DirSearch::collect(type='module', pattern) - uses component registrar to
get modules, then searches by pattern
```
<modules>/view/base/layout/*.xml --- isBase = true, shared between adminhtml and
frontend themes
<modules/view/frontend/layout/*.xml
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 52 of 128
```
* View\File\Collector\Decorator\ModuleDependency as layoutFileSourceThemeSorted
* View\File\Collector\Decorator\ModuleOutput as layoutFileSourceThemeFiltered
* *View\File\Collector\ThemeModular* as layoutFileSourceTheme, subDir = 'layout'
```
<theme>/*_*/layout/*.xml -- sets module context, e.g. Magento_Checkout
```
* View\File\Collector\Decorator\ModuleDependency as layoutFileSourceOverrideBaseSorted
* View\File\Collector\Decorator\ModuleOutput as layoutFileSourceOverrideBaseFiltered
* *View\File\Collector\Override\Base* as layoutFileSourceOverrideBase, subDir = 'layout/
override/base'
```
<theme>/*_*/layout/override/base/*.xml
```
* View\File\FileList::replace
* View\File\FileList\Collator::collate - replaces by matching keys: is base, theme,
module, filename
* View\File\Collector\Decorator\ModuleDependency as layoutFileSourceOverrideThemeSorted
* View\File\Collector\Decorator\ModuleOutput as layoutFileSourceOverrideThemeFiltered
* *View\File\Collector\Override\ThemeModular* as layoutFileSourceOverrideTheme, subDir
= 'layout/override/theme'
```
<theme>*_*/layout/override/theme/*/*/*.xml
```
```
<modules>/view/base/page_layout/*.xml --- isBase = true, shared between adminhtml and
frontend themes
<modules/view/frontend/page_layout/*.xml
<theme>/*_*/page_layout/*.xml -- sets module context, e.g. Magento_Checkout
<theme>/*_*/page_layout/override/base/*.xml
<theme>*_*/page_layout/override/theme/*/*/*.xml
```
View\Layout\ProcessorInterface = View\Model\Layout\Merge
- $updates - array of all found string XML file contents E.g.
```
updates[] = '<body><block name="someBlock"...'
updates[] = '<referenceBlock name="someBlock">...'
```
- addUpdate(string) - string XML from file
- create new theme from scratch without parent when design is very different from existing
- inherit new theme to add smaller customizations - move, hide, reorder elements, change
block arguments, html attributes
- new theme can be assigned to specific store view, for example for b2b store
- theme can apply dynamically based on browser user agent as exception - enter regexp in
_Content > Design > Implementation > [Edit] > Design Rule > User Agent Rules_
Full page cache and design exception:
```
plugin magento-store/etc/[Link]:
Magento\Framework\App\Action\[Link]:
\Magento\Framework\View\DesignLoader::load
\Magento\Framework\App\Area::load('design')
\Magento\Framework\App\Area::_initDesign
\Magento\Framework\App\Area::detectDesign
\Magento\Framework\App\Area::_applyUserAgentDesignException
\Magento\Framework\View\DesignExceptions::getThemeByRequest
- etc/[Link] file *doesn't merge* with parent themes, it either inherits completely
from parent
or your theme has new version of it
[Link] - parent
### How do you identify which exact theme file is used in different situations?
### [View\Element\AbstractBlock]([Link]
internal/Magento/Framework/View/Element/[Link]):
- data automatically assigns to _data, can access data arguments later, e.g. in _construct
- `jsLayout` data argument
- toHtml:
* event `view_block_abstract_to_html_before`
* disable module output still works
* if not in cache:
+ _beforeToHtml
+ _toHtml
+ save cache
* _afterToHtml - always, even when cached
* event `view_block_abstract_to_html_after`
### [View\Element\Template]([Link]
internal/Magento/Framework/View/Element/[Link]):
- `template` data argument
- `_viewVars` property, assign()
- `_toHtml` renders template when defined
* getTemplateFile = [View\Element\Template\File\Resolver]([Link]
magento2/blob/2.2-develop/lib/internal/Magento/Framework/View/Element/Template/File/
[Link])::getTemplateFileName
* fetchView
+ [View\Element\Template\File\Validator]([Link]
develop/lib/internal/Magento/Framework/View/Element/Template/File/[Link])::isValid
- checks allowed directory (view_preprocessed/module/theme) or symlink
+ if bad file, log and throw error in DEV mode
+ View\TemplateEnginePool::get by file extension
- phtml - [View\TemplateEngine\Php]([Link]
develop/lib/internal/Magento/Framework/View/TemplateEngine/[Link]) - extract vars,
include file
- xhtml - [View\TemplateEngine\Xhtml]([Link]
develop/lib/internal/Magento/Framework/View/TemplateEngine/[Link])
- \Magento\Developer\Model\TemplateEngine\Decorator\DebugHints
+ [Link](block, template, viewVars)
- default `getCacheKeyInfo` - store code, template, base URL. By default *store aware*
- helper methods:
* getMediaDirectory
* getRootDirectory
* getObjectData
* getBaseUrl
- $this = \Magento\Framework\View\TemplateEngine\Php
- $this->something() is proxied to $block->something() via magic `__call` function - only
public available!
- isset($this->something) -> isset($block->something)
- $this->property = $block->property
- $this->helper() gets singleton of AbstractHelper
As much business logic as possible should be moved out of template, and blocks provide
access to processed
data for templates. Actual data crunching can (and should) be proxied further to models.
- block is generated
* [Link]
* [Link]
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 55 of 128
[Link]:
- [Link] -> `__construct()` -> `_construct` -> check argument 'template' -
> setTemplate
* [Link]
* [Link](classname)
* [Link]
* [Link]([Link])
- [Link] (when available)
- [Link] (when available)
### Arguments
Block arguments are treated like data-arguments. Arguments values set in a layout file
can be accessed in templates using the get{ArgumentName}() and has{ArgumentName}()
methods.
Arguments values set in a layout file can be accessed in templates using the
get{ArgumentName}()
and has{ArgumentName}() methods. The latter returns a boolean defining whether there's
any value set.
{ArgumentName} is obtained from the name attribute the following way: for getting the
value of
`<argument name="some_string">` the method name is getSomeString().
Magento 2.2 suggests moving block business logic to separate viewModel classes.
Avoid extending this template class, because with inheriting the template block is the
constructor is very large.
If you need custom presentation logic in your blocks, use this class as block, and declare
custom view models in block arguments in layout handle file.
Example:
```xml
<block name="[Link]" class="Magento\Backend\Block\Template"
template="My_Module::[Link]" >
<arguments>
<argument name="viewModel" xsi:type="object">My\Module\ViewModel\Custom</
argument>
</arguments>
</block>
```
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 56 of 128
```php
$viewModel = $block->getData('viewModel');
// or
$viewModel = $block->getViewModel();
```
### In what cases would you put your code in the _prepareLayout(), _beforeToHtml(), and
_toHtml() methods?
At this stage all blocks in layout are generated, but _prepareLayout is only running,
so some blocks might still be not initialized completely.
```php
$this->pageConfig->getTitle()->set(__('Address Book'));
```
`_beforeToHtml`:
- can't change page title?
- some blocks are rendered, can't change them
- assign additional template values
- delay computation to the latest point until render. If block is not rendered we save
computation
`_toHtml`:
- block without template, put custom rendering here, e.g. calling external API
- suppress template output if should not render by condition - return empty string
#### How would you use events fired in the abstract block?
`view_block_abstract_to_html_before`:
- edit cache params - lifetime, ttl, tags
- add column to grid
- edit template params - e.g. set disable edit flag
`view_block_abstract_to_html_after`:
- edit html - replace, add content, add wrappers
toHtml, load cache, [when not in cache: _beforeToHtml, _toHtml], _afterToHtml, save cache
Something like CMS page - admin controls block content and layout. Another example -
dynamic [Link]
whose content is stored in DB.
### In what situation should you use a template block or other block types?
Use template block whenever possible to allow for theme markup customization.
# Demonstrate ability to use layout and XML schema
## Describe the elements of the Magento layout XML schema, including the major XML
directives.
*string*:
```xml
<argument xsi:type="string" translate="bool"></argument>
```
[*options*]([Link]
Framework/View/Layout/Argument/Interpreter/[Link]):
```xml
<argument name="options" xsi:type="options"
model="Magento\Tax\Model\TaxClass\Source\Customer"/>
```
[*url*]([Link]
Framework/View/Layout/Argument/Interpreter/[Link]):
```xml
<argument xsi:type="url" path="">
<param name="_current">1</param>
</argument>
```
```xml
<argument name="configuration" xsi:type="array">
<item name="recently_viewed_product" xsi:type="array">
<item name="requestConfig" xsi:type="array">
<item name="syncUrl" xsi:type="url" path="catalog/product/
frontend_action_synchronize"/>
</item>
</item>
</argument>
```
[*helper*]([Link]
Framework/View/Layout/Argument/Interpreter/[Link]):
```xml
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 58 of 128
<action method="setTemplate">
<argument name="template" xsi:type="helper"
helper="Magento\Paypal\Helper\Hss::getReviewButtonTemplate">
<param name="name">Magento_Paypal::hss/review/[Link]</param>
</argument>
</action>
```
*Node readers*:
- `<html>`
- `<head>`
- `<body>`
- `<move>`
- `<container>`
- `<block>`
- `<uiComponent>`
*Generators*:
- `<head>`
- `<body>`
- `<container>`
- `<block>`
- `<uiComponent>`
Html reader is used for collecting attributes of html in to the scheduled page structure.
```xml
<html>
<attribute name="html_attribute_name" value="html_attribute_value" />
</html>
```
*structure*:
`elementAttributes['html']['html_attribute_name'] = 'html_attribute_value'`
*render*:
- View\Result\Page::render
- `'htmlAttributes' => $this->pageConfigRenderer-
>renderElementAttributes($config::ELEMENT_TYPE_HTML),`
- View\Page\Config\Renderer::renderElementAttributes - join(' ', name-value pairs)
- [Link]
```html
<!doctype html>
<html <?= /* @escapeNotVerified */ $htmlAttributes ?>>
```
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 59 of 128
- `<remove src="">`
[Link]:
structure `removeAssets['path/to/src'] = 'path/to/src'`
- `<title>`
[Link]
`title = __($title)`
render:
* View\Result\Page::render
* `'headAttributes' => $this->pageConfigRenderer-
>renderElementAttributes($config::ELEMENT_TYPE_HEAD),`
* [Link]: `<head <?= /* @escapeNotVerified */ $headAttributes ?>>` - key1=value1
key2=value2
```xml
<body>
<attribute name="id" value="html-body"/>
<attribute name="class" value="preview-window"/>
</body>
```
magento2/blob/2.2-develop/lib/internal/Magento/Framework/View/Page/Config/Generator/
[Link])
```xml
<move element='' destination='' as='' (after=''|before='') />
```
- `<container>`:
* `<container name as before/after>` - schedule structure
structure.`scheduledStructure[name] = [container alias parent sibling is_after]`
* [Link]
structure.`scheduledData[name][attributes] = [tag id class label display]`
```xml
<arguments>
<argument>Text node</argument>
<argument>
<item>
<item>...parsed recursively</item>
</item>
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 61 of 128
</argument>
</arguments>
```
- [Link]
* Data\Argument\InterpreterInterface::evaluate
* Data\Argument\Interpreter\Composite as layoutArgumentReaderInterpreter
```xml
<argument name="interpreters" xsi:type="array">
<item name="options"
xsi:type="object">Magento\Framework\View\Layout\Argument\Interpreter\Options</item>
<item name="array" xsi:type="object">layoutArrayArgumentReaderInterpreterProxy</item>
<item name="boolean"
xsi:type="object">Magento\Framework\Data\Argument\Interpreter\Boolean</item>
<item name="number"
xsi:type="object">Magento\Framework\Data\Argument\Interpreter\Number</item>
<item name="string"
xsi:type="object">Magento\Framework\Data\Argument\Interpreter\StringUtils</item>
<item name="null"
xsi:type="object">Magento\Framework\Data\Argument\Interpreter\NullType</item>
<item name="object"
xsi:type="object">Magento\Framework\View\Layout\Argument\Interpreter\Passthrough</item>
<item name="url"
xsi:type="object">Magento\Framework\View\Layout\Argument\Interpreter\Passthrough</item>
<item name="helper"
xsi:type="object">Magento\Framework\View\Layout\Argument\Interpreter\Passthrough</item>
</argument>
```
Scheduled Structure:
- `scheduledPaths[$blockName] = $parent1/$parent2/$blockName`
- `scheduledStructure[$blockName] = $row`
- `scheduledData[$blockName] = [attributes actions arguments]`
View\Layout\Argument\Interpreter\Decorator\Updater as layoutArgumentGeneratorInterpreter
- modifies result via registered [View\Layout\Argument\UpdaterInterface](https://
[Link]/magento/magento2/blob/2.2-develop/lib/internal/Magento/Framework/View/Layout/
Argument/[Link])
```xml
<argument name="testArrayWithUpdater" xsi:type="array">
<updater>Magento\Framework\View\Layout\Argument\UpdaterInterface</updater>
<item name="add" xsi:type="array">
<item name="label" xsi:type="string" translate="true">Move to Archive</item>
<item name="url" xsi:type="string">*/sales_archive/massAdd</item>
</item>
</argument>
<argument name="dataSource" xsi:type="object">
<updater>Magento\Sales\Model\Grid\CollectionUpdater</updater>
</argument>
```
Data\Argument\Interpreter\Composite as layoutArgumentGeneratorInterpreterInternal
```xml
<argument name="interpreters" xsi:type="array">
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 62 of 128
<item name="options"
xsi:type="object">Magento\Framework\View\Layout\Argument\Interpreter\Options</item>
<item name="array" xsi:type="object">layoutArrayArgumentGeneratorInterpreterProxy</
item>
<item name="boolean"
xsi:type="object">Magento\Framework\Data\Argument\Interpreter\Boolean</item>
<item name="number"
xsi:type="object">Magento\Framework\Data\Argument\Interpreter\Number</item>
<item name="string"
xsi:type="object">Magento\Framework\Data\Argument\Interpreter\StringUtils</item>
<item name="null"
xsi:type="object">Magento\Framework\Data\Argument\Interpreter\NullType</item>
<item name="object" xsi:type="object">layoutObjectArgumentInterpreter</item>
<item name="url"
xsi:type="object">Magento\Framework\View\Layout\Argument\Interpreter\Url</item>
<item name="helper"
xsi:type="object">Magento\Framework\View\Layout\Argument\Interpreter\HelperMethod</item>
</argument>
```
- every block:
* [Link] -> _prepareLayout
* event `core_layout_block_create_after`
- every scheduled action:
* [Link] - evaluate arguments and call_user_func_array
```xml
<ui_component name group aclResource ifconfig>
<visibilityCondition name="" className="">
<arguments>...</arguments>
</visibilityCondition>
</ui_component>
```
- When extending page layout, don't copy-paste layout="..." attribute if not intended.
- Use standard XML namespaces:
```xml
<layout xmlns:xsi="[Link]
xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/
layout_generic.xsd">
```
```xml
<page
xmlns:xsi="[Link]
xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/
page_configuration.xsd"
>
```
```xml
<block class="Magento\Customer\Block\Account\Navigation" name="[Link]">
<arguments>
<argument name="css_class" xsi:type="string">header links</argument>
</arguments>
</block>
```
[Magento docs]([Link]
[Link]#argument)
```xml
<action method="setTemplate">
<argument name="template" xsi:type="helper"
helper="Magento\Paypal\Helper\Hss::getReviewButtonTemplate">
<param name="name">Magento_Paypal::hss/review/[Link]</param>
</argument>
</action>
```
# Utilize JavaScript in Magento
## Describe different types and uses of JavaScript modules. Which JavaScript modules are
suited for which tasks?
Good for executing regular JS code, maybe some legacy code that doesn't require
active interactions
with other existing JS components on the page.
```html
<script type="text/x-magento-init">
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 64 of 128
{
"*": {
"mage/cookies": {
"expires": null,
"path": "/",
"domain": ".[Link]",
"secure": false,
"lifetime": "3600"
}
}
}
</script>
```
Element selector syntax is good for passing container element, so module can bind
event listeners only
within given element instead of querying DOM directly. Makes code tidier.
```html
<script type="text/x-magento-init">
{
"[data-gallery-role=gallery-placeholder]": {
"mage/gallery/gallery": {
"mixins":["magnifier/magnify"],
"magnifierOpts":
{"fullscreenzoom":"20","top":"","left":"","width":"","height":"","eventType":"hover","enabled":"false"},
"data": [/*...*/],
"options": {
// ...
},
"fullscreen": {
// ...
},
"breakpoints": {"mobile":{"conditions":{"max-width":"767px"},"options":
{"options":{"nav":"dots"}}}} }
}
}
</script>
```
- view - ui component
* form Magento_Ui/js/form/form
* grid Magento_Ui/js/grid/listing
* minicart Magento_Checkout/js/view/minicart
- jQuery widget
```JS
{
$.widget('[Link]', {
... // [Link], [Link]
})
return $.[Link];
}
$(element).shippingCart(...)
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 65 of 128
```
## Describe UI components.
- uiClass
- uiElement + uiEvents + links
- uiCollection
The uiClass is an abstract class from which all components are extended. The uiClass is
a low-level class and is rarely used as direct parent for UI components’ classes.
Adds support for extending classes `extend()` and calling parent methods `_super()`.
methods:
- initialize, initConfig
properties:
- [Link]
methods:
- initialize, initObservable, initModules
properties:
- tracks, modules, source, provider,
initLinks:
- imports
- exports
- links == same value in imports and exports
```
imports: {
totalRecords: '${ $.provider }:[Link]'
}
```
same as
```
imports: {
totalRecords: 'example.example_data_source:[Link]'
}
```
```
exports: {'visible': '${ $.provider }:visibility'}
imports: {'visible': '${ $.provider }:visibility'}
links: {'visible': '${ $.provider }:visibility'}
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 66 of 128
```
listens: {'${ $.namespace }.${ $.namespace }:responseData': 'setParsed'}
listens: {'${ $.provider }:[Link]': 'overload reset validate'} -- call multiple
callbacks
listens:
'index=create_category:responseData' => 'setParsed',
'newOption' => 'toggleOptionSelected'
```
### initObservable
- observe([{Boolean} isTracked,] {String|Array|Object} listOfProperties)
- observe('var1 var2 var3')
- observe(['var1', 'var2', 'var3'])
- observe(true, 'var1 var2 var3') -- isTracked, use property accessors
- observe(false, 'var1 var2 var3') -- not tracked, use observable properties
- observe({var1: 'value', var2: true}) -- initial values from object
### initModules
```JS
defaults: {
modules: {
'%myProperty%': '%linkToTheComponent%',
externalSource: '${ $.externalProvider }',
street: '${ $.parentName }.street',
city: '${ $.parentName }.city',
country: '${ $.parentName }.country_id',
productForm: 'product_form.product_form'
}
}
```
## uiCollection - Magento_Ui/js/lib/core/[Link]
## In which situation would you use UiComponent versus a regular JavaScript module?
[Overview of UI components]([Link]
ui_comps.html)
UI components work well together: they communicate with each other via the uiRegistry
service that
tracks their asynchronous initialization.
Therefore, if we need to extend something that has already been implemented as a hierarchy
of UI components or add a new feature that should interact with other UI components,
it’s easier and more effective to use a UI component.
### [Link]
- map as preference - same as preference in DI, replace one class with another
```js
var config = {
"map": {
"*": {
"<default_component>": "<custom_component>"
}
}
};
```
* <default_component>: the name of the default component you replace
* <custom_component>: the name of the custom component
- mixins - same as around plugins in DI. This is Magento customization over requireJS
```js
var config = {
config: {
mixins: {
'Magento_Checkout/js/action/place-order': {
'Magento_CheckoutAgreements/js/model/place-order-mixin': true
}
}
}
};
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 68 of 128
```
/** Override default place order action and add agreement_ids to request */
return [Link](placeOrderAction, function (originalAction, paymentData,
messageContainer) {
agreementsAssigner(paymentData);
```
```
define([
'jquery',
'ko'
], function($, ko) {
'use strict';
return function(quote) {
return $.extend(quote, {
checkoutMethod: checkoutMethod
});
};
});
```
```html
<div class="selector"></div>
<script type="text/x-magento-init">
{
'.selector': {
'Custom_Module/js/something': {
option1: 'value1',
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 69 of 128
option2: 'value2'
}
}
}
</script>
```
```html
<div class="selector"></div>
<script>
require(['Custom_Module/js/something'], function(fn) {
fn({option1: 'value1', option2: 'value2'}, DOMElement('.selector'));
});
</script>
```
In other words:
- we return component-function
- magento gets it via require and invokes with arguments. E.g. uiComponent(config, node)
- node '*' -> false
- selector matching multiple nodes - multiple module instances (like jQuery plugin)
Benefits:
- top-level selector is defined outside js module
- initial values from server are defined outside modules
Example:
```html
<div class="selector-one" data-mage-init="{'Custom_Module/js/something': {option1:'',
option2:''}}"></div>
<div id="selector-two"></div>
<script type="text/x-magento-init">
{
'#selector-two': {
'Custom_Module/js/component': {
mixins: [],
option1: value1,
option2: value2
}
}
}
</script>
<script type="text/x-magento-init">
{
"*": {
"Magento_Ui/js/core/app": {
"components": {
"customer": {
"component": "Magento_Customer/js/view/customer"
}
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 70 of 128
}
}
}
}
</script>
```
```
Here's how:
```js
require([component], function (fn) {
if (typeof fn === 'object') {
fn = fn[component].bind(fn);
}
if (_.isFunction(fn)) {
fn(config, el);
} else if ($(el)[component]) {
$(el)[component](config);
}
});
```
```HTML
<script type="text/x-magento-init">
{
"[data-gallery-role=gallery-placeholder]": {
"mage/gallery/gallery": {
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 71 of 128
"mixins":["magnifier/magnify"],
"magnifierOpts": ...,
"data": ...,
"options": ...
}
}
}
</script>
```
```JS
element = $('[data-gallery-role=gallery-placeholder]');
config = {magnifierOpts:..., data:..., options:...}
require(['mage/gallery/gallery'], function(gallery) {
require(["magnifier/magnify"], function(magnify) {
var result = magnify(config, element); // call for each mixin. Magnify will use
[Link]
extend(config, result); // magnify returns same config, config not changed
})
gallery(config, element);
});
```
# Demonstrate ability to use data-related classes
Magento 2.2 changed repository getList approach. Before you applied filters, sorts and
pagination manually
right in the `getList` method itself.
Magento 2.2 takes this boilerplate routine off developer's shoulders. Just call
`[Link]()`.
[Api\SearchCriteria\CollectionProcessorInterface]([Link]
blob/2.2-develop/lib/internal/Magento/Framework/Api/SearchCriteria/
[Link])
[Api\SearchCriteria\CollectionProcessor]([Link]
develop/lib/internal/Magento/Framework/Api/SearchCriteria/[Link])
- this is a composite and default preference
## How do you obtain an object or set of objects from the database using a repository?
While model->load() is deprecated, your repository is the place to work with resource
model.
- create empty model object with factory
- call `[Link]($object)` just like `model->load()` would.
Example:
```xml
public function getById($blockId)
{
$block = $this->blockFactory->create();
$this->resource->load($block, $blockId);
if (!$block->getId()) {
throw new NoSuchEntityException(__('CMS Block with id "%1" does not exist.',
$blockId));
}
return $block;
}
```
## How do you configure and create a SearchCriteria instance using the builder?
- Api\SearchCriteriaBuilder
- Api\SearchCriteria
Api/:
- repository interfaces
- put operational interfaces - helpers etc. business logic API
- implementation lies in models
- usually available via WebAPI
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.
```xml
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 73 of 128
## populateWithArray helper
Based on interface, calls all SETTERS with given data to fill data object
[Api\DataObjectHelper::populateWithArray]([Link]
lib/internal/Magento/Framework/Api/[Link]#L80)
- calls data object SETTERS `set*`, `setIs*` with given raw data
- handles `custom_attributes` - data [Link]
Example: [Magento\Customer\Controller\Account\Edit::execute]([Link]
magento2/blob/2.3/app/code/Magento/Customer/Controller/Account/[Link]#L78)
## buildOutputDataArray
Based on given interface, calls all GETTERS to make resulting data array.
[Reflection\DataObjectProcessor::buildOutputDataArray]([Link]
magento2/blob/2.3/lib/internal/Magento/Framework/Reflection/[Link]#L81):
- \Magento\Framework\Reflection\MethodsMap::getMethodsMap - method name and getter return
types
- filter only getters: is..., has..., get...
- get data using interface getter, e.g. $object->getSku() - based on Interface definition
- deduce field name by getter name, e.g. 'sku'
- process custom_attributes
\Magento\Framework\Reflection\CustomAttributesProcessor::buildOutputDataArray
- process extension_attributes
\Magento\Framework\Reflection\ExtensionAttributesProcessor::buildOutputDataArray
- process return object: build return value objects with their return type annotation
- process return array: cast each element to type, e.g. int[] => (int) each value
- cast element to type
Example: [Magento\Customer\Model\Address::updateData]([Link]
blob/2.3/app/code/Magento/Customer/Model/[Link]#L145)
# Demonstrate ability to use EAV entity load and save
New Entity Manager stands aside, you don't need to extend your resource model from it,
but rather manually call its methods in your resource - load, save, delete.
\Magento\Framework\EntityManager\EntityManager is suitable for both flat and EAV tables.
## Entity Manager
Entity manager is a new way of saving both flat tables and EAV entities.
Separates individual operations into own class - read, create, update, delete.
This give a lot of freedom for extensions.
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 74 of 128
Each operation still has a lot to do - save EAV data and run extensions. Default
operations
split these activities into smaller actions:
- save main table - both for flat and EAV
- save EAV attributes - only for EAV entities. see *attribute pool*
- run extensions - see *extension pool*
Entity manager is concept separate from regular flat and EAV resource models saving, it
doesn't
call them under the hood. It does the same things but in own fashion with extensibility
in mind.
This means that you can still build your code on regular resource models:
- flat resource Magento\Framework\Model\ResourceModel\Db\AbstractDb
- EAV entity resource Magento\Eav\Model\Entity\AbstractEntity
Terms:
- *entity manager* - calls appropiate operations. Holds type resolver and operations pool
- *metadata pool* - DI injectable. Register Api Data interface and [`entityTableName`,
`identifierField`]
```xml
<item name="Magento\Cms\Api\Data\PageInterface" xsi:type="array">
<item name="entityTableName" xsi:type="string">cms_page</item>
<item name="identifierField" xsi:type="string">page_id</item>
<!-- eavEntityType - flag is eav -->
<!-- connectionName -->
<!-- sequence -->
<!-- entityContext -->
</item>
```
- create - Magento\Eav\Model\ResourceModel\CreateHandler
- update - Magento\Eav\Model\ResourceModel\UpdateHandler
## Object converters
\Magento\Framework\Reflection\DataObjectProcessor::buildOutputDataArray(object, interface)
\Magento\Framework\Reflection\CustomAttributesProcessor::buildOutputDataArray
\Magento\Framework\Reflection\ExtensionAttributesProcessor::buildOutputDataArray
\Magento\Framework\Api\\`SimpleDataObjectConverter`:
- toFlatArray:
* data object [Link]
* ConvertArray::toFlatArray
- convertKeysToCamelCase - used by Soap API, supports `custom_attributes`
\Magento\Framework\Api\\`ExtensibleDataObjectConverter`:
- toNestedArray(entity, skip custom attributes list, interface) - used by entity
[Link]:
* data object [Link]
* add `custom_attributes`
* add `extension_attributes`
- toFlatArray:
* toNestedArray
* ConvertArray::toFlatArray
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 76 of 128
- (static) convertCustomAttributesToSequentialArray
\Magento\Framework\Reflection\DataObjectProcessor::buildOutputDataArray:
- \Magento\Framework\Reflection\MethodsMap::getMethodsMap - method name and getter return
types
- filter only getters: is..., has..., get...
- get data using interface getter, e.g. $object->getSku() - based on Interface definition
- deduce field name by getter name, e.g. 'sku'
- process custom_attributes
\Magento\Framework\Reflection\CustomAttributesProcessor::buildOutputDataArray
- process extension_attributes
\Magento\Framework\Reflection\ExtensionAttributesProcessor::buildOutputDataArray
- process return object: build return value objects with their return type annotation
- process return array: cast each element to type, e.g. int[] => (int) each value
- cast element to type
[Link]:
- buildOutputDataArray
- product data = call all getters + original data
- product = initializeProductData. create new product/get existing by SKU. set product
data
- process links, unless product data `ignore_links_flag`
- abstract [Link]
- product [Link]
- entity [Link]
# Describe common structure/architecture
## Some URLs in backend start with "admin/admin/.." - 2 times admin, some only 1 -
"admin/...". How?
Sample router:
```xml
<router id="admin">
<route id="adminhtml"> <!-- frontName="admin" -->
<module name="Magento_Theme" />
</route>
<route id="theme" frontName="theme">
<module name="Magento_Theme" />
</route>
</router>
```
Fun fact: same "Content > Design > Themes" link can open by 2 different URLs:
- [Link]
- [Link]
`router id="admin"`
You can in turn *register own router* `<route name="theme" frontName="theme">` =>
`<module name="Module_Alias"/>`.
You will generate links with `getUrl("theme/controller/action")`:
- backend URL model._getActionPath adds prefix areaFrontName
- *route name* "theme", frontName "theme"
- controlles are searched *only in your module* Module_Alias, no interference with
Magento_Backend controllers.
This means that admin routes always lie in etc/adminhtml/[Link] and have `router
id="admin"`.
Frontend routes - etc/frontend/[Link] and always have `router id="standard"`.
This looks like redundancy to me.
*URL generation*:
## Describe the difference between Adminhtml and frontend. What additional tools and
requirements exist in the admin?
- ACL permissions - `_isAllowed`, change static const `ADMIN_RESOURCE`
- base controller Magento\Backend\App\Action = Magento\Backend\App\AbstractAction
- custom *URL model* - secret key `key`
- layout `acl` block attribute
- ...
# Define form and grid widgets
## UI Component generation
Generation flow:
- \Magento\Framework\View\Layout\Generator\UiComponent::generateComponent - create,
prepare, wrap
- create component \Magento\Framework\View\Element\UiComponentFactory::create
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 79 of 128
* \Magento\Framework\View\Element\UiComponentFactory::mergeMetadata -- components
definitions in array format + [Link]
+ metadata = ['cert_form' => ['children' => [Link]]]
* for each child[],
\Magento\Framework\View\Element\UiComponentFactory::createChildComponent recursively:
+ create PHP class for component, e.g. `new
Magento\Ui\Component\DataSource($components = [])`;
* create PHP class for component, e.g. `new Magento\Ui\Component\Form($components =
[children...])`;
- prepare component recursively
* getChildComponents[].prepare - update data, js_config etc.
- wrap to *UiComponent\Container* as child 'component'
* toHtml = render -> renderEngine->render (template + '.xhtml')
## AbstractComponent
*What is the difference between component data `config`, `js_config` and others?*
- looks like js_config is special - goes to `types` definition
- config - normal JS component config overriding `defaults`?
- other values - only for PHP component class usage?
This makes sense - ALL components inherit same top level `provider` via `extends`.
Example:
```
"Magento_Ui/js/core/app": {
"types": {
// [component constant type]: [data.js_config],
// TOP LEVEL specific js_config and required provider
cms_block_form: {
provider: 'cms_block_form_data_source'
},
// below are generic types by constant type
form: {
extends: 'cms_block_form'
},
fieldset: {
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 80 of 128
component: 'Magento_Ui/js/form/components/fieldset',
extends: 'cms_block_form'
},
input: {
extends: 'cms_block_form'
}
},
"components": ...
}
```
## DataSource, DataProvider
```
[
'' => [
attributes => [
class => 'Magento\Ui\Component\Form',
name => 'some_name',
],
arguments => [
data => [
js_config => [
component => 'Magento_Ui/js/form/form',
provider => 'cms_block_form.block_form_data_source',
],
template => 'templates/form/collapsible',
],
],
children => [ ...nested components... ]
]
]
```
- getFieldMetaInfo, getFieldsMetaInfo, getFieldsetMetaInfo -- wtf?
- getSearchCriteria, getSearchResult ???
You don't have to implement data provider from scratch, choose from 2 implementations:
- in framework next to interface -
`Magento\Framework\View\Element\UiComponent\DataProvider\DataProvider`.
This provider sets collects all input filters and sorts into *search criteria*.
Data is returned like `searchResult = $this->reporting->search($searchCriteria)`.
Nice and tidy.
* static properties are passed in ui component XML in `listing/dataSource/dataProvider/
settings`:
+ name ??? - `[YourComponentName]_data_source`
+ primaryFieldName, e.g. `entity_id`
+ requestFieldName, e.g. `id`
* addFilter, addOrder, setLimit - proxy to search criteria builder
collection
instance by *data provider name*.
* You register your collection as DI arguments for CollectionFactory:
```xml
<type
name="Magento\Framework\View\Element\UiComponent\DataProvider\CollectionFactory">
<arguments>
<argument name="collections" xsi:type="array">
<item name="sales_order_grid_data_source"
xsi:type="string">Magento\Sales\Model\ResourceModel\Order\Grid\Collection</item>
</argument>
<arguments>
</type>
```
- in Magento_Ui - `\Magento\Ui\DataProvider\AbstractDataProvider`
This provider works directly with *collection*, you define collection in constructor
when extending.
* as usual, static properties are passed in ui component XML as arguments or settings:
+ name
+ primaryFieldName
+ requestFieldName
+ meta
+ data - data['config'] for your own usage, you can pass some settings, preferences
etc.
* addFilter = [Link]
* addOrder = [Link]
* etc...
* getData = [Link]
`[Link]` maps what values come from where! This explains custom node names
and attributes that are
magically inserted in resulting JS configuration.
`Magento_Ui/view/base/ui_component/etc/[Link]`
```xml
<component name="dataSource">
<schema name="current">
<argument name="dataProvider" xsi:type="configurableObject">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="submit_url" type="url" xsi:type="converter">settings/
submitUrl</item>
...
<item name="clientConfig" type="item"
xsi:type="converter">dataProvider/settings/clientConfig</item>
<item name="provider" type="string" xsi:type="xpath">@provider</
item>
...
</item>
<item name="js_config" xsi:type="array">
<item name="deps" type="deps" xsi:type="converter">settings/deps</
item>
</item>
<item name="layout" xsi:type="array">
<item name="type" type="string" xsi:type="xpath">settings/layout/
type</item>
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 82 of 128
Using [Link] we can deduce full `<dataSource>` definition and what parameter
will end up where:
```xml
<dataSource class="Magento\Ui\Component\DataSource">
<aclResource/>
<settings>
<submitUrl />
<validateUrl />
<updateUrl/>
<filterUrlParams/>
<storageConfig/>
<statefull/>
<imports/>
<exports/>
<links/>
<listens/>
<ns/>
<componentType/>
<dataScope/>
<deps/>
<layout>
<type/>
<navContainerName/>
</layout>
</settings>
<dataProvider name="some_name" class="SomeDataProviderClass">
<settings>
<primaryFieldName>entity_id</primaryFieldName>
<requestFieldName>id</requestFieldName>
</settings>
</dataProvider>
</dataSource
```
With this sacred knowledge we can add/override data provider parameters explicitly in
addition to normal config:
```xml
<dataSource name="...">
<argument name="dataProvider" xsi:type="configurableObject">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="referer_url" xsi:type="url" path="sales/archive/shipments/
index"/>
</item>
</argument>
</argument>
<dataProvider class="..." name="..">
<settings>...</settings>
</dataProvider>
</dataSource>
```
Okay, ui component *data source* doesn't get any configuration, it all goes to *data
provider* - 'submit_url' etc.
Data provider can only return data and modify meta, how does it pass these JS values for
JS components?
Here's how:
- View\Layout\Generic::build (initial page open) or
View\Element\UiComponent\ContentType\Json::render (load AJAX data)
- [Link] -- merges
* [Link]
* data [Link] -- returns data['config'] -- all the params we set as
arguments
## UI Form
*Caveats:*
- default form [Link] is `templates/form/default`. But its spinner works only with
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 84 of 128
`tabs` layout.
`<div data-role="spinner" data-component="{{getName()}}.areas"`. Normal form does not
have `areas`, and
so this spinner will work forever.
Solution:
* either set [Link] = `templates/form/collapsible`:
`<div data-role="spinner" data-component="{{getName()}}.{{getName()}}"` - this
matches normal form
* or set [Link] = `tabs`. It will init `areas` suitable for default
template.
See \Magento\Ui\Component\Layout\Tabs::initAreas.
*component [Link]*:
- read ID from request, e.g. current entity ID
- filter data provider by primaryField = $requestedValue
- get row data by requested primary ID
* note that normal collections don't return rows indexed by ID and you will receive an
error
* Magento data provider override getData and index by ID manually, e.g.
\Magento\Cms\Model\Page\DataProvider::getData
Magic unraveled:
- see [Link] `<field class="Magento\Ui\Component\Form\Field"/>`
- \Magento\Ui\Component\Form\Field::prepare - creates new child ui component
`wrappedComponent`
with type = `$this->getData('config/formElement')`.
- how to set config/formElement? See [Link], search for `component
name="field"`:
`<item name="formElement" type="string" xsi:type="xpath">@formElement</item>`.
`formElement` creates arbitrary ui component and inherits same data as main `field`.
```
- multiselect
## UI Listing
`listing` structure:
- argument data.js_config.provider
- `dataSource`
* `dataProvider`
- `filterbar`
* `filters`
* `bookmark`
* `export`
* `massaction`
- `columns`
* `column`
```xml
<column>
<settings>
<dataType></dataType> <!-- creates wrapped UI component, column config =
merge([Link] + [Link]) -->
</settings>
</column>
```
DataType:
- text - default
Listing toolbar:
```xml
<listingToolbar>
<bookmark />
<columnsControls />
<filterSearch />
<filters />
<massaction>
<action name="delete">
<settings>
<label/>
<type/> - delete/edit/...
<url path=""/>
<confirm>
<message/>
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 86 of 128
<title/>
</confirm>
<callback>
<target />
<provider />
</callback>
</settings>
</action>
</massaction>
<paging />
</listingToolbar>
```
Column types:
- selection
```xml
<selectionsColumn name="ids">
<settings>
<indexField>block_id</indexField>
</settings>
</selectionsColumn>
```
- select
```xml
<column name="is_active" component="Magento_Ui/js/grid/columns/select">
<settings>
<options class="Magento\Cms\Model\Block\Source\IsActive"/>
<filter>select</filter>
<editor>
<editorType>select</editorType>
</editor>
<dataType>select</dataType>
<label translate="true">Status</label>
</settings>
</column>
```
- ...
column/settings/filter:
- textRange
- text
- select
- dateRange
- ...
## UNSORTED
Magento\Framework\Config\DataInterfaceFactory = \Magento\Ui\Config\Data::get('cert_form')
-
evil class that converts all the obscure UI component conventions into real
understandable data array.
E.g. `<settings>`, `<formElement>` and the like.
Magento_Ui/view/base/ui_component/etc/[Link]
Magento_Ui/view/base/ui_component/etc/[Link]
- config [Link]
- new config reader \Magento\Ui\Config\Reader('cert_form.xml')
- config [Link]:
* find all UI files with this name
* [Link]
+ convert file contents to DOM \Magento\Ui\Config\Reader\Dom(file content) - new
\[Link]
+ convert DOM to array - \Magento\Ui\Config\Converter::convert(DOMDocument)
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 87 of 128
## Describe how to check for permissions in the permissions management tree structures.
*How would you add a new user with a given set of permissions?*
- 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.
- 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.
```php
/**
* Get a list of available resources using user details
*
* @param string $userType
* @param int $userId
* @return string[]
* @throws AuthorizationException
* @throws LocalizedException
*/
public function getAllowedResourcesByUser($userType, $userId)
{
if ($userType == UserContextInterface::USER_TYPE_GUEST) {
return [self::PERMISSION_ANONYMOUS];
} elseif ($userType == UserContextInterface::USER_TYPE_CUSTOMER) {
return [self::PERMISSION_SELF];
}
try {
$role = $this->_getUserRole($userType, $userId);
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 88 of 128
if (!$role) {
throw new AuthorizationException(
__('We can\'t find the role for the user you wanted.')
);
}
$allowedResources = $this->getAllowedResourcesByRole($role->getId());
} catch (AuthorizationException $e) {
throw $e;
} catch (\Exception $e) {
$this->logger->critical($e);
throw new LocalizedException(
__(
'Something went wrong while compiling a list of allowed resources. '
. 'You can find out more in the exceptions log.'
)
);
}
return $allowedResources;
}
/**
* Get a list of available resource using user role id
*
* @param string $roleId
* @return string[]
*/
public function getAllowedResourcesByRole($roleId)
{
$allowedResources = [];
$rulesCollection = $this->rulesCollectionFactory->create();
$rulesCollection->getByRoles($roleId)->load();
$acl = $this->aclBuilder->getAcl();
/** @var \Magento\Authorization\Model\Rules $ruleItem */
foreach ($rulesCollection->getItems() as $ruleItem) {
$resourceId = $ruleItem->getResourceId();
if ($acl->has($resourceId) && $acl->isAllowed($roleId, $resourceId)) {
$allowedResources[] = $resourceId;
}
}
return $allowedResources;
}
```
However the actual code to set privilage permission may look like this in the core code
vendor/magento/magento2-base/setup/src/Magento/Setup/Fixtures/[Link]
```php
$adminUser = $this->userFactory->create();
$adminUser->setRoleId($role->getId())
->setEmail('admin' . $i . '@[Link]')
->setFirstName('Firstname')
->setLastName('Lastname')
->setUserName('admin' . $i)
->setPassword('123123q')
->setIsActive(1);
$adminUser->save();
```
```php
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See [Link] for license details.
*/
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 89 of 128
namespace Magento\Setup\Fixtures;
use Magento\Authorization\Model\Acl\Role\Group;
use Magento\Authorization\Model\RoleFactory;
use Magento\Authorization\Model\RulesFactory;
use Magento\Authorization\Model\UserContextInterface;
use Magento\Framework\Acl\RootResource;
use Magento\User\Model\ResourceModel\User\CollectionFactory as UserCollectionFactory;
use Magento\User\Model\UserFactory;
/**
* Generate admin users
*
* Support the following format:
* <!-- Number of admin users -->
* <admin_users>{int}</admin_users>
*/
class AdminUsersFixture extends Fixture
{
/**
* @var int
*/
protected $priority = 5;
/**
* @var UserFactory
*/
private $userFactory;
/**
* @var RoleFactory
*/
private $roleFactory;
/**
* @var UserCollectionFactory
*/
private $userCollectionFactory;
/**
* @var RulesFactory
*/
private $rulesFactory;
/**
* @var RootResource
*/
private $rootResource;
/**
* @param FixtureModel $fixtureModel
* @param UserFactory $userFactory
* @param UserCollectionFactory $userCollectionFactory
* @param RoleFactory $roleFactory
* @param RulesFactory $rulesFactory
* @param RootResource $rootResource
*/
public function __construct(
FixtureModel $fixtureModel,
UserFactory $userFactory,
UserCollectionFactory $userCollectionFactory,
RoleFactory $roleFactory,
RulesFactory $rulesFactory,
RootResource $rootResource
) {
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 90 of 128
parent::__construct($fixtureModel);
$this->userFactory = $userFactory;
$this->roleFactory = $roleFactory;
$this->userCollectionFactory = $userCollectionFactory;
$this->rulesFactory = $rulesFactory;
$this->rootResource = $rootResource;
}
/**
* {@inheritdoc}
*/
public function execute()
{
$adminUsersNumber = $this->fixtureModel->getValue('admin_users', 0);
$adminUsersStartIndex = $this->userCollectionFactory->create()->getSize();
$role = $this->createAdministratorRole();
/**
* {@inheritdoc}
*/
public function getActionTitle()
{
return 'Generating admin users';
}
/**
* {@inheritdoc}
*/
public function introduceParamLabels()
{
return [
'admin_users' => 'Admin Users'
];
}
/**
* Create administrator role with all privileges.
*
* @return \Magento\Authorization\Model\Role
*/
private function createAdministratorRole()
{
$role = $this->roleFactory->create();
$role->setParentId(0)
->setTreeLevel(1)
->setSortOrder(1)
->setRoleType(Group::ROLE_TYPE)
->setUserId(0)
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 91 of 128
->setUserType(UserContextInterface::USER_TYPE_ADMIN)
->setRoleName('Example Administrator');
$role->save();
return $role;
}
}
```
# Demonstrate ability to use products and product types
7 product types:
- [*virtual*]([Link]
Catalog/Model/Product/Type/[Link])
- [*simple*]([Link]
Catalog/Model/Product/Type/[Link])
- [*downloadable*]([Link]
Downloadable/Model/Product/[Link])
- [*configurable*]([Link]
ConfigurableProduct/Model/Product/Type/[Link])
- [*grouped*]([Link]
GroupedProduct/Model/Product/Type/[Link])
Groups multiple products on same page. Customer can buy all of some of them, edit qty.
After adding to cart, they are separate, same as if you added each of them one by one.
- [*bundle*]([Link]
Model/Product/[Link])
### What tools (in general) does a product type model provide?
*_prepareProduct* - given add to cart request, returns product items that will be
converted to quote items
Standard types:
- `simple._prepareProduct` - just returns [product]. one record will be saved in
quote_item
- `configurable._prepareProduct` - prepares and returns [parentProduct, childProduct].
Configures parent, configures child, then adds some useful linking info to their custom
options.
+ `simple_product = $subProduct`
+ recursively calls [Link]._prepareProduct. this will configure
child product as well
* [Link](`parent_product_id`, ID)
- `grouped._prepareProduct`
* loads sub products individually from `catalog_product_link` by type id
LINK_TYPE_GROUPED = 3
* subProduct[].getTypeInstance._prepareProduct
* some meta info is added to custom options:
+ `product_type = 'grouped'`
* all configured products are returned as separate items, not linked to any parent
- `bundle._prepareProduct` - [parentProduct, selectedChild1, ..., selectedChildN]
- `downloadable._prepareProduct` - same as simple, plus additionally generates product
links
* shows error if required links not selected
* [Link](`downloadable_link_ids`, '1,2,...')
`_prepareProduct` is mostly where product type customization are placed. Here you analyze
request (`$_POST` values),
validate input, can set prices, custom option.
Primary goal is to return configured products with custom options
(`[Link]`).
Each product will be converted to `quote_item` records, custom options will be converted
to `quote_item_option` records.
*processBuyRequest()*
Convert buyRequest back to options to configure when you added product to cart and click
Configure.
*checkProductBuyState()*
Check product has all required options. read [Link], throws exception on
error.
Used by quote_item.checkData() - checks if product in cart is healthy.
*getOrderOptions()*
Prepare additional options/information for order item which will be created from this
product.
- product_calculations - parent/child
- shipment_type - together/separately
*getSku()*, *getOptionSku()*
Used in:
- fulltext search to get attributes of product+children
- product flat indexer - insert children into flat
*getWeight()*
*isSalable()*
*processBuyRequest()*
Used for setting product preconfigured values - color, size, bundle selections - when
configuring added product.
[Helper\[Link]]([Link]
develop/app/code/Magento/Catalog/Helper/[Link]#L485):
```php
$optionValues = $product->processBuyRequest($buyRequest);
// $type->processBuyRequest
$optionValues->setQty($buyRequest->getQty());
$product->setPreconfiguredValues($optionValues);
```
*Terms:*
- price info factory - creates *price info* and passes new *price collection* for
specific product instance and qty
- *price info* - holds *adjustment collection* and *price collection*. same across all
product types
- *price collection* - holds price pool
- *price pool* - holds price interfaces for every type of price - regular, special, final
etc.
Class is same, but a lot of copies are created by di configuration.
- *adjustment collection* - holds *adjustment pool* and adjustment codes - `tax`, `wee`,
`wee_tax`
- *adjustment pool* - holds adjustments by code - `tax` sort 20, `wee` sort 25, `wee_tax`
sort 35
- *price interface* - holds float value in display currency, price amount obj., can
return custom amount without specific adjustments
- *amount interface* - in display currency. base, full amount, adjustments
What's `wee_tax`:
- fixed product tax (FPT) can be configured to be taxed
- tax percentage over fixed tax
Flow:
- product implements Pricing\SaleableInterface
- [Link]
- \Magento\Catalog\Model\Product\Type::getPriceInfo
- Pricing\PriceInfo\Factory::create - by product instance and qty
* di injectable type configurations: product_type_id => [infoClass, prices]
* creates *price collection* = [$type_id]['prices']
+ separate *copy* of `Pricing\Price\Collection` with according *copy* of
`Pricing\Price\Pool` with
separate set of price info classes by type - regular, final etc.
+ ALL product types have personal collection and pool instance
* creates *price info* = `Pricing\PriceInfo\Base`
+ MOST product types use SAME instance `Pricing\PriceInfo\Base` with default
`Pricing\Adjustment\Collection`
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 95 of 128
- Pricing\PriceInfoInterface
- Pricing\Price\Collection - separate pool by product type
- Pricing\Price\Pool - iterate/access by code prices. A lot of pools per product type.
`target` argument means inherit prices from other type
* Magento\Catalog\Pricing\Price\Pool
* Magento\Bundle\Pricing\Price\Pool
* Magento\ConfigurableProduct\Pricing\Price\Pool
* Magento\Downloadable\Pricing\Price\Pool
* Magento\GroupedProduct\Pricing\Price\Pool
* Magento\GiftCard\Pricing\Price\Pool
## product type -> price collection -> price pool -> prices:
- `default` -> Magento\Catalog\Pricing\Price\Collection ->
Magento\Catalog\Pricing\Price\Pool:
* regular_price = Magento\Catalog\Pricing\Price\RegularPrice
* final_price = Magento\Catalog\Pricing\Price\FinalPrice
* tier_price = Magento\Catalog\Pricing\Price\TierPrice
* special_price = Magento\Catalog\Pricing\Price\SpecialPrice
* base_price = Magento\Catalog\Pricing\Price\BasePrice
* custom_option_price = Magento\Catalog\Pricing\Price\CustomOptionPrice
* configured_price = Magento\Catalog\Pricing\Price\ConfiguredPrice
* wishlist_configured_price = Magento\Catalog\Pricing\Price\ConfiguredPrice
* catalog_rule_price = Magento\CatalogRule\Pricing\Price\CatalogRulePrice
* (staging) catalog_rule_price =
Magento\CatalogRuleStaging\Pricing\Price\CatalogRulePrice
* regular_price = Magento\Catalog\Pricing\Price\RegularPrice
* final_price = Magento\GiftCard\Pricing\Price\FinalPrice
* tier_price = Magento\Catalog\Pricing\Price\TierPrice
* special_price = Magento\Catalog\Pricing\Price\SpecialPrice
* msrp_price = Magento\Msrp\Pricing\Price\MsrpPrice
* custom_option_price = Magento\Catalog\Pricing\Price\CustomOptionPrice
* base_price = Magento\Catalog\Pricing\Price\BasePrice
* configured_price = Magento\GiftCard\Pricing\Price\ConfiguredPrice
* bundle_option = Magento\Bundle\Pricing\Price\BundleOptionPrice
* wishlist_configured_price = Magento\GiftCard\Pricing\Price\ConfiguredPrice
And so on. Most classes customize PriceInterface `getValue`, while `getAmount` and
adjustment adding is the same.
*Adjustment*:
- isIncludedInBasePrice
* tax = true if admin prices are including tax
* weee = false, they are always added on top of base price
- isIncludedInDisplayPrice
* tax = display prices incl. tax
- *extractAdjustment* - return base price without adjustment (taxes)
- *applyAdjustment* - base price + adjustment
- getSortOrder
### How would you identify what is composing the final price of the product?
`default` final price:
- `final_price` delegates calculation to `base_price`
- gets all prices info, checks implementing `Pricing\Price\BasePriceProviderInterface`
* regular_price, catalog_rule_price, special_price, tier_price
- takes min value
```xml
<type name="Magento\Framework\Pricing\Adjustment\Collection">
<arguments>
<argument name="adjustments" xsi:type="array">
<item name="somecode"
xsi:type="const">Custom\Module\Pricing\Adjustment::ADJUSTMENT_CODE</item>
</argument>
</arguments>
</type>
```
```xml
<type name="Magento\Framework\Pricing\Adjustment\Pool">
<arguments>
<argument name="adjustments" xsi:type="array">
<item name="somecode" xsi:type="array">
<item name="className"
xsi:type="string">Custom\Module\Pricing\Adjustment</item>
<item name="sortOrder" xsi:type="string">40</item>
</item>
</argument>
</arguments>
</type>
```
- implement isIncludedInBasePrice, isIncludedinDisplayPrice, extractAdjustment,
applyAdjustment
1. Register some new price type with `default` price pool, implement interface
`Pricing\Price\BasePriceProviderInterface`.
My new price can influence final price if returns lowest price.
1. Create plugin over necessary native price classes, e.g. around getValue
1. Replace price class via DI for specific virtual pool, e.g.
```xml
<virtualType name="Magento\Bundle\Pricing\Price\Pool">
<arguments>
<argument name="prices" xsi:type="array">
<item name="regular_price"
xsi:type="string">Custom\Module\Pricing\Price\BundleRegularPrice</item>
</argument>
<arguments>
</virtualType>
```
1. Replace price class via DI preference for specific class.
Terms:
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 98 of 128
* `default/default_render_class`
- creates new found *price box* block instance of `Pricing\Render\PriceBoxRenderInterface`
- finds and sets template from data arguments by patterns:
* `$type/prices/$priceCode/render_template`
* `$type/default_render_template`
* `default/prices/$priceCode/render_template`
* `default/default_render_template`
- all *price boxes* are cached: `store-template-baseUrl-template-
[price_id_prefix.productId.price_id_suffix]-priceCode`
has special = regular price ([Link]) < final price (lowest of regular_price,
catalog_rule_price, special_price, tier_price)
### How would you render price in a given place on the page
Include new copy block type `Magento\Catalog\Pricing\Render`, pass required value for
`price_type_code` argument.
### ... and how would you modify how the price is rendered?
- use data-arguments to customize data for *price box*, *amount renders* and *adjustment
renders*:
* css_classes, price_id, price_id_prefix, price_id_suffix
- create layout handle `catalog_product_prices.xml`, reference block
`[Link]` and customize arguments.
* (product type/price type or default type) *price box* class and template, *amount
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 100 of 128
Category features:
- can be anchored/non-anchored. When anchored, all products from subcategories "bubble
up" and show up in
the category listing even when _not assigned_ explicitly.
- include in menu switch
- display type - products only, cms block only, both
- you can assign direct category products and set their individual positions. Directly
assigned products
always show before "bubbled up" for anchor.
## [Category model]([Link]
Catalog/Model/[Link]) - few changes since M1
- getTreeModel, getTreeModelInstance
- move - event `catalog_category_move_before`, `catalog_category_move_after`, event
`category_move`,
[Link], clean cache by tag
- getProductCollection
- getStoreIds
- getPathIds, getLevel
- getParentId, getParentIds - without self ID
- getParentCategory
- getParentDesignCategory - Category
- isInRootCategoryList
## Describe the category hierarchy tree structure implementation (the internal structure
inside the database).
- Every category has hierarchy fields: parent_id, level, path
- To avoid recursive parent-child traversing, flat category path is saved for every
category. It includes
all parents IDs. This allows to optimize children search with single query. E.g. to
search children of
category id 2 and knowing its path '1/2', we can get children:
```
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 101 of 128
+-----------+-----------+--------------+-------+
| entity_id | parent_id | path | level |
+-----------+-----------+--------------+-------+
| 3 | 2 | 1/2/3 | 2 |
| 4 | 3 | 1/2/3/4 | 3 |
| 5 | 3 | 1/2/3/5 | 3 |
| 6 | 3 | 1/2/3/6 | 3 |
| 7 | 2 | 1/2/7 | 2 |
| 8 | 7 | 1/2/7/8 | 3 |
....
```
## Which attribute values are required to display a new category in the store?
- is_active = 1
- include_in_menu = 1, but even if not displayed in menu, can be opened by direct link
- url_key can be created automatically based on name
## What kind of strategies can you suggest for organizing products into categories?
# Determine and manage catalog rules
*Primary tables*:
- `catalogrule` - dates from/to, conditions serialized, actions serialized, simple
action, discount amount
- `catalogrule_website` - rule-website
- `catalogrule_customer_group` - rule-customer group
*Index tables*:
- `catalogrule_product` - time from/to, customer group, action operator/amount
- `catalogrule_product_price` - customer group, rule date, rule price, latest start date,
earlier end date
- `catalogrule_group_website` - rule to customer group, website.
Just unique rule/customer group/websites that currently exist in index table
`catalogrule_product` for *today*.
## Apply rules
- [*Cron job*]([Link]
CatalogRule/Cron/[Link]) 01:00 am invalidates `catalogrule_rule` indexer.
- Clicking ["Apply rules"]([Link]
Magento/CatalogRule/Model/Rule/[Link]#L54), ["Save and apply"]([Link]
magento/magento2/tree/2.2-develop/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/
Catalog/[Link]#L97) in admin panel invalidates indexer as well.
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 102 of 128
- Cron job `indexer_reindex_all_invalid`, running every minute, will pick it up and call
indexer
* Magento\CatalogRule\Model\Indexer\AbstractIndexer::executeFull
* [Magento\CatalogRule\Model\Indexer\IndexBuilder::reindexFull]([Link]
magento/magento2/tree/2.2-develop/app/code/Magento/CatalogRule/Model/Indexer/
[Link]#L280)
index builder.[doReindexFull]([Link]
code/Magento/CatalogRule/Model/Indexer/[Link]#L297):
- clear tables `catalogrule_product`, `catalogrule_product_price`
- for every rule, reindex rule product.
"one day, maybe today, maybe in a year, this rule will match these products"
* this is regardless of dates, dates are simply written in table for later uage
* get matching product IDs
* insert into `catalogrule_product` (`_replica`) combinations: product/website/customer
group
- reindex product prices `catalogrule_product_price` (`_replica`).
For yesterday, today and tomorrow reads "planned" products, and executes price update
starting from initial [Link] attribute value. If multiple rules match in same
date,
they will all apply (unless stop flag).
collection
Admin form:
- \Magento\CatalogRule\Block\Adminhtml\Promo\Catalog\Edit\Tab\Conditions
- conditions renderer \Magento\Rule\Block\Conditions
- \Magento\Rule\Model\Condition\AbstractCondition::asHtmlRecursive:
* TypeElementHtml
* AttributeElementHtml
* OperatorElementHtml
* ValueElementHtml
* RemoveLinkHtml
* ChooserContainerHtml
Matching products:
- [Magento\CatalogRule\Model\Rule::getMatchingProductIds]([Link]
magento2/tree/2.2-develop/app/code/Magento/CatalogRule/Model/[Link]#L294)
- get product collection
- for each product, run [rule::callbackValidateProduct]([Link]
magento2/tree/2.2-develop/app/code/Magento/CatalogRule/Model/[Link]#L329)
- check if product matches conditions [Magento\Rule\Model\Condition\Combine::validate]
([Link]
Condition/[Link]#L331)
Quote Item shares 2 models, quote item and quote address item.
## Describe how to modify these models and effectively use them in customizations.
- All support extension attributes
- Quote address supports custom_attributes
## Inventory validation
Qty can be updated from:
- cart [Link] by:
* checkout/cart/updatePost with update_cart_action='update_qty'
- [Link] by:
* admin/sales/order/create
* advanced checkout
* checkout/cart/updateItemOptions
* admin/cart_product_composite_cart/update
* quote [Link] via Savehandler and CartItemPersister
on qty update:
- quote [Link]
- event `sales_quote_item_qty_set_after`
- \Magento\CatalogInventory\Observer\QuantityValidatorObserver::execute
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 105 of 128
- \Magento\CatalogInventory\Model\Quote\Item\QuantityValidator::validate
* all validation errors can set [Link], quote [Link]
* check out of stock, parent out of stock
* when there's sub products with qty options:
+ check qty increments
+ for each sub product - initializer [Link]
\Magento\CatalogInventory\Model\Quote\Item\QuantityValidator\Initializer\Option::initialize
- multiply parent qty`*`sub qty, check resulting min sale qty, max sale qty, qty
increments, in stock, backorders
\Magento\CatalogInventory\Model\StockState::checkQuoteItemQty,
\Magento\CatalogInventory\Model\StockStateProvider::checkQuoteItemQty
* when no sub products qty:
+ initializer stock [Link]
\Magento\CatalogInventory\Model\Quote\Item\QuantityValidator\Initializer\StockItem::initialize
\Magento\CatalogInventory\Model\StockState::checkQuoteItemQty
\Magento\CatalogInventory\Model\StockStateProvider::checkQuoteItemQty
## Add to cart
cart model - deprecated.
- checkout/cart/updatePost
* [Link] =>
\Magento\CatalogInventory\Model\StockStateProvider::suggestQty - qty increments, min/max
qty
* [Link]
+ events `checkout_cart_update_items_before`, `checkout_cart_update_items_after`
+ quote [Link] -- triggers stock validation
* [Link]
[Link]
```xml
<section name="quote">
<group name="totals">
<item name="tax" instance="Magento\Tax\Model\Sales\Total\Quote\Tax"
sort_order="450">
<renderer name="adminhtml"
instance="Magento\Sales\Block\Adminhtml\Order\Create\Totals\Tax"/>
<renderer name="frontend" instance="Magento\Tax\Block\Checkout\Tax"/>
</item>
</group>
</section>
```
Classes:
- Quote\TotalsCollector - does the job, `collect` and `collectAddressTotals`.
(collectQuoteTotals not used)
## Quote totals
- [Link]
- total = [Link] - crunch numbers, return data to set on quote
* create *quote total object* quote\address\total. all you set here will be copied to
quote
* event `sales_quote_collect_totals_before`
* collect item qtys - [Link], [Link], [Link]
* zero total object amounts - subtotal, grand total etc.
* collect each *address totals*:
+ *collect address totals* into new address total object
- address total = quote.*collectAddressTotals* _(see below)_
+ *add* to quote total object: shipping amount, shipping description, subtotal,
subtotal with discount, grant total
(+ base versions when applicable)
* validate max grand total amount 99999999
* validate coupon code - check that at least one address has it, otherwise reset to ''
- code is invalid
* event `sales_quote_collect_totals_after`
- [Link]([Link])
## Address totals
[Link] for each address (billing, shipping) - set address fields,
return some fields for quote
- new shipping assignment obj:
+ shipping = [method, address]
+ items = address items. empty for normal checkout?
- create new *address total object* quote\address\total. all you set here will be copied
to address
- event `sales_quote_address_collect_totals_before`
- get collectors:
+ _initModels -> _initModelInstance -> [Link] -- tax collector can
change its sort programmaticaly
+ sales/totals_sort/* - admin configurable *totals retrievers* ordering - display order
- *every collector[].collect*
+ all data set on address total object will be copied to address. Main amounts will
also copy to quote total object.
+ has direct access to quote
+ has access to address via shipping [Link]
- event `sales_quote_address_collect_totals_after`
- [Link](address [Link])
## Display totals
Totals rendered in cart in UI-component fashion with JS configuration.
js model [Link]:
```
[
{address data fields = populate \Magento\Quote\Api\Data\TotalsInterface with
[Link]},
items = quote visible items,
total_segments = as returned from totals [Link]
]
```
Invoice totals:
- \Magento\Sales\Model\Order\Invoice::collectTotals
- every total - \Magento\Sales\Model\Order\Invoice\Total\AbstractTotal::collect(invoice)
+ directly update invoice object
handle `sales_order_invoice_view`:
```xml
<block class="Magento\Sales\Block\Adminhtml\Order\Invoice\Totals" name="invoice_totals"
template="Magento_Sales::order/[Link]">
```
- block totals.`_beforeToHtml`
- block order totals.`_initTotals` - add hardcoded totals - subtotal, shipping, discount,
grand_total, base_grandtotal
- every child [Link]
- child blocks call block order [Link](total, after={'first'|'last'|$code}) or
addTotalBefore(total, before)
+ total = DataObject [code, value, base_value, label, optional: {area, block_name,
is_formated, strong}]
- block order totals template
+ totals with area = 'footer'
+ totals with empty area
+ if 'block_name', getChildHtml - simply renders child block
+ otherwise print [label, value], value will be price formated unless flag 'is_formated'
## Describe how to modify the price calculation process in the shopping cart.
- when preparing product in product type model, can set `product.custom_price`
- register quote totals collector in [Link]. Edit given address_total.
Examples:
* `$total->setGrandTotal($total->getGrandTotal() - $pointsCurrencyAmountUsed);`
* call `$total->addTotalAmount(code, value)`. Grand total will then be sum of all total
amounts - they can be negative.
## How can you add a custom totals model or modify existing totals models?
- add quote totals collector - declare in [Link], implement `collect` and `fetch`
+ in collect can modify address totals object (preferred), or edit directly quote or
quote address via shipping assignment
+ fetch can return null, or one or many declarations in array format or ready
Address\Total objects
+ fetch can rewrite other totals with same 'code'
+ in cart block JS config, add view model and implement getPureValue, getValue. Can
read values from model [Link]().totals_segments
- Render totals in admin:
+ sales_invoice_view, referenceBlock 'invoice_totals', add block with method
`initTotals`, call parentBlock.`addTotal`
# Demonstrate ability to customize the shopping cart
Multiple selection:
- `salesrule_website`
- `salesrule_customer_group`
- `salesrule_coupon`
* rule_id, code, usage_limit, usage_per_customer, times_used, expiration_date,
is_primary, created_at, type
Usage tracking:
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 109 of 128
- `salesrule_coupon_usage`
* coupon_id, customer_id, times_used
- `salesrule_customer`
* rule_id, customer_id, times_used
- `salesrule_coupon_aggregated`, `salesrule_coupon_aggregated_updated`,
`salesrule_coupon_aggregated_order` - reporting stuff
Notes:
- ui component `sales_rule_form`
- conditions - html content block - *generic form* Edit\Tab\Conditions
- conditions fieldset renderer \Magento\Rule\Block\[Link]
- `$element->getRule()->getConditions()->asHtmlRecursive();`
- Magento\SalesRule\Model\Rule extends Magento\Rule\Model\AbstractModel
+ store labels
+ coupon generation
- Conditions model instance - \Magento\SalesRule\Model\Rule\Condition\Combine, extends
getNewChildSelectOptions:
+ \Magento\SalesRule\Model\Rule\Condition\Product\Found - Product attribute combination
+ \Magento\SalesRule\Model\Rule\Condition\Product\Subselect - Products subselection
+ Conditions combination - self, recursion
+ \Magento\SalesRule\Model\Rule\Condition\Address - Cart Attribute:
base_subtotal, total_qty, weight, shipping_method, postcode, region, region_id,
country_id
what is:
- `is_advanced` column - not used
- `product_ids` column - not used
- `salesrule_product_attribute` table - attributes currently used in conditions and
actions of rule.
Implementing rules:
- Rule model
* getConditionsInstance - will be used in form as `$model->getConditions()-
>asHtmlRecursive()`.
Usually starting conditions are composite - selector [all/any] and sub-conditions
selection.
- Composite conditions model
* getNewChildSelectOptions - used by Consition\[Link]
- checkout/cart/updatePost
* [Link] =>
\Magento\CatalogInventory\Model\StockStateProvider::suggestQty - qty increments, min/max
qty
* [Link]
+ events `checkout_cart_update_items_before`, `checkout_cart_update_items_after`
+ quote [Link] -- triggers stock validation
* [Link]
- \Magento\Customer\Model\Session::setCustomerDataAsLoggedIn
- event `customer_login`
- \Magento\Checkout\Observer\LoadCustomerQuoteObserver
- checkout session.`loadCustomerQuote`:
* event `load_customer_quote_before`
* finds existing customer quote
* detects that guest quote <> customer quote
* customer quote.`merge`(guest quote):
+ event `sales_quote_merge_before`
+ for each quote item.`compare` - product id, options same
+ if items same, qty++
+ if guest item is new, clone quote item, [Link], event `sales_quote_add_item`
+ event `sales_quote_merge_after`
* *delete guest quote*
Customizations:
- provide cart block data arguments `<referenceBlock name="[Link]">`:
* `renderer_list_name` - use custom renderer list
* `overriden_templates` - array by product type
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 112 of 128
```xml
<referenceBlock name="[Link]">
<block class="Magento\Checkout\Block\Cart\Item\Renderer"
name="[Link]" as="default" template="Magento_Checkout::cart/
item/[Link]">
<block class="Magento\Checkout\Block\Cart\Item\Renderer\Actions"
name="[Link]" as="actions">
<block class="Magento\Checkout\Block\Cart\Item\Renderer\Actions\Edit"
name="[Link]" template="Magento_Checkout::cart/
item/renderer/actions/[Link]"/>
<block class="Magento\Checkout\Block\Cart\Item\Renderer\Actions\Remove"
name="[Link]"
template="Magento_Checkout::cart/item/renderer/actions/[Link]"/>
</block>
</block>
</referenceBlock>
```
*Configure product*:
- params[`buyRequest`] = quote [Link]
- helper product [Link]
* product [Link]
* product helper.`prepareProductOptions` - set default selections from buy request,
e.g. selected size=XXL
+ [Link], `product [Link]`, `product
[Link]`
+ product.`setPreconfiguredValues`
* [Link] -- used to show all hidden customization options instantly
in edit mode
* event `catalog_controller_product_view`
Alternatively, you can always register extension attribute and load/save it manually.
# Demonstrate ability to customize shipping and payment methods
[Customize checkout]([Link]
checkout_overview.html)
Magento 2 replaces abstract payment method that you would normally extend with payment
adapter.
Where there used to be protected methods for every aspect - validate, set data,
authorize, invoice, refund etc. -
now it moves to separate classes. Just like with controllers where each action resides in
own class.
All you need to do is wire your pieces together with DI config.
Additional benefit - you can smoothly choose between simple class implementation or make
a composite - split
your business logic into even more fine grained pieces and mix them.
Terms:
- *payment adapter* - config-based payment method implementation. Holds operation pool,
payment validator,
value handler, form block type, info block type.
[Model\Method\Adapter]([Link]
Payment/Model/Method/[Link])
- *validator pool* - check country, currency, min-max amount etc. Magento 1 equivalent
is `[Link]()`.
[Gateway\Validator\ValidatorInterface]([Link]
code/Magento/Payment/Gateway/Validator/[Link])
* [Gateway\Config\ValueHandlerPool]([Link]
code/Magento/Payment/Gateway/Config/[Link])
* [Gateway\Config\ValueHandlerInterface]([Link]
app/code/Magento/Payment/Gateway/Config/[Link])
- [*command pool*]([Link]
gateway/[Link])
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 114 of 128
- holds commands.
[Gateway\Command\CommandPool]([Link]
Magento/Payment/Gateway/Command/[Link])
- [*gateway command*]([Link]
payment-gateway/[Link])
- authorize, sale, invoice, refund, cancel, void etc. Holds request builder, transfer
factory, client,
operation validator, result processor.
[Gateway\CommandInterface]([Link]
Magento/Payment/Gateway/[Link])
- [*request builder*]([Link]
payment-gateway/[Link])
- given subject (quote, order or invoice) as array, prepares request parameters
as array - amounts, transaction ids, cart contents etc.
[Gateway\Request\BuilderInterface]([Link]
code/Magento/Payment/Gateway/Request/[Link])
- *transfer factory* - convert request parameters into transfer object. Here you add
lowest level call params -
request method (get/post etc.), headers, API URL, encode body. Transfer object is built
with transfer factory
builder, just like you use searchCriteriaBuilder to create searchCriteria.
[Gateway\Http\TransferFactoryInterface]([Link]
app/code/Magento/Payment/Gateway/Http/[Link])
- [*gateway client*]([Link]
payment-gateway/[Link])
- simply makes a call with given parameters, e.g. sends Curl request. Don't reinvent
the wheel, you shouldn't need to implement this, just use default Zend and Soap clients,
you only need to format transfer object for them.
[Gateway\Http\ClientInterface]([Link]
Magento/Payment/Gateway/Http/[Link])
- [*response validator*]([Link]
payment-gateway/[Link])
- checks response - maybe HTTP response code, body, parse returned errors etc.
[Gateway\Validator\ResultInterface]([Link]
code/Magento/Payment/Gateway/Validator/[Link])
- [*response handler*]([Link]
payment-gateway/[Link])
- save response detauls in payment additional data, save transaction numbers, change
order status, send email etc.
[Gateway\Response\HandlerInterface]([Link]
code/Magento/Payment/Gateway/Response/[Link])
* [Block\Info]([Link]
Block/[Link])
- like in M1, shows key-value strings, overwrite `_prepareSpecificInformation`.
* [Block\ConfigurableInfo]([Link]
Magento/Payment/Block/[Link]) - automatically shows values from
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 115 of 128
payment.additional_information:
+ [Link] payment method declaration - show `paymentInfoKeys` and exclude
`privateInfoKeys` - comma separated.
+ override `getLabel` to show human labels instead of raw field keys
[Model\Checks\SpecificationInterface]([Link]
code/Magento/Payment/Model/Checks/[Link])
- `method [Link]`:
- event `payment_method_assign_data_{$code}`
- event `payment_method_assign_data`
- quote payment._beforeSave
* [Link]
- [Link]
*Commands:*
- fetchTransactionInfo() - `can_fetch_transaction_information`,
`fetch_transaction_information` gateway command
- initialize() - `can_initialize`, `initiailze` command
- order() - `can_order`, `order` gateway command. same as authorize + capture = sale?
- authorize() - `can_authorize`, `authorize` command
- capture() - `can_capture`, `capture` command
- refund() - `can_refund`, `refund` command
- cancel() - `can_cancel`, `can_cancel` command
- void() - `can_void`, `void` command
- acceptPayment() - `can_accept_payment`, `accept_payment` command
- denyPayment() - `can_deny_payment`, `deny_payment` command
Magento\Vault\Model\Method\Vault:
- pure class, no parent, interface \Magento\Vault\Model\VaultPaymentInterface
- proxies almost all methods: is`*`, can`*`, validate
- not implemented, throws error: initialize(), order(), refund(), cancel(), void(),
acceptPayment(), denyPayment()
- assignData:
* event `payment_method_assign_data_vault`
* event `payment_method_assign_data_vault_{$code}`
* original payment `[Link]`
- authorize:
* attach token extension attribute:
+ `customer_id` from payment.additional_information
+ `publish_hash` from payment.additional_information
+ order [Link] attributes[`vault_payment_token`] =
\Magento\Vault\Api\PaymentTokenManagementInterface::getByPublicHash(`publish_hash`,
`customer_id`)
+ select from `vault_payment_token` where publish_hash = ? and customer_id = ?/NULL
* commandManagerPool[`[Link]`].executeBycode(`vault_authorize`)
- capture:
* ONLY for `order`=`sale` payment action? Authorization transaction must not exist
* attach token extension attribute
* commandManagerPool[`[Link]`].executeBycode(`vault_sale`)
Virtual types can extend and augment each other pretty heavily, for example
BraintreePayPalSaleCommand is based on BraintreePayPalAuthorizeCommand
and changes only request builder.
To make Vault work, after you receive normal payment, you save token by customer.
Braintree
does this in AuthorizationCommand handler chain in one of handlers. Token
is simply read from response and put to order
payment.extension_attributes[`vault_payment_token`].
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 118 of 128
Vault on frontend:
- in checkout config provider, finds all payment methods implementing Vault interface and
returns codes for JS
- in checkout payment methods, adds `vault` payment method group
- get component providers \Magento\Vault\Model\Ui\TokenUiComponentProviderInterface -
return params for JS component
* Braintree registers component in DI \Magento\Vault\Model\Ui\TokensConfigProvider
- tokens = select from `vault_payment_token` where customer_id = ? and is_visible = 1 and
is_active = 1 and expires_at > time
- for each token, run component providers[[Link]].getComponentForToken
- [Link] = [
`{$code}_{$i}` = componentConfig,
braintree_cc_vault_0 = {...},
braintree_cc_vault_1 = {...},
]
Manually do the job in `initialize` and return custom order status and state.
- authorize, capture
- order = authorize + capture
## 9.1 Demonstrate ability to customize sales operations
Describe how to modify order processing and integrate it with a third-party ERP system.
*Describe how to modify order processing flow. How would you add new states and statuses
for an order?*
*Which refund types are available, and how are they used?*
# Overview
# 1. Topics
# 2. Resources
## 2.1. Links
+ [Magento 2 Certified Professional Developer]([Link]
professional-developer)
+ [Magento 2.2 Developer Documentation]([Link]
+ [Markdown Cheatsheet]([Link]
+ [Markdown Cheatsheet RU]([Link]
+ [Swiftotter Study Guide]([Link]
certified-developer-study-guide)
## 2.2. Credits
Inspired by [Link]
# Customer Segments
Conditions:
## Database
## Admin conditions
namespace Magento\CustomerSegment;
- Block\Adminhtml\Customersegment\Edit\Tab\General
- Block\Adminhtml\Customersegment\Edit\Tab\Conditions
- Model\Condition\Combine\AbstractCombine
- Model\Segment\Condition\Order\Address - order_address join sales_order join
customer_entity
- Model\Segment\Condition\Sales\Combine
- Model\Segment\Condition\Sales\Ordersnumber - COUNT(*)
- Model\Segment\Condition\Sales\Salesamount - sum/avg sales_order.base_grand_total
- Model\Segment\Condition\Sales\Purchasedquantity - sum/avg
sales_order.total_qty_ordered
- Model\Segment\Condition\Customer\Address
- Model\Segment\Condition\Customer\Address\DefaultAddress
Some conditions don't work for guest visitors, despite description! Example - cart grand
total amount:
```SQL
SELECT `quote`.`customer_id` FROM `quote`
WHERE (quote.is_active=1) AND (quote.store_id IN('1')) AND (quote.base_grand_total >=
'100') AND (customer_id IS NOT NULL)
```
Condition by recently viewed events **SEEMS BROKEN** when full page cache is enabled.
This condition relies on `report_viewed_product_index` table, which is handled by report
module.
Configuration:
- `reports/options/enabled`
- `reports/options/product_view_enabled`
\Magento\Reports\Model\ReportStatus::isReportEnabled
\Magento\Reports\Model\Product\Index\Viewed
This does not work when full page cache is enabled and Varnish returns HTML directly.
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 121 of 128
- Model\Segment::beforeSave
- Model\Segment::collectMatchedEvents
- recursively for each added conditions children:
- Model\Condition\Combine\AbstractCombine::getMatchedEvents
- Model\Segment::beforeSave
- Model\Rule\Condition\Combine\Root::getConditionsSql
- recursively for each added conditions children:
- Model\Condition\Combine\AbstractCombine::getConditionsSql
- `_prepareConditionsSql`
- event `customer_login`
- Observer\ProcessEventObserver::execute
- Model\Customer::processEvent
- Model\Customer::getActiveSegmentsForEvent
- Model\ResourceModel\Segment\Collection::addEventFilter - join table
`magento_customersegment_event`
- Model\Customer::_processSegmentsValidation
- Model\Segment::validateCustomer
* visitor_id, quote_id passed for guest
- Model\Segment\Condition\Combine\Root::isSatisfiedBy
* all conditions[].isSatisfiedBy
* all conditions[].Model\Condition\Combine\AbstractCombine::getConditionsSql
Result:
## Impact on performance
\Magento\CustomerSegment\Helper\Data::CONTEXT_SEGMENT = 'customer_segment'
\Magento\PageCache\Model\App\Response\HttpPlugin::beforeSendResponse
\Magento\Framework\App\Response\Http::sendVary
\Magento\Framework\App\Http\Context::getVaryString
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 122 of 128
Example:
```php
sha1($this->serializer->serialize([
'customer_group' => '1',
'customer_logged_in' => true,
'customer_segment' => ["1"]
]))
```
This lowers chances of hitting cached page, increasing load on the server.
## DepersonalizePlugin
Does 2 things:
\Magento\Framework\Pricing\Render\Layout::loadLayout:
```
$this->layout->getUpdate()->load();
// 1. \Magento\Customer\Model\Layout\DepersonalizePlugin::beforeGenerateXml:
// - remember customerGroupId and formKey from session
// 2.
\Magento\CustomerSegment\Model\Layout\DepersonalizePlugin::beforeGenerateXml:
// - remember customerSegmentIds from customer session
$this->layout->generateXml();
$this->layout->generateElements();
// 3.
\Magento\PageCache\Model\Layout\DepersonalizePlugin::afterGenerateElements:
// - event `depersonalize_clear_session`
// - session_write_close();
// - clear message session
// 4. \Magento\Customer\Model\Layout\DepersonalizePlugin::afterGenerateElements:
// - clear customer session
// - restore session._form_key
// - restore customer [Link], empty customer object with
customer group
// 5. \Magento\Catalog\Model\Layout\DepersonalizePlugin::afterGenerateElements:
// - clear catalog session
// 6.
\Magento\Persistent\Model\Layout\DepersonalizePlugin::afterGenerateElements:
// - clear persistent session
// 7.
\Magento\CustomerSegment\Model\Layout\DepersonalizePlugin::afterGenerateElements:
// - httpContext->setValue(Data::CONTEXT_SEGMENT)
// - restore customerSession->setCustomerSegmentIds
// 8. \Magento\Checkout\Model\Layout\DepersonalizePlugin::afterGenerateElements:
// - clear checkout session
```
```PHP
/** @var \Magento\CustomerSegment\Model\Segment $segment */
$segment = $this->segmentFactory->create();
$segment->setName($name);
$segment->setDescription(null);
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 123 of 128
$segment->setIsActive(1);
$segment->setWebsiteIds(array_keys($this->storeManager->getWebsites()));
$segment-
>setApplyTo(\Magento\CustomerSegment\Model\Segment::APPLY_TO_VISITORS_AND_REGISTERED);
// @see \Magento\Rule\Model\AbstractModel::loadPost
$segment->getConditions()->setConditions([])->loadArray([
'type' => \Magento\CustomerSegment\Model\Segment\Condition\Combine\Root::class,
'aggregator' => 'all',
'operator' => '',
'value' => '1',
'conditions' => [
[
'type' =>
\Magento\CustomerSegment\Model\Segment\Condition\Shoppingcart\Amount::class,
'attribute' => 'grand_total',
'operator' => '>=',
'value' => '100',
]
],
]);
$segment->save();
```
When sync is enabled, it makes 2 AJAX calls with every page opening:
- synchronize recently viewed products from local storage
- synchronize recently compared products from local storage
Configuration:
- `catalog/recently_products/synchronize_with_backend` = 0
- `catalog/recently_products/recently_viewed_lifetime` = 1000
- `catalog/recently_products/scope` - store view, store, website
Other:
- db table `catalog_product_frontend_action`
- cron `catalog_product_frontend_actions_flush` - every minute
\Magento\Catalog\Cron\FrontendActionsFlush:
- deletes compared, viewed older than (default 1000) seconds
- frontend controller 'catalog/product_frontend_action/synchronize'
Notable classes:
- Magento\Catalog\Model\FrontendStorageConfigurationPool
- Magento\Catalog\Model\Widget\RecentlyViewedStorageConfiguration
\Magento\Catalog\Block\FrontendStorageManager:
- recently_viewed_product
- recently_compared_product
- product_data_storage
- vendor/magento/module-catalog/view/frontend/web/js/[Link]
- recently_viewed_product
- recently_compared_product
- product_data_storage
- catalog_product_view.xml
- Magento\Catalog\Block\Ui\ProductViewCounter
- vendor/magento/module-catalog/view/frontend/templates/product/view/[Link]
- vendor/magento/module-catalog/view/frontend/web/js/product/view/[Link]
\Magento\Catalog\Model\ProductRender - DTO
\Magento\Catalog\Ui\DataProvider\Product\ProductRenderCollectorComposite::collect:
- Future editions of products/categories etc. are saved as a copy with a timestamped from-
to columns.
- Current staging version is saved as a flag, used in all collections to add WHERE
condition
- You can browse (preview) frontend site seeing future versions passing version and
signature GET parameters
What is a modification of the event processing mechanism introduced by the staging module?
\Magento\Staging\Model\Event\Manager:
- `catalog_category_prepare_save`
- `catalog_category_save_after`
- `catalog_category_save_before`
- `catalog_category_move_after`
- connection->select()
- \Magento\Framework\DB\Adapter\Pdo\Mysql::select
- Magento\Framework\DB\SelectFactory::create
- Magento\Framework\DB\Select\SelectRenderer
- // work with select...
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 125 of 128
- \Magento\Framework\DB\Select::assemble
- \Magento\Framework\DB\Select\SelectRenderer::render
- \Magento\Framework\DB\Select\FromRenderer
- \Magento\Staging\Model\Select\FromRenderer
```
from = [
flag = [
joinType = 'from'
tableName = 'flag'
]
]
```
```
$select->where($alias . '.created_in <= ?', $versionId);
$select->where($alias . '.updated_in > ?', $versionId);
```
**Triggers** created differently! Attributes no longer have entity_id for `_cl` tables,
so one more
extra call to load entity_id by row_id is added to each update by schedule trigger.
\Magento\Framework\Mview\View\Subscription::buildStatement
vs
\Magento\CatalogStaging\Model\Mview\View\Attribute\Subscription::buildStatement
trg_catalog_product_entity_int_after_insert:
```SQL
-- before
INSERT IGNORE INTO `catalog_product_flat_cl` (`entity_id`) VALUES (NEW.`entity_id`);
-- after
SET @entity_id = (SELECT `entity_id` FROM `catalog_product_entity` WHERE `row_id` =
NEW.`row_id`);
INSERT IGNORE INTO `catalog_product_flat_cl` (`entity_id`) values(@entity_id);
```
trg_catalog_product_entity_int_after_update:
```SQL
-- before
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;
-- after
SET @entity_id = (SELECT `entity_id` FROM `catalog_product_entity` WHERE `row_id` =
NEW.`row_id`);
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;
```
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 126 of 128
trg_catalog_product_entity_int_after_delete:
```SQL
-- before
INSERT IGNORE INTO `catalog_product_flat_cl` (`entity_id`) VALUES (OLD.`entity_id`);
-- after
SET @entity_id = (SELECT `entity_id` FROM `catalog_product_entity` WHERE `row_id` =
OLD.`row_id`);
INSERT IGNORE INTO `catalog_product_flat_cl` (`entity_id`) values(@entity_id);
```
## sequence tables
Magento fixes this by manually generating auto increments for entity_id in separate
sequence tables.
Exmple:
- \Magento\Framework\Model\ResourceModel\Db\CreateEntityRow::prepareData
- `$output[$metadata->getIdentifierField()] = $metadata->generateIdentifier();`
- \Magento\Framework\EntityManager\Sequence\Sequence::getNextValue
- insert and return next ID into `sequence_*` table
- \Magento\Framework\EntityManager\MetadataPool::getMetadata
- \Magento\Framework\EntityManager\MetadataPool::createMetadata
- new \Magento\Framework\EntityManager\EntityMetadata
- `sequence` argument:
\Magento\Framework\EntityManager\Sequence\SequenceFactory::create:
- no staging - sequence = null
- staging defines `sequenceTable` - `new
\Magento\Framework\EntityManager\Sequence\Sequence(connection, sequenceTable)`
\Magento\SalesRuleStaging\Controller\Adminhtml\Update\Save::execute
\Magento\Staging\Model\Entity\Update\Save::execute
```
staging[mode]: save
staging[update_id]:
staging[name]: 3 September
staging[description]: Everything is new
staging[start_time]: 2020-09-03T[Link].000Z
staging[end_time]:
```
\Magento\Staging\Model\Entity\Update\Action\Pool::getAction(
entityType = 'Magento\SalesRule\Api\Data\RuleInterface',
namespace = 'save',
actionType = mode = 'save'
)
- \Magento\Staging\Model\Entity\Update\Action\Save\SaveAction:
- \Magento\Staging\Model\Entity\Update\Action\Save\SaveAction::createUpdate
- \Magento\Staging\Model\EntityStaging::schedule
- \Magento\CatalogRuleStaging\Model\CatalogRuleStaging::schedule
- validates intersecting updates
- sets `created_in`, saves entity model (catalog rule)
- Magento\CatalogRuleStaging\Model\Rule\Hydrator
- \Magento\CatalogRuleStaging\Model\Rule\Retriever::getEntity - $this->ruleRepository-
>get($entityId)
## Preview
Example:
vendor/magento/module-catalog/view/frontend/layout/[Link]:
```XML
<block class="Magento\Catalog\Block\FrontendStorageManager" name="frontend-storage-
manager" before="-"
template="Magento_Catalog::frontend_storage_manager.phtml">
<arguments>
<!-- ... -->
<item name="updateRequestConfig" xsi:type="array">
<item name="url" xsi:type="serviceUrl" path="/products-render-info"/>
</item>
```
- \Magento\Store\Model\Argument\Interpreter\ServiceUrl::evaluate
- [Link]
File: /home/hb/Downloads/magento2-exam-notes-master/[Link]
Page 128 of 128
___store=default&___version=1599422820&__signature=b5cd66d9ea383ea57356f28cf2dd0f8edeecbc516b4d7982f8aef2
render-info
URL examples:
- REST - `?___version=`
- category - `?update_id=`
- [Link]
preview_store=default&preview_url=https:/[Link]/&preview_version=1599116400
- header and iframe
- [Link]
___version=1599116400&___store=default&__timestamp=1599077245&__signature=a8f3a05adb92374873a3f9c4e1d36c1
\Magento\Staging\Plugin\Framework\App\FrontController::beforeDispatch - validate
signature:
```
__signature = hash(sha256, "__version,__timestamp", secret key from [Link])
```
\Magento\CatalogStaging\Model\Category\DataProvider::getCurrentCategory
```
$updateId = (int) $this->request->getParam('update_id');
$this->versionManager->setCurrentVersionId($update->getId());
```
## checkout-staging