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 all before and after 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 plugin

Use 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 and after 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.)
Complete and Continue