Skip to content

okapi-web/php-code-transformer

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

PHP Code Transformer

License: MIT Twitter: @WalterWoshid PHP: >=8.1 Packagist Build

Coverage - PHP 8.1 Coverage - PHP 8.2

PHP Code Transformer is a PHP library that allows you to modify and transform the source code of a loaded PHP class.

Installation

composer require okapi/code-transformer

Usage

πŸ“– List of contents

Create a Kernel

<?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; }

Create a Transformer

// 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'); } } } }

Target Class

<?php class MyTargetClass { private string $myPrivateProperty = "You can't get me!"; private function myPrivateMethod(): void { echo 'Hello World!'; } }

Initialize the Kernel

// 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();

Target Class (transformed)

<?php class MyTargetClass { public string $myPrivateProperty = "You can't get me!"; public function myPrivateMethod(): void { echo 'Hello from Code Transformer!'; } } $iAmAppended = true;

Result

<?php // Just use your classes as usual $myTargetClass = new MyTargetClass(); $myTargetClass->myPrivateProperty; // You can't get me! $myTargetClass->myPrivateMethod(); // Hello from Code Transformer!

Limitations

  • Normally xdebug will point to the original source code, not the transformed one. The problem with this is if you add or remove a line of code, xdebug will point to the wrong line, so try to keep the number of lines the same as the original source code.

How it works

  • The CodeTransformerKernel registers multiple services

    • The TransformerManager service stores the list of transformers and their configuration

    • The CacheStateManager service manages the cache state

    • The StreamFilter service registers a PHP Stream Filter which allows to modify the source code before it is loaded by PHP

    • The AutoloadInterceptor service overloads the Composer autoloader, which handles the loading of classes

General workflow when a class is loaded

  • The AutoloadInterceptor service intercepts the loading of a class

  • The TransformerMatcher matches the class name with the list of transformer target classes

  • If the class is matched, query the cache state to see if the transformed source code is already cached

    • Check if the cache is valid:

      • Modification time of the caching process is less than the modification time of the source file or the transformers
      • Check if the cache file, the source file and the transformers exist
      • Check if the number of transformers is the same as the number of transformers in the cache
    • If the cache is valid, load the transformed source code from the cache

    • If not, return a stream filter path to the AutoloadInterceptor service

  • The StreamFilter modifies the source code by applying the matching transformers

    • If the modified source code is different from the original source code, cache the transformed source code
    • If not, cache it anyway, but without a cached source file path, so that the transformation process is not repeated

Testing

  • Run composer run-script test
    or
  • Run composer run-script test-coverage

Show your support

Give a ⭐ if this project helped you!

πŸ™ Thanks

πŸ“ License

Copyright Β© 2023 Valentin Wotschel.
This project is MIT licensed.

About

PHP Code Transformer is a PHP library that allows you to modify and transform the source code of a loaded PHP class.

Topics

Resources

License

Stars

Watchers

Forks

Contributors 2

  •  
  •  

Languages