PHP Aspect Oriented Programming

Original author: Simon Jaillet
  • Transfer
Hello!

We finished the first set of the course “Backend Developer in PHP” this month and employ them with might and main (well, as far as possible during the holiday season). The course was supplemented by another teacher - Evgeny Volosatov , whom many probably know. Well, we traditionally share interesting things.

Go.

In any application there are parts of the code that “intersect” several parts of the architecture simultaneously.

This problem is not so obvious when working with a full-featured framework. Most likely, your problem will be widespread, and there will be a chance that the framework has already resolved it by sacrificing the division of responsibility or by giving an abstraction over the framework. Many frameworks use event-oriented architecture to solve the problems of end-to-end functionality. But there always comes a point when the framework is not able to provide the desired level of control over a particular piece of logic. This is especially true when using microframes or developing with specialized libraries. The more your application takes into account the principles of shared responsibility, the greater the role of end-to-end functionality in your architecture.



PHP Aspect Oriented Programming


Aspect-oriented programming (AOP) is a programming paradigm focused on the organization and modularity of end-to-end functionality. Application Cases - ACL, logging, error handling, caching.

The built-in (internal) PHP assumptions (when you define a function / constant / class, it remains defined forever) make the AOP paradigm difficult to implement.

Li3 was the first to solve the problem of end-to-end functionality using a filtering mechanism that allows filtering the logic of a method through closures. To make the method filterable, implementing Li3 requires the manual addition of the template code. With such limitations, AOP techniques are limited to filtering methods.

Other well known PHP AOP implementations:


The PECL AOP extension is an interesting, but at the same time risky approach, since the support for the PECL extensions is not common. Another option is the Go! Libraries, representing the implementation of AOP, which fixes PHP code on the fly, which makes it possible to use AOP methods.

There are other implementations, but most of them are built on the proxy base (as far as I know), and this approach has many limitations.

New guy on the area


Automatic code generation has long been in PHP and is used in many libraries, such as ProxyManager. And thanks to the adoption of Composer, Go! shows that editing code on the fly is possible.

If code generation can still be considered simple, then fixing the code is somewhat more complicated. First, there is no built-in code parser in PHP, and secondly, there are very few libraries that solve PHP code parsing problems. The most famous is the PHP-Parser library.. PHP-Parser is a great tool, but even it ignores the formatting of spaces in generated abstract syntax trees. What makes it difficult to fix the code. Indeed, the code that needs to be fixed is the real executable code. Therefore, if you want the traceback to be accurate in case of errors, you need to take into account the line numbers in the corrected file.

For this task we use the Kahlan JIT code patcher . Kahlan is a new Unit & BDD test framework, thanks to JIT editing techniques that allow you to stub and monkey patch directly in Ruby or JavaScript. Under the hood, we find that this library is based on the rudimentary PHP parser. But, nevertheless, it is fast and stable enough for us to approach.

Filter library available atgithub.com/crysalead/filter and can be used as follows.

First, the JIT code patcher should be initialized as quickly as possible (for example, immediately after switching on the autoloade composer):

include__DIR__ . '/../vendor/autoload.php';
useLead\Filter\Filters;
Filters::patch(true);

Note that code editing is possible only for classes loaded by the Composer autoload. If a class is added using a require or include expression, it is already loaded before calling Filters :: patch (true) and therefore will not be fixed.

By default, all the corrected code will be stored at / tmp / jit, but you can always change it to your own:

Filters::patch(true, ['cachePath' => 'my/cache/path/jit']);

Cached files will be restored automatically every time you change the PHP file.

Attention! Filters::patch(true)- The easiest way to configure a patcher, keep in mind that all your code will be fixed. It can take a long time to wrap all the methods of your code base into a filter closure.

Fortunately, if the end-to-end functionality plays a crucial role in well-thought-out code, only a couple of methods are needed in the project. Therefore, the preferred approach would be to fix only those methods that will be filtered:

Filters::patch([
 'A\ClassName',
 'An\Example\ClassName::foo',
 'A\Second\Example\ClassName' => ['foo', 'bar'],
], [
    'cachePath' => 'my/cache/path/jit',
]);

Thus, you can choose to fix all the methods of a particular class, only one method or several of them.

API Filter


Now that the JIT patcher is enabled, let's create a logging filter:

useChaos\Filter\Filters;
useChaos\Database\Database;
useMonolog\Logger;
useMonolog\Handler\StreamHandler;
$logger = new Logger('database');
$logger->pushHandler(new StreamHandler('my/log/path/db.log', Logger::WARNING));
Filters::apply(Database::class, 'query', function($next, $sql, $data, $options)use($logger){
    $logger->info('Ran SQL query: ' . $sql);
    return $next($sql, $data, $options);
});

In the example above, a SQL query registration filter is created for the Chaos database library. More information about the API filter can be found at github.com/crysalead/filter .

Conclusion


AOP, in my opinion, is the real answer for end-to-end functionality. All other abstractions are equally superfluous and in most cases limited to certain frames. I do not lose hope that one day PHP will provide an embedded API for aspect-oriented programming. But now, I think, JIT code patching is the best option, where the advantages far outweigh the overhead CPU, which can be almost neglected when the JIT code patching is not applied globally.

THE END

As always we wait, if anything, questions, wishes and invite you to an open lesson , where you can listen to an interesting lecture and get to know the new teacher .

Also popular now: