When developing software, programmers and architects use decomposition - the representation of objects and the relationships between them in the form of classes, objects, their properties and methods.

By decomposing, it is possible to obtain a more accurate representation of objects from real life in the form of program code. Thanks to this principle, object-oriented programming has gained such wide popularity in all programming languages. The model for representing real-life objects as instances of classes is very convenient: we can endow a class with a set of methods and properties, allowing us to interact with the environment.
But is everything so convenient?
The OOP model allows answering the following questions: what or who (object), what type (class), what is similar (inheritance), what is common (abstraction), what it can do (methods), what attributes it has (properties of an object or class) , what functionality implements (interfaces). Here is a sample list of questions to which we constantly give answers in the form of our code. The answers to these questions cover
almost all the properties and phenomena that occur with objects in real life.
')
Why practically everything?
In real life, there are a lot of nuances in the interaction of objects that are difficult to imagine in the OOP: the order of execution of interrelated actions for different objects, the temporary logic of phenomena, the need to perform additional actions when performing a particular action with an object. In life, this is described in the form of tips and recommendations: “wash your hands before eating”, “brush your teeth after eating”, “before leaving the house - turn off the light” and others. These actions are not easy to describe using methods: you need to use different decorators for the classes, or explicitly introduce the logic of interaction into the object method itself. In both cases, these rules cannot be conveniently formalized in the form of code using standard tools - and this leads to a complication of the system and to a closer connection of components.
How can this problem be solved?
The solution to this problem was invented long ago - to supplement the existing OOP model with some extension that will allow describing such interactions formally. The study was conducted by a team of engineers Xerox PARC, as a result of which they proposed a new paradigm - aspect-oriented programming. The essence of the idea is simple - let the program look at itself “from the side” using the reflection mechanism and, if necessary, change the final code. Having the ability to change the final code, AOP has unlimited access to creating hooks anywhere in the code and to extending this code with the help of tips.
In order to describe this behavior, the following terms have been proposed:
1. Advice - advice. This is the action to be performed. For approval of "washing hands before eating," advice will be "wash your hands." As you can see, the councils describe quite real actions from the real world, which means that they can be represented as methods, anonymous functions and closures. Each council knows exactly where it belongs, so almost all information about the action is available.
2. Joinpoint - the point of implementation. This term defines a specific place in the program code to which a tip can be added. In AspectJ, which is the primary source of AOP, a large number of implementation points are available: accessing a method, executing a method, initializing a class, creating a new object, accessing an object property. Go Library can work with calls of public and protected methods, both dynamic and static, including in the final classes and traits; interception of protected and public properties from the object is supported. For example, the introduction point for “washing hands before eating” is “the beginning of food”, or, in OOP terms, performing the “eat” method of the “person” class.
3. Pointcut - cut-in points. A slice specifies a set of embedding points at which to apply the tip. In the world, AOP is an analogue of SELECT from SQL. The syntax for specifying the cutoff points can be different, as is the implementation itself, but as a rule, these are filters that receive a set of embedding points at the input, from which only suitable ones should be selected. In the Go! Library Annotations are mainly used for defining slices, but in the future there will be support for xml, yaml and using code. The cut of points may look like this: all public methods in a class, all static methods with the name * init (), all methods with a specific annotation, etc.
In addition to the signature itself, the slice also defines a relative place for the introduction of the board: before, after, around the point of introduction. For our case of hand washing, the cut point of the points will determine the single point of implementation: “before performing the method man-> eat ()”. If we want to achieve perfect cleanliness, then we can determine the point of implementation “before performing the methods man -> * ()”, which will allow us to indicate our desire to always wash our hands before any action. However, an attentive reader may realize that the method “man-> wash Hands ()” also falls under this slice. What it threatens and how to avoid it - will remain for self-study, to whet interest.
4. Aspect - the basic unit within AOP, which allows you to put together the cuts of the points with the tips that you need to apply. Since our case relates to a healthy lifestyle, we can call it HealthyLiveAspect and bring in our section and some tips: wash your hands before eating and brush your teeth after eating. Let's make a list of what we have at the moment. We have a “person” class with understandable methods, there is no additional logic in them that would mix with the main method code. We have a separate aspect class with the necessary advice. It remains to do just a little - to combine them into one whole and get the ready code. This process is called interweaving.
5. Weaving - the process of interweaving the tips code into the source code. This mechanism parses the source code using reflection and applies tips at the point of implementation. Go Library uses the PHP-unique technology Load-Time Weaving, which allows you to track the moment of loading the class and change this code before it is parsed by the PHP parser. This makes it possible to dynamically change the class code, without changes in the source code by the developers. It all works as follows: at the beginning of the program, we initialize the core of AOP, adding our aspects there, then transfer control to the main program. When creating a human object, an automatic loading of the class will work, which will determine the desired file name and try to load it. At this point, the call will be intercepted by the kernel, then a static code analysis and verification of the current aspects will be performed. The kernel will find that there is a class “person” in the code and that you need to embed tips into this class, so code transformers in the kernel will change the original class name, create a proxy class with the original class name and redefine the “eat” method, and send a list of tips for given point. Next, the PHP parser parses this code and loads it into memory, while the decorator class will already be located by the name of the source class, so the usual call to the “eat” method will be wrapped in the connection point with the connected tips.
Better to see once than hear a hundred times.
Let's describe everything that we discussed with the code. We realize first of all a class of a rational person who will be able to eat, sleep, work, as well as wash hands and brush teeth. We will not do the __clone method yet :)
class Human { public function eat() { echo "Eating...", PHP_EOL; } public function cleanTeeth() { echo "Cleaning teeth...", PHP_EOL; } public function washUp() { echo "Washing up...", PHP_EOL; } public function work() { echo "Working...", PHP_EOL; } public function sleep() { echo "Go to sleep...", PHP_EOL; } }
At the moment, everything is written in Feng Shui: each method deals only with its work, there is nothing superfluous in the methods. But we do not have the logic of washing hands before eating, as well as brushing teeth after eating and before sleeping:
class Human { public function eat() { $this->washUp(); echo "Eating...", PHP_EOL; $this->cleanTeeth(); }
A typical programmer without any hesitation will do as above: he will invoke the necessary methods in the code of the “eat” and “sleep” methods, violating the principle of the sole responsibility of each of these methods. Fortunately, the conditions are simple, you can understand why it was done here. In real life, everything is much sadder: how often do you come across code that does different things in one place and there is no hint that this piece of code should be here? This is the most famous metric: WTF / line of code. I think everyone has such examples in the code).
The next category of programmers feels the trick is that they need to change the logic of the method and they go to the other extreme: they make new methods in the classroom that combine the logic of several methods. Are you familiar with the methods of “washing Hands And Eat ()”?
On the one hand, the main methods will no longer contain this logic, but our class will begin to swell, it will be possible to call the “eat ()” method directly, the class interface will be swamped with unnecessary methods and the number of errors in the code will start to grow. Again, not an option.
I have already considered other options in my article on
refactoring with AOP , so I’ll go straight to AOP.
Aspect-oriented approach
Probably, you already understood that AOP allows you to divide into logical blocks something that cannot be done using OOP. However, this technique is quite complex, so AOP is designed primarily for those who have comprehended all the subtleties of the object-oriented paradigm and want to discover something new for themselves.
The founder of aspect-oriented programming (AOP), Gregor Kikzales, in
an interview shared his vision of the essence of AOP: "... AOP is essentially the next stage in the development of structuring mechanisms. Today it is clear that objects do not replace procedures, but are only a way of creating a higher level structuring mechanism And aspects also do not replace objects; they only provide another kind of structuring. "
Let's try to describe our case from the perspective of an aspect-oriented paradigm. To do this, take the “clean” class of the “person” with our “clean” methods and describe the tips for the necessary methods using the aspect class:
namespace Aspect; use Go\Aop\Aspect; use Go\Aop\Intercept\MethodInvocation; use Go\Lang\Annotation\After; use Go\Lang\Annotation\Before; use Go\Lang\Annotation\Around; use Go\Lang\Annotation\Pointcut; class HealthyLiveAspect implements Aspect { protected function humanEat() {} protected function washUpBeforeEat(MethodInvocation $invocation) { $person = $invocation->getThis(); $person->washUp(); } protected function cleanTeethAfterEating(MethodInvocation $invocation) { $person = $invocation->getThis(); $person->cleanTeeth(); } protected function cleanTeethBeforeSleep(MethodInvocation $invocation) { $person = $invocation->getThis(); $person->cleanTeeth(); } }
I think this code is quite understandable in itself, but it is better to add some comments.
First, we defined a cut of points — the empty
humanEat()
method, marked with the special
@Pointcut("execution(public Human->eat(*))")
annotation
@Pointcut("execution(public Human->eat(*))")
. In principle, it was possible not to create a separate slice, but to indicate it before each council separately, but since we have several tips for this slice, we can take it to a separate definition. The method itself and its code are not used in the definition of the slice and serve only to specify and identify the slice in the aspect itself.
Secondly, we described the councils themselves in the form of aspect methods, specifying with the help of the
@Before
and
@After
specific place of implementation for the slice. You can immediately specify the sections in the annotation, as is done in the
cleanTeethBeforeSleep
method:
@Before("execution(public Human->sleep())")
. Each advice receives as input the necessary information about the execution point due to the
MethodInvocation
object containing the callee (class for the static method), the arguments of the method being called, as well as information about the point in the program (method reflection). Similar information can be obtained to refer to the properties of objects.
Now run our code:
include isset($_GET['original']) ? './autoload.php' : './autoload_aspect.php';
If we run this code in a browser, we can see the following output:
Want to eat something, let's have a breakfast! Washing up... Eating... Cleaning teeth... I should work to earn some money Working... It was a nice day, go to bed Cleaning teeth... Go to sleep...
For those interested in other files:
autoload.php ini_set('display_errors', true); spl_autoload_register(function($originalClassName) { $className = ltrim($originalClassName, '\\'); $fileName = ''; $namespace = ''; if ($lastNsPos = strripos($className, '\\')) { $namespace = substr($className, 0, $lastNsPos); $className = substr($className, $lastNsPos + 1); $fileName = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR; } $fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php'; $resolvedFileName = stream_resolve_include_path($fileName); if ($resolvedFileName) { require_once $resolvedFileName; } return (bool) $resolvedFileName; }); ob_start(function($content) { return str_replace(PHP_EOL, "<br>" . PHP_EOL, $content); });
autoload_aspect.php include '../src/Go/Core/AspectKernel.php'; include 'DemoAspectKernel.php';
DemoAspectKernel.php use Aspect\HealthyLiveAspect; use Go\Core\AspectKernel; use Go\Core\AspectContainer; class DemoAspectKernel extends AspectKernel { protected function getApplicationLoaderPath() { return __DIR__ . '/autoload.php'; } protected function configureAop(AspectContainer $container) { $container->registerAspect(new HealthyLiveAspect()); } }
What have we got? Firstly, the logic of the methods themselves in the class of "person" does not contain any garbage. Already excellent, because they will be easier to maintain. Secondly, there are no methods that duplicate the main functionality of the class methods. Thirdly, the logic is presented in the form of understandable tips in the aspect, because the tips have real meaning, which means that we have just made a decomposition in functionality and made our application more structured! Moreover, the aspect class itself describes an understandable goal — a healthy lifestyle. Now everything is in its place and we can easily change both the additional logic and work with the pure logic of the methods themselves.
I hope this simple example will help you better understand the concept of the aspect paradigm and try your hand at describing the aspect of the processes in the real world. For those interested - this example is available in the source code of the library in the folder demos / life.php, you can run it and study it)
References:
- Official site http://go.aopphp.com
- Source code https://github.com/lisachenko/go-aop-php