Skip to content

Commit d1fac85

Browse files
committed
Updated logic
1 parent 3eb36fa commit d1fac85

14 files changed

+1234
-62
lines changed

src/CodeTransformerKernel.php

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
namespace Okapi\CodeTransformer;
44

55
use Okapi\CodeTransformer\Exception\Kernel\DirectKernelInitializationException;
6+
use Okapi\CodeTransformer\Service\AutoloadInterceptor;
7+
use Okapi\CodeTransformer\Service\CacheStateManager;
68
use Okapi\CodeTransformer\Service\Options;
7-
use Okapi\CodeTransformer\Service\TransformerLoader;
9+
use Okapi\CodeTransformer\Service\StreamFilter;
10+
use Okapi\CodeTransformer\Service\TransformerContainer;
811
use Okapi\Singleton\Singleton;
912

1013
/**
@@ -48,17 +51,23 @@ public static function init(
4851
self::ensureNotKernelNamespace();
4952

5053
$instance = self::getInstance();
51-
$instance->ensureNotAlreadyInitialized();
54+
$instance->ensureNotInitialized();
5255

56+
// Only initialize the kernel if there are transformers
5357
if ($instance->transformers) {
58+
// Pre-initialize the services
59+
60+
// Set options
5461
Options::setOptions(
5562
cacheDir: $cacheDir,
5663
cacheFileMode: $cacheFileMode,
5764
debug: $debug,
5865
);
5966

60-
TransformerLoader::addTransformers($instance->transformers);
67+
// Add the transformers
68+
TransformerContainer::addTransformers($instance->transformers);
6169

70+
// Register the services
6271
$instance->registerServices();
6372
}
6473

@@ -75,8 +84,17 @@ protected function registerServices(): void
7584
// Options provider
7685
Options::register();
7786

78-
// Load the user-defined transformers
79-
TransformerLoader::register();
87+
// Manage the user-defined transformers
88+
TransformerContainer::register();
89+
90+
// Cache path manager
91+
CacheStateManager::register();
92+
93+
// Stream filter -> Source transformer
94+
StreamFilter::register();
95+
96+
// Overload the composer class loaders
97+
AutoloadInterceptor::register();
8098
}
8199

82100
/**

src/Service/AutoloadInterceptor.php

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<?php
2+
3+
namespace Okapi\CodeTransformer\Service;
4+
5+
use Composer\Autoload\ClassLoader as ComposerClassLoader;
6+
use Okapi\CodeTransformer\Service\AutoloadInterceptor\ClassLoader;
7+
use Okapi\CodeTransformer\Util\Finder;
8+
use Okapi\Singleton\Singleton;
9+
10+
/**
11+
* # Autoload Interceptor
12+
*
13+
* The `AutoloadInterceptor` class is responsible for intercepting
14+
* the Composer Autoloader and applying the transformers.
15+
*
16+
* @see ClassLoader::__construct() - Initialization of the Code Transformer
17+
* class loader.
18+
* @see ClassLoader::findFile() - Matching the class to the transformers and
19+
* replacing the original file with a PHP stream
20+
* filter.
21+
*/
22+
class AutoloadInterceptor implements ServiceInterface
23+
{
24+
use Singleton;
25+
26+
/**
27+
* Register the autoload interceptor.
28+
*
29+
* @return void
30+
*/
31+
public static function register(): void
32+
{
33+
$instance = self::getInstance();
34+
$instance->ensureNotInitialized();
35+
36+
// Overload existing composer loaders
37+
$instance->overloadComposerLoaders();
38+
39+
$instance->setInitialized();
40+
}
41+
42+
/**
43+
* Overload existing composer loaders
44+
*
45+
* @return void
46+
*/
47+
private function overloadComposerLoaders(): void
48+
{
49+
$finder = $this->getFinder();
50+
51+
// Get existing composer loaders
52+
$loaders = spl_autoload_functions();
53+
foreach ($loaders as $loader) {
54+
$loaderToUnregister = $loader;
55+
56+
// Skip if not a class loader
57+
if (!is_array($loader)
58+
|| !isset($loader[0])
59+
|| !$loader[0] instanceof ComposerClassLoader
60+
) {
61+
// @codeCoverageIgnoreStart
62+
continue;
63+
// @codeCoverageIgnoreEnd
64+
}
65+
66+
// Register the AOP class loader
67+
$loader[0] = new ClassLoader($loader[0], $finder);
68+
69+
// Unregister the original composer loader
70+
spl_autoload_unregister($loaderToUnregister);
71+
72+
// Register the AOP class loader
73+
spl_autoload_register($loader);
74+
}
75+
}
76+
77+
/**
78+
* Get class finder.
79+
*
80+
* @return Finder
81+
*/
82+
private function getFinder(): Finder
83+
{
84+
return (new Finder)
85+
->includeClass(TransformerContainer::getTargetClasses());
86+
}
87+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<?php
2+
3+
namespace Okapi\CodeTransformer\Service\AutoloadInterceptor;
4+
5+
use Composer\Autoload\ClassLoader as ComposerClassLoader;
6+
use Okapi\CodeTransformer\Service\AutoloadInterceptor;
7+
use Okapi\CodeTransformer\Service\CacheStateManager;
8+
use Okapi\CodeTransformer\Service\Options;
9+
use Okapi\CodeTransformer\Service\StreamFilter;
10+
use Okapi\CodeTransformer\Service\StreamFilter\FilterInjector;
11+
use Okapi\CodeTransformer\Util\Finder;
12+
use Okapi\Path\Path;
13+
14+
/**
15+
* # Code Transformer Class Loader
16+
*
17+
* This class loader is responsible for loading classes that should be
18+
* intercepted by the Code Transformer.
19+
*
20+
* @see AutoloadInterceptor::overloadComposerLoaders() - Initialization of the Code Transformer class loader.
21+
* @see FilterInjector::rewrite() - Switching the original file with a PHP filter.
22+
* @see StreamFilter::filter() - Applying the transformations to the file.
23+
*/
24+
class ClassLoader extends ComposerClassLoader
25+
{
26+
/**
27+
* Code Transformer class loader constructor.
28+
*
29+
* @param ComposerClassLoader $original
30+
* @param Finder $finder
31+
*
32+
* @noinspection PhpMissingParentConstructorInspection (Parent already constructed)
33+
*/
34+
public function __construct(
35+
private readonly ComposerClassLoader $original,
36+
private readonly Finder $finder,
37+
) {}
38+
39+
/**
40+
* Autoload a class.
41+
*
42+
* @param $class
43+
*
44+
* @return bool
45+
*/
46+
public function loadClass($class): bool
47+
{
48+
if ($file = $this->findFile($class)) {
49+
include $file;
50+
51+
return true;
52+
}
53+
54+
// @codeCoverageIgnoreStart
55+
// Not sure how to test this
56+
return false;
57+
// @codeCoverageIgnoreEnd
58+
}
59+
60+
/**
61+
* Find the path to the file and apply the transformers.
62+
*
63+
* @param $class
64+
*
65+
* @return false|string
66+
*/
67+
public function findFile($class): false|string
68+
{
69+
$filePath = $this->original->findFile($class);
70+
71+
// @codeCoverageIgnoreStart
72+
// Not sure how to test this
73+
if ($filePath === false) {
74+
return false;
75+
}
76+
// @codeCoverageIgnoreEnd
77+
78+
$filePath = Path::resolve($filePath);
79+
80+
// Check if the class should be transformed
81+
if ($this->finder->hasClass($class)) {
82+
$cacheState = CacheStateManager::queryCacheState($filePath);
83+
84+
// Check if the file is cached and up to date
85+
if (!Options::$debug && $cacheState?->isFresh()) {
86+
// Use the cached file if transformations have been applied
87+
// Or return the original file if no transformations have been applied
88+
return $cacheState->cachedFilePath ?? $filePath;
89+
}
90+
91+
// Replace the file path with a PHP stream filter
92+
/** @see StreamFilter::filter() */
93+
return FilterInjector::rewrite($filePath);
94+
}
95+
96+
return $filePath;
97+
}
98+
}

src/Service/Cache/CachePaths.php

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
namespace Okapi\CodeTransformer\Service\Cache;
4+
5+
use Okapi\CodeTransformer\Service\Options;
6+
use Okapi\Path\Path;
7+
8+
/**
9+
* # Cache Paths
10+
*
11+
* The `CachePaths` class is responsible for generating the cache paths for
12+
* the different types of files that are generated by the Code Transformer.
13+
*/
14+
class CachePaths
15+
{
16+
/**
17+
* # Default cache directory.
18+
*
19+
* This directory is used if no cache directory is provided.
20+
*/
21+
public const DEFAULT_CACHE_DIR = 'cache/code-transformer';
22+
23+
/**
24+
* # Directory of the transformed classes.
25+
*
26+
* This directory creates an exact copy of the original file structure.
27+
*
28+
* The transformed class has all the transformations applied to it.
29+
*/
30+
public const TRANSFORMED_DIR = 'transformed';
31+
32+
/**
33+
* Name of the file with cache paths.
34+
*/
35+
public const CACHE_FILE_NAME = 'cache_states.php';
36+
37+
/**
38+
* Get the file path for a transformed file.
39+
*
40+
* @param string $filePath
41+
*
42+
* @return string
43+
*/
44+
public static function getTransformedCachePath(string $filePath): string
45+
{
46+
return self::appendToCachePath($filePath, self::TRANSFORMED_DIR);
47+
}
48+
49+
/**
50+
* Append a path to the cache path.
51+
*
52+
* @param string $file
53+
* @param string $append
54+
*
55+
* @return string
56+
*
57+
* @noinspection PhpSameParameterValueInspection For okapi/aop
58+
*/
59+
private static function appendToCachePath(string $file, string $append): string
60+
{
61+
// Append the string to the cache dir
62+
$newDir = Path::join(Options::$cacheDir, $append);
63+
64+
return Path::resolve(
65+
str_replace(Options::$appDir, $newDir, $file),
66+
);
67+
}
68+
69+
/**
70+
* Get the file path for the cache file.
71+
*
72+
* @return string
73+
*/
74+
public static function getCacheFilePath(): string
75+
{
76+
return Path::join(Options::$cacheDir, self::CACHE_FILE_NAME);
77+
}
78+
}

0 commit comments

Comments
 (0)