Skip to content

compiled interceptors module #22826

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

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 11 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
YevSent 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,399 @@
<?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\ObjectManagerInterface;
use Magento\Framework\Code\Generator\EntityAbstract;
use Magento\Framework\Config\ScopeInterface;
use Magento\Framework\Interception\Code\Generator\Interceptor;
use Magento\Framework\Interception\DefinitionInterface;

class CompiledInterceptor extends Interceptor
{

protected $plugins;

protected $classMethods = [];
protected $classProperties = [];

public function __construct(
$sourceClassName = null,
$resultClassName = null,
\Magento\Framework\Code\Generator\Io $ioObject = null,
\Magento\Framework\Code\Generator\CodeGeneratorInterface $classGenerator = null,
\Magento\Framework\Code\Generator\DefinedClasses $definedClasses = null,
$plugins = null
)
{
parent::__construct($sourceClassName,
$resultClassName ,
$ioObject,
$classGenerator,
$definedClasses);

if ($plugins !== null) {
$this->plugins = $plugins;
} else {
$this->plugins = [];
foreach (['primary', 'frontend', 'adminhtml', 'crontab', 'webapi_rest', 'webapi_soap'] as $scope) {
$this->plugins[$scope] = new CompiledPluginList(ObjectManager::getInstance(), $scope);
}
}
}

/**
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function setInterceptedMethods($interceptedMethods)
{
//NOOP
}

protected function _generateCode()
{
$typeName = $this->getSourceClassName();
$reflection = new \ReflectionClass($typeName);

if ($reflection->isInterface()) {
return false;
Copy link
Contributor

Choose a reason for hiding this comment

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

how does current approach support plugins for Interfaces?

The intital return type of _generateCode() method is string

Copy link
Member Author

Choose a reason for hiding this comment

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

all classes that implement an interface will have calls to plugins for this interface in the compiled interceptor. CompiledPluginsList class takes care of that using same logic as previous "runtime mode".

Copy link
Member Author

Choose a reason for hiding this comment

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

fixed return type

} else {
$this->_classGenerator->setExtendedClass($typeName);
}

$this->classMethods = [];
$this->classProperties = [];
$this->injectPropertiesSettersToConstructor($reflection->getConstructor(), [
ScopeInterface::class => '____scope',
ObjectManagerInterface::class => '____om',
]);
$this->overrideMethodsAndGeneratePluginGetters($reflection);

//return parent::_generateCode();
return EntityAbstract::_generateCode();
}

protected function overrideMethodsAndGeneratePluginGetters(\ReflectionClass $reflection)
{
$publicMethods = $reflection->getMethods(\ReflectionMethod::IS_PUBLIC);

$allPlugins = [];
foreach ($publicMethods as $method) {
if ($this->isInterceptedMethod($method)) {
$config = $this->_getPluginsConfig($method, $allPlugins);
if (!empty($config)) {
$this->classMethods[] = $this->_getCompiledMethodInfo($method, $config);
}
}
}
foreach ($allPlugins as $plugins) {
foreach ($plugins as $plugin) {
$this->classMethods[] = $this->_getPluginGetterInfo($plugin);
$this->classProperties[] = $this->_getPluginPropertyInfo($plugin);
}
}
}

protected function injectPropertiesSettersToConstructor(\ReflectionMethod $parentConstructor = null, $properties = [])
{
if ($parentConstructor == null) {
$parameters = [];
$body = [];
} else {
$parameters = $parentConstructor->getParameters();
foreach ($parameters as $parameter) {
$parentCallParams[] = '$' . $parameter->getName();
}
$body = ["parent::__construct(" . implode(', ', $parentCallParams) .");"];
}
foreach ($properties as $type => $name) {
$this->_classGenerator->addUse($type);
$this->classProperties[] = [
'name' => $name,
'visibility' => 'private',
'docblock' => [
'tags' => [['name' => 'var', 'description' => substr(strrchr($type, "\\"), 1)]],
]
];
}
$extraParams = $properties;
$extraSetters = array_combine($properties, $properties);
foreach ($parameters as $parameter) {
if ($parameter->getType()) {
$type = $parameter->getType()->getName();
if (isset($properties[$type])) {
$extraSetters[$properties[$type]] = $parameter->getName();
unset($extraParams[$type]);
}
}
}
$parameters = array_map(array($this, '_getMethodParameterInfo'), $parameters);
/* foreach ($extraParams as $type => $name) {
array_unshift($parameters, [
'name' => $name,
'type' => $type
]);
} */
foreach ($extraSetters as $name => $paramName) {
array_unshift($body, "\$this->$name = \$$paramName;");
}
foreach ($extraParams as $type => $name) {
array_unshift($body, "//TODO fix di in production mode");
array_unshift($body, "\$$name = \\Magento\\Framework\\App\\ObjectManager::getInstance()->get(\\$type::class);");
}

$this->classMethods[] = [
'name' => '__construct',
'parameters' => $parameters,
'body' => implode("\n", $body),
'docblock' => ['shortDescription' => '{@inheritdoc}'],
];

}

protected function _getClassMethods()
{
return $this->classMethods;
}

protected function _getClassProperties()
{
return $this->classProperties;
}

private function addCodeSubBlock(&$body, $sub, $indent = 1)
{
foreach ($sub as $line) {
$body[] = str_repeat("\t", $indent) . $line;
}
}

/**
* @param \ReflectionMethod $method
* @param $conf
* @param $parameters
* @return array
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.NPathComplexity)
*/
protected function _getMethodSourceFromConfig(\ReflectionMethod $method, $conf, $parameters, $returnVoid)
{
$body = [];
$first = true;
$capName = ucfirst($method->getName());
$extraParams = empty($parameters) ? '' : (', ' . $this->_getParameterList($parameters));

if (isset($conf[DefinitionInterface::LISTENER_BEFORE])) {
foreach ($conf[DefinitionInterface::LISTENER_BEFORE] as $plugin) {
if ($first) $first = false; else $body[] = "";

$call = "\$this->" . $this->getGetterName($plugin) . "()->before$capName(\$this$extraParams);";

if (!empty($parameters)) {
$body[] = "\$beforeResult = " . $call;
$body[] = "if (\$beforeResult !== null) list({$this->_getParameterList($parameters)}) = (array)\$beforeResult;";
} else {
$body[] = $call;
}
}
}


$chain = [];
$main = [];
if (isset($conf[DefinitionInterface::LISTENER_AROUND])) {
$plugin = $conf[DefinitionInterface::LISTENER_AROUND];
$main[] = "\$this->" . $this->getGetterName($plugin) . "()->around$capName(\$this, function({$this->_getParameterListForNextCallback($parameters)}){";
$this->addCodeSubBlock($main, $this->_getMethodSourceFromConfig($method, $plugin['next'] ?: [], $parameters, $returnVoid));
$main[] = "}$extraParams);";
} else {
$main[] = "parent::{$method->getName()}({$this->_getParameterList($parameters)});";
}
$chain[] = $main;

if (isset($conf[DefinitionInterface::LISTENER_AFTER])) {
foreach ($conf[DefinitionInterface::LISTENER_AFTER] as $plugin) {
if ($returnVoid) {
$chain[] = ["((\$tmp = \$this->" . $this->getGetterName($plugin) . "()->after$capName(\$this, \$result$extraParams)) !== null) ? \$tmp : \$result;"];
} else {
$chain[] = ["\$this->" . $this->getGetterName($plugin) . "()->after$capName(\$this, \$result$extraParams);"];
}
}
}
foreach ($chain as $lp => $piece) {
if ($first) $first = false; else $body[] = "";
if (!$returnVoid) {
$piece[0] = (($lp + 1 == count($chain)) ? "return " : "\$result = ") . $piece[0];
}
foreach ($piece as $line) {
$body[] = $line;
}
}

return $body;
}

/**
* @param \ReflectionParameter[] $parameters
* @return string
*/
protected function _getParameterListForNextCallback(array $parameters)
{
$ret = [];
foreach ($parameters as $parameter) {
$ret [] =
($parameter->isPassedByReference() ? '&' : '') .
"\${$parameter->getName()}" .
($parameter->isDefaultValueAvailable() ?
' = ' . ($parameter->isDefaultValueConstant() ?
$parameter->getDefaultValueConstantName() :
str_replace("\n", '', var_export($parameter->getDefaultValue(), true))) :
'');
}
return implode(', ', $ret);
}

/**
* @param \ReflectionParameter[] $parameters
* @return string
*/
protected function _getParameterList(array $parameters)
{
$ret = [];
foreach ($parameters as $parameter) {
$ret [] = "\${$parameter->getName()}";
}
return implode(', ', $ret);
}

protected function getGetterName($plugin)
{
return '____plugin_' . $plugin['clean_name'];
}

protected function _getPluginPropertyInfo($plugin)
{
return [
'name' => '____plugin_' . $plugin['clean_name'],
'visibility' => 'private',
'docblock' => [
'tags' => [['name' => 'var', 'description' => '\\' . $plugin['class']]],
]
];
}

protected function _getPluginGetterInfo($plugin)
{
$body = [];
$varName = "\$this->____plugin_" . $plugin['clean_name'];

$body[] = "if ($varName === null) {";
$body[] = "\t$varName = \$this->____om->get(\\" . "{$plugin['class']}::class);";
$body[] = "}";
$body[] = "return $varName;";

return [
'name' => $this->getGetterName($plugin),
'parameters' => [],
'body' => implode("\n", $body),
//'returnType' => $class,
'docblock' => [
'shortDescription' => 'plugin "' . $plugin['code'] . '"' . "\n" . '@return \\' . $plugin['class']
],
];
}

protected function _getCompiledMethodInfo(\ReflectionMethod $method, $config)
{
$parameters = $method->getParameters();
$returnsVoid = ($method->hasReturnType() && $method->getReturnType()->getName() == 'void');

$body = [
'switch($this->____scope->getCurrentScope()){'
];

$cases = [];
//group cases by config
foreach ($config as $scope => $conf) {
$key = md5(serialize($conf));
if (!isset($cases[$key])) $cases[$key] = ['cases'=>[], 'conf'=>$conf];
$cases[$key]['cases'][] = "\tcase '$scope':";
}
//call parent method for scopes with no plugins (or when no scope is set)
$cases[] = ['cases'=>["\tdefault:"], 'conf'=>[]];

foreach ($cases as $case) {
$body = array_merge($body, $case['cases']);
$this->addCodeSubBlock($body, $this->_getMethodSourceFromConfig($method, $case['conf'], $parameters, $returnsVoid), 2);
//$body[] = "\t\tbreak;";
}

$body[] = "}";

return [
'name' => ($method->returnsReference() ? '& ' : '') . $method->getName(),
'parameters' =>array_map(array($this, '_getMethodParameterInfo'), $parameters),
'body' => implode("\n", $body),
'returnType' => $method->getReturnType(),
'docblock' => ['shortDescription' => '{@inheritdoc}'],
];
}

protected function _getPluginInfo(CompiledPluginList $plugins, $code, $className, &$allPlugins, $next = null)
{
$className = $plugins->getPluginType($className, $code);
if (!isset($allPlugins[$code])) $allPlugins[$code] = [];
if (empty($allPlugins[$code][$className])) {
$suffix = count($allPlugins[$code]) ? count($allPlugins[$code]) + 1 : '';
$allPlugins[$code][$className] = [
'code' => $code,
'class' => $className,
'clean_name' => preg_replace("/[^A-Za-z0-9_]/", '_', $code . $suffix)
];
}
$ret = $allPlugins[$code][$className];
$ret['next'] = $next;
return $ret;

}

protected function _getPluginsChain(CompiledPluginList $plugins, $className, $method, &$allPlugins, $next = '__self')
{
$ret = $plugins->getNext($className, $method, $next);
if(!empty($ret[DefinitionInterface::LISTENER_BEFORE])) {
foreach ($ret[DefinitionInterface::LISTENER_BEFORE] as $k => $code) {
$ret[DefinitionInterface::LISTENER_BEFORE][$k] = $this->_getPluginInfo($plugins, $code, $className, $allPlugins);
}
}
if(!empty($ret[DefinitionInterface::LISTENER_AFTER])) {
foreach ($ret[DefinitionInterface::LISTENER_AFTER] as $k => $code) {
$ret[DefinitionInterface::LISTENER_AFTER][$k] = $this->_getPluginInfo($plugins, $code, $className, $allPlugins);
}
}
if (isset($ret[DefinitionInterface::LISTENER_AROUND])) {
$ret[DefinitionInterface::LISTENER_AROUND] = $this->_getPluginInfo($plugins, $ret[DefinitionInterface::LISTENER_AROUND], $className, $allPlugins,
$this->_getPluginsChain($plugins, $className, $method, $allPlugins, $ret[DefinitionInterface::LISTENER_AROUND]));
}
return $ret;
}

protected function _getPluginsConfig(\ReflectionMethod $method, &$allPlugins)
{
$className = ltrim($this->getSourceClassName(), '\\');

$ret = array();
foreach ($this->plugins as $scope => $pluginsList) {
$p = $this->_getPluginsChain($pluginsList, $className, $method->getName(), $allPlugins);
if ($p) {
$ret[$scope] = $p;
}

}
return $ret;
}

}
Loading