Magento 2
Training for PHP developers
Training based on Magento 2.3
Our Goal
Get the knowledge that enables you
to work confidently with Magento
and develop your practical skills.
Why Magento?
Quick start
A lot of extensions and integrations available.
Full extensibility
Magento 2 enables you to adjust almost everything
Support available
Rich know-how ailable on-line and huge community

Trainer: Łukasz Bajsarowicz




Magento Developer for

Framework Magento 2
E-commerce platform
Enterprise-class software for e-commerce
regardless of the scale and the intdustry
Open-source project
Software developed by contributors
from companies all around the world.
Brief history
March 2007

November 2015

June 2020
Magento 1
End of life
Developers should understand that Magento 2 way of doing something is actually OOP way of doing something. We did not invent a wheel.
We just try to follow good OOP/OOD practices.
IGOR MINIAILO Magento 2 architect, part of Community Engineering Team

Platforms based on M2






License overview
Magento 2 Commerce
The paid version (Subscription) that includes modules:
B2B, PageBuilder, Marketing Automation...
Magento 2 Open Source
E-commerce software available for free
with it's modules (management, payment, shipment...)
You are not allowed to reuse modules of Magento Commerce in projects that are not licensed.
Core development
How Magento 2 is developed?
Mostly by Community that use public Github repository
to submit their Pull Requests with changes.
... and Magento 2 Commerce and it's modules?
Developed by Magento Core teams, but also Partners get access
to Github copies of Commerce modules, to contribute.
PHP Revision
🔎 Play it yourself!
Execute PHP code snippets yourself at: 3v4l.org
Constructor
__construct()
__destruct()
This "magic" method is invoked when new instance of class is being instantiated.
Destructor
💡 Best Practice
You can use the method to perform explicit cleanup after your code.
Avoid other operations than assignment in constructors (!)
Method overloading
__call(string $name, array $arguments)
This "magic" method is invoked when calling non-existing interface method.
https://www.php.net/manual/en/language.oop5.overloading.php#object.call
class MethodTest
{
public function __call($name, $arguments)
{
// Note: value of $name is case sensitive.
echo "Calling object method '$name' "
. implode(', ', $arguments). "\n";
}
}
$obj = new MethodTest;
$obj->runTest('in object context');
// Calling object method 'runTest'
// in object context
Implementation
Example
Magic setters & getters
public function __call($method, $args)
{
switch (substr($method, 0, 3)) {
case 'get':
$key = $this->_underscore(substr($method, 3));
$index = isset($args[0]) ? $args[0] : null;
return $this->getData($key, $index);
case 'set':
$key = $this->_underscore(substr($method, 3));
$value = isset($args[0]) ? $args[0] : null;
return $this->setData($key, $value);
case 'uns':
$key = $this->_underscore(substr($method, 3));
return $this->unsetData($key);
case 'has':
$key = $this->_underscore(substr($method, 3));
return isset($this->_data[$key]);
}
throw new \Magento\Framework\Exception\LocalizedException(
new \Magento\Framework\Phrase('Invalid method %1::%2', [get_class($this), $method])
);
}
\Magento\Framework\DataObject
Meaning | Syntax | Juggling |
---|---|---|
Equal | $a == $b | ☑️ |
Unequal | $a != $b | ☑️ |
Identical | $a === $b | ❌ |
Not identical | $a !== $b | ❌ |
Less than | $a < $b | ☑️ |
Less or equal | $a <= $b | ☑️ |
Greater than | $a > $b | ☑️ |
Greater or equal | $a >= $b | ☑️ |
Spaceship 🚀 | $a <=> $b | ☑️ |
Comparison operators
http://php.net/manual/en/language.operators.comparison.php
https://www.php.net/manual/en/function.bccomp.php
Safe bcmath equivalent:
bccomp(string $left_operand, string $right_operand[, int $scale = 0]): int
Equals operator ( == )
Identity operator ( === )
Two object instances are equal if they have the same attributes and values (compared with ==), and are instances of the same class
object variables are identical if and only if they refer to the same instance of the class
Object Comparison
http://php.net/manual/en/language.oop5.object-comparison.php
Why so serious?
"secure" == 0
// true
$_POST['password'] == "AdminPassword"
{“password”: “0”}
{“password”: 0}
// false
// true
$values = array("add","edit,"view","delete");
// true
in_array(0, $values);
https://www.php.net/manual/en/types.comparisons.php
Comparison examples
1 == "1" // true
1 === "1" // false
0 == "0" // true
0 == "00" // false
0 == false // true
1 == true // true
0 === false // false
1 === true // false
100.0 <=> 1e2 // 0
🚀
1.75 <=> 0.5 // -1
"d" <=> "a" // 1
100 === 1e2 // false
100.0 === 1e2 // true
❗ It's not recommended to compare float due to the internal mechanism
of storing values
$a = 1.23456789;
$b = 1.23456780;
$epsilon = 0.00001; // 1e-5
function areEqual($a, $b, $epsilon) {
return abs($a-$b) < $epsilon;
}
Comparison of arrays and objects
echo [] <=> []; // 0
echo [1, 2, 3] <=> [1, 2, 3]; // 0
echo [1, 2, 3] <=> []; // 1
echo [1, 2, 3] <=> [1, 2, 1]; // 1
echo [1, 2, 3] <=> [1, 2, 4]; // -1
🚀
$a = (object) ["a" => "b"];
$b = (object) ["a" => "b"];
echo $a <=> $b; // 0
$a = (object) ["a" => "b"];
$b = (object) ["a" => "c"];
echo $a <=> $b; // -1
$a = (object) ["a" => "c"];
$b = (object) ["a" => "b"];
echo $a <=> $b; // 1
🚀
Operators precedence "game"
&&
and
vs
&&
vs
||
==
vs
=
🔗 http://php.net/manual/en/language.operators.precedence.php
🔗 http://php.net/manual/en/language.types.array.php
$array = array(
1 => "a",
"1" => "b",
1.5 => "c",
true => "d",
);
var_dump($array);
/*
array(1) {
[1]=>
string(1) "d"
}
*/
function login() {
if (! hasUsername()) {
return [
'success' => false,
'message' => 'Username is required'
];
}
if (! hasPassword()) {
return [
'success' => false,
'message' => 'Password is required'
];
}
if (! isPasswordCorrect()) {
return [
'success' => false,
'message' => 'Your password is invalid'
];
}
return [
'success' => true,
'message' => 'You are logged in'
];
}

echo true == $x ? 'Yes' : 'No';
if (true == $x) {
echo 'Yes';
} else {
echo 'No';
}
echo $x ?? $y;
$x = NULL;
echo $x ?? 'Alternatywa'; // Alternatywa
$x = 'Cokolwiek';
echo $x ?? 'Alternatywa'; // Cokolwiek
echo $x ?: '$x is false';
Null Coalescing Operator
Ternary operator
What actually is null?!
The special NULL value represents a variable with no value.
https://www.php.net/manual/en/language.types.null.php
How to use type declaration
https://www.php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration
https://www.php.net/manual/en/functions.returning-values.php#functions.returning-values.type-declaration
If the given value is of the incorrect type, then TypeError exception is being thrown.

function getSomeNumber(string $serviceCode): ?int {
if (!$this->isEnabled()) {
return null;
}
return $this->service->getServiceNumber($serviceCode);
}
Be careful with strict_type mode
Quick Git Revision
🔎 Play it yourself!
learngitbranching.js.org and git-school.github.io/visualizing-git/
Working
directory
Staging
Area
Local
repository
Remote
repository
Online server
Local environment
$ git add
$ git commit
$ git push
$ git pull
$ git fetch
$ git reset [FILE]
$ git reset [COMMIT]
Feature
Master
Merge
Feature
Master
Rebase
Feature
Master
Feature
Rebase
Master
🕷️
Master
A
B
D
A
B
D
Rebase + Merge
Feature
Master
A
B
C
D
Feature
Master
A-D
Work Environment


+








?

?
DIY
Development Environment
https://github.com/markshust/docker-magento

Run your environment according to the instructions
Remember about adding magento2.test to /etc/hosts
Make sure, that https://magento2.test serves Magento Store
Understanding
docker-compose.yml?

Contianer's name
Exposed TCP ports
Links between services
Declaration of storage resources
Service name
Understanding
docker-compose.yml?

Contianer's name
Exposed TCP ports
Links between services
Declaration of storage resources
Service name

Why using PhpStorm?
1. It's integrated with code (reacts to Code changes)
2. Thousands of extensions available (paid and free)
3. Evolves with PHP language (supports latest solutions)



Integrations



DIY
PhpStorm configuration
1. Set up PHP interpreter
2. Set PSR-2 coding standard
3. Configure "Schemas and DTDs"
💡 Best Practice
We recommend installing "Magento 2" extension, which is available for free!
DIY
Database Tool
Configure your favourite Database GUI
or Database plugin in PhpStorm
To connect your development environment.
Composer
A dependency manager for PHP.
Equivalent to apt in Debian or npm for Node JS
Composer installation
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
php composer-setup.php
php -r "unlink('composer-setup.php');"
sudo mv composer.phar /usr/local/bin/composer
If you want to use composer globally in environment, run:

https://getcomposer.org/doc/00-intro.md
Basic Composer commands
composer install
composer update
composer require
Installs packages exactly in the version provided in composer.lock
Installs the latest versions for packages defined in .json
If .lock file does not exist, it's going to be created.
.lock file is going to be updated
Adds dependency to
composer.json
and installs it
https://getcomposer.org/doc/03-cli.md
.lock file is going to be updated
DIY
Install language pack for Magento
Find on GitHub the name of package with Polish translation
https://github.com/magento-l10n/language-pl_PL/
and install the package with Composer
Structure of composer.json
{
"name": "symfony/symfony",
"type": "library",
"license": "MIT",
...
"require": {
"php": "^7.1.3",
"ext-xml": "*",
"doctrine/common": "~2.4",
...
},
"require-dev": {
"cache/integration-tests": "dev-master",
"doctrine/annotations": "~1.0",
"doctrine/cache": "~1.6",
...
},
...
"autoload": {
"psr-4": {
"Symfony\\Bridge\\Doctrine\\": "src/Symfony/Bridge/Doctrine/",
"Symfony\\Bridge\\Monolog\\": "src/Symfony/Bridge/Monolog/",
"Symfony\\Bridge\\ProxyManager\\": "src/Symfony/Bridge/ProxyManager/",
...
},
}
}
Information about package / project, that composer.json describes
Dependencies required to run project/package
Dependencies required for development
Autoloader configuration
https://getcomposer.org/doc/04-schema.md
Semantic versioning
[ Major ] . [ Minor ] . [ Patch ]
incompatible API changes
add functionality in a backwards compatible manner
backwards compatible bug fixes
https://semver.org/
Package Versioning
"doctrine/common": "2.4"
Exact version constraint:
"doctrine/common": "~2.4"
Major version constraint:
"doctrine/common": "2.4.*"
Version range:
"doctrine/common": ">=1.1 <2.0"
Git branch reference:
"doctrine/common": "dev-{my-feature}"

Minimum-stability setting
Global: Minimum-stability setting
{
"minimum-stability": "stable"
}
Local: Definition with @ - eg. @beta
{
"require": {
"monolog/monolog": "1.0.*@beta",
"m2coach/magic": "@dev"
}
}
API vs SPI
Backward-compatible best practices
If you implement one of such interfaces, remember about using proper version constraint.
Application Programming Interface
Service Provider Interface
API | SPI | API & SPI | |
---|---|---|---|
Removing Behaviour | ❌ | ✔️ | ❌ |
Modification of existing behaviour | ❌ | ❌ | ❌ |
Adding new behaviour | ✔️ | ❌ | ❌ |
https://community.magento.com/t5/Magento-DevBlog/Best-Practices-for-API-Design/ba-p/119868
Supported PHP versions
~7.0.0
~7.1.3
~7.2.0
~7.3.0
Magento 2.3.3
(Magento 2.3.4)
https://www.php.net/supported-versions.php
https://devdocs.magento.com/guides/v2.3/install-gde/system-requirements-tech.html
ext-bcmath
ext-ctype
ext-curl
ext-dom
ext-gd
ext-hash
ext-iconv
ext-int
ext-mbstring
ext-openssl
ext-pdo_mysql
ext-simplexml
ext-soap
ext-xsl
ext-zip
lib-libxml
DIY
Verify PHP requirements
Open Magento 2 repository on Github
Check the contents of `composer.json` file.
Verify how PHP modules are specified.
HTTP Servers
(Magento 2.3)

2.4

1.x
https://devdocs.magento.com/guides/v2.3/install-gde/system-requirements-tech.html
Database Engines
(Magento 2.3)

5.6 / 5.7


10.0 / 10.1 / 10.2
5.7
https://devdocs.magento.com/guides/v2.3/install-gde/system-requirements-tech.html
Magento project repository
.
|- app
| |- code
| | | ...
| |- design
| | | ...
| |- etc
| | |- config.php
| | |- env.php.sample
|- patches
|- composer.json
|- composer.lock
|- robots.txt
|- Gruntfile.js
|- package.json
Sample GitIgnore files
github.com/davidalger/warden-env-magento2/blob/develop/.gitignore
gist.github.com/yireo/744edcb4c70d33514ca35ebb14902056
github.com/magento/magento2/blob/2.4-develop/.gitignore
Directories structure (composer installed)
<Magento Directory>
app/code
app/design/frontend
app/design/adminhtml
app/i18n
Local non-reusable modules
Themes used by Storefront
Themes used by Admin Panel
Translation files for Store
vendor
External modules and libraries
<Magento Directory>
generated
var/log
var/view_preprocessed
var/cache
Generated code
Default log path
Pre-compiled frontend assets
Default path for file cache
var/di
Dependency Injection cache
pub
Files available from web
Directories structure (runtime environment)
DIY
Find the files
- DB connection configuration
- Magento Framework files
- Logs of current environment
DIY
Find the files
- DB connection configuration - /app/etc/env.php
- Magento Framework files - /vendor/magento/
- Logs of current environment - /var/log/
CLI tools bin/magento
admin:user:create
cron:run
indexer:reindex
Creates Admin user
Runs cron jobs once
Reindex
maintenance:status
Returns maintenance mode
module:status
Status of modules installed
info:adminuri
Returns Admin Panel path
DIY
Create Administrator's account
Use bin/magento to create new Administrator's account.
1. With interactive mode
2. Provide all the required data as argument
Find the path of Magento Admin Panel
CLI Tools bin/magento (Developer)
cache:clean
setup:upgrade
config:set
Clean only current store's cache
Installation and upgrade of modules
Set the configuration value
i18n:collect-phrases
Parse files for strings to translate
module:enable
Enable module (without running installation)
cache:flush
Flush whole cache (all scopes)
Magento run modes
developer
production
default
maintenance
Access to the store is forbidden.
Turned on with /var/.maintenance.flag file
Code needs to be already compiled, cache settings are locked.
Errors are being saved to the logs. Static files are served from cache.
Code is compiled automatically on a runtime with static files generation.
Errors are being displayed on front.
Code is compiled automatically on a runtime with static files generation.
Exceptions are being saved to logs.
https://devdocs.magento.com/guides/v2.3/config-guide/bootstrap/magento-modes.html
DIY
Your Magento mode
Verify what mode is your Magento running now
and which mechanisms are being used by it.
Change to production mode. Try to load the storefront.
Types of cache
Layout
Block HTML
Configuration
Compiled page layouts from all components and themes.
Includes: default.xml, x_y_z.xml
Collects configuration of all modules from different sources.
Includes: config.xml and core_config_data.
Caches HTML output from blocks.
Includes: Rendered blocks and CMS blocks.
Page cache
https://devdocs.magento.com/guides/v2.3/config-guide/cli/config-cli-subcommands-cache.html
Generated HTML pages output.
Does not include: Customer and Checkout pages
Translations
Merged translations from all modules.
Includes: en_US.csv and other *.csv translations
https://youtu.be/3ZvnBTK8uUM
The Ultimate Guide to Caching in Magento 2 | John Hughes

https://docs.google.com/presentation/d/1NdtNz_LBxk-JsCBy8AvekZAkSPWOE0WCzE14Y9ki-5Q
Cache cheatsheet
Cache | Recommendation | Key |
---|---|---|
Configuration | Never disable. Clean only when needed. | config |
Layout | Disable during most frontend tasks | layout |
Block HTML | Leave on most of frontend tasks, disable when working with blocks | block_html |
Translate | Disable if making many translations or clean occasionally | translate |
Full Page Cache | Disable for almost all frontend tasks, enable for testing mainly | full_page |
Check status
bin/magento cache:status
Disable cache
bin/magento cache:disable {key}
No more cache:clean
Mage2.tv to remove only the cache affected

Areas overview
adminhtml
frontend
base
cron
Magento Admin
Storefront
Fallback for Admin and Storefront
Cron Jobs
webapi_rest
REST API
webapi_soap
SOAP API
https://devdocs.magento.com/guides/v2.3/architecture/archi_perspectives/components/modules/mod_and_areas.html
💡 Best Practice
Declare `di.xml` for specific scope

Scope-specific configuration

Scopes of the store

https://docs.magento.com/m2/ce/user_guide/stores/store-scope-reference.html
https://docs.magento.com/m2/ce/user_guide/stores/store-mode-single.html
Subcategories, Locale: Currency, Language
Root category
Domain, Customers, Currency, Products,
Prices, Payment Methods, Checkout
Extension Development
Module Structure
<?php
use \Magento\Framework\Component\ComponentRegistrar;
ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Vendor_Module', __DIR__);
registration.php
etc/module.xml
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
<module name="Vendor_Module" setup_version="0.1.0">
<sequence>
<module name="Magento_Eav"/>
</sequence>
</module>
</config>
Module Installation
bin/magento setup:upgrade
• Turns on all registered modules
• Execute all installation / upgrade scripts
bin/magento module:enable Vendor_Module
• Turns on single module
• Does not execute installation / upgrade scripts
Module Reusability
{
"name": "vendor/module",
"description": "N/A",
"require": {
"magento/framework": "102.0.*",
"php": "~7.1.3||~7.2.0||~7.3.0"
},
"type": "magento2-module",
"autoload": {
"files": [
"registration.php"
],
"psr-4": {
"Vendor\\Module\\": ""
}
},
"version": "0.1.0"
}
composer.json
Language Pack Structure
etc/language.xml
<language xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:App/Language/package.xsd">
<code>en_US</code>
<vendor>Magento</vendor>
<package>en_US</package>
</language>
DIY
Create your first module
Create required files in app/code
Make sure that your module is displayed in
bin/magento module:status
across other installed modules.
Theme Structure
<?php
use \Magento\Framework\Component\ComponentRegistrar;
ComponentRegistrar::register(ComponentRegistrar::THEME, 'frontend/Vendor/name', __DIR__);
registration.php
etc/theme.xml
<theme xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Config/etc/theme.xsd">
<title>Magento Luma</title>
<parent>Magento/blank</parent>
<media>
<preview_image>media/preview.jpg</preview_image>
</media>
</theme>
Additional Files
etc/view.xml
Theme confiruation (image sizes, js bundling settings etc.)
i18n/xx_XX.csv
Custom Theme translation overrides
web/
Theme specific public files (eg. css/ for styles, js/ for Javascript)
Theme Structure
Area / Package Name / Theme Name
- adminhtml/Magento/backend - frontend/Magento/blank - frontend/Magento/luma
Bundled Themes
Theme Overrides
<Vendor>_<Module>/
eg. Magento_Cms/
-
Templates
- .phtml (PHP)
- .html (Knockout)
- .html (Email)
- Layout XML (merged)
- LESS / CSS
- Images
- Javascript
Theme Inheritance
-
vendor/magento/theme-frontend-luma/Magento_Catalog
/templates/product/price/final_price.phtml
-
app/design/frontend/M2Coach/training/Magento_Catalog
/templates/product/price/final_price.phtml
-
vendor/magento/theme-frontend-blank/Magento_Catalog
/templates/product/price/final_price.phtml
template not found?
template not found?
-
vendor/magento/module-catalog/view/base
/templates/product/price/final_price.phtml
-
vendor/magento/module-catalog/view/frontend
/templates/product/price/final_price.phtml
template not found?
template not found?
-
vendor/magento/theme-frontend-blank/Magento_Catalog
/templates/product/price/final_price.phtml
Inheritance

Preference Mechanism
<preference for="Magento\Module\Model\SomeClass"
type="M2Coach\Module\Module\MyImplementation"/>
<preference for="Magento\Module\Api\SomeInterface"
type="M2Coach\Module\Module\MyImplementation"/>
Replacing existing implementation
Preference of interface implementation
Configured using di.xml file located in <Module>/etc/ directory.
DIY
Turn off Customer registration
Vendor decided to disable the possibility of registration
Make sure that Store Visitors won't be able to register.
Hint: Magento\Customer\Model\Registration::isAllowed
Smart Inheritance is not evil
Common logical domain and Subtype of parent class
Example: Product Type
Example: Value Object (Data Object)
Downloadable Product
E-book
Company
DataObject
Video Course
Sales Representative
Dependency Injection in Magento
__construct( . . . )



Psr\Log\LoggerInterface
Magento\Framework\Event\ManagerInterface
Magento\Catalog\Api\Data\ProductInterface
public function __construct(
\Magento\Catalog\Api\ProductRepositoryInterface $productRepository
) {
$this->productRepository = $productRepository;
}
// ...
public function getProductBySku(string $productSku): ProductInterface
{
return $this->productRepository->get($productSku);
}
Composition
🔗 https://edux.pjwstk.edu.pl/mat/205/lec/Wyklad-MAS-nr-06.html

Observer Pattern
Change to original object (Event) notifies other object (Observer),
that enables you to create 1:n dependency.
+ Event Publisher is not
coupled with Observer
+ Observers can be
dynamically attached

Observer Implementation
XML declaration
<event name="controller_action_predispatch">
<observer name="MyCustomerLogger"
instance="M2Coach\Customer\Observer\LogCustomerAction" />
</event>
class LogCustomerAction implements ObserverInterface {
public function execute(Observer $observer) {
$event = $observer->getEvent();
$customer = $event->getCustomer();
$this->logger->log(
__('Customer %1 performed %2', $customer->getId(), $event->getAction())
);
}
}
ObserverInterface implemantation
Best practices vs Observers
According to the Best Practices, every event
should be written the way that we can store it in a queue,
and then run in independent from the main process.
In practice, this means that
Event Observer should treat all received payload
as immutable - it's forbidden to modify the data.
DIY
Observer after Place Order
Given that you write integration with external ERP
Immediately after placing an order, you want to notify ERP
about placed order (Order ID, SKU and Quantity)
Create Observer that is going to write information
about placed order in the log files.
Plugin (Interceptor) Mechanism
This Design Pattern enables you to dynamically add the code between the called method and final Object using Proxy. As a result, you can replace arguments, values returned by the method or completely replace the original method.

Plugin Implementation
// **Before** - Replace method arguments
public function beforeExecute(CreatePost $subject, $first, $second, $last) {
return [$first, 'secondReplaced', $last];
}
// **After** - Replace the method results
public function afterExecute(CreatePost $subject, $methodResult) {
...
return $modifiedMethodResult;
}
// **Around Plugin** - Interrupt the original method call
public function aroundExecute(CreatePost $subject, Closure $proceed, ...$args) {
return $this->myExecute($args);
}
Plugin Declaration
<type name="Magento\Customer\Controller\Account\CreatePost">
<plugin name="customerAccountCreatePostRedirect"
type="M2Coach\Customers\Plugin\CreatePostRedirectionPlugin"/>
</type>
Configured using di.xml file located in <Module>/etc/ directory.



Having multiple Plugins, the order of exectuion
both for before and after plugin follows sortOrder
("after" plugin changed after Magento 2.0)
❗ Your plugin should be completely independent from other plugins

The exception is around plugin:

You need to be cautious!
The Plugin precedence
is not always obvious!

❗
According to best practices
around plugin should be used
only to interrupt
original method execution
In any other case
only before or after plugin
should be used

DIY
Additional validation
Your client asked you to block the registration from `mailinator.com` domains using front-end. He still wants to have such possibility using Administrator Panel.
Change the behaviour of Controller to avoid registration.
DIY
Make module configurable
Using previously introduced Admin Configuration
enable your module to use the configuration values.
Hint: To fetch the configuration values, use ScopeConfigInterface
Admin: Module configuration


<tab/>
<section/>
<group/>
<field/>
Admin Panel: system.xml
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
<system>
<section id="customer">
<group id="create_account">
<field id="enabled" translate="label" type="select"
sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Enable Customer Registration</label>
<source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
</field>
</group>
</section>
</system>
</config>
Reference: customer/create_account/enabled
Admin Panel: Field types
<field type="select">
<source_model>...</source_model>
</field>
<field type="multiselect">
<source_model>...</source_model>
</field>
<field type="text">
<validate>validate-zero-or-greater</validate>
</field>
<field type="textarea">
<validate>required-entry</validate>
</field>
<field type="password"/>
<field type="obscure">
<backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model>
</field>
/lib/internal/Magento/Framework/Data/Form/Element/Factory.php#L26
DIY
Extension configuration
Create configuration Section for our
Forbid Registration by e-mail feature.
Fields:
1. Enabled
2. The domain
3. Is the domain whitelist or blacklist
Whitelist / Blacklist requires to implement OptionSourceInterface
Default values of Configuration
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd">
<default>
<customer>
<create_account>
<enable>1</enable>
</create_account>
</customer>
</default>
</config>
Defaults file: config.xml
DIY
Default configuration
To the Settings you've just created,
make sure that by default feature is disabled,
but the default domain is `mailinator.com`,
and the domain name is blacklisted.
DI: Argument Extending
<type name="Magento\Framework\Console\CommandListInterface">
<arguments>
<argument name="commands" xsi:type="array">
<item name="myCommand" xsi:type="object">M2Coach\Module\Console\Command\MyCommand</item>
</argument>
</arguments>
</type>
Notice that DI is not verifying if the argument against constructor
DIY
Creating new CLI interface
For the purpose of workshop,
we need the CLI command: `my:heavy:command`.
Extend <arguments> at CommandListInterface
VirtualType - Generated class
<virtualType name="M2Coach\MyModule\Model\DataObjectVirtual"
type="Magento\Framework\DataObject">
<arguments>
<argument name="data" xsi:type="array">
<item name="my_data" xsi:type="string">this is string</item>
</argument>
</arguments>
</virtualType>
<type name="M2Coach\MyModule\Console\Command\SomeCommand">
<arguments>
<argument name="data_container"
xsi:type="object">M2Coach\MyModule\Model\DataObjectVirtual</argument>
</arguments>
</type>
DIY
Creating VirtualType
We need to introduce EmailBlockerService for this task
We would like to know what e-mail addresses were locked
Such events should be saved in /var/log/nasty.log.
Hint: \Magento\Framework\Logger\Handler\Base
Hint: \Magento\Framework\Logger\Monolog
DI: Argument Replace
<type name="Magento\Module\Console\Command\MyCommand">
<arguments>
<argument name="someService"
xsi:type="object">M2Coach\MyModule\Service\MySomeService</argument>
</arguments>
</type>
Proxy Design Pattern

1. Proxy implements Interface
2. The Constructor takes the original class
as a parameter
3. In most of the cases, Proxy passes
the requests to original class
4. Life cycle of Proxy class ends
with original class
Pros and cons
➕ Access control to unaware class
➕ Target class does not have to be instantiated
➖ Additional delay on response
When to use Proxy?
class VeryHeavyClass {
public function __construct(ApiBasedInterface $apiBased) {
$this->someData = $apiBased->getSomeData();
}
}
<type name="M2Coach\Module\Model\CustomerModel">
<arguments>
<argument name="heavy" xsi:type="object">Vendor\Module\VeryHeavyClass\Proxy</argument>
</arguments>
</type>
Proxy Implementation
DIY
Benefits of \Proxy
Use `\Proxy` for MyHeavyDependency in `di.xml`
Server
Architecture

High Availability pattern
Blue & Green deployment

-
Environment alignment
Blue and Green are serving the same version of software, have the same settings -
New version deployment
At [n+1] environment (Blue) we execute deployment of new version of application. When we’re sure that deployment has finished successfully, and services are working properly - we switch the traffic to [n+1] (Blue). Otherwise we switch back to [n] (Green). -
Spreading new version
Last step, when we are sure that service works properly, we spread the new version across all the machines.
Following implementations
Based on exactly the same procedure.
After deployment we have possibility to previous, stable versions using load-balancer.
URL Structure
Front Name / Action Path / Action Name
Front Name / Action Path / Action Name
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
<router id="standard">
<route id="catalog" frontName="catalog">
<module name="Magento_Catalog" />
</route>
</router>
</config>
Configuration in etc/[area]/routes.xml
Example: Magento/Catalog/etc/frontend/routes.xml
Front Name / Action Path / Action Name
Action path declares path to access action
For Vendor\Module\Controller\Showrooms\List
it becomes: Showrooms
Front Name / Action Path / Action Name
Action name
For Vendor\Module\Controller\Showrooms\List
becomes List
but
Calling POST company/showrooms/list
action name becomes ListPost
Implementing Controller
- Located in Controller/ directory
- Implements Magento\Framework\App\ActionInterface
- Extends Magento\Framework\App\Action\Action (deprecated in 2.4)
DIY
JSON Endpoint
Create endpoint m2coach/registration/lock
which returns information in JSON format:
- Whether Registration lock is enabled
- Which domains are currently forbidden
Hint: etc/[area]/routes.xml
Hint: Magento\Framework\Controller\Result\Json
Hint: Magento\Framework\App\Action\Action
Factory Design Pattern

Factory is an interface used to create
a new instance of a specified interface.
In Magento 2 factories are auto-generated
+ Allows creating multiple instances
of the same interface
+ Handles DI preferences seamless
- Additional class is being generated
Where to use Factories?
The most common use-case for Factories are stateful classes:
$product = $this->productFactory->create();
$product->setTypeId('simple')
->setName('Simple Product')
->setSku('simple-product')
->setVisibility(Visibility::VISIBILITY_BOTH)
->setStatus(Status::STATUS_ENABLED)
->setStockData([
'use_config_manage_stock' => 1,
'qty' => 100,
'is_qty_decimal' => 0,
'is_in_stock' => 1,
]);
$this->productRepository->save($product);
Example: Creating new product
- Controller Action Response
- New Entities
- Collections
Layout in Magento
XML file located in Module's view/frontend/layout
It's name follows {route id}_{action path}_{action name} pattern
<page layout="2columns-left" xmlns:xsi="..."
xsi:noNamespaceSchemaLocation="...">
<body>
<referenceContainer name="root">
...
</referenceContainer>
</body>
</page>
Available Layouts
1column
2columns-left
2columns-right




3columns
Layout Blocks
Blocks are using Template, which is usually PHTML file
located in Module's view/frontend/template.
Templates have access to $block variable - which is reference to block class
<block class="M2Coach\MyModule\Block\Data" name="myBlock"
template="M2Coach_MyModule::template/file.phtml"/>
Block declaration in layout
Block needs to inherit \Magento\Framework\View\Element\Template
https://devdocs.magento.com/guides/v2.3/frontend-dev-guide/layouts/xml-instructions.html
Running Unit Tests
vendor/bin/phpunit -c dev/tests/unit/phpunit.xml.dist
Testing Approach
State-based testing
Interaction testing
Given Testing conditions
When Performing actions
Then Verification of results
Given Testing conditions
Expect Specified actions
When Performing action
Fake Dependencies
Stub
Mock
Prepared response
Interaction verifying
$this->user->method('getUsername')
->willReturn('some@e-mail.com');
$this->user->expects($this->once())
->method('save')
->with($userStub);
Fragile Tests
Current time / date
System specific (eg. \n vs \r\n)
File-based
Running Integration Tests
vendor/bin/phpunit -c $(pwd)/dev/tests/integration/phpunit.xml
All the necessary files are located in
dev/tests/integration/
... but configure them first!
Setting up phpunit.xml
Use dev/tests/integration/phpunit.xml.dist as a template.
<const name="TESTS_MEM_USAGE_LIMIT" value="1024M"/>
Recommended memory limit is 8G
(enough to run most of Magento Core tests)
<const name="TESTS_MAGENTO_MODE" value="developer"/>
Run your tests both in both modes
(with dynamic class generation and pre-generated classes)
Setting up configuration
etc/install-config-mysql.php
That configuration file should contain all the settings necessary to run your test environment. Options are compatible with `bin/magento setup:install` options.
etc/config-global.php
This is your container for configuration used for the test runtime.
Its role is the same that app/etc/config.php
Notice: Database, cache and queue configration should be different from your development environment. Otherwise, Integration tests are going to wipeout your workspace.
Tests Examples
/**
* @magentoDataFixture Magento/Customer/_files/customer_confirmation_config_enable.php
* @magentoDataFixture Magento/Customer/_files/unconfirmed_customer.php
*
* @return void
*/
public function testSubscribeUnconfirmedCustomerWithoutSubscription(): void
{
$customer = $this->customerRepository->get('unconfirmedcustomer@example.com');
$subscriber = $this->subscriberFactory->create();
$subscriber->subscribeCustomerById($customer->getId());
$this->assertEquals(Subscriber::STATUS_UNCONFIRMED, $subscriber->getStatus());
}


API Service Contract
Each and every module should define it's own Service Contract:
Set of public interfaces fulfilling the business needs.
Service Contract should be located in Api/ directory
• Data Interface
• Service Interface
• Repository Interface
• Management Interface (common practice to use with Repository Interface)

https://twitter.com/VinaiKopp/status/1008998146985349120/photo/1
Service Contract Declaration
<preference for="M2coach\Module\Api\Data\StoreInterface"
type="M2coach\Module\Model\Store"/>
namespace M2Coach\Module\Api\Data;
interface StoreInterface {
public function getName(): string;
public function setName(string $name): void;
}
etc/di.xml
Api/Data/StoreInterface.php
Service Contract Implementation
namespace M2Coach\Module\Model;
class Store implements \M2Coach\Api\Data\StoreInterface {
public function getName(): string {
return $this->getData('name');
}
public function setName(string $name): void {
$this->setData('name', $name);
}
}
Api/Data/StoreInterface.php
Web API Interfaces
HTTP requests structure
🔗 https://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html
$ curl -v google.com
> GET / HTTP/1.1
> Host: google.com
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 301 Moved Permanently
< Location: http://www.google.com/
< Content-Type: text/html; charset=UTF-8
< Date: Mon, 18 Jun 2018 22:29:54 GMT
< Expires: Wed, 18 Jul 2018 22:29:54 GMT
< Cache-Control: public, max-age=2592000
< Server: gws
< Content-Length: 219
<
<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>301 Moved</TITLE></HEAD><BODY>
<H1>301 Moved</H1>
The document has moved
<A HREF="http://www.google.com/">here</A>.
</BODY></HTML>
Method, Request URI
Server name
User Agent
Expected response format
Request
Response header
Status Code + description
Response body
The most common HTTP statuses
🔗 https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
1** Informational
2** Successful
3** Redirection
4** Client Error
5** Server Error
200 OK
301 Moved Permanently
302 Found
304 Not Modified
400 Bad Request
401 Unauthorized
403 Forbidden
404 Not Found
405 Method Not Allowed
408 Request Timeout
413 Request Entity Too Large
500 Internal Server Error
502 Bad Gateway
503 Service Unavailable
HTTP Methods (for REST interfaces)
🔗 https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
GET
Fetch the remote resource
Parameters are passed with url query (np. ?x=5&y=10)
POST
Creates new resource on remote server.
Parameters are passed in a request body.
PUT
Update of existing resource on server.
Parameters are passed in a request body.
DELETE
Removes resource on a remote server. Disabled by default.
HEAD
Similar to GET. Returns only headers with response body.
Automatic API documentation Swagger

Generated based on docblocks
Javascript Initializing
https://devdocs.magento.com/guides/v2.3/javascript-dev-guide/javascript/js_init.html
1
<div data-mage-init='{"Vendor_Module/script-name": {"argument": "value"}}'></div>
Apply JS for specified element
2
<script type="text/x-magento-init">
{
// components initialized on the element defined by selector
"<element_selector>": {
"<js_component>": ...
},
// components initialized without binding to an element
"*": {
"<js_component3>": ...
}
}
</script>
Apply globally or using CSS selector
Javascript Scripts
https://devdocs.magento.com/guides/v2.3/javascript-dev-guide/javascript/js_init.html
Should be located in view/{area}/web/js/
require([
// Dependencies to be used
'jquery',
], function ($) {
$(function () {
console.log('Page is loaded!');
})
});
AJAX Requests
https://devdocs.magento.com/guides/v2.3/javascript-dev-guide/javascript/js_init.html
$.ajax({
url: 'https://www.example.com',
method: 'POST',
data: {
id: '1'
},
success: function (data) {
$('.example-element').html(data)
.trigger('contentUpdated')
}
});
Installation Scripts
- Setup/InstallSchema (InstallSchemaInterface)
- Setup/UpgradeSchema (UpgradeSchemaInterface)
- Setup/InstallData (InstallDataInterface)
- Setup/UpgradeData (UpgradeDataInterface)
- Setup/Recurring (InstallSchemaInterface)
- Setup/Uninstall (UninstallInterface)
Interfaces to implement (\Magento\Framework\Setup\...)
Declarative Schema
In etc/db_schema.xml files
<schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd">
<table name="catalog_product_entity" resource="default" engine="innodb" comment="Catalog Product Table">
<column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="true"
comment="Entity ID"/>
...
<constraint xsi:type="foreign" referenceId="CAT_PRD_ENTT_DEC_ENTT_ID_CAT_PRD_ENTT_ENTT_ID"
table="catalog_product_entity_decimal" column="entity_id"
referenceTable="catalog_product_entity" referenceColumn="entity_id" onDelete="CASCADE"/>
</table>
</schema>
https://devdocs.magento.com/guides/v2.3/extension-dev-guide/declarative-schema/db-schema.html
DIY
Physical stores location
Create a database table which contains information
about physical stores and it's locations.
Fields we need so far
- ID
- City
- Street
- Phone
Migrating Schema
https://devdocs.magento.com/guides/v2.3/extension-dev-guide/declarative-schema/migration-commands.html
bin/magento setup:install --convert-old-scripts=1 bin/magento setup:upgrade --convert-old-scripts=1
Patch Scripts
Patch\DataPatchInterface (DataPatchInterface)
Patch\SchemaPatchInterface (SchemaPatchInterface)
Patch\PatchRevertableInterface (PatchRevertableInterface)
Interfaces to implement (\Magento\Framework\Setup\Patch\...)
https://devdocs.magento.com/guides/v2.3/extension-dev-guide/declarative-schema/data-patches.html
Patch Generating
https://devdocs.magento.com/guides/v2.3/extension-dev-guide/declarative-schema/data-patches.html
bin/magento setup:db-declaration:generate-patch
[options] <module-name> <patch-name>
bin/magento setup:db-declaration:generate-patch M2Coach_MyModule SomePatch
DIY
Install Stores data
Using Data Patch - install 5 sample stores
to the table you have just created.
bin/magento module:uninstall Vendor_ModuleName
or
bin/magento module:uninstall --non-composer Vendor_ModuleName
CLI uninstalling extension
Destructive operations
- Deleting a table
- Deleting a column
- Reducing column length
- Changing column precision
- Changing the column type
--safe-mode=1
Creates a data dump during the installation or upgrade process.
--data-restore=1
(Used with the setup:upgrade command only.) Performs a rollback. Before you rollback, you must first check out code to the previous version of Magento. Then run setup:upgrade --data-restore=1.
https://devdocs.magento.com/guides/v2.3/extension-dev-guide/declarative-schema/migration-commands.html

EAV Entity Attribute Value
Data is stored vertically in RDB
1. Attributes are declared in eav_attribute table.
2. The way of storing values depends on backend_type.
• static - Column in main entity table (eg. w product_entity)
• In all other cases - saved as a row in a table specific for type
(eg. product_entity_varchar for backend_type: varchar)
EAV Available OOTB
• Customer
• Customer Address
• Catalog Product
• Catalog Category
• Order
• Invoice
• Credit Memo
• Shipment
EAV Adding Product attribute
$eavSetup = $this->eavSetupFactory->create(['setup' => $setup]);
$eavSetup->addAttribute(
\Magento\Catalog\Model\Product::ENTITY,
'sample_attribute',
[
'type' => 'text',
'backend' => 'Magento\Eav\Model\Entity\Attribute\Backend\ArrayBackend',
'frontend' => '',
'label' => 'Sample Atrribute',
'input' => 'multiselect',
'option' => ['values' => ["Cokolwiek","Innekolwiek"]]
'class' => '',
'source' => '',
'global' => \Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface::SCOPE_GLOBAL,
'visible' => true,
'required' => true,
'user_defined' => false,
'default' => '',
'searchable' => false,
'filterable' => false,
'comparable' => false,
'visible_on_front' => false,
'used_in_product_listing' => true,
'unique' => false,
'apply_to' => ''
]
);
https://devdocs.magento.com/guides/v2.3/extension-dev-guide/attributes.html
EAV Adding attribute to Customer
$eavSetup = $this->eavSetupFactory->create(['setup' => $setup]);
$eavSetup->addAttribute(
\Magento\Customer\Model\Customer::ENTITY,
'sample_attribute',
[
'type' => 'varchar',
'label' => 'Sample Attribute',
'input' => 'text',
'required' => false,
'visible' => true,
'user_defined' => true,
'position' => 999,
'system' => 0,
]
);
$sampleAttribute = $this->eavConfig->getAttribute(Customer::ENTITY, 'sample_attribute');
// more used_in_forms
// ['adminhtml_checkout','adminhtml_customer','adminhtml_customer_address','customer_account_edit',
// 'customer_address_edit','customer_register_address']
$sampleAttribute->setData(
'used_in_forms',
['adminhtml_customer']
);
$sampleAttribute->save();
https://devdocs.magento.com/guides/v2.3/extension-dev-guide/attributes.html
EAV Flat Tables
Flat Tables are enables you to improve the application performance
by generating tables for entities that are based on EAV models with attributes.
When it comes to products, depending on the settings
and the size of index (SKU amount), flat tables are updated:
- up to 50.000 SKU - immeditely
- over 50.000 SKU - with Cron jobs
Resource Model Implementation
namespace M2Coach\Module\Model\ResourceModel;
class MyEntity extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
{
public const PRIMARY_KEY = 'my_entity_id';
public const TABLE = 'my_entity_table';
protected function _construct() {
$this->_init(self::TABLE, self::PRIMARY_KEY);
}
}
Collection Implementation
namespace M2Coach\Module\Model\ResourceModel\MyEntity;
use Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection;
class Collection extends AbstractCollection
{
protected $_idFieldName = 'my_entity_id';
protected function _construct() {
$this->_init(
'M2Coach\Module\Model\MyEntity',
'M2Coach\Module\Model\ResourceModel\MyEntity'
);
}
}
DIY
Create your collection
You already created table that contains Physical Stores data.
Build Collection that enables you to fetch the data from table.
EAV Extending entities

https://devdocs.magento.com/guides/v2.3/extension-dev-guide/attributes.html
Repositories - Tweaked Collections!
Repository is an interface that fulfills very narrow area of business logic.
According to the best practices, these interfaces should contain
very simple access methods - for example:
• getItems(SearchCriteria $criteria)
• getById(int $entityId)
• save(EntityInterface $entity)
• delete(EntityInterface $entity)

DIY
Physical Store repository
Create set of interfaces for physical stores:
- Single Store data interface
- Physical Stores repository interface
Implement these methods
Hint: Best practices can be found at Vinai's blog
http://vinaikopp.com/2017/02/18/magento2_repositories_interfaces_and_webapi/
Builder
Design Pattern
Builder is an interface used to create
an instance based on provided criteria.
In Magento builders are not generated,
you need to implement them.
+ Reusability of construction code when
building various representations
+ Single Responsibility: Isolate complex
construction code from the business logic
- Increased code complexity due to
need of creating new instances of classes

Repository Filtering criteria
$this->searchCriteriaBuilder->addFilter(OrderItemInterface::ORDER_ID, $searchForIds, 'in');
$searchCriteria = $this->searchCriteriaBuilder->create();
https://devdocs.magento.com/guides/v2.3/extension-dev-guide/searching-with-repositories.html
Filtering Criteria
https://devdocs.magento.com/guides/v2.3/extension-dev-guide/searching-with-repositories.html
"eq" => equalValue "neq" => notEqualValue "like" => likeValue "nlike" => notLikeValue "is" => isValue "in" => inValues "nin" => notInValues "notnull" => valueIsNotNull "null" => valueIsNull "moreq" => moreOrEqualValue "gt" => greaterValue "lt" => lessValue "gteq" => greaterOrEqualValue "lteq" => lessOrEqualValue "finset" => valueInSet "from" => fromValue, "to" => toValue
DIY
Fetching data from Repository
Use SearchCriteriaBuilder to find the stores
that are active (status = 1) and are located in Warsaw or Glasgow.
Using GraphQL
Connection endpoint:
https://magento2.test/graphql
{
products(
filter: { sku: { eq: "24-WB01" } }
) {
items {
name
sku
image {
url
}
categories {
name
}
}
}
}
Recommended tool for handling GraphQL requests is Chrome iQL
Request
{
"data": {
"products": {
"items": [
{
"name": "Voyage Yoga Bag",
"sku": "24-WB01",
"categories": [
{
"name": "Gear",
"url_path": "gear"
},
{
"name": "Bags",
"url_path": "gear/bags"
}
]
}
]
}
}
}
Response

Cron Jobs
The mechanism responsible for running repetitive tasks on schedule.
bin/magento cron:install
bin/magento cron:install --non-optional


Starting from Magento 2.3.4
Cron Installation
Performance overhead
bin/magento cron:run [--group="<cron group name>"]
https://devdocs.magento.com/guides/v2.3/config-guide/cli/config-cli-subcommands-cron.html
Adding your task to Cron Jobs
https://devdocs.magento.com/guides/v2.3/config-guide/cron/custom-cron-tut.html
etc/cron.xml
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Cron:etc/crontab.xsd">
<group id="default">
<job name="m2coach_own_task"
instance="M2Coach\Module\Cron\DoSomething" method="execute">
<schedule>* * * * *</schedule>
</job>
</group>
</config>
Implementing own Cron task
https://devdocs.magento.com/guides/v2.3/config-guide/cron/custom-cron-tut.html
namespace M2Coach\MyModule\Cron;
use Magento\Framework\Lock\LockManagerInterface;
class CustomTask
{
public function execute(): void
{
// Do your job!
}
}
Using Lock (semaphore)
\Magento\Framework\Lock\LockManagerInterface
Avoid tasks overlapping
Styles customization
Module LESS file location: view/frontend/web/css/source/_module.less
https://devdocs.magento.com/guides/v2.3/frontend-dev-guide/responsive-web-design/rwd_css.html
Including files (eg. from subdirectory)
@import "_feature/_header";
Using predefined variables
color: @theme-primary-color;
Predefined variables can be found at
lib/web/css/source/lib/variables/
.lib-icon-font(
@_icon-font-content: @icon-search,
@_icon-font-size: 35px,
@_icon-font-line-height: 33px,
@_icon-font-color: @header-icons-color,
@_icon-font-color-hover: @header-icons-color-hover,
@_icon-font-color-active: @header-icons-color-hover,
@_icon-font-text-hide: true
);
Using LESS libraries
Responsive design
https://devdocs.magento.com/guides/v2.3/frontend-dev-guide/responsive-web-design/rwd_css.html
Breakpoint | Width |
---|---|
@screen__xxs | 320px |
@screen__xs | 480px |
@screen__s | 640px |
@screen__m | 768px |
@screen__l | 1024px |
@screen__xl | 1440px |
& when (@media-common = true) {
// your code
}
Common design
.media-width(@extremum, @break)
when (@extremum = 'max')
and (@break = @screen__m) {
// your code
}
Mobile ( < 768px)

I'm not a great programmer; I'm just a good programmer with great habits
Kent Beck
Where to Best Practices?
Technical Guidelines
https://devdocs.magento.com/guides/v2.3/coding-standards/technical-guidelines.html
Programming Best Practices
https://devdocs.magento.com/guides/v2.3/ext-best-practices/extension-coding/common-programming-bp.html
Vinai Kopp
vinaikopp.com/blog and mage2.tv
Martin Fowler design rules
https://martinfowler.com/bliki/BeckDesignRules.html

Developer's Toolbox
Generate Magento 2 code
https://mage2gen.com
N98 Magerun
github.com/netz98/n98-magerun2
Magento 2 File Templates
https://github.com/lfolco/phpstorm-m2-filetemplates

Magento 2 Cache Cleaner
https://github.com/mage2tv/magento-cache-clean

Recommended Course
Materials about Frontend Development

Your own Templates
Settings > Editor > File and Code Templates

Be lazy, Generate!
Mage2gen.com as reference-code generator

Copy of 2020 - Magento 2.3
By Łukasz Bajsarowicz
Copy of 2020 - Magento 2.3
- 184