diff --git a/README.md b/README.md index 05957e6..a0a0ac1 100644 --- a/README.md +++ b/README.md @@ -59,15 +59,20 @@ composer require okapi/code-transformer ## 📖 List of contents -- [Create a kernel](#create-a-kernel) -- [Create a transformer](#create-a-transformer) -- [Initialize the kernel](#initialize-the-kernel) +- [Create a Kernel](#create-a-kernel) +- [Create a Transformer](#create-a-transformer) +- [Target Class](#target-class) +- [Initialize the Kernel](#initialize-the-kernel) +- [Target Class (transformed)](#target-class-transformed) - [Result](#result) - [Limitations](#limitations) +- [How it works](#how-it-works) +- [Testing](#testing) +- [TODO](#todo) -## Create a kernel +## Create a Kernel ```php getStringContentsText() === 'Hello World!' ) { // Replace it with 'Hello from Code Transformer!' - // Edit method accepts a Token class + // Edit method accepts a Token or Node class $code->edit( $node->children, "'Hello from Code Transformer!'", @@ -184,59 +197,43 @@ class UnPrivateTransformer extends Transformer ``` -## Initialize the kernel +## Target Class ```php -// Initialize the kernel early in the application lifecycle - myPrivateProperty; // You can't get me! -$myTargetClass->myPrivateMethod(); // Hello from Code Transformer! -``` +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 @@ -263,36 +274,47 @@ $iAmAppended = true; # How it works -- The `Kernel` registers multiple services +- The `CodeTransformerKernel` registers multiple services - - The `TransformerContainer` service stores the list of transformers and their configuration + - 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](https://www.php.net/manual/wrappers.php.php#wrappers.php.filter) + - The `StreamFilter` service registers a + [PHP Stream Filter](https://www.php.net/manual/wrappers.php.php#wrappers.php.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 + - 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 - - It expects a class file path -- The `TransformerContainer` matches the class name with the list of transformer target classes +- 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 -- If the class is matched, we 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 + - 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, we load the transformed source code from the cache - - If not, we convert the class file path to a stream filter path + - Check if the number of transformers is the same as the number of + transformers in the cache -- The `StreamFilter` modifies the source code by applying the matching transformers - - If the modified source code is different from the original source code, we cache the transformed source code - - If not, we cache it anyway, but without a cached source file path, so that the transformation process is not repeated + - 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 @@ -310,6 +332,27 @@ Give a ⭐ if this project helped you! +## TODO + +- Add support for Production/Development environments: + - Production: Cache will not be checked for updates (better performance) + - Development: Cache will be checked for updates (better debugging experience) + +- Create a flowchart (WIP) + +- Cache lifetime + + + +## 🙏 Thanks + +- Big thanks to [lisachenko](https://github.com/lisachenko) for their pioneering + work on the [Go! Aspect-Oriented Framework for PHP](https://github.com/goaop/framework). + This project drew inspiration from their innovative approach and served as a + foundation for this project. + + + ## 📝 License Copyright © 2023 [Valentin Wotschel](https://github.com/WalterWoshid).
diff --git a/TODO.md b/TODO.md deleted file mode 100644 index ed313f3..0000000 --- a/TODO.md +++ /dev/null @@ -1,32 +0,0 @@ -# 1. Class Name matching -Optimize the class name matching by using efficient data structures or -algorithms (e.g. trie-based matching or regular expressions). - -# 2. Cache management -More sophisticated cache management strategy, such as Least Recently Used (LRU) -or Time-To-Live (TTL) based eviction, to ensure that cache remains up-to-date -and efficient. - -# 3. Parallelization -Parallelize the process of matching and transforming classes and methods to -reduce the overall processing time. - -# 4. Incremental updates -Incremental update mechanism to process only new or changed classes and methods. - -# 5. Monitoring -Monitor the performance of the library to identify bottlenecks and areas that -need optimization (e.g. Profilers or benchmarking suites). - -# 6. Documentation -- Document how to use xdebug with php-unit tests that use the - `#[RunTestsInSeparateProcesses]` attribute (PhpStorm) -- Create a flowchart - -# 7. Testing -- Add tests for the `order` property of the `Transformer` class - -# 8. Production/Development support -- Add support for production/development environments: - - Production: Cache will not be checked for updates (better performance). - - Development: Cache will be checked for updates (better debugging experience). diff --git a/composer.json b/composer.json index 62c5241..638b3c8 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "okapi/code-transformer", "description": "PHP Code Transformer is a PHP library that allows you to modify and transform the source code of a loaded PHP class.", - "version": "1.3.1", + "version": "1.3.2", "type": "library", "homepage": "https://github.com/okapi-web/php-code-transformer", "license": "MIT", diff --git a/src/CodeTransformerKernel.php b/src/CodeTransformerKernel.php index 0b0a364..54ae496 100644 --- a/src/CodeTransformerKernel.php +++ b/src/CodeTransformerKernel.php @@ -31,6 +31,7 @@ abstract class CodeTransformerKernel #[Inject] private Options $options; + /** @internal */ #[Inject] protected TransformerManager $transformerManager; @@ -44,7 +45,10 @@ abstract class CodeTransformerKernel private AutoloadInterceptor $autoloadInterceptor; /** - * Make the constructor public to allow the DI container to instantiate the class. + * Make the constructor public to allow the DI container to instantiate the + * class. + * + * @internal */ public function __construct() {} @@ -70,12 +74,23 @@ public function __construct() {} /** * Enable debug mode. This will disable the cache. - *
Default: false
+ *
Default: {@link false}
* * @var bool */ protected bool $debug = false; + /** + * Throw an exception if the kernel is initialized twice. + *
Default: {@link false}
+ * + * If {@link false}, any subsequent call to {@link init()} will be + * ignored. + * + * @var bool + */ + protected bool $throwExceptionOnDoubleInitialization = false; + // endregion /** @@ -111,7 +126,12 @@ public static function init(): void static::ensureNotKernelNamespace(); $instance = static::getInstance(); - $instance->ensureNotInitialized(); + + if ($instance->throwExceptionOnDoubleInitialization) { + $instance->ensureNotInitialized(); + } elseif ($instance->initialized) { + return; + } // Initialize the services $instance->preInit(); diff --git a/src/Core/Cache/CacheState/TransformedCacheState.php b/src/Core/Cache/CacheState/TransformedCacheState.php index da0dff0..ecc5483 100644 --- a/src/Core/Cache/CacheState/TransformedCacheState.php +++ b/src/Core/Cache/CacheState/TransformedCacheState.php @@ -54,7 +54,9 @@ public function isFresh(): bool } if (filemtime($transformerFilePath) > $this->modificationTime) { + // @codeCoverageIgnoreStart return false; + // @codeCoverageIgnoreEnd } } diff --git a/src/Core/DI.php b/src/Core/DI.php index 0b9f728..3c75b78 100644 --- a/src/Core/DI.php +++ b/src/Core/DI.php @@ -30,7 +30,7 @@ class DI implements ServiceInterface private ContainerBuilder $containerBuilder; /** - * Reg + * Register the dependency injection container. * * @return void * @@ -93,6 +93,8 @@ public static function get(string $class) * @param $value * * @return void + * + * @codeCoverageIgnore */ public static function set(string $class, $value): void { diff --git a/tests/Functional/AlreadyInitializedKernelTest.php b/tests/Functional/AlreadyInitializedKernelTest.php new file mode 100644 index 0000000..d59d468 --- /dev/null +++ b/tests/Functional/AlreadyInitializedKernelTest.php @@ -0,0 +1,34 @@ +assertTrue(true); + } + + public function testInitializeKernelTwiceWithExceptionOnDoubleInitializationOption(): void + { + Util::clearCache(); + + $this->expectException(AlreadyInitializedException::class); + + ExceptionOnDoubleInitializationKernel::init(); + ExceptionOnDoubleInitializationKernel::init(); + } +} diff --git a/tests/Stubs/Kernel/EmptyKernel.php b/tests/Stubs/Kernel/EmptyKernel.php new file mode 100644 index 0000000..d5ae497 --- /dev/null +++ b/tests/Stubs/Kernel/EmptyKernel.php @@ -0,0 +1,13 @@ +initCount++; + + if ($this->initCount > 1) { + throw new Exception('I should not be initialized twice!'); + } + + parent::preInit(); + } +}