code design and best practices for magento 2 using plugins · © 2017 magento, inc. code design and...

49

Upload: others

Post on 30-May-2020

13 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Code Design and Best Practices for Magento 2 Using Plugins · © 2017 Magento, Inc. Code Design and Best Practices for Magento 2 Using Plugins
Page 2: Code Design and Best Practices for Magento 2 Using Plugins · © 2017 Magento, Inc. Code Design and Best Practices for Magento 2 Using Plugins

© 2017 Magento, Inc.

Douglas RadburnSenior Magento DeveloperPinpoint Designs, Harrogate UK

Page 3: Code Design and Best Practices for Magento 2 Using Plugins · © 2017 Magento, Inc. Code Design and Best Practices for Magento 2 Using Plugins

© 2017 Magento, Inc.

Code Design and Best Practices for Magento 2 Using Plugins

Page 4: Code Design and Best Practices for Magento 2 Using Plugins · © 2017 Magento, Inc. Code Design and Best Practices for Magento 2 Using Plugins

Customisation & Extensibility

Page 5: Code Design and Best Practices for Magento 2 Using Plugins · © 2017 Magento, Inc. Code Design and Best Practices for Magento 2 Using Plugins

(what we could do with Magento 1)

A History Lesson

Page 6: Code Design and Best Practices for Magento 2 Using Plugins · © 2017 Magento, Inc. Code Design and Best Practices for Magento 2 Using Plugins

© 2017 Magento, Inc.

Code Pools

• Allow overriding of whole files

• We have the following code pools:– CORE– COMMUNITY– LOCAL

• We can extend functionality within the inheritance structure of Magento• Magento will read from local, then community, then core• Given this structure, you can already (in theory) override a CORE Magento

file in two places

Page 7: Code Design and Best Practices for Magento 2 Using Plugins · © 2017 Magento, Inc. Code Design and Best Practices for Magento 2 Using Plugins

© 2017 Magento, Inc.

Class Rewrites

• Allow customisation of specific classes

• For example, you might wish to override validate() in Mage_Review_Model_Review.

• This allows you to be more specific about what you’re overriding

– It allows you to pull this functionality into your own namespace

– Allows you to extend an existing class, but replace single method

<review><rewrite>

<review>Pinpoint_Override_Model_Review_Review</review></rewrite>

</review>

Page 8: Code Design and Best Practices for Magento 2 Using Plugins · © 2017 Magento, Inc. Code Design and Best Practices for Magento 2 Using Plugins

© 2017 Magento, Inc.

Events

• Allows for customisation of a data flow at specific points

• Magento (or a third party) has to have allowed an event to happen

• These usually happen in one of the following circumstances:

– Before / After– Success / Failure– Prepare Form

• Allows you to hook into that event and do something with the data that has been / is to be processed

Mage::dispatchEvent('store_delete',array('store' => $model)

);

Mage::dispatchEvent($this->_eventPrefix .'_save_after',$this->_getEventData()

);

Page 9: Code Design and Best Practices for Magento 2 Using Plugins · © 2017 Magento, Inc. Code Design and Best Practices for Magento 2 Using Plugins

What Magento2 has given us

The new way

Page 10: Code Design and Best Practices for Magento 2 Using Plugins · © 2017 Magento, Inc. Code Design and Best Practices for Magento 2 Using Plugins

© 2017 Magento, Inc.

• Events (exactly the same as Magento1)• Dependency Injection• Injection of Interfaces (with preferences set in di.xml)• Plugins

Code Updating in Magento2

Page 11: Code Design and Best Practices for Magento 2 Using Plugins · © 2017 Magento, Inc. Code Design and Best Practices for Magento 2 Using Plugins

© 2017 Magento, Inc.

• Allows the external environment to inject dependencies for an object instead of that object manually creating them itself

• Creates clean, readable code

Dependency Injection

protected function _construct(){

$this->_init('catalog/attribute');}

public function __construct(\Magento\Framework\App\RequestInterface$request

) {$this->request = $request;

}

Page 12: Code Design and Best Practices for Magento 2 Using Plugins · © 2017 Magento, Inc. Code Design and Best Practices for Magento 2 Using Plugins

© 2017 Magento, Inc.

• New Magento code follows the “dependency inversion principle”

• This stipulates you use abstractions to reduce code dependencies

• Dependencies can then be updated via di.xml without rewriting code when interfaces are used

Injection of Interfaces

<preference for="Magento\Catalog\Api\Data\ProductInterface" type="Magento\Catalog\Model\Product" />

<preference for="Magento\Catalog\Api\Data\ProductInterface" type="Pinpoint\Catalog\Model\Product" />

public function getGalleryImages(ProductInterface $product

){

$images = $product->getMediaGalleryImages();

Page 13: Code Design and Best Practices for Magento 2 Using Plugins · © 2017 Magento, Inc. Code Design and Best Practices for Magento 2 Using Plugins

An AOP Approach

Plugins

Page 14: Code Design and Best Practices for Magento 2 Using Plugins · © 2017 Magento, Inc. Code Design and Best Practices for Magento 2 Using Plugins

© 2017 Magento, Inc.

• AOP stands for Aspect-oriented programming

• A programming paradigm that aims to increase modularity by allowing the separation of cross-cutting concerns

• It does so by adding additional behaviour to existing code (an “advice”)

• Without modifying the code itself; instead separately specifying which code is modified via a "point cut“ specification

What is AOP?

Page 15: Code Design and Best Practices for Magento 2 Using Plugins · © 2017 Magento, Inc. Code Design and Best Practices for Magento 2 Using Plugins

© 2017 Magento, Inc.

• A crosscutting concern is a concern which is applicable throughout the application and it affects the entire application

• For example: logging, security and data transfer are the concerns which are needed in almost every module of an application, hence they are cross-cutting concerns

What’s a cross cutting concern?

Page 16: Code Design and Best Practices for Magento 2 Using Plugins · © 2017 Magento, Inc. Code Design and Best Practices for Magento 2 Using Plugins

© 2017 Magento, Inc.

SOLID

Segway into …

ingle responsibility

pen-closed

iskov substitution

nterface segregation

ependency Inversion

Page 17: Code Design and Best Practices for Magento 2 Using Plugins · © 2017 Magento, Inc. Code Design and Best Practices for Magento 2 Using Plugins

© 2017 Magento, Inc.

• Business logic of method shuffled with the logging logic:

• Let’s add some logging

Logic Example

public function doSomething(){

// do something}

public function doSomething(){

try {//do something$this->logger->debug(“we did something”);

} catch (\Exception $e) {$this->logger->critical($e);

}}

Page 18: Code Design and Best Practices for Magento 2 Using Plugins · © 2017 Magento, Inc. Code Design and Best Practices for Magento 2 Using Plugins

Let’s take a look

Plugins in Magento2 (AOP)

Page 19: Code Design and Best Practices for Magento 2 Using Plugins · © 2017 Magento, Inc. Code Design and Best Practices for Magento 2 Using Plugins

© 2017 Magento, Inc.

Allows us to “Listen” to any public method call made on an object manager controlled object and take programmatic action

We can change the return value of any method call made on an object manager controlled object

Change the arguments of any method call made to an object manager controlled object

Do so while other modules are doing the same thing to the same method

Some things we can do…

Page 20: Code Design and Best Practices for Magento 2 Using Plugins · © 2017 Magento, Inc. Code Design and Best Practices for Magento 2 Using Plugins

© 2017 Magento, Inc.

Some things we can’t do…

- Final methods- Final classes- Any class that contains at

least one final public method

- Non-public methods- Class methods

(such as static methods)

- __construct- Virtual types

- Objects that are instantiated before early in the pipeline

Page 21: Code Design and Best Practices for Magento 2 Using Plugins · © 2017 Magento, Inc. Code Design and Best Practices for Magento 2 Using Plugins

© 2017 Magento, Inc.

• AOP is very powerful, but Plugins within Magento don’t actually allow for the true definition

– They don’t allow you to add behavior to wildcard/pattern methods

However,• Plugins are a powerful new feature, which is a convenient way to avoid

class rewrite conflicts• Plugins are also a fantastic replacement for not having events defined at

the right place, at the right time within Magento2 Core• They can be added to interfaces

How should you be using them?

Page 22: Code Design and Best Practices for Magento 2 Using Plugins · © 2017 Magento, Inc. Code Design and Best Practices for Magento 2 Using Plugins

© 2017 Magento, Inc.

Recent examples

• GitHub #9582: Eliminate the need for inheritance for action controllers

• FooMan module: Anatomy of my first extension conflict with Magento 2 -https://store.fooman.co.nz/blog/anatomy-of-my-first-extension-conflict-on-magento2.html

How should you be using them?Plugins in Magento2

Page 23: Code Design and Best Practices for Magento 2 Using Plugins · © 2017 Magento, Inc. Code Design and Best Practices for Magento 2 Using Plugins

There are three types of Interceptor

Page 24: Code Design and Best Practices for Magento 2 Using Plugins · © 2017 Magento, Inc. Code Design and Best Practices for Magento 2 Using Plugins

© 2017 Magento, Inc.

• Plugins allow you to create non-conflicting extensions to methods using:– Before– After– Around (with $proceed call)

• Plugins allow you to also create conflicting method replacement– Around (without the $proceed call)

Basics of Plugins

Page 25: Code Design and Best Practices for Magento 2 Using Plugins · © 2017 Magento, Inc. Code Design and Best Practices for Magento 2 Using Plugins

© 2017 Magento, Inc.

• Before: Executes before a join point.• It doesn’t have the ability to prevent execution flow proceeding to the join

point (unless it throws an exception)

Before

beforeMethodName

Page 26: Code Design and Best Practices for Magento 2 Using Plugins · © 2017 Magento, Inc. Code Design and Best Practices for Magento 2 Using Plugins

© 2017 Magento, Inc.

CORE Example of Before

<type name="Magento\Checkout\Api\PaymentInformationManagementInterface"><plugin name="validate-agreements"

type="Magento\CheckoutAgreements\Model\Checkout\Plugin\Validation"/></type>

public function beforeSavePaymentInformation(\Magento\Checkout\Api\PaymentInformationManagementInterface $subject,$cartId,\Magento\Quote\Api\Data\PaymentInterface $paymentMethod,\Magento\Quote\Api\Data\AddressInterface $billingAddress = null

){

if ($this->isAgreementEnabled()) {$this->validateAgreements($paymentMethod);

}}

Page 27: Code Design and Best Practices for Magento 2 Using Plugins · © 2017 Magento, Inc. Code Design and Best Practices for Magento 2 Using Plugins

© 2017 Magento, Inc.

• After: Executed after a join point completes normally: for example, if a method returns without throwing an exception

After

afterMethodName

Page 28: Code Design and Best Practices for Magento 2 Using Plugins · © 2017 Magento, Inc. Code Design and Best Practices for Magento 2 Using Plugins

© 2017 Magento, Inc.

CORE Example of After

<type name="Magento\Customer\Model\ResourceModel\Visitor"><plugin name="reportLog" type="Magento\Reports\Model\Plugin\Log" />

</type>

public function afterClean(\Magento\Customer\Model\ResourceModel\Visitor $subject, $logResourceModel

){

$this->_reportEvent->clean();$this->_comparedProductIdx->clean();$this->_viewedProductIdx->clean();return $logResourceModel;

}

Page 29: Code Design and Best Practices for Magento 2 Using Plugins · © 2017 Magento, Inc. Code Design and Best Practices for Magento 2 Using Plugins

© 2017 Magento, Inc.

• Around: Surrounds a join point such as a method invocation. This is the most powerful kind

• Around can perform custom behaviour before and after the method invocation

• It is also responsible for choosing whether to proceed to the join point or to shortcut the advised method execution by returning its own return value or throwing an exception

Around

aroundMethodName

Page 30: Code Design and Best Practices for Magento 2 Using Plugins · © 2017 Magento, Inc. Code Design and Best Practices for Magento 2 Using Plugins

© 2017 Magento, Inc.

CORE Example of Around

public function aroundDelete(\Magento\Catalog\Model\ResourceModel\Category $subject,\Closure $proceed,CategoryInterface $category

) {$categoryIds = $this->childrenCategoriesProvider->getChildrenIds(

$category, true);$categoryIds[] = $category->getId();$result = $proceed($category);foreach ($categoryIds as $categoryId) {

$this->deleteRewritesForCategory($categoryId);}return $result;

}

<type name="Magento\Catalog\Model\ResourceModel\Category"><plugin

name="category_delete_plugin" type="Magento\CatalogUrlRewrite\Model\Category\Plugin\Category\Remove" />

</type>

Page 31: Code Design and Best Practices for Magento 2 Using Plugins · © 2017 Magento, Inc. Code Design and Best Practices for Magento 2 Using Plugins

© 2017 Magento, Inc.

• Around uses an anonymous function (PHP Closure) called $proceed()

• Calling $proceed() runs the original function

• All function variables should be passed

Using Around

public function aroundDelete(...

) {

...

$result = $proceed($category);

...

return $result;}

Page 32: Code Design and Best Practices for Magento 2 Using Plugins · © 2017 Magento, Inc. Code Design and Best Practices for Magento 2 Using Plugins

It’s really easy to break things with AROUND

Warning

Page 33: Code Design and Best Practices for Magento 2 Using Plugins · © 2017 Magento, Inc. Code Design and Best Practices for Magento 2 Using Plugins

© 2017 Magento, Inc.

• Plugins are defined within di.xml files

• As with all di.xml files, these can be specific for adminhtml, frontend or they can be global

Defining a Plugin

<type name="Magento\Framework\Url\ScopeInterface"><plugin name="urlSignature" type="Magento\Theme\Model\Url\Plugin\Signature" />

</type>

Page 34: Code Design and Best Practices for Magento 2 Using Plugins · © 2017 Magento, Inc. Code Design and Best Practices for Magento 2 Using Plugins

© 2017 Magento, Inc.

• type name: A name of a class or interface that needs to be followed

• plugin name: Set name for the new plugin in Magento 2

• plugin type: The name of a plugin’s class or its virtual type. You can refer the following naming convention for this field:

• \Vendor\Module\Plugin<ModelName>Plugin\ClassName

Required Options

Page 35: Code Design and Best Practices for Magento 2 Using Plugins · © 2017 Magento, Inc. Code Design and Best Practices for Magento 2 Using Plugins

© 2017 Magento, Inc.

• plugin sortOrder: Set order when the plugin calls the other same methods in the process

• plugin disabled: That allows you enable or disable a plugin quickly. As the default configuration, the chosen value is false

Optional Options

Page 36: Code Design and Best Practices for Magento 2 Using Plugins · © 2017 Magento, Inc. Code Design and Best Practices for Magento 2 Using Plugins

The Running Order

Page 37: Code Design and Best Practices for Magento 2 Using Plugins · © 2017 Magento, Inc. Code Design and Best Practices for Magento 2 Using Plugins

© 2017 Magento, Inc.

Plugin A Plugin B Plugin CsortOrder 10before beforeDispatch() beforeDispatch() beforeDispatch()around aroundDispatch() aroundDispatch()after afterDispatch() afterDispatch() afterDispatch()

A really good example from Magento Dev DocsPlugins in Magento2

Page 38: Code Design and Best Practices for Magento 2 Using Plugins · © 2017 Magento, Inc. Code Design and Best Practices for Magento 2 Using Plugins

© 2017 Magento, Inc.

Plugins in Magento2

Plugin A BEFORE

Plugin B BEFORE

Plugin B AROUND(beginning)

Plugin C BEFORE

Plugin C AROUND (beginning)

ACTION

Page 39: Code Design and Best Practices for Magento 2 Using Plugins · © 2017 Magento, Inc. Code Design and Best Practices for Magento 2 Using Plugins

A (semi) practical example

Page 40: Code Design and Best Practices for Magento 2 Using Plugins · © 2017 Magento, Inc. Code Design and Best Practices for Magento 2 Using Plugins

© 2017 Magento, Inc.

• Example: When submitting a review on the product view page, make sure that the username does not contain dashes

• If it does, show an error message

Plugins – an example

Page 41: Code Design and Best Practices for Magento 2 Using Plugins · © 2017 Magento, Inc. Code Design and Best Practices for Magento 2 Using Plugins

© 2017 Magento, Inc.

In Magento 1

postAction()Product Controller

$validate = $review->validate();

validate()Review Model

Page 42: Code Design and Best Practices for Magento 2 Using Plugins · © 2017 Magento, Inc. Code Design and Best Practices for Magento 2 Using Plugins

© 2017 Magento, Inc.

The validate function in M1

public function validate(){

$errors = array();

if (!Zend_Validate::is($this->getTitle(), 'NotEmpty')) {$errors[] = Mage::helper('review')->__('Review summary can\'t be empty');

}

if (!Zend_Validate::is($this->getNickname(), 'NotEmpty')) {$errors[] = Mage::helper('review')->__('Nickname can\'t be empty');

}

if (!Zend_Validate::is($this->getDetail(), 'NotEmpty')) {$errors[] = Mage::helper('review')->__('Review can\'t be empty');

}

if (empty($errors)) {return true;

}return $errors;

}

Page 43: Code Design and Best Practices for Magento 2 Using Plugins · © 2017 Magento, Inc. Code Design and Best Practices for Magento 2 Using Plugins

© 2017 Magento, Inc.

In Magento 2

execute()Product Post Controller

$validate = $review->validate();

validate()Review Model

Page 44: Code Design and Best Practices for Magento 2 Using Plugins · © 2017 Magento, Inc. Code Design and Best Practices for Magento 2 Using Plugins

© 2017 Magento, Inc.

The validate function in M2

public function validate(){

$errors = [];

if (!\Zend_Validate::is($this->getTitle(), 'NotEmpty')) {$errors[] = __('Please enter a review summary.');

}

if (!\Zend_Validate::is($this->getNickname(), 'NotEmpty')) {$errors[] = __('Please enter a nickname.');

}

if (!\Zend_Validate::is($this->getDetail(), 'NotEmpty')) {$errors[] = __('Please enter a review.');

}

if (empty($errors)) {return true;

}return $errors;

}

Page 45: Code Design and Best Practices for Magento 2 Using Plugins · © 2017 Magento, Inc. Code Design and Best Practices for Magento 2 Using Plugins

© 2017 Magento, Inc.

The di.xml

<?xml version="1.0"?><config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"><type name="Magento\Review\Model\Review"> <!-- the class we're going to watch -->

<plugin name="checkForDashes" <!-- an arbitrary name -->type="PinPoint\Review\Model\Plugin\ValidateReview" <!-- our class -->sortOrder="10" <!-- the sort order for processing -->

/></type>

</config>

Page 46: Code Design and Best Practices for Magento 2 Using Plugins · © 2017 Magento, Inc. Code Design and Best Practices for Magento 2 Using Plugins

© 2017 Magento, Inc.

The plugin class

namespace PinPoint\Review\Model\Plugin;

class ValidateReview{

const DASH_VALIDATION_ERROR = 'Your username cannot contain dashes.';

function afterValidate(\Magento\Review\Model\Review $subject, $result

) {if (stristr($subject->getNickname(), '-')) {

// add and return hereif($result === true) {

return array(__(self::DASH_VALIDATION_ERROR));} elseif (is_array($result)) {

$result[] = __(self::DASH_VALIDATION_ERROR);return $result;

}}return $result;

}

Page 47: Code Design and Best Practices for Magento 2 Using Plugins · © 2017 Magento, Inc. Code Design and Best Practices for Magento 2 Using Plugins

© 2017 Magento, Inc.

Flexible way to avoid class rewrites

Usually a predictable way to flow

Can be inherited through parent/child classes & interfaces

Incredibly hard to follow if you don’t know they’re there

If there is an event for something, it would be more readable to use

Summary

Page 48: Code Design and Best Practices for Magento 2 Using Plugins · © 2017 Magento, Inc. Code Design and Best Practices for Magento 2 Using Plugins

Douglas Radburn@douglasradburn@PinpointAgency

That’s all folks!

Page 49: Code Design and Best Practices for Magento 2 Using Plugins · © 2017 Magento, Inc. Code Design and Best Practices for Magento 2 Using Plugins