Preference/Rewrite: When a Preference Is Needed Instead of a Plugin
Preference/Rewrite: When a Preference Is Needed Instead of a Plugin
~7 Min. Lesezeit Zuletzt aktualisiert am August 9, 2026
CLAUDE.md, this project's binding coding rules, says it plainly: "use plugins (interceptors) instead of preferences." That's not a style preference, it's a deliberate default for good reason - and block 5 has honored it so far: two plugins (chapters 38/39), zero preferences. This chapter honestly explains where that default hits its limit, before chapter 41 implements this series' one concrete exception.
What is a preference, technically?
A preference is a simple di.xml entry: <preference for="Interface" type="Implementation"/>. Unlike a plugin, there's no code generation and no interceptor wrapping involved - while building the object graph, the DI container simply remembers: "when Interface is requested, hand back an instance of Implementation." It's a complete, one-time swap of the class itself, not a targeted, runtime method interception.
Why plugins are the default
The reason behind the CLAUDE.md rule is purely practical. Plugins are additive and non-destructive: any number of modules can share the same interception point (chapter 43 shows how sortOrder governs the order), the PHP compiler checks method signatures during setup:di:compile and reports incompatibilities immediately, and disabling one module never accidentally takes another module's entire logic down with it. A preference, by contrast, is an all-or-nothing swap: exactly one implementation wins per interface (chapter 42 explores what that means in a conflict).
The three real exceptions
- The target method is
finalor not publicly reachable - an interceptor would have to override it through inheritance, and PHP outright forbids that forfinalmethods with a fatal error, and forprivate/protectedmethods altogether (chapter 38). No plugin can reach here, no matter what the use case looks like. - Constructor behavior needs to change - different default values, a different injected dependency, a fundamentally different object setup. Plugins never intercept
__construct()at all, regardless offinal/private. - The entire implementation behind an interface needs to be swapped, not just one method adjusted - for example because a third-party module implements an interface in a way that's fundamentally incompatible with your own process, not just off in one spot.
Preview: chapter 41's concrete example
Chapter 41 uses the first case: a third-party extension already licensed in this project for ERP integration of the B2B special conditions (chapter 22) ships a calculation method the vendor deliberately declared final - and the requirement is still to combine its result with project-specific logic.
Tipp: This series' checking order in one sentence: first ask whether a matching event exists (observer, chapters 29-37), then check whether a method interception is enough (plugin, chapters 38-39), and only reach for a preference once both are technically off the table. That order isn't arbitrary - it rises exactly in step with how much the intervention can clash with future Magento and third-party updates.
Achtung: Before writing a preference: really make sure no plugin-capable hook exists. In practice, many developers reach for a preference because they know the pattern from older Magento 1 projects ("rewrites") - not because it's technically required. Every preference that could, in hindsight, have been solved as a plugin is unnecessary compatibility risk taken on for nothing (chapter 42).