不需要任何 PECL 扩展的纯 PHP 中的 AOP(开始!) - 如何?
有一个框架叫做Go!面向方面的 PHP 框架
它是用纯 PHP 制作的,不需要任何 PECL 扩展和 DI 容器即可工作.
And it is made in plain PHP, doesn't require any PECL-extentions and DI-containers to work.
更重要的是可以与任何现有的 PHP 框架和库集成(有或没有额外的配置).
What's more can be integrated with any existing PHP frameworks and libraries (with or without additional configuration).
并且没有切入点的运行时检查,没有运行时注释解析,没有 eval 和 __call 方法,没有慢速代理和 call_user_func_array().快速引导过程(2-20 毫秒)和建议调用.
And there is no runtime checks of pointcuts, no runtime annotations parsing, no evals and __call methods, no slow proxies and call_user_func_array(). Fast bootstraping process (2-20ms) and advice invocation.
所以我印象非常深刻,但我想知道的是,它实际上是如何工作的?
So I am very impressed, but what I want to know, is how does that actually work?
我在这里列出的这些要点...
These points that I listed here...
我查看了 github 和官方网站,以及其他一些文章,但找不到任何关于它是如何工作的具体信息(一般和具体).
I looked on github and official website, and some other articles but couldn't find any concrete information about how does this work (in general and in specific).
我很想知道这是如何工作的?它是如何实现的?
I'm so eager to know how does this work? How it was implemented?
推荐答案
这个框架使用了很多隐藏的技巧来完成它的工作,但是如果我们从鸟瞰的角度来看,那么过程可以描述如下:
This framework is using many hidden tricks to perform its job, but if we look from the bird's view, then process can be described as following:
- 当前版本的 AOP 引擎旨在与 composer 紧密配合,因此它使用自己的代理包装了 composer loader.从那时起,AOP 就知道应该加载哪个类以及在哪里查找其源代码.
- 当某个类
Foo
从文件Foo.php
加载时,AOP 会将其包装到特殊的过滤器流中,如下所示:include 'php://filter/读取=go.source.transforming.loader/resource=Foo.php';
.您可以在 'php://stream' 手册上阅读有关此流过滤器的更多信息 - 此时,类并没有加载到 PHP 内存中,但框架已经知道它的内容,可以对源代码进行分析甚至修改.
- 然后将源代码标记化,通过 nikic/PHP-Parser 库解析成 AST,然后是静态的通过 goaop/parser-reflection
- Engine从方面检查所有注册的切入点并执行原始类
Foo
的转换:它被重命名为Foo__AopProxied
和类Foo extends Foo__AopProxied
在缓存中生成. - 引擎然后指示自动加载器从新文件而不是原始文件加载此类,因此您拥有原始类名,但具有来自建议的附加逻辑.它看起来像是在运行时自动生成装饰器.
- Current version of AOP engine is designed to work tightly with composer, so it wraps composer loader with own proxy. From that point of time, AOP knows about which class should be loaded and where to look for its source code.
- When some class
Foo
is loading from fileFoo.php
, AOP wraps it into special filter stream like this:include 'php://filter/read=go.source.transforming.loader/resource=Foo.php';
. You can read more about this stream filter at 'php://stream' manual - At that point of time, class is not loaded into PHP memory, but framework already knows about it's content and can perform analysis or even modification of source code.
- Source code then tokenized, parsed into AST via nikic/PHP-Parser library and then static reflection of this code is generated (still without loading this file into PHP's memory) via goaop/parser-reflection
- Engine checks all registered pointcuts from aspects and performs transformation of original class
Foo
: it's renamed toFoo__AopProxied
and new file with classFoo extends Foo__AopProxied
is generated in cache. - Engine then direct autoloader to load this class from that new file instead of original one, so you have your original class name, but with additional logic from advices. It's look like automatic decorator generation in runtime.
当然,这只是少量的信息,因为在纯 PHP 中实现 AOP 是一项非常艰巨的任务,在发现可行的解决方案之前我尝试了很多次,因此挖掘源代码以发现隐藏的宝石可能会很有趣:) 我的PhpSerbia谈PHP中的横切关注点中也有一些资料,大家可以观看它是为了更好地理解(对不起我的英语).
Of course, it's only small amount of information, because implementing of AOP in pure PHP was very hard task and I tried many times before discovering of working solution, so it can be interesting to dig into source code to discover hidden gems :) Some information is also available in my PhpSerbia talk about cross-cutting concerns in PHP, you can watch it for better understanding (sorry for my English).
此外,我们现在正在为框架编写文档,所以如果你想让它变得更好,只需向我们发送 PR 到 官方文档.
Also we are working on documentation for framework right now, so if you want to make it better, just send us a PR to the official documentation.
您还应该使用 PhpStorm 插件,它为在 PHP 项目中使用 AOP 的开发人员提供了许多功能.
You should also use PhpStorm plugin, which provides many features for developers that use AOP in PHP projects.
相关文章