Rewriting Classes in Magento 2

Class rewrites are used to override core functionality of Magento without editing the core files themselves.
  • Dejvid Haxhiu
  • Back End Developer
  • August 29, 2019 Estimated reading time: 3 minutes

In this article, we are going to look at how class rewrites work in Magento 2. Class rewrites are used to override core functionality of Magento without editing the core files themselves. Magento 2 can seem intimidating because it has so many files and classes. In fact, creating a module for Magento 2 can be easier than it was in Magento 1 because of the file structure. In Magento 1, the development of a module meant creating a collection of folders throughout the file tree for PHTML templates, stylesheets, and scripts. This process is detailed in this article. In Magento 2, all the necessary files are contained in a single Module directory. Simpler, right?

You may ask, why take the time to create override modules at all? As open-source software, isn’t the advantage of Magento Commerce that its core files can be modified as needed? Technically, they can, but making changes to the core software will prevent Magento from being updated with new versions—the upgrade will overwrite the customized code. More than that, though, creating overrides is just standard best practice. It ensures that all changes to Magento’s functionality can be tracked, tested and disabled if anything goes wrong. Magento stores are powerful and complicated, and it’s important for merchants to know that the developers supporting their site follow Magento’s coding conventions and care about quality.

If you are familiar with how class rewrites work in Magento 1, it will be easier to understand rewrites in Magento 2. As with any development in Magento 2, this can take some time to understand. But with a little bit of practice, you’ll get used to it.

So without further ado, let’s jump in. In this article, we’ll explore two ways to change classes or methods in Magento 2:

  1. Preferences
  2. Plugins

Preference

Class preferences basically do the same thing in Magento 2 that rewrites did in Magento 1. It states a preference for one class over another, which allows you to specify which class/type is selected by Magento’s object manager.

This means that you can override which method you want from the class, along with the methods that this class extends. 

To try it out, we’ll look at the steps involved in creating a new module. The module structure will look like this:

Under the etc folder we have created module.xml with the following content:

<?xml version="1.0"?>
<!--
/**
* Copyright (c) 2019 Shero Commerce.
* *
* @Project All
* @date 3/20/2019
* @author Shero
* @link https://www.www.swoonchic.com/
* @module Shero_Mage2rewrite
* @description configuration file
*/
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module\etc\module.xsd">
   <module name="Shero_Mage2rewrite" setup_version="1.0.3">
 
   </module>
</config>

The registration.php file is used to register our module. It will contain the following code:

<?php
/**
* Copyright (c) 2019 Shero Commerce.
* *
* @Project All
* @date 3/20/2019
* @author Shero
* @link https://www.www.swoonchic.com/
* @module Shero_Mage2rewrite
* @description module registration
*/
 
 
\Magento\Framework\Component\ComponentRegistrar::register(
   \Magento\Framework\Component\ComponentRegistrar::MODULE,
   'Shero_Mage2rewrite',
   __DIR__
);

To enable this module, open your command line interface at the Magento installation root and execute this command:

$ php bin/magento setup:upgrade

You will see a list of enabled modules, with our new module Module ‘Shero_Mage2rewrite’.

Using class preference I am going to rewrite:

  • Model class
  • Block Class
  • Controller Class

Let’s start first with rewriting the model/block/controller class. In the Shero / Mag2rewrite / etc / folder, create a file called di.xml with the following content:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/e?c/config.xsd">
 
   <!--Orverriding Customer.php model using preference method -->
   <preference for="Magento\Customer\Model\Customer" type="Shero\Mage2rewrite\Model\Customer"/>
 
   <!--Overriding Login.php Block using preference method-->
   <preference for="Magento\Customer\Block\Form\Login" type="Shero\Mage2rewrite\Block\Form\Login"/>
 
   <!--Rewriting Create.php Controller using preference method-->
   <preference for="Magento\Customer\Controller\Account\Create" type="Shero\Mage2rewrite\Controller\Account\Create"/>
</config>

We’re overriding the default Customer.php class by setting a preference for another class. Now Magento is looking to our class for methods first, instead of to Customer.php. Any methods that exist in our class are now executed. It works the same for blocks and controllers. 

After that, we need to create our class files. By way of example, we are creating preferences for the following Magento core classes: 

Base ClassOverride Class
Magento\Customer\Model\CustomerShero\Mage2rewrite\Model\Customer
Magento\Customer\Block\Form\LoginShero\Mage2rewrite\Block\Form\Login
Magento\Customer\Controller\Account\CreateShero\Mage2rewrite\Controller\Account\Create


The module structure will look like this:

As in Magento 1, the classes seen here will extend from the base class. 
The content for Block class for Login.php will look like this:

<?php
/**
* Copyright (c) 2019 Shero Commerce.
* *
* @Project All
* @date 3/20/2019
* @author Shero
* @link https://www.www.swoonchic.com/
* @module Shero_Mage2rewrite
* @description Block Rewrite
*/
 
 
namespace Shero\Mage2rewrite\Block\Form;
 
class Login extends \Magento\Customer\Block\Form\Login
{
   //override your methods here
}

Magento 2 uses namespaces to help avoid conflicts. We specify our namespaces for our class After that, our class extends from the base class that we are overriding. 

The content for Model class for Login.php will look like this:

<?php
/**
* Copyright (c) 2019 Shero Commerce.
* *
* @Project All
* @date 3/20/2019
* @author Shero
* @link https://www.www.swoonchic.com/
* @module Shero_Mage2rewrite
* @description Model Rewrite
*/
 
namespace Shero\Mage2rewrite\Model;
 
class Customer extends \Magento\Customer\Model\Customer
{
   public function getName() {
       return "Shero Commerce";
   }
 
}

And the content for the Controller class will be like this:

<?php
/**
* Copyright (c) 2019 Shero Commerce.
* *
* @Project All
* @date 3/20/2019
* @author Shero
* @link https://www.www.swoonchic.com/
* @module Shero_Mage2rewrite
* @description Controller Rewrite
*/
namespace Shero\Mage2rewrite\Controller\Account;
 
 
class Create extends \Magento\Customer\Controller\Account\Create
{
 
   public function execute() {
       //your code here
       parent::execute();
   }
}

Now that we’ve done all this hard work, how can we tell if the class preferences are working? Let’s debug these classes and see if they are overridden properly.  To see if our Block, Model, and Controller are rewritten successfully, save the following code in your Magento 2 webroot as test.php and load it in your browser or terminal:

<?php
require __DIR__ . '/app/bootstrap.php';
 
class TestApp extends \Magento\Framework\App\Http implements \Magento\Framework\AppInterface
{
   public function launch()
   {
       $areaCode = $this->_areaList->getCodeByFrontName($this->_request->getFrontName());
       $this->_state->setAreaCode($areaCode);
       $this->_objectManager->configure($this->_configLoader->load($areaCode));
 
       //enter your code here
$customerModel = $this->_objectManager->create('\Magento\Customer\Model\Customer'); //model
$customerController = $this->_objectManager->create('\Magento\Customer\Block\Form\Login'); //block
$customerBlock = $this->_objectManager->create('\Magento\Customer\Controller\Account\Create'); //controller
 
echo "Model overridden class name "; var_dump(get_class($customerModel)) . "<br>";
echo "Block overridden class name ";  var_dump(get_class($customerController)) . "<br>";
echo "Controller overriddden class name"; var_dump(get_class($customerBlock)) . "<br>";
 
   }
 
   public function catchException(\Magento\Framework\App\Bootstrap $bootstrap, \Exception $exception)
   {
       return false;
   }
}
 
$bootstrap = \Magento\Framework\App\Bootstrap::create(BP, $_SERVER);
$app = $bootstrap->createApplication('TestApp');
$bootstrap->run($app);

After running this file from yourstore.com/test.php, you should get an output like this:

Model overridden class name /chroot/home/example/shero.examplewebsite.com/html/test.php:17: string(33) "Shero\Mage2rewrite\Model\Customer"
 
Block overridden class name /chroot/home/example/shero.examplewebsite.com/html/test.php:18: string(35) "Shero\Mage2rewrite\Block\Form\Login"
 
Controller overriddden class name/chroot/home/example/shero.examplewebsite.com/html/test.php:19: string(56) "Shero\Mage2rewrite\Controller\Account\Create\Interceptor"

If you do, the class preferences are working properly!

Clearly, the class preference method is a powerful way to configure Magento’s core functionality. In some cases, though, it might be too heavy-handed. The disadvantage of using preferences is that can cause conflicts if multiple classes extend the same original class. Under some circumstances, you may want Magento’s default class selection to remain in place. That leads us to our second available method…

Plugins

Interception is a software design pattern that is used when we want to insert code dynamically without necessarily changing the original class behavior. The interception pattern in Magento 2 is implemented via plugins. 

Plugins are an amazing tool in Magento 2. They allow you to change the behavior of methods for classes without having to rewrite the classes as we did above. 

There are 3 different ways to use a plugin to change method behavior. You may have heard them on Sesame Street: 

  1. Before 
  2. After
  3. Around

Plugins cannot be used with any of the following:

  • Objects that are instantiated before Magento\Framework\Interception is bootstrapped
  • Final methods
  • Final classes
  • Any class that contains at least one final public method
  • Non-public methods
  • Static methods
  • __construct
  • Virtual types

Plugins are defined within a module under di.xml file. To define a plugin by using the type element and its name attribute, we first map the class that we want to an observer. This is a plugin declaration in the di.xml file:

<config>
	<type name="Magento\Catalog\block\Product\Abstract\Product">
	<plugin name="ledian"
		type="Ledian\Plugin\Block\Catalog\Product\Abstract\Product"
		Disabled="false"
		sortOrder="200"
	/>
	</type>
</config>

Under the type element, we define one or more plugins using the plugin element.

The plugin element has the following four attributes assigned to it: 

name – Using this attribute, you can provide a unique and recognizable name value that is specific to the plugin.

sortOrder – This attribute determines the order of execution when multiple plugins are observing the same method. 

disabled – The default value of this attribute is set to false, but if it is set to true, it will disable the current plugin, and it will not get executed. 

type – This attribute points to the class that we will be using to implement the before, after or around the listener. 

Assuming we are writing a plugin for a specific method, let’s choose a random method under Customer.php class, the getName() method. 
We define the before, after and around listeners for the getName() method by writing the naming conventions as follows.

Before + getName() => beforeGetName();
After + getName()  => afterGetName();
Around + getName() => aroundGetName();

Using the before listener

The before listener is used when we want to change the arguments of the original method or add some behavior before an original method is called. With the before method listener, the first property is always the $subject property which contains the instance of the object type being observed. 

Using the after listener

The after listeners are used when we want to change the values returned by an original method or add some behavior after an original method is called. The after listener does not need to have a return value. 

Using the around listener

The around listeners are used when we want to change both the arguments and returned values of an original method or add some behaviour before and after an original method is called.

Reference for plugins:
http://devdocs.magento.com/guides/v2.0/extension-dev-guide/plugins.html

Plugins in practice! An Example:

In this example, we’ll use plugins for the Customer.php class and the getName() method.

Before Method

<?php
/**
* Copyright (c) 2019 Shero
* *
* @Project All
* @date 3/20/2019
* @author Shero
* @link https://www.www.swoonchic.com/
* @module Shero_Mage2rewrite
* @description Before plugin
*/
 
 
namespace Shero\Mage2rewrite\Plugin;
 
 
class BeforeSaveAddressInformation
{
 
   public function beforeSaveAddressInformation(
       \Magento\Checkout\Model\ShippingInformationManagement $shippingInformationManagement,
       $cartId,
       \Magento\Checkout\Model\ShippingInformation $shippingInformation
   ) {
 
     //TODO: your code here
   }
 
}

Around Method

<?php
/**
* Copyright (c) 2019 Shero
* *
* @author Shero
* @link https://www.www.swoonchic.com/
* @module Shero_Mage2rewrite
* @description Around plugin
*/
 
 
namespace Shero\Mage2rewrite\Plugin;
 
 
class AroundEstimateByAddressId
{
 
   public function aroundEstimateByAddressId(
       \Magento\Quote\Model\ShippingMethodManagement $shippingMethodManagement,
       \Closure $proceed,
       $cartId,
       $addressId
   ) {
 
       //TODO:your code here
 
   }
 
}

After method

<?php
/**
* Copyright (c) 2018 Sherocommerce.
* *
* @author Shero Commerce | Ledian Hymetllari
* @link https://www.Shero Commerce.com/
* @module Shero_Mage2rewrite
* @description After plugin
*/
 
namespace Shero\Mage2rewrite\Plugin;
 
 
class AfterGetInstructions
{
   public function afterGetInstructions(\Magento\OfflinePayments\Model\Cashondelivery $subject, $proceed)
   {
 
       //TODO: your code here
   }
 
 
}

Conclusion

We’re just scratching the surface of Magento class overrides, but we hope that you have found this introduction helpful. We’ve looked at two methods, one that is blunt and powerful, another that is more precise. Either method will prevent you from making modifications to the Magento core, and that’s the goal. Happy coding!

Related Services

在线中文字幕亚洲日韩