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
sequence
declarations. before
plugins proceed from smallest to greatest.after
plugins proceed from smallest to greatest.around
plugins create a "subroutine" in which allbefore
andafter
plugins 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
before
pluginUse this anytime you need to modify the arguments going into a method. Return
null
if 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.
after
Use 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
before
andafter
set, but passing values between these is cumbersome. - You wish to wrap a method in a
try...catch
statement.
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.)