Magento 2 Backend optimization. Using proxy classes
Introduction
In the realm of backend optimization, proxy classes play a crucial role in enhancing performance. A proxy class is a class used as a stand-in for another class, designed specifically to improve performance. Instead of instantiating the original class right away, a proxy class sits in front of it, delaying the instantiation process until a function within the original class is called. This way, proxy classes ensure that the original class loads instantly, preventing performance bottlenecks and allowing your application to run smoothly. This is applied to Magento 2 Backend as well.
Proxy Pattern Usage: When and Why to Implement It in Magento 2
If you fall under any of these category, you should use proxy patters.
- Managing High-Demand Classes: The proxy pattern offers an effective approach to deal with resource-intensive, slow, or heavy classes, ultimately enhancing the performance of your Magento 2 application.
- Enhancing Console Script Execution: Proxies enable the efficient execution of console scripts in Magento 2 by ensuring that the constructor of a specific command is only executed when needed, rather than for every available command.
- Resolving Circular Dependencies: Proxies can act as a workaround for addressing issues with circular dependencies in modules, allowing developers to prevent specific classes from being loaded when modifying the code isn’t feasible.
Utilizing the proxy pattern in Magento 2 is crucial for optimizing application performance and maintainability. So it is helpful to know of this possibility as implementing the proxy pattern contributes to a more efficient Magento 2 application and easier development process.
Exploring Proxy Pattern with an Example
Let’s create a simple Magento 2 module.
ITD/BackendOptimization/registration.php
<?php declare(strict_types=1);
use Magento\Framework\Component\ComponentRegistrar;
ComponentRegistrar::register(
ComponentRegistrar::MODULE,
'ITD_BackendOptimization',
__DIR__
);
ITD/BackendOptimization/etc/module.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
<module name="ITD_BackendOptimization"/>
</config>
Let’s assume we have two models FastLoadingModel and SlowLoadingModel. The only difference is that our “slow” model has a delay inside “fetchData” method to imitate the slow loading process.
ITD/BackendOptimization/Model/FastLoadingModel.php
<?php declare(strict_types=1);
namespace ITD\BackendOptimization\Model;
class FastLoadingModel
{
public function fetchData(): array
{
return ['data'];
}
}
ITD/BackendOptimization/Model/SlowLoadingModel.php
<?php declare(strict_types=1);
namespace ITD\BackendOptimization\Model;
class SlowLoadingModel
{
public function fetchData(): array
{
sleep(5);
return ['slow performance data'];
}
}
We will demonstrate using these models for illustrative purposes inside Magento 2 CLI command. So let’s create a CLI command next.
ITD/BackendOptimization/etc/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\Framework\Console\CommandListInterface">
<arguments>
<argument name="commands" xsi:type="array">
<item name="itd_backendoptimization_fetch_data" xsi:type="object">ITD\BackendOptimization\Console\Command\FetchData</item>
</argument>
</arguments>
</type>
</config>
ITD\BackendOptimization\Console\Command\FetchData
<?php declare(strict_types=1);
namespace ITD\BackendOptimization\Console\Command;
use ITD\BackendOptimization\Model\FastLoadingModel;
use ITD\BackendOptimization\Model\SlowLoadingModel;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class FetchData extends Command
{
private const IS_SLOW_PERFORMANCE = 'is-slow';
public function __construct(
private SlowLoadingModel $slowLoadingModel,
private FastLoadingModel $fastLoadingModel,
string $name = null
) {
parent::__construct($name);
}
protected function configure(): void
{
$this->setName('itd:fetch:data');
$this->setDescription('Example of using a proxy class');
$this->addOption(
self::IS_SLOW_PERFORMANCE,
null,
InputOption::VALUE_REQUIRED,
'Is slow performance used?',
false
);
parent::configure();
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$output->writeln("<info>Start fetching data</info>");
// Start measuring execution time
$startTime = microtime(true);
if ($input->getOption(self::IS_SLOW_PERFORMANCE)) {
$data = $this->slowLoadingModel->fetchData();
} else {
$data = $this->fastLoadingModel->fetchData();
}
// End measuring execution time
$endTime = microtime(true);
// Calculate the execution time in seconds
$executionTime = $endTime - $startTime;
$output->writeln("<info>The result: $data with execution time: $executionTime</info>");
return 0;
}
}
This CLI command class has an injection with our “fast” and “slow” models. Also, there is an “is-slow” CLI option to call one of the models depending on the flag true|false (false by default). And inside the execution method, we are measuring the time of execution so we can see the difference.
Now we are going to create our Magento proxy class. For this, we need to use Magento 2 dependency injection config file (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\Framework\Console\CommandListInterface">
<arguments>
<argument name="commands" xsi:type="array">
<item name="itd_backendoptimization_fetch_data" xsi:type="object">ITD\BackendOptimization\Console\Command\FetchData</item>
</argument>
</arguments>
</type>
<type name="ITD\BackendOptimization\Console\Command\FetchData">
<arguments>
<argument name="slowLoadingModel" xsi:type="object">ITD\BackendOptimization\Model\SlowLoadingModel\Proxy</argument>
</arguments>
</type>
</config>
We instruct Magento to inject the ITD\BackendOptimization\Model\SlowLoadingModel\Proxy class to our FetchData CLI class as a named argument – “slowLoadingModel”. Magento Object Manager will recognize it by “seeing” Proxy after our original class name and create the Proxy class for us.
Important: we have to use Dependency injection and di.xml to create proxy classes and do not inject Proxy directly in __construct method.
Now let’s run our newly created CLI command with different true|false flags and compare the results.
bin/magento itd:fetch:data
Start fetching data
The result: data with execution time: 1.1920928955078E-5
bin/magento itd:fetch:data — is-slow true
Start fetching data
The result: slow performance data with execution time: 5.0001769065857
As we can see switching the models works as expected. Next, let’s dive deeper into the Magento 2 Proxy class creation process.
Inside Magento 2 Proxy generated class
Object Manager has created a Proxy class in the generated directory generated/code/ITD/BackendOptimization/Model/SlowLoadingModel/Proxy. For us, two methods inside the generated proxy class are the most important:
/**
* Get proxied instance
*
* @return \ITD\BackendOptimization\Model\SlowLoadingModel
*/
protected function _getSubject()
{
if (!$this->_subject) {
$this->_subject = true === $this->_isShared
? $this->_objectManager->get($this->_instanceName)
: $this->_objectManager->create($this->_instanceName);
}
return $this->_subject;
}
/**
* {@inheritdoc}
*/
public function fetchData() : string
{
return $this->_getSubject()->fetchData();
}
The _getSubject() method is responsible for retrieving the actual instance of the ITD\BackendOptimization\Model\SlowLoadingModel class.
If the instance has not been created yet, it will create one using the object manager ($this->_objectManager). The creation of the instance depends on the value of the _isShared property, which determines whether to use a shared instance or create a new one. The fetchData() method, which implements the interface method, delegates the call to the _getSubject() method and returns the result.
In summary, this proxy class acts as an intermediary, handling the creation and retrieval of the actual SlowLoadingModel instance, and forwarding method calls to it. So we can be sure that our SlowLoadingModel will not be instantiated unless we call its method.
Conclusion
Magento 2 Proxy classes improve performance by postponing the instantiation of the original class until required. They are beneficial for managing high-demand classes, optimizing console script execution, and resolving circular dependencies in modules. Utilizing the proxy pattern in Magento 2 results in a more efficient and maintainable application, ultimately streamlining the development process.