Jednym z najczęstszych pytań przy pracy z Magento 2 jest: czy nadpisać klasę przez preference, czy użyć plugin? Odpowiedź jest prawie zawsze ta sama – używaj pluginu. Ale „prawie” robi tu dużą robotę. Wyjaśniam różnice i pokazuję, kiedy preference ma sens.
Czym jest Plugin (Interceptor)?
Plugin to mechanizm AOP (Aspect-Oriented Programming) w Magento 2. Pozwala Ci „owinąć” istniejącą metodę publiczną bez jej nadpisywania. Masz do dyspozycji trzy typy:
- before – wykonuje się przed metodą, może modyfikować argumenty
- after – wykonuje się po metodzie, może modyfikować wynik
- around – przejmuje pełną kontrolę, wywołuje (lub nie) oryginalną metodę
<?php
// Przykład pluginu "after" – modyfikacja tytułu produktu
namespace Vendor\Module\Plugin;
use Magento\Catalog\Model\Product;
class ProductNamePlugin
{
public function afterGetName(Product $subject, string $result): string
{
// Dodajemy sufiks do nazwy każdego produktu
return $result . ' – Promocja!';
}
}
Rejestracja w di.xml:
<?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\Catalog\Model\Product">
<plugin name="vendor_module_product_name"
type="Vendor\Module\Plugin\ProductNamePlugin"
sortOrder="10"
disabled="false"/>
</type>
</config>
Czym jest Preference?
Preference to dosłowne zastąpienie klasy A klasą B w kontenerze DI. Magento zawsze będzie tworzyć Twoją klasę zamiast oryginalnej.
<preference for="Magento\Catalog\Model\Product"
type="Vendor\Module\Model\Product"/>
<?php
namespace Vendor\Module\Model;
class Product extends \Magento\Catalog\Model\Product
{
public function getName(): string
{
return parent::getName() . ' – Promocja!';
}
}
Dlaczego Plugin jest prawie zawsze lepszy?
Kluczowy problem z preference: może być tylko jedno preference dla danej klasy. Jeśli Ty ustawisz preference dla Magento\Catalog\Model\Product, a vendor zewnętrznej wtyczki zrobi to samo – jeden z nich „wygra”, a drugi zostanie cicho zignorowany. To klasyczne źródło konfliktów w projektach z wieloma modułami.
Pluginy natomiast układają się w stos – parametr sortOrder decyduje o kolejności, ale wszystkie działają. Możesz mieć 5 pluginów od różnych modułów modyfikujących tę samą metodę i wszystkie będą działać razem.
Kiedy Preference ma sens?
Preference jest uzasadnione w kilku scenariuszach:
- Musisz nadpisać metodę prywatną lub chronioną – pluginy działają tylko na metodach publicznych.
- Musisz zmienić konstruktor – pluginy nie mogą modyfikować
__construct(). - Implementujesz interfejs inaczej – np. chcesz podmienić całą strategię działania klasy, nie tylko jeden aspekt.
- Klasa jest oznaczona jako
@api– w tym przypadku jej publiczny kontrakt jest stabilny, a preference jest relatywnie bezpieczne.
Uwaga na plugin „around”
Plugin around daje największą kontrolę, ale też największe ryzyko. Jeśli zapomnisz wywołać $proceed(), oryginalny kod nigdy się nie wykona – i inne pluginy w kolejce też nie. Używaj around tylko wtedy, gdy naprawdę potrzebujesz warunkowego pominięcia oryginalnej metody:
<?php
namespace Vendor\Module\Plugin;
class ConditionalPlugin
{
public function aroundExecute(
\Magento\Catalog\Model\SomeModel $subject,
callable $proceed,
...$args
) {
if ($this->shouldSkip()) {
// Uwaga: inne pluginy "around" z wyższym sortOrder też nie zostaną wywołane!
return null;
}
// Zawsze wywołuj $proceed() jeśli nie masz powodu, żeby tego nie robić
return $proceed(...$args);
}
private function shouldSkip(): bool
{
return false; // twoja logika
}
}
Podsumowanie – krótka reguła
Zanim sięgniesz po preference, zadaj sobie pytanie: czy plugin nie wystarczy? W 90% przypadków wystarczy. Preference zostawia po sobie ślad trudny do zdebugowania przy aktualizacjach Magento – klasa-potomek może przestać kompilować się po upgrade, gdy Magento zmieni sygnaturę konstruktora. Plugin jest bardziej odporny na te zmiany.
