A little about AOP

Aspect Oriented Programming (AOP) is a programming paradigm that tries to increase modularity of code. It does this by allowing the separation of “cross-cutting” concerns.

This means that the code is streamlined to do exactly what it needs to do, without having to worry about code that operates to undertake processing throughout the rest of the application.

An example

Let’s have a look at a quick example. This example focuses on the need to log within an application.

As you can see from the example, we have a large amount of code that is taking care of logging, and logging any exceptions, but the main code is relatively small. If we wanted to swap out our logging pattern, it could be quite a large undertaking, as you can imagine that this kind of code would be littered everywhere within our application.

Wouldn’t it be much nicer, if our code just did what it needed to do.

How does AOP work?

AOP tries to overcome this by adding additional behaviour to our existing code – an “advice” – without modifying the code itself. Instead, it specifies which code is modified via a “point cut” specification.

AOP allows developers to apply these “advice” to crosscutting concerns – logic applicable throughout the application, that affects the entire application – such as logging in our example above. Security and data transfer are also concerns which are needed in almost every module of an application.

A segway into SOLID

Let’s have a quick segway into SOLID principles. These are five core principles of object oriented programming and design. The concept behind these principles is that it will allow for greater maintainable code that can be extended over time.

The principles are:

  • Single responsibility
  • Open-closed
  • Liskov substitution
  • Interface segregation
  • Dependency inversion

What we’re looking at is the Single responsibility. We want the code to take care of it’s concerns/responsibilities only. Classes and methods should only have one responsibility, and do one thing.

AOP allows us to satisfy this requirement. Our core classes and methods do the task that we expect, and the cross cutting concerns are taken care of within code that interacts with our existing code.

Plugins in Magento2

Magento2’s implementation of AOP is done in the form of Plugins – you may also see these referred to as interceptors in Magento documentation.

Plugins allow a developer to “Listen” to any public method call made on an object manager controlled object and take programmatic action. It allows for the behaviour of the method to be modified and have code run before, after, or around the method call.

This means you could extend, or even substitute an existing method.

Additionally, you can change the return value of any method call made on an object manager controlled object, change the arguments of any method call made to an object manager controlled object, all whilst other plugins are doing the same thing to the same method in a predictable way.

Magento implements this using the interceptor pattern (why you might see these referred to as interceptors).

As mentioned in the wikipedia article referenced above, one of the key aspects of the interceptor pattern is that the rest of the system does not have to know something has been added or changed and can keep working as before.

Limitations to the implementation

Magento’s implementation means that you cannot use AOP with any of the following:

  • Objects that are instantiated before Magento\Framework\Interception is bootstrapped
  • Final methods
  • Final classes
  • Any class that contains at least one final public method
  • Non-public methods
  • Class methods (such as static methods)
  • __construct
  • Virtual types

Our AOP options

Before: Executes before a join point, but which does not have the ability to prevent execution flow proceeding to the join point (unless it throws an exception).

In Magento2, we use the method prefix before and simply camel case the method name.

After: Executed after a join point completes normally: for example, if a method returns without throwing an exception.

In Magento2, we use the method prefix after and simply camel case the method name.

Around: Surrounds a join point such as a method invocation.

Around can perform custom behaviour before and after the method invocation. It is also responsible for choosing whether to proceed to the join point or to shortcut the advised method execution by returning its own return value or throwing an exception.

In Magento2, we use the method prefix around and simply camel case the method name.

Defining Plugins in Magento2

Magento2 allows developers to create plugins using standard PHP classes, and reference them using XML within di.xml.

There are a series of Required options:

  • Type name: The name of a class or interface that needs to be followed.
  • Plugin name: The name for the new plugin in Magento 2. This is arbitrary and is simply used during mergin XML to give your plugin a unique reference.
  • Plugin type: The name of a plugin’s class or its virtual type. \Vendor\Module\Plugin\ModelName\Plugin.

There are additionally couple of optional options:

  • Plugin sortOrder: Set the order in which the plugin should be called.
  • Plugin disabled: That allows you enable or disable a plugin quickly. As the default configuration, the chosen value is false. This allows you to write a global di.xml and then override (and disable) a Plugin in an area specific di.xml such as frontend / adminhtml.

Some examples from core

Before

Here we have an example from \Magento\Theme\Model\Theme\Plugin\Registration.

Note the keyword before in front of the method we want to intercept dispatch.

It’s achieved with the following entry in di.xml specifically in the adminhtml scope.

After

This function specifically invalidates the indexer registry when an item is deleted.

It’s achieved with the following entry in di.xml.

Around

Here we have an example from \Magento\Theme\Model\Url\Plugin\Signature.

This functionality ensures that any call to getBaseUrl() returns a string that ends with a forward slash. Note the keyword around in front of the method we want to intercept getBaseUrl.

It’s achieved with the following entry in di.xml

Using around

You’ll notice in the method above that there is a call to $proceed, a PHP closure that allows the method to call the method it is intercepting. With great power, comes great responsibility. It is the around method’s responsibility to call the callable. If it doesn’t, it prevents the execution of all the plugins that follow it.

When you use around on a method that accepts arguments, your plugin method must also accept those arguments – and you must forward them when you invoke the proceed callable. You must match the original signature of the method, including default parameters and type hints – otherwise, your plugin may introduce unintended exceptions.

If you’re doing nothing with these arguments, you can use variadics and argument unpacking to achieve this.

This doesn’t seem to be used in CORE at the moment, but allows you to do the following:

Prioritisation

I’d suggest checking out the dev docs for more details on prioritisation, as they’re got some great examples: Prioritizing plugins.

Image Credit: Push buttons and extra gauges