A Plugin That Runs Twice and Slows Checkout
A developer builds a module Vendor_CheckoutTweaks that adds an after plugin on Magento\Quote\Model\QuoteManagement::submitOrder to log order metadata. In production, the log entry appears twice per order, and checkout latency rose by ~300 ms. The module's di.xml is in etc/, and a second di.xml exists in etc/frontend/. The developer also finds the same plugin declared in a different third-party module that the merchant installed. bin/magento module:status shows both modules enabled. There are no compilation errors, and setup:di:compile succeeds.
1. Why does the plugin fire twice, and what determines plugin execution order when multiple modules intercept the same method?
Read the worked answer
Two independent causes combine here:
- Duplicate declaration across modules. If both
Vendor_CheckoutTweaksand the third-party module declare anafterplugin on the same method, the interceptor chain contains both. Plugins are not deduplicated by class - they are collected per (type, method) from every enabled module'sdi.xml. So the log line appears once per plugin. - Area-specific
di.xmlmerging.etc/di.xml(global) andetc/frontend/di.xmlare both merged for the frontend area. If the same plugin is declared in both files, it can be registered twice for frontend requests. The global declaration plus the frontend declaration both apply in the frontend area.
Execution order among plugins is governed by the sortOrder attribute (default 0) and, when equal, by module load order from app/etc/config.php / module.xml sequence. sortOrder controls relative order, not uniqueness.
Diagnosis: inspect generated/code/.../Interceptor.php for the method and count the before/after/around calls, and check bin/magento dev:di:info "Magento\Quote\Model\QuoteManagement" to list registered plugins and their sort orders.
2. The developer wants a single, predictable log call with minimal overhead. What changes should they make, and what are the risks of an `around` plugin here?
Read the worked answer
Recommended changes:
- Remove the duplicate. Keep exactly one declaration. If the third-party module already logs, disable or remove the redundant plugin rather than layering both. If the developer's own module declares it in both
etc/di.xmlandetc/frontend/di.xml, keep only the area-appropriate one. - Use
afterif you only need the result, which is what logging metadata needs.afterplugins do not wrap the call and add minimal overhead compared toaround. - Set an explicit
sortOrderso behavior is deterministic regardless of module load order. - Guard the work: only log when a config flag is enabled, and avoid heavy serialization inside the plugin.
Risks of around: it must call $proceed() exactly once and return its result; failing to do so breaks order submission. It also wraps the entire submitOrder call, so any slow logging inside it directly adds to checkout latency (the ~300 ms symptom). around should be reserved for cases that genuinely need to short-circuit or modify arguments/return value, not for passive logging.