Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

Preference vs. Plugin: Weighing Compatibility and Upgrade Risk

Preference vs. Plugin: Weighing Compatibility and Upgrade Risk

~7 Min. Lesezeit Zuletzt aktualisiert am August 9, 2026

Chapter 41 wrote a real, clean preference - and still paid a certain price for it. This chapter makes that price explicit: two concrete kinds of risk a preference takes on that a plugin doesn't, and how to keep both in check in practice.

Risk 1: multiple preferences on the same target

If any third module also registers a preference for ErpSync\CorporateRewards\Api\MultiplierResolverInterface, module load order (sequence in module.xml, then alphabetical) alone decides which of the two preferences actually ends up instantiated. There's no error message, no compile-time warning, no log entry - the code just keeps running, with the wrong implementation. That stands in sharp contrast to plugins: several modules can share the same interception point, and Magento runs all registered plugins in sortOrder sequence (chapter 43), instead of letting only one of them win.

In practice, the most reliable way to spot such a conflict is to search the whole project for every <preference> entry targeting a given interface:

grep -rl 'for="ErpSync\\\\CorporateRewards\\\\Api\\\\MultiplierResolverInterface"' app/code vendor

Risk 2: coupling to the entire class surface

A plugin couples exclusively to one method signature - if the vendor changes it incompatibly in an update, setup:di:compile reports the error immediately and loudly. A preference, on the other hand, couples to the entire class: if a future extension update adds a new method to MultiplierResolverInterface, CompanyMultiplierPreference (chapter 41) has to implement it too, or the preference breaks completely with a "class must implement interface" error - not one broken call, but the whole service becomes uninstantiable.

Even trickier: if the vendor only changes resolveForCompany()'s internal behavior (not its signature), nothing sounds an alarm at all - CompanyMultiplierPreference keeps using the old logic wired in through composition, as long as MultiplierResolver itself stays compatible. That's harmless in this specific case (composition shields exactly against this), but would be a silent drift bug for a preference that fully reimplements the original code instead of composing it.

Practical trade-offs

  • Wherever possible, preference against an interface, not a concrete class - narrows coupling to the contractually guaranteed part.
  • Composition over reimplementation (chapter 41): the more original logic gets reused instead of copied, the smaller the surface that can drift apart on an update.
  • Comment every preference clearly in the code as a deliberate exception (which structural obstacle, why no plugin worked) - the next developer shouldn't mistake it for a template for the next, actually plugin-capable case.
  • After every third-party module update, specifically check whether the preferenced interface signature is still identical - setup:di:compile catches missing methods, but not pure behavior changes.

Tipp: CLAUDE.md requires "plugins instead of preferences" not out of dogmatism, but because these two exact risks structurally don't exist for plugins. Block 5 uses the preference exception exactly once, in chapter 41, exactly justified, exactly documented - that's the standard to hold, not the frequency.

Achtung: Using a preference and a plugin on the same interface at the same time works technically, but easily causes confusion: the preference decides, while the object graph is being built, which class gets instantiated at all - only after that does the plugin interceptor wrap around the result of that decision. A plugin on MultiplierResolverInterface would therefore automatically act on CompanyMultiplierPreference, not on the original MultiplierResolver class anymore.

With a preference's risks now named explicitly, chapter 43 turns back to plugins - specifically, how several plugins on the same target from different modules cleanly play together.