-
Notifications
You must be signed in to change notification settings - Fork 9.4k
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
base: 2.5-develop
Are you sure you want to change the base?
compiled interceptors module #22826
Changes from 11 commits
2fb8f2a
e0057b6
53709eb
36f069e
7f499e1
d97dba0
cb0a213
d19f4f9
32e3cca
224dd4e
000d0a5
e57df09
f08394d
8071fb2
4948604
75e65ac
5fc398c
1999072
f30e9cf
82dc39f
41ca3b0
944144a
5227be0
d64c89a
16add49
1058be9
ecd9b54
6285273
5cb7dc9
5b85a71
d22a34e
5e62ebc
9c0aad8
bb6cc62
c334bcf
5665eec
c3019f3
dfd3c28
a7fc720
1657b46
07bfca9
7f268ce
8b3cae6
2bc739d
29a6d8e
dca21f7
219382f
f96d546
528408d
aa62a4d
eb1018b
1ef18eb
53e1e8d
75dea82
b422bb1
b40eabe
94a29c7
7fbcecd
53fa5c4
727cfc9
23bdd02
c263481
c082b69
5b57dbc
de6e95b
7bc9f69
d1b674e
f6dccdd
f6e7ac4
9d1f67c
3fb9422
76864e0
8f80b6e
c9f47ef
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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; | ||
fsw marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
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 | ||
) | ||
{ | ||
fsw marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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) { | ||
fsw marked this conversation as resolved.
Show resolved
Hide resolved
|
||
$this->plugins[$scope] = new CompiledPluginList(ObjectManager::getInstance(), $scope); | ||
fsw marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
} | ||
|
||
/** | ||
* @SuppressWarnings(PHPMD.UnusedFormalParameter) | ||
*/ | ||
public function setInterceptedMethods($interceptedMethods) | ||
{ | ||
//NOOP | ||
} | ||
|
||
protected function _generateCode() | ||
{ | ||
$typeName = $this->getSourceClassName(); | ||
$reflection = new \ReflectionClass($typeName); | ||
|
||
if ($reflection->isInterface()) { | ||
return false; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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(); | ||
fsw marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
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 = []) | ||
fsw marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
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; | ||
} | ||
fsw marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
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) | ||
fsw marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
$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);"]; | ||
} | ||
} | ||
} | ||
fsw marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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']; | ||
fsw marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
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) | ||
fsw marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
$parameters = $method->getParameters(); | ||
$returnsVoid = ($method->hasReturnType() && $method->getReturnType()->getName() == 'void'); | ||
|
||
$body = [ | ||
'switch($this->____scope->getCurrentScope()){' | ||
fsw marked this conversation as resolved.
Show resolved
Hide resolved
|
||
]; | ||
|
||
$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); | ||
fsw marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if ($p) { | ||
$ret[$scope] = $p; | ||
} | ||
|
||
} | ||
return $ret; | ||
} | ||
|
||
} |
Uh oh!
There was an error while loading. Please reload this page.