Magento bringt mit dev:di:info bereits einen hilfreichen Befehl für Dependency Injection mit. Eine komplette Plugin Kette für eine bestimmte Methode lässt sich damit allerdings nicht anzeigen. Mit einem kleinen eigenen CLI Command kann man sich genau diese Information direkt aus Magentos Interception Konfiguration holen.
Der folgende Befehl ist also kein Magento Standardbefehl, sondern wird durch das nachfolgende Beispiel implementiert:
|
1 2 3 |
bin/magento dev:plugin-chain \ 'Magento\Catalog\Api\ProductRepositoryInterface' \ save |
Der Command wird zunächst in der etc/di.xml des eigenen Moduls registriert:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<?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="plugin_chain" xsi:type="object"> Vendor\Module\Console\Command\PluginChain </item> </argument> </arguments> </type> </config> |
Die eigentliche Command Klasse greift anschließend direkt auf Magentos PluginListInterface zu:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 |
<?php declare(strict_types=1); namespace Vendor\Module\Console\Command; use Magento\Framework\Config\ScopeInterface; use Magento\Framework\Interception\DefinitionInterface; use Magento\Framework\Interception\PluginListInterface; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; class PluginChain extends Command { private const ARG_TYPE = 'type'; private const ARG_METHOD = 'method'; private const OPT_AREA = 'area'; public function __construct( private readonly PluginListInterface $pluginList, private readonly ScopeInterface $configScope ) { parent::__construct(); } protected function configure(): void { $this->setName('dev:plugin-chain') ->setDescription('Shows the effective Magento plugin chain') ->addArgument( self::ARG_TYPE, InputArgument::REQUIRED, 'Class or interface' ) ->addArgument( self::ARG_METHOD, InputArgument::REQUIRED, 'Method name' ) ->addOption( self::OPT_AREA, null, InputOption::VALUE_REQUIRED, 'Magento area', 'frontend' ); parent::configure(); } protected function execute( InputInterface $input, OutputInterface $output ): int { $type = ltrim( (string) $input->getArgument(self::ARG_TYPE), '\\' ); $method = (string) $input->getArgument(self::ARG_METHOD); $area = (string) $input->getOption(self::OPT_AREA); $this->configScope->setCurrentScope($area); $pluginInfo = $this->pluginList->getNext( $type, $method ); if (!$pluginInfo) { $output->writeln('<comment>No plugins found.</comment>'); return Command::SUCCESS; } $output->writeln( sprintf( '<info>%s::%s() [%s]</info>', $type, $method, $area ) ); $this->renderChain( $output, $type, $method, $pluginInfo ); return Command::SUCCESS; } private function renderChain( OutputInterface $output, string $type, string $method, array $pluginInfo, int $level = 0 ): void { $indent = str_repeat(' ', $level); foreach ( $pluginInfo[DefinitionInterface::LISTENER_BEFORE] ?? [] as $code ) { $this->writePlugin( $output, $type, $code, 'BEFORE', $indent ); } $around = $pluginInfo[ DefinitionInterface::LISTENER_AROUND ] ?? null; if ($around) { $this->writePlugin( $output, $type, $around, 'AROUND', $indent ); $next = $this->pluginList->getNext( $type, $method, $around ); if ($next) { $this->renderChain( $output, $type, $method, $next, $level + 1 ); } } foreach ( $pluginInfo[DefinitionInterface::LISTENER_AFTER] ?? [] as $code ) { $this->writePlugin( $output, $type, $code, 'AFTER', $indent ); } } private function writePlugin( OutputInterface $output, string $type, string $code, string $position, string $indent ): void { $plugin = $this->pluginList->getPlugin( $type, $code ); $output->writeln( sprintf( '%s%-6s %s => %s', $indent, $position, $code, get_class($plugin) ) ); } } |
Nach Einbau des Commands lässt sich beispielsweise die Plugin Kette für ProductRepositoryInterface::save() ausgeben:
|
1 2 3 |
bin/magento dev:plugin-chain \ 'Magento\Catalog\Api\ProductRepositoryInterface' \ save |
Auch der Magento Admin Bereich kann berücksichtigt werden:
|
1 2 3 4 |
bin/magento dev:plugin-chain \ 'Magento\Catalog\Api\ProductRepositoryInterface' \ save \ --area=adminhtml |
Interessant wird die Ausgabe vor allem bei mehreren Extensions, die dieselbe Methode verändern. Besonders around Plugins können die Ausführung beeinflussen, da ein Plugin durch den Aufruf oder eben Nichtaufruf des nächsten Callables bestimmt, ob die weitere Plugin Kette und die eigentliche Methode noch erreicht werden.
Damit lässt sich ziemlich genau nachvollziehen, welche Plugins Magento für eine bestimmte Methode tatsächlich hintereinander ausführt.