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

1

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

1

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

1

🔎 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.

A variable is considered to be null if:

  • it has been assigned the constant NULL.

  • it has not been set to any value yet.

  • it has been unset().

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

1

🔎 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

1
1

+

?

?

DIY

Development Environment

1
1

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?

1
1

Contianer's name

Exposed TCP ports

Links between services

Declaration of storage resources

Service name

Understanding
docker-compose.yml?

1
1

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
1

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

1
1

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

1
1

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

1
1

[ 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

1

~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

1
1

Open Magento 2 repository on Github
Check the contents of `composer.json` file.
Verify how PHP modules are specified.

HTTP Servers 

1

(Magento 2.3)

2.4

1.x

https://devdocs.magento.com/guides/v2.3/install-gde/system-requirements-tech.html

Database Engines

1
1

(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

1
.
|- 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

2

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

1
1

- DB connection configuration

- Magento Framework files

- Logs of current environment

DIY

Find the files

1
1

- 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

2

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

1
1

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

2

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.

1

https://devdocs.magento.com/guides/v2.3/config-guide/bootstrap/magento-modes.html

DIY

Your Magento mode

1
1

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.

1

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

0
 Advanced issue found
 

Translations

Merged translations from all modules.
Includes: en_US.csv and other *.csv translations

0
 Advanced issue found
 

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

1

1
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

1

1

Mage2.tv to remove only the cache affected

Areas overview

adminhtml

frontend

base

cron

Magento Admin

2

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

1
1

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

1
1

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

1
1

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

1
1

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

1
1

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

1

1

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

1

1

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

1
1

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

1
1

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

🤝
2

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

1
1

Use `\Proxy` for MyHeavyDependency in `di.xml`

Server
Architecture

1

1

High Availability pattern

Blue & Green deployment

1

1
  1. Environment alignment
    Blue and Green are serving the same version of software, have the same settings

  2. 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).

  3. 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

  1. Located in Controller/ directory
  2. Implements Magento\Framework\App\ActionInterface
  3. Extends Magento\Framework\App\Action\Action (deprecated in 2.4)

DIY

JSON Endpoint

1

1

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

1
vendor/bin/phpunit -c dev/tests/unit/phpunit.xml.dist

Testing Approach

1

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

1

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

1

Current time / date

System specific (eg. \n vs \r\n)

File-based

Running Integration Tests

1
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

1

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

1
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

1
    /**
     * @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

1

1

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

1

1

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

1

1

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

1

1

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

1
1

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>
0
 Advanced issue found
 
bin/magento setup:db-declaration:generate-patch
                                M2Coach_MyModule SomePatch

DIY

Install Stores data

1
1

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

1

1

You already created table that contains Physical Stores data.
Build Collection that enables you to fetch the data from table.

EAV Extending entities

1

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

1

1

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

1
$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

1

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

1

1

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?

1

1

Vinai Kopp
vinaikopp.com/blog and mage2.tv

Developer's Toolbox

1

1

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

1

1

Materials about Frontend Development

1

1

Your own Templates

1

1

Settings > Editor > File and Code Templates

Be lazy, Generate!

1

1

Mage2gen.com as reference-code generator

Made with Slides.com