PluginList.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Framework\Interception\PluginList;
  7. use Magento\Framework\Config\CacheInterface;
  8. use Magento\Framework\Config\Data\Scoped;
  9. use Magento\Framework\Config\ReaderInterface;
  10. use Magento\Framework\Config\ScopeInterface;
  11. use Magento\Framework\Interception\DefinitionInterface;
  12. use Magento\Framework\Interception\PluginListInterface as InterceptionPluginList;
  13. use Magento\Framework\Interception\ObjectManager\ConfigInterface;
  14. use Magento\Framework\ObjectManager\RelationsInterface;
  15. use Magento\Framework\ObjectManager\DefinitionInterface as ClassDefinitions;
  16. use Magento\Framework\ObjectManagerInterface;
  17. use Magento\Framework\Serialize\SerializerInterface;
  18. use Magento\Framework\Serialize\Serializer\Serialize;
  19. /**
  20. * Plugin config, provides list of plugins for a type
  21. *
  22. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  23. */
  24. class PluginList extends Scoped implements InterceptionPluginList
  25. {
  26. /**
  27. * Inherited plugin data
  28. *
  29. * @var array
  30. */
  31. protected $_inherited = [];
  32. /**
  33. * Inherited plugin data, preprocessed for read
  34. *
  35. * @var array
  36. */
  37. protected $_processed;
  38. /**
  39. * Type config
  40. *
  41. * @var ConfigInterface
  42. */
  43. protected $_omConfig;
  44. /**
  45. * Class relations information provider
  46. *
  47. * @var RelationsInterface
  48. */
  49. protected $_relations;
  50. /**
  51. * List of interception methods per plugin
  52. *
  53. * @var DefinitionInterface
  54. */
  55. protected $_definitions;
  56. /**
  57. * List of interceptable application classes
  58. *
  59. * @var ClassDefinitions
  60. */
  61. protected $_classDefinitions;
  62. /**
  63. * @var \Magento\Framework\ObjectManagerInterface
  64. */
  65. protected $_objectManager;
  66. /**
  67. * @var array
  68. */
  69. protected $_pluginInstances = [];
  70. /**
  71. * @var \Psr\Log\LoggerInterface
  72. */
  73. private $logger;
  74. /**
  75. * @var SerializerInterface
  76. */
  77. private $serializer;
  78. /**
  79. * Constructor
  80. *
  81. * @param ReaderInterface $reader
  82. * @param ScopeInterface $configScope
  83. * @param CacheInterface $cache
  84. * @param RelationsInterface $relations
  85. * @param ConfigInterface $omConfig
  86. * @param DefinitionInterface $definitions
  87. * @param ObjectManagerInterface $objectManager
  88. * @param ClassDefinitions $classDefinitions
  89. * @param array $scopePriorityScheme
  90. * @param string|null $cacheId
  91. * @param SerializerInterface|null $serializer
  92. * @SuppressWarnings(PHPMD.ExcessiveParameterList)
  93. */
  94. public function __construct(
  95. ReaderInterface $reader,
  96. ScopeInterface $configScope,
  97. CacheInterface $cache,
  98. RelationsInterface $relations,
  99. ConfigInterface $omConfig,
  100. DefinitionInterface $definitions,
  101. ObjectManagerInterface $objectManager,
  102. ClassDefinitions $classDefinitions,
  103. array $scopePriorityScheme = ['global'],
  104. $cacheId = 'plugins',
  105. SerializerInterface $serializer = null
  106. ) {
  107. $this->serializer = $serializer ?: $objectManager->get(Serialize::class);
  108. parent::__construct($reader, $configScope, $cache, $cacheId, $this->serializer);
  109. $this->_omConfig = $omConfig;
  110. $this->_relations = $relations;
  111. $this->_definitions = $definitions;
  112. $this->_classDefinitions = $classDefinitions;
  113. $this->_scopePriorityScheme = $scopePriorityScheme;
  114. $this->_objectManager = $objectManager;
  115. }
  116. /**
  117. * Collect parent types configuration for requested type
  118. *
  119. * @param string $type
  120. * @return array
  121. * @throws \InvalidArgumentException
  122. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  123. * @SuppressWarnings(PHPMD.NPathComplexity)
  124. */
  125. protected function _inheritPlugins($type)
  126. {
  127. $type = ltrim($type, '\\');
  128. if (!array_key_exists($type, $this->_inherited)) {
  129. $realType = $this->_omConfig->getOriginalInstanceType($type);
  130. if ($realType !== $type) {
  131. $plugins = $this->_inheritPlugins($realType);
  132. } elseif ($this->_relations->has($type)) {
  133. $relations = $this->_relations->getParents($type);
  134. $plugins = [];
  135. foreach ($relations as $relation) {
  136. if ($relation) {
  137. $relationPlugins = $this->_inheritPlugins($relation);
  138. if ($relationPlugins) {
  139. $plugins = array_replace_recursive($plugins, $relationPlugins);
  140. }
  141. }
  142. }
  143. } else {
  144. $plugins = [];
  145. }
  146. if (isset($this->_data[$type])) {
  147. if (!$plugins) {
  148. $plugins = $this->_data[$type];
  149. } else {
  150. $plugins = array_replace_recursive($plugins, $this->_data[$type]);
  151. }
  152. }
  153. $this->_inherited[$type] = null;
  154. if (is_array($plugins) && count($plugins)) {
  155. $this->filterPlugins($plugins);
  156. uasort($plugins, [$this, '_sort']);
  157. $this->trimInstanceStartingBackslash($plugins);
  158. $this->_inherited[$type] = $plugins;
  159. $lastPerMethod = [];
  160. foreach ($plugins as $key => $plugin) {
  161. // skip disabled plugins
  162. if (isset($plugin['disabled']) && $plugin['disabled']) {
  163. unset($plugins[$key]);
  164. continue;
  165. }
  166. $pluginType = $this->_omConfig->getOriginalInstanceType($plugin['instance']);
  167. if (!class_exists($pluginType)) {
  168. throw new \InvalidArgumentException('Plugin class ' . $pluginType . ' doesn\'t exist');
  169. }
  170. foreach ($this->_definitions->getMethodList($pluginType) as $pluginMethod => $methodTypes) {
  171. $current = isset($lastPerMethod[$pluginMethod]) ? $lastPerMethod[$pluginMethod] : '__self';
  172. $currentKey = $type . '_' . $pluginMethod . '_' . $current;
  173. if ($methodTypes & DefinitionInterface::LISTENER_AROUND) {
  174. $this->_processed[$currentKey][DefinitionInterface::LISTENER_AROUND] = $key;
  175. $lastPerMethod[$pluginMethod] = $key;
  176. }
  177. if ($methodTypes & DefinitionInterface::LISTENER_BEFORE) {
  178. $this->_processed[$currentKey][DefinitionInterface::LISTENER_BEFORE][] = $key;
  179. }
  180. if ($methodTypes & DefinitionInterface::LISTENER_AFTER) {
  181. $this->_processed[$currentKey][DefinitionInterface::LISTENER_AFTER][] = $key;
  182. }
  183. }
  184. }
  185. }
  186. return $plugins;
  187. }
  188. return $this->_inherited[$type];
  189. }
  190. /**
  191. * Trims starting backslash from plugin instance name
  192. *
  193. * @param array $plugins
  194. * @return void
  195. */
  196. private function trimInstanceStartingBackslash(&$plugins)
  197. {
  198. foreach ($plugins as &$plugin) {
  199. $plugin['instance'] = ltrim($plugin['instance'], '\\');
  200. }
  201. }
  202. /**
  203. * Sort items
  204. *
  205. * @param array $itemA
  206. * @param array $itemB
  207. * @return int
  208. */
  209. protected function _sort($itemA, $itemB)
  210. {
  211. if (isset($itemA['sortOrder'])) {
  212. if (isset($itemB['sortOrder'])) {
  213. return $itemA['sortOrder'] - $itemB['sortOrder'];
  214. }
  215. return $itemA['sortOrder'];
  216. } elseif (isset($itemB['sortOrder'])) {
  217. return (0 - (int)$itemB['sortOrder']);
  218. } else {
  219. return 0;
  220. }
  221. }
  222. /**
  223. * Retrieve plugin Instance
  224. *
  225. * @param string $type
  226. * @param string $code
  227. * @return mixed
  228. */
  229. public function getPlugin($type, $code)
  230. {
  231. if (!isset($this->_pluginInstances[$type][$code])) {
  232. $this->_pluginInstances[$type][$code] = $this->_objectManager->get(
  233. $this->_inherited[$type][$code]['instance']
  234. );
  235. }
  236. return $this->_pluginInstances[$type][$code];
  237. }
  238. /**
  239. * Retrieve next plugins in chain
  240. *
  241. * @param string $type
  242. * @param string $method
  243. * @param string $code
  244. * @return array
  245. */
  246. public function getNext($type, $method, $code = '__self')
  247. {
  248. $this->_loadScopedData();
  249. if (!isset($this->_inherited[$type]) && !array_key_exists($type, $this->_inherited)) {
  250. $this->_inheritPlugins($type);
  251. }
  252. $key = $type . '_' . lcfirst($method) . '_' . $code;
  253. return $this->_processed[$key] ?? null;
  254. }
  255. /**
  256. * Load configuration for current scope
  257. *
  258. * @return void
  259. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  260. */
  261. protected function _loadScopedData()
  262. {
  263. $scope = $this->_configScope->getCurrentScope();
  264. if (false == isset($this->_loadedScopes[$scope])) {
  265. if (false == in_array($scope, $this->_scopePriorityScheme)) {
  266. $this->_scopePriorityScheme[] = $scope;
  267. }
  268. $cacheId = implode('|', $this->_scopePriorityScheme) . "|" . $this->_cacheId;
  269. $data = $this->_cache->load($cacheId);
  270. if ($data) {
  271. list($this->_data, $this->_inherited, $this->_processed) = $this->serializer->unserialize($data);
  272. foreach ($this->_scopePriorityScheme as $scopeCode) {
  273. $this->_loadedScopes[$scopeCode] = true;
  274. }
  275. } else {
  276. $virtualTypes = [];
  277. foreach ($this->_scopePriorityScheme as $scopeCode) {
  278. if (false == isset($this->_loadedScopes[$scopeCode])) {
  279. $data = $this->_reader->read($scopeCode) ?: [];
  280. unset($data['preferences']);
  281. if (count($data) > 0) {
  282. $this->_inherited = [];
  283. $this->_processed = [];
  284. $this->merge($data);
  285. foreach ($data as $class => $config) {
  286. if (isset($config['type'])) {
  287. $virtualTypes[] = $class;
  288. }
  289. }
  290. }
  291. $this->_loadedScopes[$scopeCode] = true;
  292. }
  293. if ($this->isCurrentScope($scopeCode)) {
  294. break;
  295. }
  296. }
  297. foreach ($virtualTypes as $class) {
  298. $this->_inheritPlugins($class);
  299. }
  300. foreach ($this->getClassDefinitions() as $class) {
  301. $this->_inheritPlugins($class);
  302. }
  303. $this->_cache->save(
  304. $this->serializer->serialize([$this->_data, $this->_inherited, $this->_processed]),
  305. $cacheId
  306. );
  307. }
  308. $this->_pluginInstances = [];
  309. }
  310. }
  311. /**
  312. * Whether scope code is current scope code
  313. *
  314. * @param string $scopeCode
  315. * @return bool
  316. */
  317. protected function isCurrentScope($scopeCode)
  318. {
  319. return $this->_configScope->getCurrentScope() == $scopeCode;
  320. }
  321. /**
  322. * Returns class definitions
  323. *
  324. * @return array
  325. */
  326. protected function getClassDefinitions()
  327. {
  328. return $this->_classDefinitions->getClasses();
  329. }
  330. /**
  331. * Merge configuration
  332. *
  333. * @param array $config
  334. * @return void
  335. */
  336. public function merge(array $config)
  337. {
  338. foreach ($config as $type => $typeConfig) {
  339. if (isset($typeConfig['plugins'])) {
  340. $type = ltrim($type, '\\');
  341. if (isset($this->_data[$type])) {
  342. $this->_data[$type] = array_replace_recursive($this->_data[$type], $typeConfig['plugins']);
  343. } else {
  344. $this->_data[$type] = $typeConfig['plugins'];
  345. }
  346. }
  347. }
  348. }
  349. /**
  350. * Remove from list not existing plugins
  351. *
  352. * @param array $plugins
  353. * @return void
  354. */
  355. private function filterPlugins(array &$plugins)
  356. {
  357. foreach ($plugins as $name => $plugin) {
  358. if (empty($plugin['instance'])) {
  359. unset($plugins[$name]);
  360. $this->getLogger()->info("Reference to undeclared plugin with name '{$name}'.");
  361. }
  362. }
  363. }
  364. /**
  365. * Get logger
  366. *
  367. * @return \Psr\Log\LoggerInterface
  368. * @deprecated 101.0.0
  369. */
  370. private function getLogger()
  371. {
  372. if ($this->logger === null) {
  373. $this->logger = $this->_objectManager->get(\Psr\Log\LoggerInterface::class);
  374. }
  375. return $this->logger;
  376. }
  377. }