O código está lá, o di.xml está no lugar, você limpou o cache — e o plugin não roda. Antes de sair espalhando die() pelo módulo, confere se o método que você quer interceptar é um dos que o Magento simplesmente não consegue interceptar.
O motivo é bem mecânico. O Magento não injeta nada dentro do seu método. Ele gera uma classe Interceptor em generated/code/ que estende a classe alvo e sobrescreve o método público. Se o PHP não deixa estender ou sobrescrever, acabou a conversa.
Os 8 casos em que o plugin não roda
Essa é a lista da documentação da Adobe, sem invenção minha:
- método
final; - método de classe declarada
final; - método não público (
protectedouprivate); - método estático;
__constructe__destruct;- virtual type;
- objeto instanciado antes de
Magento\Framework\Interceptionsubir; - classe que implementa
Magento\Framework\ObjectManager\NoninterceptableInterface.
Os quatro primeiros são PHP puro: não dá para sobrescrever aquilo, ponto. Os quatro últimos é que costumam queimar tempo.
Repare no último: toda classe \Proxy que o Magento gera implementa a NoninterceptableInterface. Então plugin no proxy nunca é aplicado. O plugin na classe real continua valendo — mas se você apontou o di.xml para o proxy, não vai acontecer nada.
E o penúltimo pega quem tenta interceptar o que roda no bootstrap: a leitura do app/etc/env.php, a montagem do próprio ObjectManager, tudo que o framework precisa ter de pé antes de existir interceptação. Ali plugin não é o caminho — se você precisa mudar esse comportamento, o caminho é preference ou configuração.
Conferir se o Magento enxergou o seu plugin
Passou pela lista e o método é interceptável? Então a pergunta vira outra: o Magento chegou a registrar o plugin. Quem responde é o dev:di:info:
php bin/magento dev:di:info 'Magento\Catalog\Model\Product'A saída traz Preference, os parâmetros do construtor, e duas tabelas: Plugins: e Plugins for the Preference:, com as colunas Plugin, Method e Type. Se o seu nome não aparece em nenhuma das duas, o problema é declaração — não é lógica.
Agora a pegadinha que mais me aparece em consultoria: sem o segundo argumento, o comando só olha a área global. Plugin declarado em etc/frontend/di.xml ou etc/adminhtml/di.xml não aparece ali. Peça a área:
php bin/magento dev:di:info 'Magento\Checkout\Model\Session' frontend
php bin/magento dev:di:info 'Magento\Sales\Model\Order' adminhtmlA segunda tabela também vale ouro: se você fez plugin numa interface e o alvo tem preference, é em Plugins for the Preference que ele vai aparecer.
Três erros de declaração que não geram aviso nenhum
Quando o plugin não aparece no dev:di:info, quase sempre é um destes:
Área errada. Plugin em etc/adminhtml/di.xml não roda no site. Se precisa valer nos dois lados, o arquivo é etc/di.xml.
Nome do método fora do camelCase. Para getName() os nomes válidos são beforeGetName, afterGetName e aroundGetName. Escreveu afterGetname? O Magento não reclama, só ignora.
Cache de configuração velho. Em produção, di.xml novo pede compilação:
php bin/magento setup:di:compile
php bin/magento cache:flushE tem um quarto que não é de registro, é de execução: o after precisa devolver alguma coisa. Esqueceu o return $result e o método passa a devolver null para a loja inteira — o erro aparece longe do seu módulo e você perde a tarde.
Perguntas rápidas
Posso trocar o método para public só para conseguir fazer o plugin?
No seu próprio módulo, sim. Em classe do core, não: você estaria editando vendor, e some no próximo composer update. Nesse caso o caminho é interceptar o método público que chama o privado, ou usar preference.
O plugin aparece no dev:di:info mas continua não rodando. E agora?
Registro está certo, então olhe a execução. Confira o nome do método em camelCase, se algum outro plugin com around está engolindo a chamada sem invocar o proceed, e se a classe realmente é instanciada pelo ObjectManager naquele fluxo.
Preciso rodar setup:di:compile toda vez que mexo num plugin?
Em produção, sim, porque as classes interceptor são geradas na compilação. Em modo developer o Magento gera sob demanda e um cache:clean config normalmente basta.
Pra conferir na fonte
- Plugins (interceptors) — Adobe Developer
- Command-line reference: Commerce on-premises — Adobe Experience League
- Dependency injection — Adobe Developer