okapi / code-transformer
PHP代码转换器是一个PHP库,允许您修改和转换已加载的PHP类的源代码。
1.3.7
2024-09-07 17:14 UTC
Requires
- php: >=8.1
- microsoft/tolerant-php-parser: ^0.1.2
- okapi/filesystem: ^1.0
- okapi/path: ^1.0
- okapi/singleton: ^1.0
- okapi/wildcards: ^1.0
- php-di/php-di: ^7.0
- roave/better-reflection: ^6.8
Requires (Dev)
- phpunit/phpunit: ^10
- symfony/var-dumper: ^6.2
README
PHP代码转换器
PHP代码转换器是一个PHP库,允许您修改和转换已加载的PHP类的源代码。
安装
composer require okapi/code-transformer
使用
📖 内容列表
创建一个内核
<?php use Okapi\CodeTransformer\CodeTransformerKernel; // Extend from the "CodeTransformerKernel" class class Kernel extends CodeTransformerKernel { // Define a list of transformer classes protected array $transformers = [ StringTransformer::class, UnPrivateTransformer::class, ]; // Define the settings of the kernel from the "protected" properties // The directory where the transformed source code will be stored protected ?string $cacheDir = __DIR__ . '/var/cache'; // The cache file mode protected ?int $cacheFileMode = 0777; }
创建一个转换器
// String Transformer <?php use Okapi\CodeTransformer\Transformer; use Okapi\CodeTransformer\Transformer\Code; // Extend from the "Transformer" class class StringTransformer extends Transformer { // Define the target class(es) public function getTargetClass(): string|array { // You can specify a single class or an array of classes // You can also use wildcards, see https://github.com/okapi-web/php-wildcards return MyTargetClass::class; } // The "transform" method will be called when the target class is loaded // Here you can modify the source code of the target class(es) public function transform(Code $code): void { // I recommend using the Microsoft\PhpParser library to parse the source // code. It's already included in the dependencies of this package and // the "$code->getSourceFileNode()" property contains the parsed source code. // But you can also use any other library or manually parse the source // code with basic PHP string functions and "$code->getOriginalSource()" $sourceFileNode = $code->getSourceFileNode(); // Iterate over all nodes foreach ($sourceFileNode->getDescendantNodes() as $node) { // Find 'Hello World!' string if ($node instanceof StringLiteral && $node->getStringContentsText() === 'Hello World!' ) { // Replace it with 'Hello from Code Transformer!' // Edit method accepts a Token or Node class $code->edit( $node->children, "'Hello from Code Transformer!'", ); // You can also manually edit the source code $code->editAt( $node->getStartPosition() + 1, $node->getWidth() - 2, "Hello from Code Transformer!", ); // Append a new line of code $code->append('$iAmAppended = true;'); } } } }
// UnPrivate Transformer <?php namespace Okapi\CodeTransformer\Tests\Stubs\Transformer; use Microsoft\PhpParser\TokenKind; use Okapi\CodeTransformer\Transformer; use Okapi\CodeTransformer\Transformer\Code; // Replace all "private" keywords with "public" class UnPrivateTransformer extends Transformer { public function getTargetClass(): string|array { return MyTargetClass::class; } public function transform(Code $code): void { $sourceFileNode = $code->getSourceFileNode(); // Iterate over all tokens foreach ($sourceFileNode->getDescendantTokens() as $token) { // Find "private" keyword if ($token->kind === TokenKind::PrivateKeyword) { // Replace it with "public" $code->edit($token, 'public'); } } } }
目标类
<?php class MyTargetClass { private string $myPrivateProperty = "You can't get me!"; private function myPrivateMethod(): void { echo 'Hello World!'; } }
初始化内核
// Initialize the kernel early in the application lifecycle // Preferably after the autoloader is registered <?php use MyKernel; require_once __DIR__ . '/vendor/autoload.php'; // Initialize the Code Transformer Kernel $kernel = MyKernel::init();
目标类(已转换)
<?php class MyTargetClass { public string $myPrivateProperty = "You can't get me!"; public function myPrivateMethod(): void { echo 'Hello from Code Transformer!'; } } $iAmAppended = true;
结果
<?php // Just use your classes as usual $myTargetClass = new MyTargetClass(); $myTargetClass->myPrivateProperty; // You can't get me! $myTargetClass->myPrivateMethod(); // Hello from Code Transformer!
限制
- 通常xdebug会指向原始源代码,而不是转换后的代码。问题在于,如果您添加或删除一行代码,xdebug会指向错误的行,因此请尽量保持行数与原始源代码相同。
工作原理
-
CodeTransformerKernel注册了多个服务-
TransformerManager服务存储转换器和它们的配置列表 -
CacheStateManager服务管理缓存状态 -
StreamFilter服务注册了一个 PHP 流过滤器,允许在PHP加载之前修改源代码 -
AutoloadInterceptor服务覆盖了Composer自动加载器,该加载器处理类的加载
-
类加载时的通用工作流程
-
AutoloadInterceptor服务拦截类的加载 -
TransformerMatcher将类名与转换器目标类列表进行匹配 -
如果类匹配,查询缓存状态以查看转换后的源代码是否已缓存
-
检查缓存是否有效
- 缓存过程的修改时间小于源文件或转换器的修改时间
- 检查缓存文件、源文件和转换器是否存在
- 检查转换器的数量是否与缓存中的转换器数量相同
-
如果缓存有效,则从缓存中加载转换后的源代码
-
如果没有,则向
AutoloadInterceptor服务返回流过滤器路径
-
-
StreamFilter通过应用匹配的转换器来修改源代码- 如果修改后的源代码与原始源代码不同,则将转换后的源代码缓存在缓存中
- 如果没有,仍然将其缓存在缓存中,但不包含缓存的源文件路径,这样转换过程就不会再次执行
测试
- 运行
composer run-script test
或者 - 运行
composer run-script test-coverage
显示您的支持
如果这个项目对您有帮助,请给一个⭐!
🙏 感谢
- 特别感谢 lisachenko 在 Go! Aspect-Oriented Framework for PHP 上的开创性工作。本项目受到了他们创新方法的启发,并为该项目奠定了基础。
📝 许可证
版权所有 © 2023 Valentin Wotschel。
本项目采用 MIT 许可证。