From ce48a00d6d2ac8a8050c1e77b8d6a9cfedcd3b78 Mon Sep 17 00:00:00 2001 From: WalterWoshid Date: Mon, 10 Apr 2023 16:47:53 +0200 Subject: [PATCH 01/18] Refactored code --- .editorconfig | 1 + README.md | 6 +- TODO.md | 24 +++ composer.json | 4 +- src/CodeTransformerKernel.php | 150 +++++++++---- .../InvalidTransformerClassException.php | 4 +- src/Exception/Transformer/SyntaxError.php | 2 +- .../TransformerNotFoundException.php | 4 +- src/Service/AutoloadInterceptor.php | 25 ++- src/Service/Cache/CachePaths.php | 46 ++-- src/Service/Cache/CacheState.php | 112 ++++++++-- src/Service/CacheStateManager.php | 82 ++++---- src/Service/ClassLoader/ClassContainer.php | 38 ++++ .../ClassLoader.php | 90 ++++++-- src/Service/DI.php | 127 +++++++++++ src/Service/Matcher/TransformerMatcher.php | 78 +++++++ src/Service/Options.php | 79 +++++-- .../Processor/TransformerProcessor.php | 121 +++++++++++ src/Service/ServiceInterface.php | 2 +- src/Service/StreamFilter.php | 23 +- src/Service/StreamFilter/FilterInjector.php | 4 +- src/Service/StreamFilter/Metadata.php | 19 +- src/Service/StreamFilter/Metadata/Code.php | 106 +++++++--- src/Service/TransformerContainer.php | 198 ++---------------- src/Transformer.php | 2 +- src/Util/CodeChecker.php | 40 ++++ tests/ClassLoaderMockTrait.php | 6 +- tests/Functional/DirectKernelTest.php | 5 +- .../InvalidTransformerClassTest.php | 4 +- tests/Functional/Workflow/ApplicationTest.php | 5 +- .../Workflow/CachedApplicationTest.php | 2 +- tests/Stubs/Kernel/ApplicationKernel.php | 3 + tests/Stubs/Kernel/CachedKernel.php | 8 +- .../Kernel/TransformerDoesNotExistKernel.php | 3 + ...nsformerDoesNotExtendTransformerKernel.php | 3 + tests/Stubs/Transformer/AddedTransformer1.php | 2 +- tests/Stubs/Transformer/AddedTransformer2.php | 2 +- .../Stubs/Transformer/ChangedTransformer.php | 2 +- tests/Stubs/Transformer/StringTransformer.php | 2 +- .../Transformer/UnPrivateTransformer.php | 2 +- 40 files changed, 1004 insertions(+), 432 deletions(-) create mode 100644 TODO.md create mode 100644 src/Service/ClassLoader/ClassContainer.php rename src/Service/{AutoloadInterceptor => ClassLoader}/ClassLoader.php (51%) create mode 100644 src/Service/DI.php create mode 100644 src/Service/Matcher/TransformerMatcher.php create mode 100644 src/Service/Processor/TransformerProcessor.php create mode 100644 src/Util/CodeChecker.php diff --git a/.editorconfig b/.editorconfig index 4e49f94..650290f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,6 +10,7 @@ ij_php_align_key_value_pairs = true ij_php_align_multiline_parameters_in_calls = true ij_php_align_phpdoc_comments = true ij_php_align_phpdoc_param_names = true +ij_php_blank_lines_before_package = 0 ij_php_comma_after_last_argument = true ij_php_comma_after_last_array_element = true ij_php_comma_after_last_closure_use_var = true diff --git a/README.md b/README.md index 7f8cbd6..846e220 100644 --- a/README.md +++ b/README.md @@ -112,12 +112,12 @@ class StringTransformer extends Transformer { // 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->sourceFileNode" property contains the parsed source code. + // 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->sourceFileNode; + $sourceFileNode = $code->getSourceFileNode(); // Iterate over all nodes foreach ($sourceFileNode->getDescendantNodes() as $node) { @@ -168,7 +168,7 @@ class UnPrivateTransformer extends Transformer public function transform(Code $code): void { - $sourceFileNode = $code->sourceFileNode; + $sourceFileNode = $code->getSourceFileNode(); // Iterate over all tokens foreach ($sourceFileNode->getDescendantTokens() as $token) { diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..4574dfd --- /dev/null +++ b/TODO.md @@ -0,0 +1,24 @@ +# 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 diff --git a/composer.json b/composer.json index 41dd890..cb98687 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,9 @@ "okapi/filesystem": "^1.0", "okapi/path": "^1.0", "okapi/singleton": "^1.0", - "okapi/wildcards": "^1.0" + "okapi/wildcards": "^1.0", + "php-di/php-di": "^7.0", + "roave/better-reflection": "^6.8" }, "require-dev": { "phpunit/phpunit": "dev-main", diff --git a/src/CodeTransformerKernel.php b/src/CodeTransformerKernel.php index 1ccb5d2..4a10bad 100644 --- a/src/CodeTransformerKernel.php +++ b/src/CodeTransformerKernel.php @@ -2,9 +2,11 @@ namespace Okapi\CodeTransformer; +use DI\Attribute\Inject; use Okapi\CodeTransformer\Exception\Kernel\DirectKernelInitializationException; use Okapi\CodeTransformer\Service\AutoloadInterceptor; use Okapi\CodeTransformer\Service\CacheStateManager; +use Okapi\CodeTransformer\Service\DI; use Okapi\CodeTransformer\Service\Options; use Okapi\CodeTransformer\Service\StreamFilter; use Okapi\CodeTransformer\Service\TransformerContainer; @@ -24,6 +26,34 @@ abstract class CodeTransformerKernel { use Singleton; + // region Settings + + /** + * The cache directory. + *
Default: ROOT_DIR/cache/code-transformer
+ * + * @var string|null + */ + protected ?string $cacheDir = null; + + /** + * The cache file mode. + *
Default: 0777 & ~{@link umask()}
+ * + * @var int|null + */ + protected ?int $cacheFileMode = null; + + /** + * Enable debug mode. This will disable the cache. + *
Default: false
+ * + * @var bool + */ + protected bool $debug = false; + + // endregion + /** * List of transformers to be applied. * @@ -31,48 +61,92 @@ abstract class CodeTransformerKernel */ protected array $transformers = []; + // region DI + + #[Inject] + private Options $options; + + #[Inject] + protected TransformerContainer $transformerContainer; + + #[Inject] + private CacheStateManager $cacheStateManager; + + #[Inject] + private StreamFilter $streamFilter; + + #[Inject] + private AutoloadInterceptor $autoloadInterceptor; + /** - * Initialize the kernel. + * Make the constructor public to allow the DI container to instantiate the class. + */ + public function __construct() {} + + // endregion + + /** + * Resolve instance with dependency injection. * - * @param string|null $cacheDir The cache directory. - *
Default: ROOT_DIR/cache/code-transformer
- * @param int|null $cacheFileMode The cache file mode. - *
Default: 0777 & ~{@link umask()}
- * @param bool|null $debug Enable debug mode. This will disable the cache. - *
Default: false
+ * @inheritDoc + */ + public static function getInstance(): static + { + if (!isset(static::$instance)) { + static::registerDependencyInjection(); + + static::$instance = DI::get(static::class); + } + + return static::$instance; + } + + /** + * Initialize the kernel. * * @return void */ - public static function init( - ?string $cacheDir, - ?int $cacheFileMode = null, - bool $debug = false, - ): void { - self::ensureNotKernelNamespace(); - - $instance = self::getInstance(); - $instance->ensureNotInitialized(); + public static function init(): void + { + static::ensureNotKernelNamespace(); - // Only initialize the kernel if there are transformers - if ($instance->transformers) { - // Pre-initialize the services + $instance = static::getInstance(); + $instance->ensureNotInitialized(); - // Set options - Options::setOptions( - cacheDir: $cacheDir, - cacheFileMode: $cacheFileMode, - debug: $debug, - ); + // Initialize the services + $instance->preInit(); + $instance->registerServices(); + $instance->registerAutoloadInterceptor(); - // Add the transformers - TransformerContainer::addTransformers($instance->transformers); + $instance->setInitialized(); + } - // Register the services - $instance->registerServices(); - $instance->registerAutoloadInterceptor(); - } + /** + * Register the dependency injection. + * + * @return void + */ + protected static function registerDependencyInjection(): void + { + DI::getInstance()->register(); + } - $instance->setInitialized(); + /** + * Pre-initialize the services. + * + * @return void + */ + protected function preInit(): void + { + // Set options + $this->options->setOptions( + cacheDir: $this->cacheDir, + cacheFileMode: $this->cacheFileMode, + debug: $this->debug, + ); + + // Add the transformers + $this->transformerContainer->addTransformers($this->transformers); } /** @@ -83,16 +157,16 @@ public static function init( protected function registerServices(): void { // Options provider - Options::register(); + $this->options->register(); // Manage the user-defined transformers - TransformerContainer::register(); + $this->transformerContainer->register(); // Cache path manager - CacheStateManager::register(); + $this->cacheStateManager->register(); // Stream filter -> Source transformer - StreamFilter::register(); + $this->streamFilter->register(); } /** @@ -103,7 +177,7 @@ protected function registerServices(): void protected function registerAutoloadInterceptor(): void { // Overload the composer class loaders - AutoloadInterceptor::register(); + $this->autoloadInterceptor->register(); } /** @@ -111,7 +185,7 @@ protected function registerAutoloadInterceptor(): void * * @return void */ - private static function ensureNotKernelNamespace(): void + protected static function ensureNotKernelNamespace(): void { // Get current namespace and class name $namespace = get_called_class(); diff --git a/src/Exception/Transformer/InvalidTransformerClassException.php b/src/Exception/Transformer/InvalidTransformerClassException.php index 053edef..e02e2fa 100644 --- a/src/Exception/Transformer/InvalidTransformerClassException.php +++ b/src/Exception/Transformer/InvalidTransformerClassException.php @@ -13,13 +13,13 @@ class InvalidTransformerClassException extends TransformerException { /** - * Create a new `TransformerMissingInterfaceException` instance. + * InvalidTransformerClassException constructor. * * @param class-string $transformerClass */ public function __construct(string $transformerClass) { parent::__construct( - message: "Transformer class '$transformerClass' does not extend the Transformer class.", + "Transformer class '$transformerClass' does not extend the Transformer class.", ); } } diff --git a/src/Exception/Transformer/SyntaxError.php b/src/Exception/Transformer/SyntaxError.php index dfa20d3..0b13081 100644 --- a/src/Exception/Transformer/SyntaxError.php +++ b/src/Exception/Transformer/SyntaxError.php @@ -25,7 +25,7 @@ public function __construct( ?SyntaxError $previous = null ) { parent::__construct( - "Syntax error in transformed code: $diagnostic->message\n\nFull code:\n$code", + "Syntax error in transformed code: $diagnostic->message\n\nFull code:\n```php\n$code\n```", previous: $previous, ); } diff --git a/src/Exception/Transformer/TransformerNotFoundException.php b/src/Exception/Transformer/TransformerNotFoundException.php index cf0f612..b4cbda0 100644 --- a/src/Exception/Transformer/TransformerNotFoundException.php +++ b/src/Exception/Transformer/TransformerNotFoundException.php @@ -12,14 +12,14 @@ class TransformerNotFoundException extends TransformerException { /** - * Create a new instance of the exception. + * TransformerNotFoundException constructor. * * @param class-string $transformerClass */ public function __construct(string $transformerClass) { parent::__construct( - message: "Transformer class '$transformerClass' does not exist.", + "Transformer class '$transformerClass' does not exist.", ); } } diff --git a/src/Service/AutoloadInterceptor.php b/src/Service/AutoloadInterceptor.php index fa084e8..4405469 100644 --- a/src/Service/AutoloadInterceptor.php +++ b/src/Service/AutoloadInterceptor.php @@ -3,8 +3,7 @@ namespace Okapi\CodeTransformer\Service; use Composer\Autoload\ClassLoader as ComposerClassLoader; -use Okapi\CodeTransformer\Service\AutoloadInterceptor\ClassLoader; -use Okapi\Singleton\Singleton; +use Okapi\CodeTransformer\Service\ClassLoader\ClassLoader; /** * # Autoload Interceptor @@ -20,22 +19,20 @@ */ class AutoloadInterceptor implements ServiceInterface { - use Singleton; + /** + * The DI key for the original composer class loader. + */ + public const DI = 'okapi.code-transformer.service.composer.class-loader'; /** * Register the autoload interceptor. * * @return void */ - public static function register(): void + public function register(): void { - $instance = self::getInstance(); - $instance->ensureNotInitialized(); - // Overload existing composer loaders - $instance->overloadComposerLoaders(); - - $instance->setInitialized(); + $this->overloadComposerLoaders(); } /** @@ -60,8 +57,14 @@ private function overloadComposerLoaders(): void // @codeCoverageIgnoreEnd } + // Get the original composer loader + $original = $loader[0]; + DI::set(self::DI, $original); + // Register the AOP class loader - $loader[0] = new ClassLoader($loader[0]); + $loader[0] = DI::make(ClassLoader::class, [ + 'original' => $original, + ]); // Unregister the original composer loader spl_autoload_unregister($loaderToUnregister); diff --git a/src/Service/Cache/CachePaths.php b/src/Service/Cache/CachePaths.php index 6021fd8..a1cd0d4 100644 --- a/src/Service/Cache/CachePaths.php +++ b/src/Service/Cache/CachePaths.php @@ -1,7 +1,8 @@ appendToCachePath($filePath, $this->transformedDir); } /** @@ -53,16 +58,17 @@ public static function getTransformedCachePath(string $filePath): string * @param string $append * * @return string - * - * @noinspection PhpSameParameterValueInspection For okapi/aop */ - private static function appendToCachePath(string $file, string $append): string + protected function appendToCachePath(string $file, string $append): string { + $appDir = $this->options->getAppDir(); + $cacheDir = $this->options->getCacheDir(); + // Append the string to the cache dir - $newDir = Path::join(Options::$cacheDir, $append); + $newDir = Path::join($cacheDir, $append); return Path::resolve( - str_replace(Options::$appDir, $newDir, $file), + str_replace($appDir, $newDir, $file), ); } @@ -71,8 +77,10 @@ private static function appendToCachePath(string $file, string $append): string * * @return string */ - public static function getCacheFilePath(): string + public function getCacheFilePath(): string { - return Path::join(Options::$cacheDir, self::CACHE_FILE_NAME); + $cacheDir = $this->options->getCacheDir(); + + return Path::join($cacheDir, $this->cacheFileName); } } diff --git a/src/Service/Cache/CacheState.php b/src/Service/Cache/CacheState.php index 2a72148..6579957 100644 --- a/src/Service/Cache/CacheState.php +++ b/src/Service/Cache/CacheState.php @@ -1,8 +1,9 @@ cachedFilePath !== null) { - // Prevent infinite recursion - if ($this->originalFilePath === $this->cachedFilePath) { - // @codeCoverageIgnoreStart - // This should only happen if the project is misconfigured - return false; - // @codeCoverageIgnoreEnd - } + // @codeCoverageIgnoreStart + // This should only happen if the project is misconfigured + if ($this->checkInfiniteLoop()) { + return false; } + // @codeCoverageIgnoreEnd $allFiles = array_merge( [$this->originalFilePath], $this->transformerFilePaths, ); - // Check if the files have been modified - $lastModified = max(array_map('filemtime', $allFiles)); - if ($lastModified >= $this->transformedTime) { + if ($this->checkFilesModified($allFiles)) { return false; } - if ($this->cachedFilePath !== null) { + if ($this->cachedFilePath) { $allFiles[] = $this->cachedFilePath; } + if (!$this->checkFilesExist($allFiles)) { + return false; + } + + if (!$this->checkTransformerCount()) { + return false; + } + + return true; + } + + /** + * Check if the cache is in an infinite loop. + * + * @return bool True if the cache is in an infinite loop + */ + protected function checkInfiniteLoop(): bool + { + if ($this->cachedFilePath !== null) { + // Same original file and cached file + if ($this->originalFilePath === $this->cachedFilePath) { + return true; + } + } + + return false; + } + + /** + * Check if the files have been modified. + * + * @param string[] $files + * + * @return bool True if any file has been modified + */ + protected function checkFilesModified(array $files): bool + { + $lastModified = max(array_map('filemtime', $files)); + if ($lastModified >= $this->transformedTime) { + return true; + } + + return false; + } + + /** + * Check if the files exist. + * + * @param string[] $files + * + * @return bool True if all files exist + */ + protected function checkFilesExist(array $files): bool + { // Check if the cache file exists - foreach ($allFiles as $file) { + foreach ($files as $file) { if (!file_exists($file)) { return false; } } - // Check if the transformer count is the same + return true; + } + + /** + * Check if the transformer count is the same. + * + * @return bool True if the count is the same + */ + protected function checkTransformerCount(): bool + { // Checking the count alone should be enough $cachedTransformerCount = count($this->transformerFilePaths); - $currentTransformerCount = count(TransformerContainer::matchTransformers($this->className)); + $currentTransformerCount = count( + $this->transformerMatcher->match($this->className) + ); if ($cachedTransformerCount !== $currentTransformerCount) { return false; } diff --git a/src/Service/CacheStateManager.php b/src/Service/CacheStateManager.php index 2a1b11a..5295078 100644 --- a/src/Service/CacheStateManager.php +++ b/src/Service/CacheStateManager.php @@ -1,11 +1,11 @@ ensureNotInitialized(); - - $instance->initializeCacheDirectory(); - $instance->loadCacheState(); - - $instance->setInitialized(); + $this->initializeCacheDirectory(); + $this->loadCacheState(); } /** @@ -73,7 +78,10 @@ public static function register(): void */ private function initializeCacheDirectory(): void { - Filesystem::mkdir(Options::$cacheDir, Options::$cacheFileMode); + Filesystem::mkdir( + $this->options->getCacheDir(), + $this->options->getCacheFileMode(), + ); } /** @@ -83,7 +91,7 @@ private function initializeCacheDirectory(): void */ private function loadCacheState(): void { - $cacheFilePath = CachePaths::getCacheFilePath(); + $cacheFilePath = $this->cachePaths->getCacheFilePath(); if (!file_exists($cacheFilePath)) { return; @@ -92,10 +100,13 @@ private function loadCacheState(): void // Read file $cacheStateContent = Filesystem::readFile($cacheFilePath); + $appDir = $this->options->getAppDir(); + $cacheDir = $this->options->getCacheDir(); + // Replace the keywords $cacheStateContent = str_replace( - [self::CODE_TRANSFORMER_APP_DIR, self::CODE_TRANSFORMER_CACHE_DIR], - [addslashes(Options::$appDir), addslashes(Options::$cacheDir)], + [static::CODE_TRANSFORMER_APP_DIR, static::CODE_TRANSFORMER_CACHE_DIR], + [addslashes($appDir), addslashes($cacheDir)], $cacheStateContent, ); @@ -120,13 +131,13 @@ function ($cacheStateItem) { array_walk( $cacheState, function (&$cacheStateItem, $originalFilePath) { - $cacheStateItem = new CacheState( - originalFilePath: $originalFilePath, - className: $cacheStateItem['className'], - cachedFilePath: $cacheStateItem['cachedFilePath'], - transformedTime: $cacheStateItem['transformedTime'], - transformerFilePaths: $cacheStateItem['transformerFilePaths'] - ); + $cacheStateItem = DI::make(CacheState::class, [ + 'originalFilePath' => $originalFilePath, + 'className' => $cacheStateItem['className'], + 'cachedFilePath' => $cacheStateItem['cachedFilePath'], + 'transformedTime' => $cacheStateItem['transformedTime'], + 'transformerFilePaths' => $cacheStateItem['transformerFilePaths'], + ]); }, ); @@ -173,16 +184,19 @@ private function saveCacheState(): void // Semicolon $phpCode .= ';'; + $appDir = $this->options->getAppDir(); + $cacheDir = $this->options->getCacheDir(); + // Replace the keywords $phpCode = str_replace( - [addslashes(Options::$appDir), addslashes(Options::$cacheDir)], - [self::CODE_TRANSFORMER_APP_DIR, self::CODE_TRANSFORMER_CACHE_DIR], + [addslashes($appDir), addslashes($cacheDir)], + [static::CODE_TRANSFORMER_APP_DIR, static::CODE_TRANSFORMER_CACHE_DIR], $phpCode, ); // Write file Filesystem::writeFile( - CachePaths::getCacheFilePath(), + $this->cachePaths->getCacheFilePath(), $phpCode, ); } @@ -196,12 +210,9 @@ private function saveCacheState(): void * * @return ?CacheState */ - public static function queryCacheState(string $filePath): ?CacheState + public function queryCacheState(string $filePath): ?CacheState { - $instance = self::getInstance(); - $instance->ensureInitialized(); - - return $instance->cacheState[$filePath] ?? null; + return $this->cacheState[$filePath] ?? null; } /** @@ -212,15 +223,12 @@ public static function queryCacheState(string $filePath): ?CacheState * * @return void */ - public static function setCacheState( + public function setCacheState( string $filePath, CacheState $cacheState, ): void { - $instance = self::getInstance(); - $instance->ensureInitialized(); - - $instance->cacheStateChanged = true; + $this->cacheStateChanged = true; - $instance->cacheState[$filePath] = $cacheState; + $this->cacheState[$filePath] = $cacheState; } } diff --git a/src/Service/ClassLoader/ClassContainer.php b/src/Service/ClassLoader/ClassContainer.php new file mode 100644 index 0000000..e6b375e --- /dev/null +++ b/src/Service/ClassLoader/ClassContainer.php @@ -0,0 +1,38 @@ + + */ + private array $namespacedClassPaths = []; + + /** + * Add a class path. + * + * @param string $path + * @param string $class + * + * @return void + */ + public function addNamespacedClassPath(string $path, string $class): void + { + $this->namespacedClassPaths[$path] = $class; + } + + /** + * Get a class path. + * + * @param string $path + * + * @return string + */ + public function getNamespacedClassByPath(string $path): string + { + return $this->namespacedClassPaths[$path]; + } +} diff --git a/src/Service/AutoloadInterceptor/ClassLoader.php b/src/Service/ClassLoader/ClassLoader.php similarity index 51% rename from src/Service/AutoloadInterceptor/ClassLoader.php rename to src/Service/ClassLoader/ClassLoader.php index 79fa4d0..21ebbd4 100644 --- a/src/Service/AutoloadInterceptor/ClassLoader.php +++ b/src/Service/ClassLoader/ClassLoader.php @@ -1,16 +1,16 @@ findFile($class)) { + if ($file = $this->findFile($namespacedClass)) { include $file; return true; @@ -59,18 +78,13 @@ public function loadClass($class): bool /** * Find the path to the file and apply the transformers. * - * @param $class + * @param $namespacedClass * * @return false|string */ - public function findFile($class): false|string + public function findFile($namespacedClass): false|string { - $filePath = $this->original->findFile($class); - - // Prevent infinite recursion - if ($class === Regex::class) { - return $filePath; - } + $filePath = $this->original->findFile($namespacedClass); // @codeCoverageIgnoreStart // Not sure how to test this @@ -79,24 +93,60 @@ public function findFile($class): false|string } // @codeCoverageIgnoreEnd + // Prevent infinite recursion + if ($this->isInternal($namespacedClass)) { + return $filePath; + } + $filePath = Path::resolve($filePath); // Check if the class should be transformed - if (TransformerContainer::shouldTransform($class)) { - $cacheState = CacheStateManager::queryCacheState($filePath); + if ($this->transformerMatcher->shouldTransform($namespacedClass)) { + $cacheState = $this->cacheStateManager->queryCacheState($filePath); // Check if the file is cached and up to date - if (!Options::$debug && $cacheState?->isFresh()) { + if (!$this->options->isDebug() && $cacheState?->isFresh()) { // Use the cached file if transformations have been applied // Or return the original file if no transformations have been applied return $cacheState->cachedFilePath ?? $filePath; } + // Add the class to store the file path + $this->classContainer->addNamespacedClassPath($filePath, $namespacedClass); + // Replace the file path with a PHP stream filter /** @see StreamFilter::filter() */ - return FilterInjector::rewrite($filePath); + return $this->filterInjector->rewrite($filePath); } return $filePath; } + + /** + * Check if the class is internal to the Code Transformer. + * + * @param string $class + * + * @return bool + */ + protected function isInternal(string $class): bool + { + // Code Transformer + if (str_starts_with($class, "Okapi\\CodeTransformer\\") + && !str_starts_with($class, "Okapi\\CodeTransformer\\Tests\\")) { + return true; + } + + // Wildcards + if (str_starts_with($class, "Okapi\\Wildcards\\")) { + return true; + } + + // DI + if (str_starts_with($class, "DI\\")) { + return true; + } + + return false; + } } diff --git a/src/Service/DI.php b/src/Service/DI.php new file mode 100644 index 0000000..0bf899a --- /dev/null +++ b/src/Service/DI.php @@ -0,0 +1,127 @@ +ensureNotInitialized(); + + $containerBuilder = $this->getContainerBuilder(); + $containerBuilder->useAttributes(true); + + /** @noinspection PhpUnhandledExceptionInspection */ + $this->container = $containerBuilder->build(); + + $this->setInitialized(); + } + + /** + * Get the dependency injection container builder. + * + * @return ContainerBuilder + */ + public static function getContainerBuilder(): ContainerBuilder + { + $instance = static::getInstance(); + $instance->ensureNotInitialized(); + + if (!isset($instance->containerBuilder)) { + $instance->containerBuilder = new ContainerBuilder(); + } + + return $instance->containerBuilder; + } + + /** + * Get an (already initialized) instance of the given class. + * + * @template T + * + * @param class-string $class + * + * @return T + * + * @noinspection PhpDocMissingThrowsInspection + */ + public static function get(string $class) + { + $instance = static::getInstance(); + $instance->ensureInitialized(); + + /** @noinspection PhpUnhandledExceptionInspection */ + return $instance->container->get($class); + } + + /** + * Replace an existing instance of the given class. + * + * @param string $class + * @param $value + * + * @return void + */ + public static function set(string $class, $value): void + { + $instance = static::getInstance(); + $instance->ensureInitialized(); + + $instance->container->set($class, $value); + } + + /** + * Create an instance of the given class. + * + * This method behaves like a factory. + * + * @template T + * + * @param class-string $class + * @param array $parameters + * + * @return T + * + * @noinspection PhpDocMissingThrowsInspection + */ + public static function make(string $class, array $parameters = []) + { + $instance = static::getInstance(); + $instance->ensureInitialized(); + + /** @noinspection PhpUnhandledExceptionInspection */ + return $instance->container->make($class, $parameters); + } +} diff --git a/src/Service/Matcher/TransformerMatcher.php b/src/Service/Matcher/TransformerMatcher.php new file mode 100644 index 0000000..abf8f52 --- /dev/null +++ b/src/Service/Matcher/TransformerMatcher.php @@ -0,0 +1,78 @@ + + */ + private array $transformerQueryResultCache = []; + + /** + * Check if the class should be transformed. + * + * @param string $namespacedClass + * + * @return bool + */ + public function shouldTransform(string $namespacedClass): bool + { + return $this->match($namespacedClass) !== []; + } + + /** + * Return the list of transformers that match the given class name. + * + * @param string $namespacedClass + * + * @return Transformer[] + */ + public function match(string $namespacedClass): array + { + // Check if the query has been cached + if (isset($this->transformerQueryResultCache[$namespacedClass])) { + return $this->transformerQueryResultCache[$namespacedClass]; + } + + // Match the transformers + $matchedInstances = []; + foreach ($this->transformerContainer->getTransformerTargets() as $classRegex => $instances) { + $regex = Regex::fromWildcard($classRegex); + if ($regex->matches($namespacedClass)) { + // Check if the transformer has already been matched + $alreadyMatched = array_filter( + $matchedInstances, + function (Transformer $transformer) use ($instances) { + return in_array($transformer, $instances, true); + }, + ); + + if ($alreadyMatched) { + continue; + } + + $matchedInstances = array_merge($matchedInstances, $instances); + } + } + + // Cache the query result + $this->transformerQueryResultCache[$namespacedClass] = $matchedInstances; + + return $matchedInstances; + } +} diff --git a/src/Service/Options.php b/src/Service/Options.php index 0e3bf5c..23c2820 100644 --- a/src/Service/Options.php +++ b/src/Service/Options.php @@ -2,46 +2,56 @@ namespace Okapi\CodeTransformer\Service; -use Okapi\CodeTransformer\Service\Cache\CachePaths; use Okapi\Path\Path; -use Okapi\Singleton\Singleton; /** * # Options * - * The `Options` class provides access to the options passed to the `CodeTransformerKernel`. + * The `Options` class provides access to the options passed to the + * `CodeTransformerKernel`. */ class Options implements ServiceInterface { - use Singleton; + // region Options /** * The application directory. * * @var string */ - public static string $appDir; + private string $appDir; /** * The cache directory. * * @var string */ - public static string $cacheDir; + private string $cacheDir; /** * The cache file mode. * * @var int */ - public static int $cacheFileMode; + private int $cacheFileMode; /** * Enable debug mode. This will disable the cache. * * @var bool */ - public static bool $debug; + private bool $debug; + + // endregion + + /** + * # Default cache directory. + * + * This directory is used if no cache directory is provided. + * + * @var string + */ + public string $defaultCacheDir = 'cache/code-transformer'; // region Pre-Initialization @@ -52,7 +62,7 @@ class Options implements ServiceInterface * @param int|null $cacheFileMode * @param bool|null $debug */ - public static function setOptions( + public function setOptions( ?string $cacheDir, ?int $cacheFileMode, ?bool $debug, @@ -65,10 +75,10 @@ public static function setOptions( // @codeCoverageIgnoreEnd } - self::$appDir = $rootDir; - self::$cacheDir = $cacheDir ?? Path::join($rootDir, CachePaths::DEFAULT_CACHE_DIR); - self::$cacheFileMode = $cacheFileMode ?? (0777 & ~umask()); - self::$debug = $debug ?? false; + $this->appDir = $rootDir; + $this->cacheDir = $cacheDir ?? Path::join($rootDir, $this->defaultCacheDir); + $this->cacheFileMode = $cacheFileMode ?? (0777 & ~umask()); + $this->debug = $debug ?? false; } // endregion @@ -76,17 +86,44 @@ public static function setOptions( // region Initialization /** - * Register the options. - * - * @return void + * @inheritDoc */ - public static function register(): void + public function register(): void { - $instance = self::getInstance(); - $instance->ensureNotInitialized(); - - $instance->setInitialized(); + // Nothing to do here. } // endregion + + /** + * Get the application directory. + */ + public function getAppDir(): string + { + return $this->appDir; + } + + /** + * Get the cache directory. + */ + public function getCacheDir(): string + { + return $this->cacheDir; + } + + /** + * Get the cache file mode. + */ + public function getCacheFileMode(): int + { + return $this->cacheFileMode; + } + + /** + * Check if debug mode is enabled. + */ + public function isDebug(): bool + { + return $this->debug; + } } diff --git a/src/Service/Processor/TransformerProcessor.php b/src/Service/Processor/TransformerProcessor.php new file mode 100644 index 0000000..754e2bd --- /dev/null +++ b/src/Service/Processor/TransformerProcessor.php @@ -0,0 +1,121 @@ +code->getClassName(); + + // Process the transformers + $transformers = $this->transformerMatcher->match($className); + $this->processTransformers($metadata, $transformers); + + $originalFilePath = $metadata->uri; + $cacheFilePath = $this->cachePaths->getTransformedCachePath($originalFilePath); + $transformed = $metadata->code->hasChanges(); + + // Save the transformed code + if ($transformed) { + Filesystem::writeFile( + $cacheFilePath, + $metadata->code->getNewSource(), + ); + } + + // Update the cache state + $fileModificationTime = $_SERVER['REQUEST_TIME'] ?? time(); + $transformerFilePaths = $this->getTransformerFilePaths($transformers); + $cacheState = DI::make(CacheState::class, [ + 'originalFilePath' => $originalFilePath, + 'className' => $className, + 'cachedFilePath' => $transformed ? $cacheFilePath : null, + 'transformedTime' => $fileModificationTime, + 'transformerFilePaths' => $transformerFilePaths, + ]); + $this->cacheStateManager->setCacheState($originalFilePath, $cacheState); + } + + /** + * Process the transformers. + * + * @param Metadata $metadata + * @param Transformer[] $transformers + * + * @return void + */ + private function processTransformers( + Metadata $metadata, + array $transformers + ): void { + // Sort the transformers by priority + usort( + $transformers, + function (Transformer $a, Transformer $b) { + return $a->order <=> $b->order; + }, + ); + + foreach ($transformers as $transformer) { + $transformer->transform($metadata->code); + } + } + + /** + * Get the file paths of the given transformers. + * + * @param Transformer[] $transformers + * + * @return string[] + * + * @noinspection PhpDocMissingThrowsInspection Handled by TransformerNotFoundException + */ + protected function getTransformerFilePaths(array $transformers): array + { + return array_map( + function (Transformer $transformer) { + /** @noinspection PhpUnhandledExceptionInspection Handled by TransformerNotFoundException */ + $reflection = new ReflectionClass($transformer); + return $reflection->getFileName(); + }, + $transformers, + ); + } +} diff --git a/src/Service/ServiceInterface.php b/src/Service/ServiceInterface.php index ce0ad71..f9f8fd5 100644 --- a/src/Service/ServiceInterface.php +++ b/src/Service/ServiceInterface.php @@ -14,5 +14,5 @@ interface ServiceInterface * * @return void */ - public static function register(): void; + public function register(): void; } diff --git a/src/Service/StreamFilter.php b/src/Service/StreamFilter.php index c6354fb..026e111 100644 --- a/src/Service/StreamFilter.php +++ b/src/Service/StreamFilter.php @@ -2,8 +2,8 @@ namespace Okapi\CodeTransformer\Service; +use Okapi\CodeTransformer\Service\Processor\TransformerProcessor; use Okapi\CodeTransformer\Service\StreamFilter\Metadata; -use Okapi\Singleton\Singleton; use php_user_filter as PhpStreamFilter; /** @@ -13,8 +13,6 @@ */ class StreamFilter extends PhpStreamFilter implements ServiceInterface { - use Singleton; - /** * Filter ID. */ @@ -32,15 +30,10 @@ class StreamFilter extends PhpStreamFilter implements ServiceInterface * * @return void */ - public static function register(): void + public function register(): void { - $instance = self::getInstance(); - $instance->ensureNotInitialized(); - // Register the stream filter - stream_filter_register(self::FILTER_ID, self::class); - - $instance->setInitialized(); + stream_filter_register(static::FILTER_ID, static::class); } /** @@ -53,7 +46,7 @@ public static function register(): void * * @return int * - * @see https://www.php.net/manual/php-user-filter.filter.php + * @see https://www.php.net/manual/php-user-filter.filter.php */ public function filter($in, $out, &$consumed, bool $closing): int { @@ -67,10 +60,14 @@ public function filter($in, $out, &$consumed, bool $closing): int $consumed = strlen($this->data); // Store the metadata - $metadata = new Metadata($this->stream, $this->data); + $metadata = DI::make(Metadata::class, [ + 'stream' => $this->stream, + 'originalSource' => $this->data, + ]); // Transform the code - TransformerContainer::transform($metadata); + $transformerProcessor = DI::get(TransformerProcessor::class); + $transformerProcessor->transform($metadata); // Set the new source code $source = $metadata->code->getNewSource(); diff --git a/src/Service/StreamFilter/FilterInjector.php b/src/Service/StreamFilter/FilterInjector.php index 35ff1bb..4076758 100644 --- a/src/Service/StreamFilter/FilterInjector.php +++ b/src/Service/StreamFilter/FilterInjector.php @@ -31,12 +31,12 @@ class FilterInjector * * @return string */ - public static function rewrite(string $filePath): string + public function rewrite(string $filePath): string { // Create a filter for the given file return sprintf( "%s%s/resource=%s", - self::PHP_FILTER_READ, + static::PHP_FILTER_READ, StreamFilter::FILTER_ID, $filePath ); diff --git a/src/Service/StreamFilter/Metadata.php b/src/Service/StreamFilter/Metadata.php index 8de5c66..b187c54 100644 --- a/src/Service/StreamFilter/Metadata.php +++ b/src/Service/StreamFilter/Metadata.php @@ -3,6 +3,8 @@ namespace Okapi\CodeTransformer\Service\StreamFilter; use Okapi\CodeTransformer\Exception\StreamFilter\InvalidStreamException; +use Okapi\CodeTransformer\Service\ClassLoader\ClassContainer; +use Okapi\CodeTransformer\Service\DI; use Okapi\CodeTransformer\Service\StreamFilter\Metadata\Code; use Okapi\Path\Path; @@ -40,12 +42,14 @@ class Metadata /** * Metadata constructor. * - * @param mixed $stream - * @param string $originalSource + * @param mixed $stream + * @param string $originalSource + * @param ClassContainer $classContainer */ public function __construct( - mixed $stream, - string $originalSource + mixed $stream, + string $originalSource, + ClassContainer $classContainer, ) { if (!is_resource($stream)) { // @codeCoverageIgnoreStart @@ -53,8 +57,6 @@ public function __construct( // @codeCoverageIgnoreEnd } - $this->code = new Code($originalSource); - // Create metadata from stream $metadata = stream_get_meta_data($stream); if (preg_match('/resource=(.+)$/', $metadata['uri'], $matches)) { @@ -74,5 +76,10 @@ public function __construct( $this->uri = $metadata['uri']; $this->crypto = $metadata['crypto'] ?? null; $this->mediatype = $metadata['mediatype'] ?? null; + + $this->code = DI::make(Code::class, [ + 'source' => $originalSource, + 'namespacedClass' => $classContainer->getNamespacedClassByPath($this->uri), + ]); } } diff --git a/src/Service/StreamFilter/Metadata/Code.php b/src/Service/StreamFilter/Metadata/Code.php index 3c27aea..14d9b20 100644 --- a/src/Service/StreamFilter/Metadata/Code.php +++ b/src/Service/StreamFilter/Metadata/Code.php @@ -2,13 +2,17 @@ namespace Okapi\CodeTransformer\Service\StreamFilter\Metadata; -use Microsoft\PhpParser\DiagnosticsProvider; use Microsoft\PhpParser\Node\SourceFileNode; -use Microsoft\PhpParser\Node\Statement\ClassDeclaration; use Microsoft\PhpParser\Parser; use Microsoft\PhpParser\Token; -use Okapi\CodeTransformer\Exception\Transformer\SyntaxError; +use Okapi\CodeTransformer\Service\AutoloadInterceptor; +use Okapi\CodeTransformer\Service\DI; +use Okapi\CodeTransformer\Util\CodeChecker; use Okapi\CodeTransformer\Util\StringMutator; +use Roave\BetterReflection\BetterReflection; +use Roave\BetterReflection\Reflection\ReflectionClass; +use Roave\BetterReflection\Reflector\DefaultReflector; +use Roave\BetterReflection\SourceLocator\Type\ComposerSourceLocator; /** * # Code @@ -37,21 +41,26 @@ class Code * * @var SourceFileNode */ - public SourceFileNode $sourceFileNode; + private SourceFileNode $sourceFileNode; /** * Code constructor. * - * @param string $source The source code. + * @param string $source The source code. + * @param string $namespacedClass The namespaced class name. */ public function __construct( private readonly string $source, + private readonly string $namespacedClass, ) { - $parser = new Parser; - $this->sourceFileNode = $parser->parseSourceFile($this->source); - $this->stringMutator = new StringMutator($this->source); + // Create the string mutator + $this->stringMutator = DI::make(StringMutator::class, [ + 'string' => $this->source, + ]); } + // region Mutators + /** * Add an edit to the source code. * @@ -102,6 +111,10 @@ public function append(string $string): void $this->appendList[] = $string; } + // endregion + + // region Getters + /** * Get the original source code. * @@ -112,12 +125,44 @@ public function getOriginalSource(): string return $this->source; } + /** + * Get source file node. + * + * @return SourceFileNode + */ + public function getSourceFileNode(): SourceFileNode + { + if (!isset($this->sourceFileNode)) { + $this->sourceFileNode = (new Parser)->parseSourceFile($this->source); + } + + return $this->sourceFileNode; + } + + /** + * Get the reflection class. + * + * @return ReflectionClass + */ + public function getReflectionClass(): ReflectionClass + { + static $classLoader, $astLocator, $reflector; + + if (!isset($classLoader, $astLocator, $reflector)) { + $classLoader = DI::get(AutoloadInterceptor::DI); + $astLocator = (new BetterReflection)->astLocator(); + $reflector = (new DefaultReflector( + new ComposerSourceLocator($classLoader, $astLocator) + )); + } + + return $reflector->reflectClass($this->getNamespacedClass()); + } + /** * Get the new source code with all edits and appends applied. * * @return string - * - * @internal */ public function getNewSource(): string { @@ -138,25 +183,13 @@ public function getNewSource(): string } // Check the new source code for syntax errors - $parser = new Parser; - $sourceFileNode = $parser->parseSourceFile($newSource); - $errors = DiagnosticsProvider::getDiagnostics($sourceFileNode); - - if (count($errors) > 0) { - $errors = array_reverse($errors); - - // Chain errors - $error = null; - foreach ($errors as $e) { - $error = new SyntaxError($e, $newSource, $error); - } - - throw $error; - } + DI::get(CodeChecker::class)->isValidPhpCode($newSource); return $newSource; } + // endregion + /** * Whether the source code has any edits or appends. * @@ -164,19 +197,30 @@ public function getNewSource(): string */ public function hasChanges(): bool { - return count($this->stringMutator->edits) > 0 || count($this->appendList) > 0; + return count($this->stringMutator->edits) > 0 + || count($this->appendList) > 0; } /** - * Get the full class name. + * Get the namespaced class name. * * @return string */ - public function getFullClassName(): string + public function getNamespacedClass(): string { - /** @var ClassDeclaration $classDeclaration */ - $classDeclaration = $this->sourceFileNode->getFirstDescendantNode(ClassDeclaration::class); + return $this->namespacedClass; + } - return $classDeclaration->getNamespacedName()->getFullyQualifiedNameText(); + /** + * Get the class name. + * + * @return string + */ + public function getClassName(): string + { + return substr( + $this->namespacedClass, + strrpos($this->namespacedClass, '\\') + 1, + ); } } diff --git a/src/Service/TransformerContainer.php b/src/Service/TransformerContainer.php index c767a0d..850828f 100644 --- a/src/Service/TransformerContainer.php +++ b/src/Service/TransformerContainer.php @@ -1,18 +1,12 @@ + * @var array The key is a wildcard target class name. */ private array $transformerTargets = []; - /** - * Cached transformer target query results. - * - * @var array - */ - private array $transformerQueryResults = []; - // region Pre-Initialization /** @@ -53,13 +38,10 @@ class TransformerContainer implements ServiceInterface * * @return void */ - public static function addTransformers(array $transformers): void + public function addTransformers(array $transformers): void { - $instance = self::getInstance(); - $instance->ensureNotInitialized(); - - $instance->transformers = array_merge( - $instance->transformers, + $this->transformers = array_merge( + $this->transformers, $transformers, ); } @@ -73,14 +55,9 @@ public static function addTransformers(array $transformers): void * * @return void */ - public static function register(): void + public function register(): void { - $instance = self::getInstance(); - $instance->ensureNotInitialized(); - - $instance->loadTransformers(); - - $instance->setInitialized(); + $this->loadTransformers(); } /** @@ -93,8 +70,8 @@ private function loadTransformers(): void foreach ($this->transformers as $transformer) { // Instantiate the transformer try { - $transformerInstance = new $transformer(); - } catch (Error) { + $transformerInstance = DI::make($transformer); + } catch (Error|Exception) { throw new TransformerNotFoundException($transformer); } @@ -103,12 +80,13 @@ private function loadTransformers(): void if (!$isTransformer) { throw new InvalidTransformerClassException($transformer); } + assert($transformerInstance instanceof Transformer); /** @var string[] $targets */ $targets = (array)$transformerInstance->getTargetClass(); - foreach ($targets as $target) { - $this->transformerTargets[$target][] = $transformerInstance; + foreach ($targets as $classRegex) { + $this->transformerTargets[$classRegex][] = $transformerInstance; } } } @@ -116,154 +94,12 @@ private function loadTransformers(): void // endregion /** - * Check if the class should be transformed. - * - * @param string $className - * - * @return bool - */ - public static function shouldTransform(string $className): bool - { - $instance = self::getInstance(); - $instance->ensureInitialized(); - - return $instance->matchTransformers($className) !== []; - } - - // region Transform Code - - /** - * Transform the code. - * - * @param Metadata $metadata - * - * @return void - * - * @noinspection PhpMissingReturnTypeInspection For okapi/aop - */ - public static function transform(Metadata $metadata) - { - $instance = self::getInstance(); - $instance->ensureInitialized(); - - $fullClassName = $metadata->code->getFullClassName(); - - // Process the transformers - $transformers = $instance->matchTransformers($fullClassName); - $instance->processTransformers($metadata, $transformers); - - $originalFilePath = $metadata->uri; - $cacheFilePath = CachePaths::getTransformedCachePath($originalFilePath); - $transformed = $metadata->code->hasChanges(); - - // Save the transformed code - if ($transformed) { - Filesystem::writeFile( - $cacheFilePath, - $metadata->code->getNewSource(), - ); - } - - // Update the cache state - $fileModificationTime = $_SERVER['REQUEST_TIME'] ?? time(); - $transformerFilePaths = $instance->getTransformerFilePaths($transformers); - $cacheState = new CacheState( - originalFilePath: $originalFilePath, - className: $fullClassName, - cachedFilePath: $transformed ? $cacheFilePath : null, - transformedTime: $fileModificationTime, - transformerFilePaths: $transformerFilePaths, - ); - CacheStateManager::setCacheState($originalFilePath, $cacheState); - } - - /** - * Return the list of transformers that match the class name. + * Get the transformer targets. * - * @param string $className - * - * @return Transformer[] - */ - public static function matchTransformers(string $className): array - { - $instance = self::getInstance(); - - // Check if the query has been cached - if (isset($instance->transformerQueryResults[$className])) { - return $instance->transformerQueryResults[$className]; - } - - // Match the transformers - $matchedInstances = []; - foreach ($instance->transformerTargets as $target => $instances) { - $regex = Regex::fromWildcard($target); - if ($regex->matches($className)) { - // Check if the transformer is already in the list - $alreadyMatched = array_filter( - $matchedInstances, - function (Transformer $transformer) use ($instances) { - return in_array($transformer, $instances, true); - }, - ); - - if ($alreadyMatched) { - continue; - } - - $matchedInstances = array_merge($matchedInstances, $instances); - } - } - - // Cache the query result - $instance->transformerQueryResults[$className] = $matchedInstances; - - return $matchedInstances; - } - - /** - * Get the list of transformer file paths. - * - * @param Transformer[] $transformers - * - * @return string[] - * - * @noinspection PhpDocMissingThrowsInspection + * @return array The key is a wildcard target class name. */ - private function getTransformerFilePaths(array $transformers): array + public function getTransformerTargets(): array { - return array_map( - function (Transformer $transformer) { - /** @noinspection PhpUnhandledExceptionInspection Handled by TransformerNotFoundException */ - $reflection = new ReflectionClass($transformer); - return $reflection->getFileName(); - }, - $transformers, - ); + return $this->transformerTargets; } - - /** - * Process the transformers. - * - * @param Metadata $metadata - * @param Transformer[] $transformers - * - * @return void - * @noinspection PhpMissingReturnTypeInspection For okapi/aop - */ - private function processTransformers(Metadata $metadata, array $transformers) - { - // Sort the transformers by priority - usort( - $transformers, - function (Transformer $a, Transformer $b) { - return $a->order <=> $b->order; - }, - ); - - foreach ($transformers as $transformer) { - $transformer->transform($metadata->code); - } - } - - // endregion } diff --git a/src/Transformer.php b/src/Transformer.php index c5c11c9..0a98de4 100644 --- a/src/Transformer.php +++ b/src/Transformer.php @@ -32,7 +32,7 @@ abstract public function getTargetClass(): string|array; /** * Transform the source code. * - * @param Code $code + * @param Code $code * * @return void */ diff --git a/src/Util/CodeChecker.php b/src/Util/CodeChecker.php new file mode 100644 index 0000000..e6b6e71 --- /dev/null +++ b/src/Util/CodeChecker.php @@ -0,0 +1,40 @@ +parseSourceFile($code); + $errors = DiagnosticsProvider::getDiagnostics($sourceFileNode); + + if (count($errors) > 0) { + $errors = array_reverse($errors); + + // Chain errors + $error = null; + foreach ($errors as $e) { + $error = new SyntaxError($e, $code, $error); + } + + throw $error; + } + } +} diff --git a/tests/ClassLoaderMockTrait.php b/tests/ClassLoaderMockTrait.php index 7d96f4d..f01846c 100644 --- a/tests/ClassLoaderMockTrait.php +++ b/tests/ClassLoaderMockTrait.php @@ -2,8 +2,9 @@ namespace Okapi\CodeTransformer\Tests; -use Okapi\CodeTransformer\Service\AutoloadInterceptor\ClassLoader; +use Okapi\CodeTransformer\Service\ClassLoader\ClassLoader; use Okapi\CodeTransformer\Service\Cache\CachePaths; +use Okapi\CodeTransformer\Service\DI; use Okapi\CodeTransformer\Service\StreamFilter; use Okapi\CodeTransformer\Service\StreamFilter\FilterInjector; use Okapi\Path\Path; @@ -65,7 +66,8 @@ public function assertWillBeTransformed(string $className): void public function assertTransformerLoadedFromCache(string $className): void { $filePath = $this->findOriginalClassMock($className); - $cachePath = CachePaths::getTransformedCachePath($filePath); + $cachePaths = DI::get(CachePaths::class); + $cachePath = $cachePaths->getTransformedCachePath($filePath); $filePathMock = $this->findClassMock($className); Assert::assertEquals( diff --git a/tests/Functional/DirectKernelTest.php b/tests/Functional/DirectKernelTest.php index 50e6bd0..3b5b3a6 100644 --- a/tests/Functional/DirectKernelTest.php +++ b/tests/Functional/DirectKernelTest.php @@ -13,9 +13,6 @@ public function testDirectKernelInitialization(): void { $this->expectException(DirectKernelInitializationException::class); - CodeTransformerKernel::init( - cacheDir: Util::CACHE_DIR, - debug: true, - ); + CodeTransformerKernel::init(); } } diff --git a/tests/Functional/InvalidTransformerClassTest.php b/tests/Functional/InvalidTransformerClassTest.php index 909d58b..1351306 100644 --- a/tests/Functional/InvalidTransformerClassTest.php +++ b/tests/Functional/InvalidTransformerClassTest.php @@ -17,13 +17,13 @@ public function testTransformerNotFound(): void { $this->expectException(TransformerNotFoundException::class); - TransformerDoesNotExistKernel::init(cacheDir: Util::CACHE_DIR); + TransformerDoesNotExistKernel::init(); } public function testTransformerDoesNotExtendTransformer(): void { $this->expectException(InvalidTransformerClassException::class); - TransformerDoesNotExtendTransformerKernel::init(cacheDir: Util::CACHE_DIR); + TransformerDoesNotExtendTransformerKernel::init(); } } diff --git a/tests/Functional/Workflow/ApplicationTest.php b/tests/Functional/Workflow/ApplicationTest.php index 91a22ca..665e6b0 100644 --- a/tests/Functional/Workflow/ApplicationTest.php +++ b/tests/Functional/Workflow/ApplicationTest.php @@ -4,6 +4,7 @@ use Okapi\CodeTransformer\Exception\Transformer\SyntaxError; use Okapi\CodeTransformer\Service\CacheStateManager; +use Okapi\CodeTransformer\Service\DI; use Okapi\CodeTransformer\Tests\ClassLoaderMockTrait; use Okapi\CodeTransformer\Tests\Stubs\ClassesToTransform; use Okapi\CodeTransformer\Tests\Stubs\Kernel\ApplicationKernel; @@ -26,7 +27,7 @@ public function testKernel(): void Util::clearCache(); $this->assertFalse(ApplicationKernel::isInitialized()); - ApplicationKernel::init(cacheDir: Util::CACHE_DIR); + ApplicationKernel::init(); $this->assertTrue(ApplicationKernel::isInitialized()); $this->assertFileDoesNotExist(Util::CACHE_STATES_FILE); @@ -121,7 +122,7 @@ public function testAddedTransformer(): void public function testDestructor(): void { - $cacheStateManager = CacheStateManager::getInstance(); + $cacheStateManager = DI::get(CacheStateManager::class); $this->assertFileDoesNotExist(Util::CACHE_STATES_FILE); $cacheStateManager->__destruct(); diff --git a/tests/Functional/Workflow/CachedApplicationTest.php b/tests/Functional/Workflow/CachedApplicationTest.php index a854638..8d9b717 100644 --- a/tests/Functional/Workflow/CachedApplicationTest.php +++ b/tests/Functional/Workflow/CachedApplicationTest.php @@ -20,7 +20,7 @@ class CachedApplicationTest extends TestCase public function testKernel(): void { $this->assertFalse(CachedKernel::isInitialized()); - CachedKernel::init(cacheDir: Util::CACHE_DIR); + CachedKernel::init(); $this->assertTrue(CachedKernel::isInitialized()); $this->assertFileExists(Util::CACHE_STATES_FILE); diff --git a/tests/Stubs/Kernel/ApplicationKernel.php b/tests/Stubs/Kernel/ApplicationKernel.php index 69fc3d4..e3085f0 100644 --- a/tests/Stubs/Kernel/ApplicationKernel.php +++ b/tests/Stubs/Kernel/ApplicationKernel.php @@ -10,9 +10,12 @@ use Okapi\CodeTransformer\Tests\Stubs\Transformer\StringTransformer; use Okapi\CodeTransformer\Tests\Stubs\Transformer\SyntaxErrorTransformer; use Okapi\CodeTransformer\Tests\Stubs\Transformer\UnPrivateTransformer; +use Okapi\CodeTransformer\Tests\Util; class ApplicationKernel extends CodeTransformerKernel { + protected ?string $cacheDir = Util::CACHE_DIR; + protected array $transformers = [ StringTransformer::class, NoChangesTransformer::class, diff --git a/tests/Stubs/Kernel/CachedKernel.php b/tests/Stubs/Kernel/CachedKernel.php index 9324ee3..20dc1ea 100644 --- a/tests/Stubs/Kernel/CachedKernel.php +++ b/tests/Stubs/Kernel/CachedKernel.php @@ -2,7 +2,6 @@ namespace Okapi\CodeTransformer\Tests\Stubs\Kernel; -use Okapi\CodeTransformer\Service\TransformerContainer; use Okapi\CodeTransformer\Tests\Stubs\Transformer\AddedTransformer2; class CachedKernel extends ApplicationKernel @@ -11,9 +10,10 @@ class CachedKernel extends ApplicationKernel AddedTransformer2::class, ]; - /** @noinspection PhpMissingParentConstructorInspection */ - public function __construct() + protected function preInit(): void { - TransformerContainer::addTransformers($this->addedTransformers); + parent::preInit(); + + $this->transformerContainer->addTransformers($this->addedTransformers); } } diff --git a/tests/Stubs/Kernel/TransformerDoesNotExistKernel.php b/tests/Stubs/Kernel/TransformerDoesNotExistKernel.php index d4e9234..d4cee34 100644 --- a/tests/Stubs/Kernel/TransformerDoesNotExistKernel.php +++ b/tests/Stubs/Kernel/TransformerDoesNotExistKernel.php @@ -3,9 +3,12 @@ namespace Okapi\CodeTransformer\Tests\Stubs\Kernel; use Okapi\CodeTransformer\CodeTransformerKernel; +use Okapi\CodeTransformer\Tests\Util; class TransformerDoesNotExistKernel extends CodeTransformerKernel { + protected ?string $cacheDir = Util::CACHE_DIR; + protected array $transformers = [ 'IDoNotExist', ]; diff --git a/tests/Stubs/Kernel/TransformerDoesNotExtendTransformerKernel.php b/tests/Stubs/Kernel/TransformerDoesNotExtendTransformerKernel.php index dbdf6f1..7efc63c 100644 --- a/tests/Stubs/Kernel/TransformerDoesNotExtendTransformerKernel.php +++ b/tests/Stubs/Kernel/TransformerDoesNotExtendTransformerKernel.php @@ -4,9 +4,12 @@ use Okapi\CodeTransformer\CodeTransformerKernel; use Okapi\CodeTransformer\Tests\Stubs\Transformer\InvalidTransformer; +use Okapi\CodeTransformer\Tests\Util; class TransformerDoesNotExtendTransformerKernel extends CodeTransformerKernel { + protected ?string $cacheDir = Util::CACHE_DIR; + protected array $transformers = [ InvalidTransformer::class, ]; diff --git a/tests/Stubs/Transformer/AddedTransformer1.php b/tests/Stubs/Transformer/AddedTransformer1.php index bcc52fa..4f0b15e 100644 --- a/tests/Stubs/Transformer/AddedTransformer1.php +++ b/tests/Stubs/Transformer/AddedTransformer1.php @@ -19,7 +19,7 @@ public function getTargetClass(): string|array public function transform(Code $code): void { - $sourceFileNode = $code->sourceFileNode; + $sourceFileNode = $code->getSourceFileNode(); foreach ($sourceFileNode->getDescendantNodes() as $node) { if ($node instanceof ExpressionStatement) { diff --git a/tests/Stubs/Transformer/AddedTransformer2.php b/tests/Stubs/Transformer/AddedTransformer2.php index b30ea7f..b6712bc 100644 --- a/tests/Stubs/Transformer/AddedTransformer2.php +++ b/tests/Stubs/Transformer/AddedTransformer2.php @@ -19,7 +19,7 @@ public function getTargetClass(): string|array public function transform(Code $code): void { - $sourceFileNode = $code->sourceFileNode; + $sourceFileNode = $code->getSourceFileNode(); foreach ($sourceFileNode->getDescendantNodes() as $node) { if ($node instanceof ExpressionStatement) { diff --git a/tests/Stubs/Transformer/ChangedTransformer.php b/tests/Stubs/Transformer/ChangedTransformer.php index aa7ad90..1a9d10b 100644 --- a/tests/Stubs/Transformer/ChangedTransformer.php +++ b/tests/Stubs/Transformer/ChangedTransformer.php @@ -16,7 +16,7 @@ public function getTargetClass(): string|array public function transform(Code $code): void { - $sourceFileNode = $code->sourceFileNode; + $sourceFileNode = $code->getSourceFileNode(); foreach ($sourceFileNode->getDescendantNodes() as $node) { // Find 'Hello World!' string diff --git a/tests/Stubs/Transformer/StringTransformer.php b/tests/Stubs/Transformer/StringTransformer.php index 431e014..fc0f4bf 100644 --- a/tests/Stubs/Transformer/StringTransformer.php +++ b/tests/Stubs/Transformer/StringTransformer.php @@ -19,7 +19,7 @@ public function getTargetClass(): string|array public function transform(Code $code): void { - $sourceFileNode = $code->sourceFileNode; + $sourceFileNode = $code->getSourceFileNode(); foreach ($sourceFileNode->getDescendantNodes() as $node) { // Find 'Hello World!' string diff --git a/tests/Stubs/Transformer/UnPrivateTransformer.php b/tests/Stubs/Transformer/UnPrivateTransformer.php index b79904f..66bb16b 100644 --- a/tests/Stubs/Transformer/UnPrivateTransformer.php +++ b/tests/Stubs/Transformer/UnPrivateTransformer.php @@ -16,7 +16,7 @@ public function getTargetClass(): string|array public function transform(Code $code): void { - $sourceFileNode = $code->sourceFileNode; + $sourceFileNode = $code->getSourceFileNode(); foreach ($sourceFileNode->getDescendantTokens() as $token) { if ($token->kind === TokenKind::PrivateKeyword) { From fb96fa9bb520c1be9ceb13c8a711816391fc94c9 Mon Sep 17 00:00:00 2001 From: WalterWoshid Date: Wed, 19 Apr 2023 05:40:31 +0200 Subject: [PATCH 02/18] Added functions.php --- composer.json | 5 ++++- src/functions.php | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 src/functions.php diff --git a/composer.json b/composer.json index cb98687..a04b4f3 100644 --- a/composer.json +++ b/composer.json @@ -39,7 +39,10 @@ "autoload": { "psr-4": { "Okapi\\CodeTransformer\\": "src/" - } + }, + "files": [ + "src/functions.php" + ] }, "autoload-dev": { "psr-4": { diff --git a/src/functions.php b/src/functions.php new file mode 100644 index 0000000..879d0c8 --- /dev/null +++ b/src/functions.php @@ -0,0 +1,39 @@ + Date: Wed, 19 Apr 2023 05:42:31 +0200 Subject: [PATCH 03/18] Moved "Code" class to public folder --- .../Metadata => Transformer}/Code.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) rename src/{Service/StreamFilter/Metadata => Transformer}/Code.php (87%) diff --git a/src/Service/StreamFilter/Metadata/Code.php b/src/Transformer/Code.php similarity index 87% rename from src/Service/StreamFilter/Metadata/Code.php rename to src/Transformer/Code.php index 14d9b20..baf3af1 100644 --- a/src/Service/StreamFilter/Metadata/Code.php +++ b/src/Transformer/Code.php @@ -1,23 +1,23 @@ Date: Wed, 19 Apr 2023 05:43:30 +0200 Subject: [PATCH 04/18] Moved services to "Core" folder --- src/{Service => Core}/AutoloadInterceptor.php | 14 +++++++------- src/{Service => Core}/DI.php | 4 ++-- src/{Service => Core}/Options.php | 7 ++++--- src/{Service => Core}/ServiceInterface.php | 2 +- src/{Service => Core}/StreamFilter.php | 10 +++++----- 5 files changed, 19 insertions(+), 18 deletions(-) rename src/{Service => Core}/AutoloadInterceptor.php (80%) rename src/{Service => Core}/DI.php (92%) rename src/{Service => Core}/Options.php (88%) rename src/{Service => Core}/ServiceInterface.php (79%) rename src/{Service => Core}/StreamFilter.php (83%) diff --git a/src/Service/AutoloadInterceptor.php b/src/Core/AutoloadInterceptor.php similarity index 80% rename from src/Service/AutoloadInterceptor.php rename to src/Core/AutoloadInterceptor.php index 4405469..4a49c67 100644 --- a/src/Service/AutoloadInterceptor.php +++ b/src/Core/AutoloadInterceptor.php @@ -1,15 +1,15 @@ $original, + 'originalClassLoader' => $originalClassLoader, ]); // Unregister the original composer loader diff --git a/src/Service/DI.php b/src/Core/DI.php similarity index 92% rename from src/Service/DI.php rename to src/Core/DI.php index 0bf899a..0b9f728 100644 --- a/src/Service/DI.php +++ b/src/Core/DI.php @@ -1,6 +1,6 @@ Date: Wed, 19 Apr 2023 05:43:54 +0200 Subject: [PATCH 05/18] Moved "Util" folder to "Core" folder --- src/{ => Core}/Util/CodeChecker.php | 4 ++-- src/{ => Core}/Util/StringMutator.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename src/{ => Core}/Util/CodeChecker.php (84%) rename src/{ => Core}/Util/StringMutator.php (93%) diff --git a/src/Util/CodeChecker.php b/src/Core/Util/CodeChecker.php similarity index 84% rename from src/Util/CodeChecker.php rename to src/Core/Util/CodeChecker.php index e6b6e71..3849a28 100644 --- a/src/Util/CodeChecker.php +++ b/src/Core/Util/CodeChecker.php @@ -1,10 +1,10 @@ Date: Wed, 19 Apr 2023 05:44:24 +0200 Subject: [PATCH 06/18] Moved "StreamFilter" folder to "Core" folder --- .../StreamFilter/FilterInjector.php | 6 ++--- .../StreamFilter/Metadata.php | 26 +++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) rename src/{Service => Core}/StreamFilter/FilterInjector.php (80%) rename src/{Service => Core}/StreamFilter/Metadata.php (75%) diff --git a/src/Service/StreamFilter/FilterInjector.php b/src/Core/StreamFilter/FilterInjector.php similarity index 80% rename from src/Service/StreamFilter/FilterInjector.php rename to src/Core/StreamFilter/FilterInjector.php index 4076758..a90fbc2 100644 --- a/src/Service/StreamFilter/FilterInjector.php +++ b/src/Core/StreamFilter/FilterInjector.php @@ -1,13 +1,13 @@ crypto = $metadata['crypto'] ?? null; $this->mediatype = $metadata['mediatype'] ?? null; + $classContainer = DI::get(ClassContainer::class); + $this->code = DI::make(Code::class, [ 'source' => $originalSource, 'namespacedClass' => $classContainer->getNamespacedClassByPath($this->uri), From d4abd992a427b86f005b5632f6435c98a2f720b4 Mon Sep 17 00:00:00 2001 From: WalterWoshid Date: Wed, 19 Apr 2023 05:46:49 +0200 Subject: [PATCH 07/18] Moved "TransformerProcessor" to "Processor" folder --- src/Core/Processor/TransformerProcessor.php | 142 ++++++++++++++++++ .../Processor/TransformerProcessor.php | 121 --------------- 2 files changed, 142 insertions(+), 121 deletions(-) create mode 100644 src/Core/Processor/TransformerProcessor.php delete mode 100644 src/Service/Processor/TransformerProcessor.php diff --git a/src/Core/Processor/TransformerProcessor.php b/src/Core/Processor/TransformerProcessor.php new file mode 100644 index 0000000..19a1e37 --- /dev/null +++ b/src/Core/Processor/TransformerProcessor.php @@ -0,0 +1,142 @@ +code->getNamespacedClass(); + + // Process the transformers + $transformerContainers = $this->transformerMatcher->getMatchedTransformerContainers($namespacedClass); + $this->processTransformers($metadata, $transformerContainers); + + $originalFilePath = $metadata->uri; + $cacheFilePath = $this->cachePaths->getTransformedCachePath($originalFilePath); + $transformed = $metadata->code->hasChanges(); + + // Save the transformed code + if ($transformed) { + Filesystem::writeFile( + $cacheFilePath, + $metadata->code->getNewSource(), + ); + } + + // Update the cache state + $modificationTime = $_SERVER['REQUEST_TIME'] ?? time(); + if ($transformed) { + $transformerFilePaths = $this->getTransformerFilePaths($transformerContainers); + + $cacheState = DI::make(TransformedCacheState::class, [ + 'data' => [ + 'originalFilePath' => $originalFilePath, + 'modificationTime' => $modificationTime, + 'transformedFilePath' => $cacheFilePath, + 'transformerFilePaths' => $transformerFilePaths, + ], + ]); + } else { + $cacheState = DI::make(NoTransformationsCacheState::class, [ + 'data' => [ + 'originalFilePath' => $originalFilePath, + 'modificationTime' => $modificationTime, + ], + ]); + } + + $this->cacheStateManager->setCacheState($originalFilePath, $cacheState); + } + + /** + * Process the transformers. + * + * @param Metadata $metadata + * @param TransformerContainer[] $transformerContainers + * + * @return void + */ + protected function processTransformers( + Metadata $metadata, + array $transformerContainers, + ): void { + // Sort the transformers by priority + usort( + $transformerContainers, + function (TransformerContainer $a, TransformerContainer $b) { + return $a->transformerInstance->order <=> $b->transformerInstance->order; + }, + ); + + foreach ($transformerContainers as $transformerContainer) { + $transformerContainer->transformerInstance->transform( + $metadata->code, + ); + } + } + + /** + * Get the file paths of the given transformers. + * + * @param TransformerContainer[] $transformerContainers + * + * @return string[] + * + * @noinspection PhpDocMissingThrowsInspection Handled by TransformerNotFoundException + * + * @todo: Move this logic to the CacheStateManager by checking + * if the transformers exist and are up-to-date. + */ + protected function getTransformerFilePaths( + array $transformerContainers + ): array { + return array_map( + function (TransformerContainer $transformerContainer) { + /** @noinspection PhpUnhandledExceptionInspection Handled by TransformerNotFoundException */ + $reflection = new BaseReflectionClass( + $transformerContainer->transformerInstance + ); + return $reflection->getFileName(); + }, + $transformerContainers, + ); + } +} diff --git a/src/Service/Processor/TransformerProcessor.php b/src/Service/Processor/TransformerProcessor.php deleted file mode 100644 index 754e2bd..0000000 --- a/src/Service/Processor/TransformerProcessor.php +++ /dev/null @@ -1,121 +0,0 @@ -code->getClassName(); - - // Process the transformers - $transformers = $this->transformerMatcher->match($className); - $this->processTransformers($metadata, $transformers); - - $originalFilePath = $metadata->uri; - $cacheFilePath = $this->cachePaths->getTransformedCachePath($originalFilePath); - $transformed = $metadata->code->hasChanges(); - - // Save the transformed code - if ($transformed) { - Filesystem::writeFile( - $cacheFilePath, - $metadata->code->getNewSource(), - ); - } - - // Update the cache state - $fileModificationTime = $_SERVER['REQUEST_TIME'] ?? time(); - $transformerFilePaths = $this->getTransformerFilePaths($transformers); - $cacheState = DI::make(CacheState::class, [ - 'originalFilePath' => $originalFilePath, - 'className' => $className, - 'cachedFilePath' => $transformed ? $cacheFilePath : null, - 'transformedTime' => $fileModificationTime, - 'transformerFilePaths' => $transformerFilePaths, - ]); - $this->cacheStateManager->setCacheState($originalFilePath, $cacheState); - } - - /** - * Process the transformers. - * - * @param Metadata $metadata - * @param Transformer[] $transformers - * - * @return void - */ - private function processTransformers( - Metadata $metadata, - array $transformers - ): void { - // Sort the transformers by priority - usort( - $transformers, - function (Transformer $a, Transformer $b) { - return $a->order <=> $b->order; - }, - ); - - foreach ($transformers as $transformer) { - $transformer->transform($metadata->code); - } - } - - /** - * Get the file paths of the given transformers. - * - * @param Transformer[] $transformers - * - * @return string[] - * - * @noinspection PhpDocMissingThrowsInspection Handled by TransformerNotFoundException - */ - protected function getTransformerFilePaths(array $transformers): array - { - return array_map( - function (Transformer $transformer) { - /** @noinspection PhpUnhandledExceptionInspection Handled by TransformerNotFoundException */ - $reflection = new ReflectionClass($transformer); - return $reflection->getFileName(); - }, - $transformers, - ); - } -} From 3738409875ac6980a7e2436d9069516f0493f405 Mon Sep 17 00:00:00 2001 From: WalterWoshid Date: Wed, 19 Apr 2023 05:49:24 +0200 Subject: [PATCH 08/18] Renamed "TransformerContainer" to "TransformerManager" --- .../Container/TransformerManager.php} | 52 ++++++++++++------- 1 file changed, 33 insertions(+), 19 deletions(-) rename src/{Service/TransformerContainer.php => Core/Container/TransformerManager.php} (53%) diff --git a/src/Service/TransformerContainer.php b/src/Core/Container/TransformerManager.php similarity index 53% rename from src/Service/TransformerContainer.php rename to src/Core/Container/TransformerManager.php index 850828f..9e0d6cc 100644 --- a/src/Service/TransformerContainer.php +++ b/src/Core/Container/TransformerManager.php @@ -1,33 +1,37 @@ [] + * @todo CHECK if all class-string have a type */ private array $transformers = []; /** - * Associative array of transformer instances by target class name. + * The list of transformer containers. * - * @var array The key is a wildcard target class name. + * @var TransformerContainer[] */ - private array $transformerTargets = []; + private array $transformerContainers = []; // region Pre-Initialization @@ -82,24 +86,34 @@ private function loadTransformers(): void } assert($transformerInstance instanceof Transformer); - /** @var string[] $targets */ - $targets = (array)$transformerInstance->getTargetClass(); + // Create transformer container + $transformerContainer = DI::make(TransformerContainer::class, [ + 'transformerInstance' => $transformerInstance, + ]); - foreach ($targets as $classRegex) { - $this->transformerTargets[$classRegex][] = $transformerInstance; - } + $this->transformerContainers[] = $transformerContainer; } } // endregion /** - * Get the transformer targets. + * Get the transformers. + * + * @return class-string[] + */ + public function getTransformers(): array + { + return $this->transformers; + } + + /** + * Get the transformer containers. * - * @return array The key is a wildcard target class name. + * @return TransformerContainer[] */ - public function getTransformerTargets(): array + public function getTransformerContainers(): array { - return $this->transformerTargets; + return $this->transformerContainers; } } From 178e67ca1f4ec0737665c4706f41d65ce17eb7d4 Mon Sep 17 00:00:00 2001 From: WalterWoshid Date: Wed, 19 Apr 2023 05:49:47 +0200 Subject: [PATCH 09/18] Added "TransformerContainer" and refactored code --- src/Core/Container/TransformerContainer.php | 18 ++++ src/Core/Matcher/TransformerMatcher.php | 107 ++++++++++++++++++++ src/Service/Matcher/TransformerMatcher.php | 78 -------------- 3 files changed, 125 insertions(+), 78 deletions(-) create mode 100644 src/Core/Container/TransformerContainer.php create mode 100644 src/Core/Matcher/TransformerMatcher.php delete mode 100644 src/Service/Matcher/TransformerMatcher.php diff --git a/src/Core/Container/TransformerContainer.php b/src/Core/Container/TransformerContainer.php new file mode 100644 index 0000000..f0f9adc --- /dev/null +++ b/src/Core/Container/TransformerContainer.php @@ -0,0 +1,18 @@ + + */ + private array $matchedTransformerContainers = []; + + /** + * Check if the class should be transformed. + * + * @param string $namespacedClass + * @param string $filePath + * + * @return bool + */ + public function match(string $namespacedClass, string $filePath): bool + { + // Get the transformers + $transformerContainers = $this->transformerContainer->getTransformerContainers(); + + // Match the transformers + $matchedTransformerContainers = []; + foreach ($transformerContainers as $transformerContainer) { + $wildcardPatterns = (array)$transformerContainer->transformerInstance->getTargetClass(); + + foreach ($wildcardPatterns as $wildcardPattern) { + $regex = Regex::fromWildcard($wildcardPattern); + if ($regex->matches($namespacedClass)) { + // Check if the transformer has already been matched + $alreadyMatched = array_reduce( + $matchedTransformerContainers, + function (bool $carry, TransformerContainer $container) use ($transformerContainer) { + return $carry || $container === $transformerContainer; + }, + false, + ); + + if ($alreadyMatched) { + continue; + } + + $matchedTransformerContainers[] = $transformerContainer; + } + } + } + + // Cache the result + $this->matchedTransformerContainers[$namespacedClass] = $matchedTransformerContainers; + + // Cache the result + if (!$matchedTransformerContainers) { + $cacheState = DI::make(EmptyResultCacheState::class, [ + 'data' => [ + 'originalFilePath' => $filePath, + 'modificationTime' => filemtime($filePath), + ], + ]); + + // Set the cache state + $this->cacheStateManager->setCacheState( + $filePath, + $cacheState, + ); + } + + return (bool)$matchedTransformerContainers; + } + + /** + * Get the matched transformers for the given class. + * + * @param string $namespacedClass + * + * @return TransformerContainer[] + */ + public function getMatchedTransformerContainers(string $namespacedClass): array + { + // Check if the query has been cached + return $this->matchedTransformerContainers[$namespacedClass] ?? []; + } +} diff --git a/src/Service/Matcher/TransformerMatcher.php b/src/Service/Matcher/TransformerMatcher.php deleted file mode 100644 index abf8f52..0000000 --- a/src/Service/Matcher/TransformerMatcher.php +++ /dev/null @@ -1,78 +0,0 @@ - - */ - private array $transformerQueryResultCache = []; - - /** - * Check if the class should be transformed. - * - * @param string $namespacedClass - * - * @return bool - */ - public function shouldTransform(string $namespacedClass): bool - { - return $this->match($namespacedClass) !== []; - } - - /** - * Return the list of transformers that match the given class name. - * - * @param string $namespacedClass - * - * @return Transformer[] - */ - public function match(string $namespacedClass): array - { - // Check if the query has been cached - if (isset($this->transformerQueryResultCache[$namespacedClass])) { - return $this->transformerQueryResultCache[$namespacedClass]; - } - - // Match the transformers - $matchedInstances = []; - foreach ($this->transformerContainer->getTransformerTargets() as $classRegex => $instances) { - $regex = Regex::fromWildcard($classRegex); - if ($regex->matches($namespacedClass)) { - // Check if the transformer has already been matched - $alreadyMatched = array_filter( - $matchedInstances, - function (Transformer $transformer) use ($instances) { - return in_array($transformer, $instances, true); - }, - ); - - if ($alreadyMatched) { - continue; - } - - $matchedInstances = array_merge($matchedInstances, $instances); - } - } - - // Cache the query result - $this->transformerQueryResultCache[$namespacedClass] = $matchedInstances; - - return $matchedInstances; - } -} From a82e253a1830b7e0bcbd969a4abe25f1cce91cb5 Mon Sep 17 00:00:00 2001 From: WalterWoshid Date: Wed, 19 Apr 2023 05:50:19 +0200 Subject: [PATCH 10/18] Moved exceptions to "Core/Exception" folder --- .../Exception/CodeTransformerException.php | 2 +- .../DirectKernelInitializationException.php | 8 +++--- src/{ => Core}/Exception/KernelException.php | 2 +- .../StreamFilter/InvalidStreamException.php | 6 ++--- .../Exception/StreamFilterException.php | 2 +- .../InvalidTransformerClassException.php | 27 +++++++++++++++++++ .../Exception/Transformer/SyntaxError.php | 12 ++++----- .../TransformerNotFoundException.php | 6 ++--- .../Exception/TransformerException.php | 2 +- .../InvalidTransformerClassException.php | 25 ----------------- 10 files changed, 47 insertions(+), 45 deletions(-) rename src/{ => Core}/Exception/CodeTransformerException.php (76%) rename src/{ => Core}/Exception/Kernel/DirectKernelInitializationException.php (56%) rename src/{ => Core}/Exception/KernelException.php (74%) rename src/{ => Core}/Exception/StreamFilter/InvalidStreamException.php (64%) rename src/{ => Core}/Exception/StreamFilterException.php (74%) create mode 100644 src/Core/Exception/Transformer/InvalidTransformerClassException.php rename src/{ => Core}/Exception/Transformer/SyntaxError.php (57%) rename src/{ => Core}/Exception/Transformer/TransformerNotFoundException.php (65%) rename src/{ => Core}/Exception/TransformerException.php (76%) delete mode 100644 src/Exception/Transformer/InvalidTransformerClassException.php diff --git a/src/Exception/CodeTransformerException.php b/src/Core/Exception/CodeTransformerException.php similarity index 76% rename from src/Exception/CodeTransformerException.php rename to src/Core/Exception/CodeTransformerException.php index 5723535..f14cba3 100644 --- a/src/Exception/CodeTransformerException.php +++ b/src/Core/Exception/CodeTransformerException.php @@ -1,6 +1,6 @@ message\n\nFull code:\n```php\n$code\n```", + message: "Syntax error in transformed code: $diagnostic->message\n\nFull code:\n```php\n$code\n```", previous: $previous, ); } diff --git a/src/Exception/Transformer/TransformerNotFoundException.php b/src/Core/Exception/Transformer/TransformerNotFoundException.php similarity index 65% rename from src/Exception/Transformer/TransformerNotFoundException.php rename to src/Core/Exception/Transformer/TransformerNotFoundException.php index b4cbda0..b8f90f6 100644 --- a/src/Exception/Transformer/TransformerNotFoundException.php +++ b/src/Core/Exception/Transformer/TransformerNotFoundException.php @@ -1,8 +1,8 @@ Date: Wed, 19 Apr 2023 05:52:05 +0200 Subject: [PATCH 11/18] Refactored autoload interceptor --- .../AutoloadInterceptor}/ClassContainer.php | 8 +- .../AutoloadInterceptor}/ClassLoader.php | 97 ++++++++++--------- 2 files changed, 57 insertions(+), 48 deletions(-) rename src/{Service/ClassLoader => Core/AutoloadInterceptor}/ClassContainer.php (72%) rename src/{Service/ClassLoader => Core/AutoloadInterceptor}/ClassLoader.php (51%) diff --git a/src/Service/ClassLoader/ClassContainer.php b/src/Core/AutoloadInterceptor/ClassContainer.php similarity index 72% rename from src/Service/ClassLoader/ClassContainer.php rename to src/Core/AutoloadInterceptor/ClassContainer.php index e6b375e..0ffeab9 100644 --- a/src/Service/ClassLoader/ClassContainer.php +++ b/src/Core/AutoloadInterceptor/ClassContainer.php @@ -1,7 +1,13 @@ original->findFile($namespacedClass); + $filePath = $this->originalClassLoader->findFile($namespacedClass); // @codeCoverageIgnoreStart // Not sure how to test this @@ -100,53 +104,52 @@ public function findFile($namespacedClass): false|string $filePath = Path::resolve($filePath); + + + // Query cache state + $cacheState = $this->cacheStateManager->queryCacheState($filePath); + + // Check if the file is cached and up to date + if ($cacheState?->isFresh() && !$this->options->isDebug()) { + // Use the cached file if transformations have been applied + // Or return the original file if no transformations have been applied + return $cacheState->getFilePath() ?? $filePath; + } + + // Check if the class should be transformed - if ($this->transformerMatcher->shouldTransform($namespacedClass)) { - $cacheState = $this->cacheStateManager->queryCacheState($filePath); - - // Check if the file is cached and up to date - if (!$this->options->isDebug() && $cacheState?->isFresh()) { - // Use the cached file if transformations have been applied - // Or return the original file if no transformations have been applied - return $cacheState->cachedFilePath ?? $filePath; - } - - // Add the class to store the file path - $this->classContainer->addNamespacedClassPath($filePath, $namespacedClass); - - // Replace the file path with a PHP stream filter - /** @see StreamFilter::filter() */ - return $this->filterInjector->rewrite($filePath); + if (!$this->transformerMatcher->match($namespacedClass, $filePath)) { + return $filePath; } - return $filePath; + // Add the class to store the file path + $this->classContainer->addNamespacedClassPath($filePath, $namespacedClass); + + // Replace the file path with a PHP stream filter + /** @see StreamFilter::filter() */ + return $this->filterInjector->rewrite($filePath); } /** * Check if the class is internal to the Code Transformer. * - * @param string $class + * @param string $namespacedClass * * @return bool */ - protected function isInternal(string $class): bool + protected function isInternal(string $namespacedClass): bool { - // Code Transformer - if (str_starts_with($class, "Okapi\\CodeTransformer\\") - && !str_starts_with($class, "Okapi\\CodeTransformer\\Tests\\")) { - return true; - } - - // Wildcards - if (str_starts_with($class, "Okapi\\Wildcards\\")) { - return true; - } - - // DI - if (str_starts_with($class, "DI\\")) { - return true; - } - - return false; + return str_starts_with_any_but_not( + $namespacedClass, + [ + 'Okapi\\CodeTransformer\\', + 'Okapi\\Path\\', + 'Okapi\\Wildcards\\', + 'DI\\' + ], + [ + 'Okapi\\CodeTransformer\\Tests\\' + ] + ); } } From fb433c0297eeb246fbf73cb2bad1b09177e5b461 Mon Sep 17 00:00:00 2001 From: WalterWoshid Date: Wed, 19 Apr 2023 05:52:19 +0200 Subject: [PATCH 12/18] Refactored caching system --- src/{Service => Core}/Cache/CachePaths.php | 22 +- src/Core/Cache/CacheState.php | 297 ++++++++++++++++++ .../CacheState/EmptyResultCacheState.php | 19 ++ .../NoTransformationsCacheState.php | 17 + .../CacheState/TransformedCacheState.php | 69 ++++ src/Core/Cache/CacheStateFactory.php | 73 +++++ .../Cache}/CacheStateManager.php | 118 ++++--- src/Service/Cache/CacheState.php | 163 ---------- 8 files changed, 542 insertions(+), 236 deletions(-) rename src/{Service => Core}/Cache/CachePaths.php (84%) create mode 100644 src/Core/Cache/CacheState.php create mode 100644 src/Core/Cache/CacheState/EmptyResultCacheState.php create mode 100644 src/Core/Cache/CacheState/NoTransformationsCacheState.php create mode 100644 src/Core/Cache/CacheState/TransformedCacheState.php create mode 100644 src/Core/Cache/CacheStateFactory.php rename src/{Service => Core/Cache}/CacheStateManager.php (64%) delete mode 100644 src/Service/Cache/CacheState.php diff --git a/src/Service/Cache/CachePaths.php b/src/Core/Cache/CachePaths.php similarity index 84% rename from src/Service/Cache/CachePaths.php rename to src/Core/Cache/CachePaths.php index a1cd0d4..998db4a 100644 --- a/src/Service/Cache/CachePaths.php +++ b/src/Core/Cache/CachePaths.php @@ -1,19 +1,26 @@ $data + */ + public function __construct( + array $data = [], + ) { + $this->setData($data); + } + + /** + * Get the cache state as an array. + * + * @return array + */ + public function toArray(): array + { + return [ + static::TYPE => $this->getType(), + ...$this->getAllData(), + ]; + } + + /** + * Get the cache state as an array. + * + * @return array + */ + private function getAllData(): array + { + $data = []; + + foreach ($this->getRequiredKeys() as $key) { + $data[$key] = $this->{$key}; + } + + return $data; + } + + /** + * Get the cache state type. + * + * @return string + */ + protected function getType(): string + { + // Return the class name without the namespace + return substr( + static::class, + strrpos(static::class, '\\') + 1, + ); + } + + /** + * Get the required keys for the cache state. + * + * @return string[] + */ + public function getRequiredKeys(): array + { + return [ + static::ORIGINAL_FILE_PATH_KEY, + static::MODIFICATION_TIME_KEY, + ]; + } + + /** + * Validate the cache state. + * + * @param array $cacheStateArray + * + * @return bool + */ + public function valid(array $cacheStateArray): bool + { + // Check if all required keys are present + foreach ($this->getRequiredKeys() as $requiredKey) { + if (!isset($cacheStateArray[$requiredKey])) { + // @codeCoverageIgnoreStart + return false; + // @codeCoverageIgnoreEnd + } + } + + return true; + } + + /** + * Set the cache state data. + * + * @param array $cacheStateArray + */ + public function setData(array $cacheStateArray): void + { + foreach ($cacheStateArray as $key => $value) { + $this->{$key} = $value; + } + } + + /** + * Check if the cache state is fresh (not outdated). + * + * @return bool + */ + public function isFresh(): bool + { + if (filemtime($this->originalFilePath) > $this->modificationTime) { + return false; + } + + return true; + } + + /** + * Get the file path. + * + * @return string|null + */ + abstract public function getFilePath(): ?string; + + // /** + // * CacheState constructor. + // * + // * @param string $originalFilePath + // * @param string $className + // * @param string|null $cachedFilePath + // * @param int|null $transformedTime + // * @param string[]|null $transformerFilePaths + // */ + // public function __construct( + // public string $originalFilePath, + // public string $className, + // public ?string $cachedFilePath, + // public ?int $transformedTime, + // public ?array $transformerFilePaths, + // ) {} + // + // /** + // * Use the cached file path if aspects have been applied. + // * Otherwise, use the original file path if no aspects have been applied. + // * + // * @return string + // */ + // public function getFilePath(): string + // { + // return $this->cachedFilePath ?? $this->originalFilePath; + // } + // + // + // + // + // /** + // * Get the cache state as an array. + // * + // * @return array + // */ + // public function toArray(): array + // { + // return [ + // $this->originalFilePath, + // $this->className, + // $this->cachedFilePath, + // $this->transformedTime, + // $this->transformerFilePaths, + // ]; + // } + // + // /** + // * Check if the cache is not outdated. + // * + // * @return bool + // */ + // public function isFresh(): bool + // { + // // @codeCoverageIgnoreStart + // // This should only happen if the project is misconfigured + // if ($this->checkInfiniteLoop()) { + // return false; + // } + // // @codeCoverageIgnoreEnd + // + // $allFiles = array_merge( + // [$this->originalFilePath], + // $this->transformerFilePaths, + // ); + // + // if ($this->checkFilesModified($allFiles)) { + // return false; + // } + // + // if ($this->cachedFilePath) { + // $allFiles[] = $this->cachedFilePath; + // } + // + // if (!$this->checkFilesExist($allFiles)) { + // return false; + // } + // + // if (!$this->checkTransformerCount()) { + // return false; + // } + // + // return true; + // } + // + // /** + // * Check if the cache is in an infinite loop. + // * + // * @return bool True if the cache is in an infinite loop + // */ + // protected function checkInfiniteLoop(): bool + // { + // if ($this->cachedFilePath !== null) { + // // Same original file and cached file + // if ($this->originalFilePath === $this->cachedFilePath) { + // return true; + // } + // } + // + // return false; + // } + // + // /** + // * Check if the files have been modified. + // * + // * @param string[] $files + // * + // * @return bool True if any file has been modified + // */ + // protected function checkFilesModified(array $files): bool + // { + // $lastModified = max(array_map('filemtime', $files)); + // if ($lastModified >= $this->transformedTime) { + // return true; + // } + // + // return false; + // } + // + // /** + // * Check if the files exist. + // * + // * @param string[] $files + // * + // * @return bool True if all files exist + // */ + // protected function checkFilesExist(array $files): bool + // { + // // Check if the cache file exists + // foreach ($files as $file) { + // if (!file_exists($file)) { + // return false; + // } + // } + // + // return true; + // } + // + // /** + // * Check if the transformer count is the same. + // * + // * @return bool True if the count is the same + // */ + // protected function checkTransformerCount(): bool + // { + // // Checking the count alone should be enough + // $cachedTransformerCount = count($this->transformerFilePaths); + // $currentTransformerCount = count( + // $this->transformerMatcher->match($this->className), + // ); + // if ($cachedTransformerCount !== $currentTransformerCount) { + // return false; + // } + // + // return true; + // } +} diff --git a/src/Core/Cache/CacheState/EmptyResultCacheState.php b/src/Core/Cache/CacheState/EmptyResultCacheState.php new file mode 100644 index 0000000..9c98dc3 --- /dev/null +++ b/src/Core/Cache/CacheState/EmptyResultCacheState.php @@ -0,0 +1,19 @@ +transformerFilePaths as $transformerFilePath) { + if (!file_exists($transformerFilePath)) { + // @codeCoverageIgnoreStart + return false; + // @codeCoverageIgnoreEnd + } + + if (filemtime($transformerFilePath) > $this->modificationTime) { + return false; + } + } + + // Check if the transformed file has been deleted + if (!file_exists($this->transformedFilePath)) { + return false; + } + + return true; + } + + /** + * @inheritDoc + */ + public function getFilePath(): ?string + { + return $this->transformedFilePath; + } +} diff --git a/src/Core/Cache/CacheStateFactory.php b/src/Core/Cache/CacheStateFactory.php new file mode 100644 index 0000000..58494d8 --- /dev/null +++ b/src/Core/Cache/CacheStateFactory.php @@ -0,0 +1,73 @@ + TransformedCacheState::class, + 'EmptyResultCacheState' => EmptyResultCacheState::class, + 'NoTransformationsCacheState' => NoTransformationsCacheState::class, + ]; + + /** + * Create cache states from the cache state file. + * + * @param array> $cacheStatesArray + * + * @return CacheState[] + */ + public function createCacheStates(array $cacheStatesArray): array + { + $cacheStateObjects = []; + + foreach ($cacheStatesArray as $cacheStateArray) { + // Skip cache states without a type + if (!isset($cacheStateArray[CacheState::TYPE])) { + // @codeCoverageIgnoreStart + continue; + // @codeCoverageIgnoreEnd + } + + // Get type + $type = $cacheStateArray[CacheState::TYPE]; + unset($cacheStateArray[CacheState::TYPE]); + + // Instantiate cache state + try { + /** @var CacheState $cacheState */ + $cacheState = DI::make(self::CACHE_STATE_MAP[$type]); + // @codeCoverageIgnoreStart + } catch (TypeError) { + continue; + } + // @codeCoverageIgnoreEnd + + // Validate cache state + if (!$cacheState->valid($cacheStateArray)) { + // @codeCoverageIgnoreStart + continue; + // @codeCoverageIgnoreEnd + } + + // Set cache state data + $cacheState->setData($cacheStateArray); + + // Add cache state to array + $cacheStateObjects[$cacheState->originalFilePath] = $cacheState; + } + + return $cacheStateObjects; + } +} diff --git a/src/Service/CacheStateManager.php b/src/Core/Cache/CacheStateManager.php similarity index 64% rename from src/Service/CacheStateManager.php rename to src/Core/Cache/CacheStateManager.php index 5295078..c66bb04 100644 --- a/src/Service/CacheStateManager.php +++ b/src/Core/Cache/CacheStateManager.php @@ -1,16 +1,17 @@ + * The hash keyword. */ - public array $cacheState = []; + public const HASH = 'hash'; /** - * Whether the cache state has changed. - * - * @var bool + * Cached metadata for the transformation state of a file. * - * @todo Implement this + * @var array The key is the original file path. */ - private bool $cacheStateChanged = false; - - // region DI - - #[Inject] - private Options $options; - - #[Inject] - private CachePaths $cachePaths; - - // endregion + public array $cacheState = []; // region Initialization @@ -68,6 +71,7 @@ class CacheStateManager implements ServiceInterface public function register(): void { $this->initializeCacheDirectory(); + $this->loadCacheState(); } @@ -98,50 +102,38 @@ private function loadCacheState(): void } // Read file - $cacheStateContent = Filesystem::readFile($cacheFilePath); + $cacheFileContent = Filesystem::readFile($cacheFilePath); $appDir = $this->options->getAppDir(); $cacheDir = $this->options->getCacheDir(); // Replace the keywords - $cacheStateContent = str_replace( + $cacheFileContent = str_replace( [static::CODE_TRANSFORMER_APP_DIR, static::CODE_TRANSFORMER_CACHE_DIR], [addslashes($appDir), addslashes($cacheDir)], - $cacheStateContent, + $cacheFileContent, ); // Remove the opening PHP tag - $cacheStateContent = preg_replace('/^<\?php/', '', $cacheStateContent); + $cacheFileContent = preg_replace('/^<\?php/', '', $cacheFileContent); // Unserialize - $cacheState = eval($cacheStateContent); - - // Filter out invalid items - $cacheState = array_filter( - $cacheState, - function ($cacheStateItem) { - return key_exists('className', $cacheStateItem) - && key_exists('cachedFilePath', $cacheStateItem) - && key_exists('transformedTime', $cacheStateItem) - && key_exists('transformerFilePaths', $cacheStateItem); - }, - ); + $cacheStatesArray = eval($cacheFileContent); + + // Check the hash + $transformers = $this->transformerManager->getTransformers(); + $hash = md5(serialize($transformers)); + if (!isset($cacheStatesArray[static::HASH]) + || $cacheStatesArray[static::HASH] !== $hash + ) { + return; + } + unset($cacheStatesArray[static::HASH]); - // Convert to array of CacheState objects - array_walk( - $cacheState, - function (&$cacheStateItem, $originalFilePath) { - $cacheStateItem = DI::make(CacheState::class, [ - 'originalFilePath' => $originalFilePath, - 'className' => $cacheStateItem['className'], - 'cachedFilePath' => $cacheStateItem['cachedFilePath'], - 'transformedTime' => $cacheStateItem['transformedTime'], - 'transformerFilePaths' => $cacheStateItem['transformerFilePaths'], - ]); - }, - ); + // Create the cache state + $cacheStates = $this->cacheStateFactory->createCacheStates($cacheStatesArray); - $this->cacheState = $cacheState; + $this->cacheState = $cacheStates; } // endregion @@ -172,15 +164,19 @@ private function saveCacheState(): void // Opening PHP tag $phpCode = " $cacheState->toArray(), - $this->cacheState, - ), - true, + // Create the cache state array + $cacheStateArray = array_map( + fn (CacheState $cacheState) => $cacheState->toArray(), + $this->cacheState, ); + // Set the hash + $transformers = $this->transformerManager->getTransformers(); + $cacheStateArray[static::HASH] = md5(serialize($transformers)); + + // Serialize the cache state + $phpCode .= var_export($cacheStateArray, true); + // Semicolon $phpCode .= ';'; @@ -227,8 +223,6 @@ public function setCacheState( string $filePath, CacheState $cacheState, ): void { - $this->cacheStateChanged = true; - $this->cacheState[$filePath] = $cacheState; } } diff --git a/src/Service/Cache/CacheState.php b/src/Service/Cache/CacheState.php deleted file mode 100644 index 6579957..0000000 --- a/src/Service/Cache/CacheState.php +++ /dev/null @@ -1,163 +0,0 @@ - $this->className, - 'cachedFilePath' => $this->cachedFilePath, - 'transformedTime' => $this->transformedTime, - 'transformerFilePaths' => $this->transformerFilePaths, - ]; - } - - /** - * Check if the cache is not outdated. - * - * @return bool - */ - public function isFresh(): bool - { - // @codeCoverageIgnoreStart - // This should only happen if the project is misconfigured - if ($this->checkInfiniteLoop()) { - return false; - } - // @codeCoverageIgnoreEnd - - $allFiles = array_merge( - [$this->originalFilePath], - $this->transformerFilePaths, - ); - - if ($this->checkFilesModified($allFiles)) { - return false; - } - - if ($this->cachedFilePath) { - $allFiles[] = $this->cachedFilePath; - } - - if (!$this->checkFilesExist($allFiles)) { - return false; - } - - if (!$this->checkTransformerCount()) { - return false; - } - - return true; - } - - /** - * Check if the cache is in an infinite loop. - * - * @return bool True if the cache is in an infinite loop - */ - protected function checkInfiniteLoop(): bool - { - if ($this->cachedFilePath !== null) { - // Same original file and cached file - if ($this->originalFilePath === $this->cachedFilePath) { - return true; - } - } - - return false; - } - - /** - * Check if the files have been modified. - * - * @param string[] $files - * - * @return bool True if any file has been modified - */ - protected function checkFilesModified(array $files): bool - { - $lastModified = max(array_map('filemtime', $files)); - if ($lastModified >= $this->transformedTime) { - return true; - } - - return false; - } - - /** - * Check if the files exist. - * - * @param string[] $files - * - * @return bool True if all files exist - */ - protected function checkFilesExist(array $files): bool - { - // Check if the cache file exists - foreach ($files as $file) { - if (!file_exists($file)) { - return false; - } - } - - return true; - } - - /** - * Check if the transformer count is the same. - * - * @return bool True if the count is the same - */ - protected function checkTransformerCount(): bool - { - // Checking the count alone should be enough - $cachedTransformerCount = count($this->transformerFilePaths); - $currentTransformerCount = count( - $this->transformerMatcher->match($this->className) - ); - if ($cachedTransformerCount !== $currentTransformerCount) { - return false; - } - - return true; - } -} From 03ae131f2ba297af53b096cf99493986af73b62c Mon Sep 17 00:00:00 2001 From: WalterWoshid Date: Wed, 19 Apr 2023 05:52:46 +0200 Subject: [PATCH 13/18] Refactored code --- src/CodeTransformerKernel.php | 64 +++++++++++++++++------------------ src/Transformer.php | 8 +++-- 2 files changed, 37 insertions(+), 35 deletions(-) diff --git a/src/CodeTransformerKernel.php b/src/CodeTransformerKernel.php index 4a10bad..9323179 100644 --- a/src/CodeTransformerKernel.php +++ b/src/CodeTransformerKernel.php @@ -3,19 +3,19 @@ namespace Okapi\CodeTransformer; use DI\Attribute\Inject; -use Okapi\CodeTransformer\Exception\Kernel\DirectKernelInitializationException; -use Okapi\CodeTransformer\Service\AutoloadInterceptor; -use Okapi\CodeTransformer\Service\CacheStateManager; -use Okapi\CodeTransformer\Service\DI; -use Okapi\CodeTransformer\Service\Options; -use Okapi\CodeTransformer\Service\StreamFilter; -use Okapi\CodeTransformer\Service\TransformerContainer; +use Okapi\CodeTransformer\Core\AutoloadInterceptor; +use Okapi\CodeTransformer\Core\Cache\CacheStateManager; +use Okapi\CodeTransformer\Core\Container\TransformerManager; +use Okapi\CodeTransformer\Core\DI; +use Okapi\CodeTransformer\Core\Exception\Kernel\DirectKernelInitializationException; +use Okapi\CodeTransformer\Core\Options; +use Okapi\CodeTransformer\Core\StreamFilter; use Okapi\Singleton\Singleton; /** * # Code Transformer Kernel * - * The `CodeTransformerKernel` is the heart of the Code Transformer library. + * This class is the heart of the Code Transformer library. * It manages an environment for Code Transformation. * * 1. Extends this class and define a list of transformers in the @@ -26,6 +26,30 @@ abstract class CodeTransformerKernel { use Singleton; + // region DI + + #[Inject] + private Options $options; + + #[Inject] + protected TransformerManager $transformerContainer; + + #[Inject] + private CacheStateManager $cacheStateManager; + + #[Inject] + private StreamFilter $streamFilter; + + #[Inject] + private AutoloadInterceptor $autoloadInterceptor; + + /** + * Make the constructor public to allow the DI container to instantiate the class. + */ + public function __construct() {} + + // endregion + // region Settings /** @@ -61,30 +85,6 @@ abstract class CodeTransformerKernel */ protected array $transformers = []; - // region DI - - #[Inject] - private Options $options; - - #[Inject] - protected TransformerContainer $transformerContainer; - - #[Inject] - private CacheStateManager $cacheStateManager; - - #[Inject] - private StreamFilter $streamFilter; - - #[Inject] - private AutoloadInterceptor $autoloadInterceptor; - - /** - * Make the constructor public to allow the DI container to instantiate the class. - */ - public function __construct() {} - - // endregion - /** * Resolve instance with dependency injection. * diff --git a/src/Transformer.php b/src/Transformer.php index 0a98de4..9e783d9 100644 --- a/src/Transformer.php +++ b/src/Transformer.php @@ -2,12 +2,12 @@ namespace Okapi\CodeTransformer; -use Okapi\CodeTransformer\Service\StreamFilter\Metadata\Code; +use Okapi\CodeTransformer\Transformer\Code; /** * # Code Transformer * - * The `CodeTransformer` class provides a foundation for creating a transformer. + * This class provides a foundation for creating a transformer. * * Transformers extend this class and implement following methods: * - `getTargetClass()` - Returns the target class name(s) that this transformer @@ -25,6 +25,8 @@ abstract class Transformer /** * Get the target class name that this transformer will be applied to. * + * Wildcards are supported. See Okapi/Wildcards for more information. + * * @return class-string|class-string[] */ abstract public function getTargetClass(): string|array; @@ -32,7 +34,7 @@ abstract public function getTargetClass(): string|array; /** * Transform the source code. * - * @param Code $code + * @param Code $code * * @return void */ From cc6510e1812381f9e79b11609e5627a503990df7 Mon Sep 17 00:00:00 2001 From: WalterWoshid Date: Wed, 19 Apr 2023 05:52:57 +0200 Subject: [PATCH 14/18] Refactored tests --- tests/ClassLoaderMockTrait.php | 14 +- tests/Functional/DirectKernelTest.php | 3 +- .../InvalidTransformerClassTest.php | 5 +- ...licationTest.php => A_ApplicationTest.php} | 42 +++--- ...onTest.php => B_CachedApplicationTest.php} | 125 ++++++++++-------- .../Workflow/C_AddedTransformerTest.php | 46 +++++++ .../ClassesToTransform/ChangedTransformer.php | 11 ++ ...dKernel.php => AddedTransformerKernel.php} | 2 +- tests/Stubs/Kernel/ApplicationKernel.php | 6 +- tests/Stubs/Transformer/AddedTransformer1.php | 4 +- tests/Stubs/Transformer/AddedTransformer2.php | 2 +- ...former.php => ChangedClassTransformer.php} | 8 +- .../ChangedTransformerTransformer.php | 40 ++++++ .../DeleteCacheFileTransformer.php | 2 +- .../Stubs/Transformer/InvalidTransformer.php | 2 +- .../Transformer/NoChangesTransformer.php | 2 +- tests/Stubs/Transformer/StringTransformer.php | 2 +- .../Transformer/SyntaxErrorTransformer.php | 2 +- .../Transformer/UnPrivateTransformer.php | 2 +- 19 files changed, 223 insertions(+), 97 deletions(-) rename tests/Functional/Workflow/{ApplicationTest.php => A_ApplicationTest.php} (76%) rename tests/Functional/Workflow/{CachedApplicationTest.php => B_CachedApplicationTest.php} (54%) create mode 100644 tests/Functional/Workflow/C_AddedTransformerTest.php create mode 100644 tests/Stubs/ClassesToTransform/ChangedTransformer.php rename tests/Stubs/Kernel/{CachedKernel.php => AddedTransformerKernel.php} (83%) rename tests/Stubs/Transformer/{ChangedTransformer.php => ChangedClassTransformer.php} (80%) create mode 100644 tests/Stubs/Transformer/ChangedTransformerTransformer.php diff --git a/tests/ClassLoaderMockTrait.php b/tests/ClassLoaderMockTrait.php index f01846c..acc835b 100644 --- a/tests/ClassLoaderMockTrait.php +++ b/tests/ClassLoaderMockTrait.php @@ -2,11 +2,11 @@ namespace Okapi\CodeTransformer\Tests; -use Okapi\CodeTransformer\Service\ClassLoader\ClassLoader; -use Okapi\CodeTransformer\Service\Cache\CachePaths; -use Okapi\CodeTransformer\Service\DI; -use Okapi\CodeTransformer\Service\StreamFilter; -use Okapi\CodeTransformer\Service\StreamFilter\FilterInjector; +use Okapi\CodeTransformer\Core\AutoloadInterceptor\ClassLoader; +use Okapi\CodeTransformer\Core\Cache\CachePaths; +use Okapi\CodeTransformer\Core\DI; +use Okapi\CodeTransformer\Core\StreamFilter; +use Okapi\CodeTransformer\Core\StreamFilter\FilterInjector; use Okapi\Path\Path; use PHPUnit\Framework\Assert; use ReflectionProperty; @@ -30,7 +30,7 @@ private function findOriginalClassMock(string $class): string $this->findClassLoader(); } - $original = new ReflectionProperty(ClassLoader::class, 'original'); + $original = new ReflectionProperty(ClassLoader::class, 'originalClassLoader'); $original = $original->getValue($this->classLoader); return $original->findFile($class); } @@ -59,7 +59,7 @@ public function assertWillBeTransformed(string $className): void Assert::assertEquals( $transformPath, $filePathMock, - $className . ' will not be transformed', + "$className will not be transformed", ); } diff --git a/tests/Functional/DirectKernelTest.php b/tests/Functional/DirectKernelTest.php index 3b5b3a6..5706bb1 100644 --- a/tests/Functional/DirectKernelTest.php +++ b/tests/Functional/DirectKernelTest.php @@ -3,8 +3,7 @@ namespace Okapi\CodeTransformer\Tests\Functional; use Okapi\CodeTransformer\CodeTransformerKernel; -use Okapi\CodeTransformer\Exception\Kernel\DirectKernelInitializationException; -use Okapi\CodeTransformer\Tests\Util; +use Okapi\CodeTransformer\Core\Exception\Kernel\DirectKernelInitializationException; use PHPUnit\Framework\TestCase; class DirectKernelTest extends TestCase diff --git a/tests/Functional/InvalidTransformerClassTest.php b/tests/Functional/InvalidTransformerClassTest.php index 1351306..3cd92e0 100644 --- a/tests/Functional/InvalidTransformerClassTest.php +++ b/tests/Functional/InvalidTransformerClassTest.php @@ -2,11 +2,10 @@ namespace Okapi\CodeTransformer\Tests\Functional; -use Okapi\CodeTransformer\Exception\Transformer\InvalidTransformerClassException; -use Okapi\CodeTransformer\Exception\Transformer\TransformerNotFoundException; +use Okapi\CodeTransformer\Core\Exception\Transformer\InvalidTransformerClassException; +use Okapi\CodeTransformer\Core\Exception\Transformer\TransformerNotFoundException; use Okapi\CodeTransformer\Tests\Stubs\Kernel\TransformerDoesNotExistKernel; use Okapi\CodeTransformer\Tests\Stubs\Kernel\TransformerDoesNotExtendTransformerKernel; -use Okapi\CodeTransformer\Tests\Util; use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses; use PHPUnit\Framework\TestCase; diff --git a/tests/Functional/Workflow/ApplicationTest.php b/tests/Functional/Workflow/A_ApplicationTest.php similarity index 76% rename from tests/Functional/Workflow/ApplicationTest.php rename to tests/Functional/Workflow/A_ApplicationTest.php index 665e6b0..458018c 100644 --- a/tests/Functional/Workflow/ApplicationTest.php +++ b/tests/Functional/Workflow/A_ApplicationTest.php @@ -2,9 +2,9 @@ namespace Okapi\CodeTransformer\Tests\Functional\Workflow; -use Okapi\CodeTransformer\Exception\Transformer\SyntaxError; -use Okapi\CodeTransformer\Service\CacheStateManager; -use Okapi\CodeTransformer\Service\DI; +use Okapi\CodeTransformer\Core\Cache\CacheStateManager; +use Okapi\CodeTransformer\Core\DI; +use Okapi\CodeTransformer\Core\Exception\Transformer\SyntaxError; use Okapi\CodeTransformer\Tests\ClassLoaderMockTrait; use Okapi\CodeTransformer\Tests\Stubs\ClassesToTransform; use Okapi\CodeTransformer\Tests\Stubs\Kernel\ApplicationKernel; @@ -15,7 +15,7 @@ use PHPUnit\Framework\TestCase; #[RunClassInSeparateProcess] -class ApplicationTest extends TestCase +class A_ApplicationTest extends TestCase { use ClassLoaderMockTrait; @@ -44,7 +44,7 @@ public function testStringClass(): void $stringClass = new $class(); $this->assertSame('Hello from Code Transformer!', $stringClass->test()); - $file = __DIR__ . '/../../Stubs/ClassesToTransform/StringClass.php'; + $file = __DIR__ . '/../../Stubs/ClassesToTransform/StringClass.php'; $content = Filesystem::readFile($file); $this->assertEquals($content, StringTransformer::$originalSourceCode); @@ -78,9 +78,26 @@ public function testChangedClass(): void $class = ClassesToTransform\ChangedClass::class; $this->assertWillBeTransformed($class); - /** @noinspection PhpUnusedLocalVariableInspection */ $changedClass = new $class(); - $this->assertSame('Hello from Code Transformer!', $changedClass->test()); + $this->assertSame( + 'Hello World from Code Transformer!', + $changedClass->test(), + ); + } + + /** + * Cached test in {@see CachedApplicationTest::testChangedTransformer()} + */ + public function testChangedTransformer(): void + { + $class = ClassesToTransform\ChangedTransformer::class; + $this->assertWillBeTransformed($class); + + $changedTransformer = new $class(); + $this->assertSame( + 'Hello World from Code Transformer!', + $changedTransformer->test(), + ); } /** @@ -104,6 +121,7 @@ public function testMultipleTransformers(): void $this->assertWillBeTransformed($class); $multipleTransformersClass = new $class(); + $this->assertSame('Hello from Code Transformer!', $multipleTransformersClass->test()); $this->assertSame("You can't get me!", $multipleTransformersClass->privateProperty); } @@ -133,15 +151,5 @@ public function testDestructor(): void $key = 'CODE_TRANSFORMER_APP_DIR\tests\Stubs\ClassesToTransform\StringClass.php'; $key = str_replace('\\', DIRECTORY_SEPARATOR, $key); $this->assertArrayHasKey($key, $file); - - $cachedFilePath = 'CODE_TRANSFORMER_APP_DIR\tests\cache\transformed\tests\Stubs\ClassesToTransform\StringClass.php'; - $cachedFilePath = str_replace('\\', DIRECTORY_SEPARATOR, $cachedFilePath); - $this->assertArrayHasKey('cachedFilePath', $file[$key]); - $this->assertEquals($cachedFilePath, $file[$key]['cachedFilePath']); - $this->assertArrayHasKey('transformedTime', $file[$key]); - $this->assertIsInt($file[$key]['transformedTime']); - $this->assertArrayHasKey('transformerFilePaths', $file[$key]); - $this->assertIsArray($file[$key]['transformerFilePaths']); - $this->assertGreaterThan(0, count($file[$key]['transformerFilePaths'])); } } diff --git a/tests/Functional/Workflow/CachedApplicationTest.php b/tests/Functional/Workflow/B_CachedApplicationTest.php similarity index 54% rename from tests/Functional/Workflow/CachedApplicationTest.php rename to tests/Functional/Workflow/B_CachedApplicationTest.php index 8d9b717..1eb5cf1 100644 --- a/tests/Functional/Workflow/CachedApplicationTest.php +++ b/tests/Functional/Workflow/B_CachedApplicationTest.php @@ -4,24 +4,70 @@ use Okapi\CodeTransformer\Tests\ClassLoaderMockTrait; use Okapi\CodeTransformer\Tests\Stubs\ClassesToTransform; -use Okapi\CodeTransformer\Tests\Stubs\Kernel\CachedKernel; +use Okapi\CodeTransformer\Tests\Stubs\Kernel\ApplicationKernel; use Okapi\CodeTransformer\Tests\Util; use Okapi\Filesystem\Filesystem; +use PHPUnit\Framework\Attributes\RunClassInSeparateProcess; use PHPUnit\Framework\TestCase; -use Throwable; /** * This test has to be run after ApplicationTest. */ -class CachedApplicationTest extends TestCase +#[RunClassInSeparateProcess] +class B_CachedApplicationTest extends TestCase { use ClassLoaderMockTrait; + private static string $classFileContent; + private static string $transformerFileContent; + + public static function setUpBeforeClass(): void + { + // Change files + + $classFilePath = __DIR__ . '/../../Stubs/ClassesToTransform/ChangedClass.php'; + self::$classFileContent = Filesystem::readFile($classFilePath); + + $changedFileContent = str_replace( + 'Hello World!', + 'Hello Changed World!', + self::$classFileContent, + ); + + usleep(500 * 1000); + Filesystem::writeFile($classFilePath, $changedFileContent); + + + + $transformerFilePath = __DIR__ . '/../../Stubs/Transformer/ChangedTransformerTransformer.php'; + self::$transformerFileContent = Filesystem::readFile($transformerFilePath); + + $changedFileContent = str_replace( + 'Hello World from Code Transformer!', + 'Hello Changed World from Code Transformer!', + self::$transformerFileContent, + ); + + usleep(500 * 1000); + Filesystem::writeFile($transformerFilePath, $changedFileContent); + } + + public static function tearDownAfterClass(): void + { + // Restore files + + $classFilePath = __DIR__ . '/../../Stubs/ClassesToTransform/ChangedClass.php'; + Filesystem::writeFile($classFilePath, self::$classFileContent); + + $transformerFilePath = __DIR__ . '/../../Stubs/Transformer/ChangedTransformerTransformer.php'; + Filesystem::writeFile($transformerFilePath, self::$transformerFileContent); + } + public function testKernel(): void { - $this->assertFalse(CachedKernel::isInitialized()); - CachedKernel::init(); - $this->assertTrue(CachedKernel::isInitialized()); + $this->assertFalse(ApplicationKernel::isInitialized()); + ApplicationKernel::init(); + $this->assertTrue(ApplicationKernel::isInitialized()); $this->assertFileExists(Util::CACHE_STATES_FILE); } @@ -61,7 +107,7 @@ public function testNoChangesClass(): void $stringClass = new $class(); $originalFilePath = __DIR__ . '/../../Stubs/ClassesToTransform/NoChangesClass.php'; - $cachedFilePath = Util::CACHE_DIR . '/transformed/tests/Stubs/ClassesToTransform/NoChangesClass.php'; + $cachedFilePath = Util::CACHE_DIR . '/transformed/tests/Stubs/ClassesToTransform/NoChangesClass.php'; $this->assertFileExists($originalFilePath); $this->assertFileDoesNotExist($cachedFilePath); } @@ -71,35 +117,29 @@ public function testNoChangesClass(): void */ public function testChangedClass(): void { - $originalFilePath = __DIR__ . '/../../Stubs/ClassesToTransform/ChangedClass.php'; - - $originalFileContent = Filesystem::readFile($originalFilePath); - - $exception = null; - try { - $changedFileContent = str_replace( - 'Hello World!', - 'Hello Changed World!', - $originalFileContent, - ); - - Filesystem::writeFile($originalFilePath, $changedFileContent); - - $class = ClassesToTransform\ChangedClass::class; - $this->assertWillBeTransformed($class); + $class = ClassesToTransform\ChangedClass::class; + $this->assertWillBeTransformed($class); - $changedClass = new $class(); - $this->assertSame('Hello Changed World from Code Transformer!', $changedClass->test()); - } catch (Throwable $e) { - $exception = $e; - } + $changedClass = new $class(); + $this->assertSame( + 'Hello Changed World from Code Transformer!', + $changedClass->test(), + ); + } - Filesystem::writeFile($originalFilePath, $originalFileContent); + /** + * Cached by {@see ApplicationTest::testChangedTransformer()} + */ + public function testChangedTransformer(): void + { + $class = ClassesToTransform\ChangedTransformer::class; + $this->assertWillBeTransformed($class); - if ($exception !== null) { - /** @noinspection PhpUnhandledExceptionInspection */ - throw $exception; - } + $classInstance = new $class; + $this->assertSame( + 'Hello Changed World from Code Transformer!', + $classInstance->test(), + ); } /** @@ -134,23 +174,4 @@ public function testMultipleTransformers(): void $this->assertSame('Hello from Code Transformer!', $multipleTransformersClass->test()); $this->assertSame("You can't get me!", $multipleTransformersClass->privateProperty); } - - /** - * Cached by {@see ApplicationTest::testAddedTransformer()} - */ - public function testAddedTransformer(): void - { - $class = ClassesToTransform\AddedTransformerClass::class; - $this->assertWillBeTransformed($class); - - $addedTransformerClass = new $class(); - $this->assertSame('Hello from Code Transformer!', $addedTransformerClass->test()); - } - - public function testClearCache(): void - { - Util::clearCache(); - - $this->expectNotToPerformAssertions(); - } } diff --git a/tests/Functional/Workflow/C_AddedTransformerTest.php b/tests/Functional/Workflow/C_AddedTransformerTest.php new file mode 100644 index 0000000..2ca25f3 --- /dev/null +++ b/tests/Functional/Workflow/C_AddedTransformerTest.php @@ -0,0 +1,46 @@ +assertFalse(AddedTransformerKernel::isInitialized()); + ApplicationKernel::init(); + $this->assertTrue(ApplicationKernel::isInitialized()); + + $this->assertFileExists(Util::CACHE_STATES_FILE); + } + + /** + * Cached by {@see ApplicationTest::testAddedTransformer()} + */ + public function testAddedTransformer(): void + { + $class = ClassesToTransform\AddedTransformerClass::class; + $this->assertWillBeTransformed($class); + + $addedTransformerClass = new $class(); + $this->assertSame('Hello from Code Transformer!', $addedTransformerClass->test()); + } +} diff --git a/tests/Stubs/ClassesToTransform/ChangedTransformer.php b/tests/Stubs/ClassesToTransform/ChangedTransformer.php new file mode 100644 index 0000000..901070d --- /dev/null +++ b/tests/Stubs/ClassesToTransform/ChangedTransformer.php @@ -0,0 +1,11 @@ +getStringContentsText() === 'Hello World!') { - // Replace it with 'Hello from Code Transformer!' + // Replace it with 'Hello World from Code Transformer!' $code->edit( $node->children, - "'Hello from Code Transformer!'", + "'Hello World from Code Transformer!'", ); } else if ($node->getStringContentsText() === 'Hello Changed World!') { // Replace it with 'Hello Changed World from Code Transformer!' diff --git a/tests/Stubs/Transformer/ChangedTransformerTransformer.php b/tests/Stubs/Transformer/ChangedTransformerTransformer.php new file mode 100644 index 0000000..9731442 --- /dev/null +++ b/tests/Stubs/Transformer/ChangedTransformerTransformer.php @@ -0,0 +1,40 @@ +getSourceFileNode(); + + foreach ($sourceFileNode->getDescendantNodes() as $node) { + // Find 'Hello World!' string + if ($node instanceof StringLiteral) { + if ($node->getStringContentsText() === 'Hello World!') { + // Replace it with 'Hello Changed World from Code Transformer!' + $code->edit( + $node->children, + "'Hello World from Code Transformer!'", + ); + } + } + } + + $reflection = $code->getReflectionClass(); + assert($reflection->getName() === ChangedTransformer::class); + + $className = $code->getClassName(); + assert($className === 'ChangedTransformer'); + } +} diff --git a/tests/Stubs/Transformer/DeleteCacheFileTransformer.php b/tests/Stubs/Transformer/DeleteCacheFileTransformer.php index 9f2814b..bb6831f 100644 --- a/tests/Stubs/Transformer/DeleteCacheFileTransformer.php +++ b/tests/Stubs/Transformer/DeleteCacheFileTransformer.php @@ -2,9 +2,9 @@ namespace Okapi\CodeTransformer\Tests\Stubs\Transformer; -use Okapi\CodeTransformer\Service\StreamFilter\Metadata\Code; use Okapi\CodeTransformer\Tests\Stubs\ClassesToTransform\DeleteCacheFileClass; use Okapi\CodeTransformer\Transformer; +use Okapi\CodeTransformer\Transformer\Code; class DeleteCacheFileTransformer extends Transformer { diff --git a/tests/Stubs/Transformer/InvalidTransformer.php b/tests/Stubs/Transformer/InvalidTransformer.php index bb617a9..ea3c32b 100644 --- a/tests/Stubs/Transformer/InvalidTransformer.php +++ b/tests/Stubs/Transformer/InvalidTransformer.php @@ -2,8 +2,8 @@ namespace Okapi\CodeTransformer\Tests\Stubs\Transformer; -use Okapi\CodeTransformer\Service\StreamFilter\Metadata\Code; use Okapi\CodeTransformer\Tests\Stubs\ClassesToTransform\StringClass; +use Okapi\CodeTransformer\Transformer\Code; class InvalidTransformer { diff --git a/tests/Stubs/Transformer/NoChangesTransformer.php b/tests/Stubs/Transformer/NoChangesTransformer.php index 2298c95..d5a9150 100644 --- a/tests/Stubs/Transformer/NoChangesTransformer.php +++ b/tests/Stubs/Transformer/NoChangesTransformer.php @@ -2,9 +2,9 @@ namespace Okapi\CodeTransformer\Tests\Stubs\Transformer; -use Okapi\CodeTransformer\Service\StreamFilter\Metadata\Code; use Okapi\CodeTransformer\Tests\Stubs\ClassesToTransform\NoChangesClass; use Okapi\CodeTransformer\Transformer; +use Okapi\CodeTransformer\Transformer\Code; class NoChangesTransformer extends Transformer { diff --git a/tests/Stubs/Transformer/StringTransformer.php b/tests/Stubs/Transformer/StringTransformer.php index fc0f4bf..9fd458d 100644 --- a/tests/Stubs/Transformer/StringTransformer.php +++ b/tests/Stubs/Transformer/StringTransformer.php @@ -3,10 +3,10 @@ namespace Okapi\CodeTransformer\Tests\Stubs\Transformer; use Microsoft\PhpParser\Node\StringLiteral; -use Okapi\CodeTransformer\Service\StreamFilter\Metadata\Code; use Okapi\CodeTransformer\Tests\Stubs\ClassesToTransform\MultipleTransformersClass; use Okapi\CodeTransformer\Tests\Stubs\ClassesToTransform\StringClass; use Okapi\CodeTransformer\Transformer; +use Okapi\CodeTransformer\Transformer\Code; class StringTransformer extends Transformer { diff --git a/tests/Stubs/Transformer/SyntaxErrorTransformer.php b/tests/Stubs/Transformer/SyntaxErrorTransformer.php index a49fee0..56670cb 100644 --- a/tests/Stubs/Transformer/SyntaxErrorTransformer.php +++ b/tests/Stubs/Transformer/SyntaxErrorTransformer.php @@ -2,9 +2,9 @@ namespace Okapi\CodeTransformer\Tests\Stubs\Transformer; -use Okapi\CodeTransformer\Service\StreamFilter\Metadata\Code; use Okapi\CodeTransformer\Tests\Stubs\ClassesToTransform\SyntaxErrorClass; use Okapi\CodeTransformer\Transformer; +use Okapi\CodeTransformer\Transformer\Code; class SyntaxErrorTransformer extends Transformer { diff --git a/tests/Stubs/Transformer/UnPrivateTransformer.php b/tests/Stubs/Transformer/UnPrivateTransformer.php index 66bb16b..77ab019 100644 --- a/tests/Stubs/Transformer/UnPrivateTransformer.php +++ b/tests/Stubs/Transformer/UnPrivateTransformer.php @@ -3,9 +3,9 @@ namespace Okapi\CodeTransformer\Tests\Stubs\Transformer; use Microsoft\PhpParser\TokenKind; -use Okapi\CodeTransformer\Service\StreamFilter\Metadata\Code; use Okapi\CodeTransformer\Tests\Stubs\ClassesToTransform\MultipleTransformersClass; use Okapi\CodeTransformer\Transformer; +use Okapi\CodeTransformer\Transformer\Code; class UnPrivateTransformer extends Transformer { From 726ca96f9eb3d6a2e3d9f8d86ced9c14d289def7 Mon Sep 17 00:00:00 2001 From: WalterWoshid Date: Wed, 19 Apr 2023 05:53:16 +0200 Subject: [PATCH 15/18] Updated README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 846e220..59e8864 100644 --- a/README.md +++ b/README.md @@ -92,8 +92,8 @@ class Kernel extends CodeTransformerKernel Date: Wed, 19 Apr 2023 06:03:40 +0200 Subject: [PATCH 16/18] Added php-doc comments and todos --- src/Core/Cache/CacheState/EmptyResultCacheState.php | 9 ++++++++- .../Cache/CacheState/NoTransformationsCacheState.php | 8 +++++++- src/Core/Cache/CacheState/TransformedCacheState.php | 7 +++++-- src/Core/Cache/CacheStateFactory.php | 7 +++++-- src/Core/Container/TransformerContainer.php | 6 +++++- src/Core/Container/TransformerManager.php | 2 -- src/Core/Matcher/TransformerMatcher.php | 6 +++++- 7 files changed, 35 insertions(+), 10 deletions(-) diff --git a/src/Core/Cache/CacheState/EmptyResultCacheState.php b/src/Core/Cache/CacheState/EmptyResultCacheState.php index 9c98dc3..779b16c 100644 --- a/src/Core/Cache/CacheState/EmptyResultCacheState.php +++ b/src/Core/Cache/CacheState/EmptyResultCacheState.php @@ -5,7 +5,14 @@ use Okapi\CodeTransformer\Core\Cache\CacheState; /** - * TODO: docs + * # Empty Result Cache State + * + * This class is used to represent an empty result cache state, which means that + * the class was not matched by any transformer. + * + * @todo: I think when a transformer is changed, the cache state should be + * invalidated. This is not currently the case. Maybe clear the whole cache + * when a transformer is changed? */ class EmptyResultCacheState extends CacheState { diff --git a/src/Core/Cache/CacheState/NoTransformationsCacheState.php b/src/Core/Cache/CacheState/NoTransformationsCacheState.php index 02b9c1e..7279604 100644 --- a/src/Core/Cache/CacheState/NoTransformationsCacheState.php +++ b/src/Core/Cache/CacheState/NoTransformationsCacheState.php @@ -4,7 +4,13 @@ use Okapi\CodeTransformer\Core\Cache\CacheState; -// TODO: docs +/** + * # No Transformations Cache State + * + * This class is used to represent a no transformations cache state, which + * means that the file was matched by the transformers, but no transformations + * were applied. + */ class NoTransformationsCacheState extends CacheState { /** diff --git a/src/Core/Cache/CacheState/TransformedCacheState.php b/src/Core/Cache/CacheState/TransformedCacheState.php index a95a4f2..f9b3985 100644 --- a/src/Core/Cache/CacheState/TransformedCacheState.php +++ b/src/Core/Cache/CacheState/TransformedCacheState.php @@ -2,10 +2,13 @@ namespace Okapi\CodeTransformer\Core\Cache\CacheState; - use Okapi\CodeTransformer\Core\Cache\CacheState; -// TODO: docs +/** + * # Transformed Cache State + * + * This class is used to store the cache state for transformed files. + */ class TransformedCacheState extends CacheState { diff --git a/src/Core/Cache/CacheStateFactory.php b/src/Core/Cache/CacheStateFactory.php index 58494d8..a3a9bdf 100644 --- a/src/Core/Cache/CacheStateFactory.php +++ b/src/Core/Cache/CacheStateFactory.php @@ -2,14 +2,17 @@ namespace Okapi\CodeTransformer\Core\Cache; - use Okapi\CodeTransformer\Core\Cache\CacheState\EmptyResultCacheState; use Okapi\CodeTransformer\Core\Cache\CacheState\NoTransformationsCacheState; use Okapi\CodeTransformer\Core\Cache\CacheState\TransformedCacheState; use Okapi\CodeTransformer\Core\DI; use TypeError; -// TODO: docs +/** + * # Cache State Factory + * + * This class is used to create cache states from the cache state file. + */ class CacheStateFactory { /** diff --git a/src/Core/Container/TransformerContainer.php b/src/Core/Container/TransformerContainer.php index f0f9adc..d4d5ebc 100644 --- a/src/Core/Container/TransformerContainer.php +++ b/src/Core/Container/TransformerContainer.php @@ -4,7 +4,11 @@ use Okapi\CodeTransformer\Transformer; -// TODO: docs +/** + * # Transformer Container + * + * This class is used to store the transformer instances. + */ class TransformerContainer { /** diff --git a/src/Core/Container/TransformerManager.php b/src/Core/Container/TransformerManager.php index 9e0d6cc..4414fb2 100644 --- a/src/Core/Container/TransformerManager.php +++ b/src/Core/Container/TransformerManager.php @@ -14,7 +14,6 @@ * # Transformer Manager * * This class is used to register and manage the transformers. - * @todo Don't repeat class name in docs */ class TransformerManager implements ServiceInterface { @@ -22,7 +21,6 @@ class TransformerManager implements ServiceInterface * The list of transformer class strings. * * @var class-string[] - * @todo CHECK if all class-string have a type */ private array $transformers = []; diff --git a/src/Core/Matcher/TransformerMatcher.php b/src/Core/Matcher/TransformerMatcher.php index 9ab51e9..5a38795 100644 --- a/src/Core/Matcher/TransformerMatcher.php +++ b/src/Core/Matcher/TransformerMatcher.php @@ -11,7 +11,11 @@ use Okapi\CodeTransformer\Transformer; use Okapi\Wildcards\Regex; -// TODO: docs +/** + * # Transformer Matcher + * + * This class is used to match the transformers to the classes. + */ class TransformerMatcher { // region DI From 1a95102d009bcf6771f561e9adf736e047b728dd Mon Sep 17 00:00:00 2001 From: WalterWoshid Date: Wed, 19 Apr 2023 06:06:42 +0200 Subject: [PATCH 17/18] Added todo --- TODO.md | 3 +++ tests/Functional/Workflow/B_CachedApplicationTest.php | 3 +++ 2 files changed, 6 insertions(+) diff --git a/TODO.md b/TODO.md index 4574dfd..c2e17a3 100644 --- a/TODO.md +++ b/TODO.md @@ -22,3 +22,6 @@ need optimization (e.g. Profilers or benchmarking suites). - 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 diff --git a/tests/Functional/Workflow/B_CachedApplicationTest.php b/tests/Functional/Workflow/B_CachedApplicationTest.php index 1eb5cf1..e23a6dd 100644 --- a/tests/Functional/Workflow/B_CachedApplicationTest.php +++ b/tests/Functional/Workflow/B_CachedApplicationTest.php @@ -12,6 +12,9 @@ /** * This test has to be run after ApplicationTest. + * + * @todo For some reason, it takes a long time for me until this test is + * executed. Maybe because of RunClassInSeparateProcess? */ #[RunClassInSeparateProcess] class B_CachedApplicationTest extends TestCase From 336a9d0819845a8a3d409047577b58c0f3f4f744 Mon Sep 17 00:00:00 2001 From: WalterWoshid Date: Wed, 19 Apr 2023 06:11:39 +0200 Subject: [PATCH 18/18] Bumped version --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index a04b4f3..9f9036c 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.1.1", + "version": "1.2.0", "type": "library", "homepage": "https://github.com/okapi-web/php-code-transformer", "license": "MIT",