Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

compiled interceptors module #22826

Open
wants to merge 74 commits into
base: 2.5-develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
2fb8f2a
Merge pull request #2 from magento/2.3-develop
fsw Jan 7, 2019
e0057b6
Merge pull request #3 from magento/2.3-develop
fsw May 10, 2019
53709eb
add creatuity compiled interceptors module
May 10, 2019
36f069e
Merge remote-tracking branch 'origin/2.3-develop' into user-2.3-develop
May 12, 2019
7f499e1
added scope as constructor parameter and properties for plugins cache
May 13, 2019
d97dba0
Merge branch '2.3-develop' of github.com:fsw/magento2 into 2.3-develop
May 13, 2019
cb0a213
get object manager from DI
May 14, 2019
d19f4f9
clean up code repetition by extending core plugins list
May 15, 2019
32e3cca
static code check fixes
May 15, 2019
224dd4e
hotfic for production mode
May 21, 2019
000d0a5
Merge pull request #4 from magento/2.3-develop
fsw May 21, 2019
e57df09
removed unnecessary inheritance, fix for methods returning self
May 23, 2019
f08394d
Merge branch '2.3-develop' of github.com:fsw/magento2 into 2.3-develop
May 23, 2019
8071fb2
bugfix for methods returning void
May 23, 2019
4948604
void methods fix
May 23, 2019
75e65ac
void methods fix
May 23, 2019
5fc398c
refactor of CompiledInterceptor
May 24, 2019
1999072
fixes for coding standard
May 24, 2019
f30e9cf
coding standards fix
May 27, 2019
82dc39f
coding standard fixes
May 27, 2019
41ca3b0
coding standard fixes
May 27, 2019
944144a
cyclomatic complexity refactor
May 27, 2019
5227be0
Merge pull request #5 from magento/2.3-develop
fsw May 27, 2019
d64c89a
coding standards fixes
May 27, 2019
16add49
Merge branch '2.3-develop' of github.com:fsw/magento2 into 2.3-develop
May 27, 2019
1058be9
fixes for compiled mode
May 30, 2019
ecd9b54
bugfix for primary scope for compiled mode
Jun 6, 2019
6285273
generating interceptors performance fix
Jun 7, 2019
5cb7dc9
Merge pull request #6 from magento/2.3-develop
fsw Jun 10, 2019
5b85a71
Merge branch '2.3-develop' of github.com:fsw/magento2 into 2.3-develop
Jun 10, 2019
d22a34e
optimise generation time from empty cache
Jun 10, 2019
5e62ebc
further optimisation and default scope fix
Jun 12, 2019
9c0aad8
Replaced preferences usage
joni-jones Jun 20, 2019
bb6cc62
Merge pull request #7 from magento/2.3-develop
fsw Jun 24, 2019
c334bcf
Merge branch '2.3-develop' of github.com:fsw/magento2 into 2.3-develop
Jun 24, 2019
5665eec
Merge pull request #8 from joni-jones/interceptors
fsw Jun 24, 2019
c3019f3
Merge branch '2.3-develop' of github.com:fsw/magento2 into 2.3-develop
Jun 24, 2019
dfd3c28
manual backport of preferences usage fix
Jun 24, 2019
a7fc720
save plugins cache in generated code folder to simplify cache cleaning
Jun 24, 2019
1657b46
coding standards fix
Jun 24, 2019
07bfca9
move sompiledinterceptorsubstitution to avoid dependency issues, fix …
Jun 24, 2019
7f268ce
fix for plugins overriden by preferences
Jul 12, 2019
8b3cae6
Removed commented line
sidolov Oct 1, 2019
2bc739d
magento/magento2#22826: Refactoring.
engcom-Foxtrot Oct 17, 2019
29a6d8e
magento/magento2#22826: Static test fix.
engcom-Foxtrot Oct 18, 2019
dca21f7
magento/magento2#22826: Class level caching for compiled plugins remo…
engcom-Foxtrot Oct 22, 2019
219382f
magento/magento2#22826: Admin infinite redirect loop fix.
engcom-Foxtrot Oct 23, 2019
f96d546
magento/magento2#22826: Compiled plugin list optimization.
engcom-Foxtrot Oct 27, 2019
528408d
magento/magento2#22826: Static cache path update.
engcom-Foxtrot Oct 27, 2019
aa62a4d
magento/magento2#22826: Compiled interceptor return type fix.
engcom-Foxtrot Nov 1, 2019
eb1018b
magento/magento2#22826: Integration tests fix.
engcom-Foxtrot Nov 1, 2019
1ef18eb
Merge remote-tracking branch 'mainline/2.3-develop' into 2.3-develop
engcom-Foxtrot Nov 1, 2019
53e1e8d
magento/magento2#22826: Enable compiled interceptors.
engcom-Foxtrot Nov 1, 2019
75dea82
magento/magento2#22826: Remove ExecuteCommitCallbacks plugin.
engcom-Foxtrot Nov 1, 2019
b422bb1
magento/magento2#22826: Autoload fix.
engcom-Foxtrot Nov 3, 2019
b40eabe
magento/magento2#22826: Arguments resolving for interceptors fix.
engcom-Foxtrot Nov 6, 2019
94a29c7
Merge remote-tracking branch 'mainline/2.3-develop' into 2.3-develop
engcom-Foxtrot Nov 6, 2019
7fbcecd
Merge branch '2.3-develop' into 2.3-develop
engcom-Foxtrot Nov 6, 2019
53fa5c4
magento/magento2#22826: After plugin calls generation fix.
engcom-Foxtrot Nov 8, 2019
727cfc9
magento/magento2#22826: Tests fix.
engcom-Foxtrot Nov 8, 2019
23bdd02
magento/magento2#22826: Refactoring.
engcom-Foxtrot Nov 13, 2019
c263481
magento/magento2#22826: Compiled interceptor constructors generation …
engcom-Foxtrot Nov 14, 2019
c082b69
magento/magento2#22826: Static test fix.
engcom-Foxtrot Nov 15, 2019
5b57dbc
magento/magento2#22826: Compiled plugin list argument update.
engcom-Foxtrot Nov 18, 2019
de6e95b
magento/magento2#22826: Readme update.
engcom-Foxtrot Nov 18, 2019
7bc9f69
magento/magento2#22826: Disable compiled interceptors.
engcom-Foxtrot Nov 18, 2019
d1b674e
Merge branch '2.4-develop' into 2.3-develop
Sep 16, 2020
f6dccdd
Merge branch '2.4-develop' into 2.3-develop
Sep 17, 2020
f6e7ac4
Fixed incorrectly resolved merge conflicts
Sep 17, 2020
9d1f67c
Fixed incorrectly resolved merge conflicts
Sep 17, 2020
3fb9422
Fixed tests failures
Sep 17, 2020
76864e0
Merge branch '2.4-develop' into 2.3-develop
Sep 17, 2020
8f80b6e
Merge branch '2.5-develop' of https://github.com/magento/magento2 int…
fsw Mar 5, 2021
c9f47ef
Merge branch 'magento-2.5-develop' into 2.3-develop
fsw Mar 5, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\Framework\CompiledInterception\Generator;

use Magento\Framework\App\ObjectManager;
use Magento\Framework\Interception\PluginList\PluginList;
use Magento\Framework\Interception\ObjectManager\ConfigInterface;
use Magento\Framework\ObjectManager\Config\Reader\Dom;

class CompiledPluginList extends PluginList
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to inherit from PluginList,
just implement the interface \Magento\Framework\Interception\PluginListInterface

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This inherits from PluginList not to duplicate code. It uses parent class logic to make sure plugins will be executed exactly in the same order and using same logic as they were previously.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you inherit from PluginList then you have to implement anything which is private.
Properties: $logger, $serializer
Methods: trimInstanceStartingBackslash, filterPlugins, getLogger

Make life easier for anyone who wants to subclass from you and define them as protected. You are allowed to raise the visibility of a private method in a child since the parent and any siblings don't know about it.

{
/**
* CompiledPluginList constructor.
* @param $objectManager ObjectManager
* @param $scope
* @param null $reader
* @param null $omConfig
* @param null $cachePath
*/
public function __construct(
$objectManager,
$scope,
$reader = null,
$omConfig = null,
$cachePath = null
) {
if (!$reader || !$omConfig) {
$reader = $objectManager->get(Dom::class);
$omConfig = $objectManager->get(ConfigInterface::class);
}
parent::__construct(
$reader,
new StaticScope($scope),
new FileCache($cachePath),
new \Magento\Framework\ObjectManager\Relations\Runtime(),
fsw marked this conversation as resolved.
Show resolved Hide resolved
$omConfig,
new \Magento\Framework\Interception\Definition\Runtime(),
$objectManager,
new \Magento\Framework\ObjectManager\Definition\Runtime(),
['first' => 'global'],
'compiled_plugins_' . $scope,
new NoSerialize()
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

avoiding inheritance will make this class much nicer

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class actually exists so we can use logic from _loadScopedData and _inheritPlugins methods of PluginList without modifying this class. It mostly removes few DI dependencies so interceptors can be compiled with as few dependencies as possible (because even core classes can have plugins in theory).

Alternatively I could just modify PluginList class but this way we keep original method intact.

}

/**
* Retrieve plugin Instance
*
* @param string $type
* @param string $code
* @return mixed
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function getPlugin($type, $code)
{
return null;
}

/**
* @param $type
* @param $code
* @return mixed
*/
public function getPluginType($type, $code)
{
return $this->_inherited[$type][$code]['instance'];
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\Framework\CompiledInterception\Generator;


use Magento\Framework\Config\CacheInterface;

class FileCache implements CacheInterface
{

private $cachePath;

/**
* FileCache constructor.
* @param null $cachePath
*/
public function __construct($cachePath = null)
{
$this->cachePath = ($cachePath === null ? BP . DIRECTORY_SEPARATOR . 'var' . DIRECTORY_SEPARATOR . 'cache' : $cachePath);
}


/**
* @param $identifier
* @return string
*/
private function getCachePath($identifier)
{
return $this->cachePath . DIRECTORY_SEPARATOR . str_replace('|', '_', $identifier . '.php');
}

/**
* Test if a cache is available for the given id
*
* @param string $identifier Cache id
* @return int|bool Last modified time of cache entry if it is available, false otherwise
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function test($identifier)
{
// TODO: Implement test() method.
}

/**
* Load cache record by its unique identifier
*
* @param string $identifier
* @return string|bool
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function load($identifier)
{
return $this->cachePath ? @include($this->getCachePath($identifier)) : false;
}

/**
* Save cache record
*
* @param string $data
* @param string $identifier
* @param array $tags
* @param int|bool|null $lifeTime
* @return bool
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function save($data, $identifier, array $tags = [], $lifeTime = null)
{
if ($this->cachePath) {
$path = $this->getCachePath($identifier);
if (!is_dir(dirname($path))) {
mkdir(dirname($path));
}
file_put_contents(
$path,
'<?php return ' . var_export($data, true) . '?>'
);
}
}

/**
* Remove cache record by its unique identifier
*
* @param string $identifier
* @return bool
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function remove($identifier)
{
// TODO: Implement remove() method.
}

/**
* Clean cache records matching specified tags
*
* @param string $mode
* @param array $tags
* @return bool
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function clean($mode = \Zend_Cache::CLEANING_MODE_ALL, array $tags = [])
joni-jones marked this conversation as resolved.
Show resolved Hide resolved
{
// TODO: Implement clean() method.
}

/**
* Retrieve backend instance
*
* @return \Zend_Cache_Backend_Interface
*/
public function getBackend()
{
// TODO: Implement getBackend() method.
}

/**
* Retrieve frontend instance compatible with Zend Locale Data setCache() to be used as a workaround
*
* @return \Zend_Cache_Core
*/
public function getLowLevelFrontend()
{
// TODO: Implement getLowLevelFrontend() method.
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\Framework\CompiledInterception\Generator;

use Magento\Framework\Serialize\SerializerInterface;

class NoSerialize implements SerializerInterface
{
/**
* Serialize data into string
*
* @param string|int|float|bool|array|null $data
* @return string|bool
* @throws \InvalidArgumentException
* @since 101.0.0
*/
public function serialize($data)
{
return $data;
}

/**
* Unserialize the given string
*
* @param string $string
* @return string|int|float|bool|array|null
* @throws \InvalidArgumentException
* @since 101.0.0
*/
public function unserialize($string)
{
return $string;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\Framework\CompiledInterception\Generator;

use Magento\Framework\Config\ScopeInterface;

class StaticScope implements ScopeInterface
{
/**
* @var string
*/
protected $scope;
engcom-Foxtrot marked this conversation as resolved.
Show resolved Hide resolved

public function __construct($scope)
{
$this->scope = $scope;
}

/**
* Get current configuration scope identifier
*
* @return string
*/
public function getCurrentScope() {
return $this->scope;
}

/**
* @param string $scope
* @throws \Exception
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function setCurrentScope($scope){
throw new \Exception('readonly');
}

}
88 changes: 88 additions & 0 deletions lib/internal/Magento/Framework/CompiledInterception/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
### ABOUT
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, make sure the latest README is updated according to the latest changes.


This component changes the way Magento 2 generates Interceptor classes (a mechanism that allows plugins to work together).

Instead of generating boilerplate code it compiles the Interceptor using information from source code.
This makes plugins slightly faster and as Magento uses a lot of plugins, even at its core, it lowers request time by ~10%.
This is important in places where there is a lot of non-cached PHP logic going on (for example admin panel is noticeably faster).

The default method uses code that is called on nearly each method to see if there are any plugins connected, in generated code this is not required and call-stack is reduced.

Having plugins called directly also makes code easier to debug and bugs easier to find.

The Interceptors generated by this plugin are 100% compatible with the ones generated by Magento by default, so there is no need to change anything in your plugins.

### ENABLING

To use compiled interceptors in developer mode please add following preference to your di.xml
```
<preference for="Magento\Framework\Interception\Code\Generator\Interceptor" type="Magento\Framework\CompiledInterception\Generator\CompiledInterceptor" />
```

To use compiled interceptors in production mode please add following preference to your di.xml
fsw marked this conversation as resolved.
Show resolved Hide resolved
```
<preference for="Magento\Setup\Module\Di\Code\Generator\Interceptor" type="Magento\Framework\CompiledInterception\Generator\CompiledInterceptor" />
```

Clear generated files and cache:

`rm -rf generated/* && rm -rf var/cache/* && bin/magento cache:clean`

### DISABLING

Remove preferences from `app/etc/di.xml`, remove module and clear cache and generated files.

### TECHNICAL DETAILS

Instead of interceptors that read plugins config at runtime like this:

```
public function methodX($arg) {
$pluginInfo = $this->pluginList->getNext($this->subjectType, 'methodX');
if (!$pluginInfo) {
return parent::methodX($arg);
} else {
return $this->___callPlugins('methodX', func_get_args(), $pluginInfo);
}
}
```

This generator generates static interceptors like this:


```
public function methodX($arg) {
switch(getCurrentScope()){
case 'frontend':
$this->_get_example_plugin()->beforeMethodX($this, $arg);
$this->_get_another_plugin()->beforeMethodX($this, $arg);
$result = $this->_get_around_plugin()->aroundMethodX($this, function($arg){
return parent::methodX($arg);
});
return $this->_get_after_plugin()->afterMethodX($this, $result);
case 'adminhtml':
// ...
default:
return parent::methodX($arg);
}
}
```


#### PROS

* Easier debugging.
* If you ever stumbled upon `___callPlugins` when debugging you should know how painful it is to debug issues inside plugins.
* Generated code is decorated with PHPDoc for easier debugging in IDE

* Fastest response time (5%-15% faster in developer and production mode)
* No redundant calls to `___callPlugins` in call stack.
* Methods with no plugins are not overridden in parent at all.

* Implemented as a module and can be easily reverted to the default `Generator\Interceptor`

#### CONS

* Each time after making change in etc plugins config, `generated/code/*` needs to be purged
* Tiny longer code generation step when run with no cache
* As this does not load plugins at runtime, might not work in an edge case of plugging into core Magento classes like `PluginsList` etc.
Loading