Adobe Commerce Developer Professional study materials
Adobe Commerce Developer Professional study resources

A Plugin That Runs Twice and Slows Checkout

Adobe Commerce Developer Professional · Put your knowledge to work in context.

All case studies

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:

  1. Duplicate declaration across modules. If both Vendor_CheckoutTweaks and the third-party module declare an after plugin 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's di.xml. So the log line appears once per plugin.
  2. Area-specific di.xml merging. etc/di.xml (global) and etc/frontend/di.xml are 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.xml and etc/frontend/di.xml, keep only the area-appropriate one.
  • Use after if you only need the result, which is what logging metadata needs. after plugins do not wrap the call and add minimal overhead compared to around.
  • Set an explicit sortOrder so 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.