1.1.6: Plugin Sort Order
Sort order
The rules:
- Absent of sort orders, plugins are first sorted by module sort order: Magento modules alphabetically, then non-Magento alphabetically - mixed up by module
sequencedeclarations. beforeplugins proceed from smallest to greatest.afterplugins proceed from smallest to greatest.aroundplugins create a "subroutine" in which allbeforeandafterplugins that have a higher sort order are executed within this "subroutine".
The example:
- Before 1, sort 10
- Before 2, sort 16
- Before 3, sort 20
- After 1, sort 10
- After 2, sort 16
- After 3, sort 20
This would run in the exact order listed above.
Let's add an around plugin with sort 15:
- Before 1, sort 10
- Before 2, sort 16
- Before 3, sort 20
- Around 1, sort 15
- After 1, sort 10
- After 2, sort 16
- After 3, sort 20
Here is the order:
- Before 1, sort 10
- Around 1 before
$proceed, sort 15 - Before 2, sort 16
- Before 3, sort 20
- Method executed
- After 2, sort 16
- After 3, sort 20
- Around 1 after
$proceed, sort 15 - After 1, sort 10
Do you see how this is confusing, but also is tremendously important to understand? Adding an around plugin can seem entirely harmless, but it can introduce strange behaviors when sorting is critical.
That said, I don't recall ever running into this as a problem. It's a potential, though.
The gut punch
It's very well and fine to discuss sort orders, but we are leaving out one of the most important pieces of this.
If a plugin has no sort order specified, how does that fit into the mix?
// \Magento\Framework\ObjectManager\Config\Mapper\Dom::convert
$pluginData = [
'sortOrder' => $pluginSortOrderNode
? (int)$pluginSortOrderNode->nodeValue
: 0,
];
No sort order means a sort order of 0. Thus, our options are incredibly limited. Few plugins have a sortOrder node specified.
Jisse Reitsma pointed out that plugins can have negative sort orders. If you wish to get in front of a plugin that has no sort order, use a negative number.
Plugin usage review
The
beforepluginUse this anytime you need to modify the arguments going into a method. Return
nullif you aren't making any changes or if you are only changing objects. If you are changing scalar values, then you need to return these in an array of arguments to pass into the method.
afterUse this whenever you need to change the output of a method.
Remember that the method's parameters are passed to the end of the method signature:
public function save( \Magento\Customer\Api\CustomerRepositoryInterface $subject, \Magento\Customer\Api\Data\CustomerInterface $response, \Magento\Customer\Api\Data\CustomerInterface $customer, $passwordHash = null ) { return $response; }
In this example, we might want to do something with the $passwordHash.
around:
You should rarely have to use an around plugin. Here are two instances:
- When your update needs to span the input and the output of a method. For example, you need to make a determination before
the method, and do something with those values after the method. You could use a
beforeandafterset, but passing values between these is cumbersome. - You wish to wrap a method in a
try...catchstatement.
One of the benefits of around plugins is you can halt continued execution. It is incredibly rare that you would utilize this capability. Doing so can result in strange behavior.
A trick:
Don't forget about PHP functions! Here you can capture all applicable parameters and pass them back to the callable:
$proceed(...array_slice(func_get_args(), 2));
The unscientific review:
I surveyed the plugins in one of our projects that has over 90 custom modules (there's a lot happening here):
- 68% are
after - 18% are
before - 13% are
around(There are significant parts of this project that we did not write, and I'm guessing this number can be reduced.)