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();
+ }
+}