diff --git a/.gitignore b/.gitignore
index 136335f..1fbf909 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,4 @@ build
/.phpcs-cache
.phpunit.result.cache
/coverage.xml
+/tests/Fake/tmp
diff --git a/README.md b/README.md
index 6c1a990..da10ab3 100644
--- a/README.md
+++ b/README.md
@@ -9,60 +9,20 @@
Ray.Compiler compiles Ray.Di bindings into PHP code, providing a performance boost that makes Dependency Injection couldn't be any faster.
-## Script Injector
+## Compile Injector
-`ScriptInjector` has the same interface as Ray.Di Injector; whereas Ray.Di Injector resolves dependencies based on memory bindings, ScriptInjector executes pre-compiled PHP code and is faster.
+`CompileInjector` is designed to enhance performance by utilizing `Compiler` to compile modules and initializing `AirInjector` for managing dependencies. This approach ensures efficient dependency management and faster execution.
-Ray.Di injector
```php
-$injector = new Injector(new CarModule); // Ray.Di injector
-```
-
-Ray.Compiler injector
-```php
-$injector = new ScriptInjector($tmpDir, fn => new CarModule);
+$injector = new CompileInjector($tmpDir, fn => new CarModule);
+$car = $injector->getInstance(CarInterface::class);
```
## Precompile
-You will want to compile all dependencies into code before deploying the production. The `DiCompiler` will compile all bindings into PHP code.
+You will want to compile all dependencies into code before deploying the production.
```php
-$compiler = new DiCompiler(new CarModule, $tmpDir);
+$compiler = new Compiler($tmpDir, new CarModule);
$compiler->compile();
```
-
-## Object graph visualization
-
-Object graph can be visualized with `dumpGraph()`.
-Graph HTML files will be output at `graph` folder under `$tmpDir`.
-
-```php
-$compiler = new DiCompiler(new Module, $tmpDir);
-$compiler->compile();
-$compiler->dumpGraph();
-```
-
-```
-open tmp/graph/Ray_Compiler_FakeCarInterface-.html
-```
-
-## CompileInjector
-
-The `CompileInjector` gives you the best performance in both development (x2) and production (x10) by switching two injector.
-
-Get the injector by specifying the binding and cache, depending on the execution context of the application.
-
-```php
-$injector = new CompileInjector($tmpDir, $injectorContext);
-```
-
-`$injectorContext` example:
-
- * [dev](docs/exmaple/DevInjectorContext.php)
- * [prod](docs/exmaple/ProdInjectorContext.php)
-
-The `__invoke()` method prepares the modules needed in that context.
-The `getCache()` method specifies the cache of the injector itself.
-
-Install `DiCompileModule` in the context for production. The injector is more optimized and dependency errors are reported at compile-time instead of run-time.
diff --git a/composer-require-checker.json b/composer-require-checker.json
index 1731fe8..9123139 100644
--- a/composer-require-checker.json
+++ b/composer-require-checker.json
@@ -4,19 +4,8 @@
"static", "self", "parent",
"array", "string", "int", "float", "bool", "iterable", "callable", "void", "object",
"Attribute", "ReflectionAttribute",
- "Ray\\Di\\AbstractModule", "Ray\\Di\\Annotation\\ScriptDir",
- "Ray\\Di\\Argument", "Ray\\Di\\Arguments",
- "Ray\\Di\\AssistedModule", "Ray\\Di\\Bind",
- "Ray\\Di\\Container", "Ray\\Di\\Dependency",
- "Ray\\Di\\DependencyInterface","Ray\\Di\\DependencyProvider",
- "Ray\\Di\\Di\\Qualifier", "Ray\\Di\\Exception\\NotFound",
- "Ray\\Di\\Exception\\Unbound", "Ray\\Di\\InjectionPointInterface",
- "Ray\\Di\\Injector", "Ray\\Di\\InjectorInterface", "Ray\\Di\\Instance",
- "Ray\\Di\\MultiBinding\\Map", "Ray\\Di\\MultiBinding\\MapProvider",
- "Ray\\Di\\Name", "Ray\\Di\\NullCache",
- "Ray\\Di\\NullModule", "Ray\\Di\\NullObjectDependency",
- "Ray\\Di\\ProviderInterface", "Ray\\Di\\ProviderSetModule",
- "Ray\\Di\\SetContextInterface"
-
+ "Doctrine\\Common\\Annotations\\Reader",
+ "Doctrine\\Common\\Cache\\CacheProvider",
+ "Koriym\\Printo\\Printo"
]
}
diff --git a/composer.json b/composer.json
index 8c44678..d162dcf 100644
--- a/composer.json
+++ b/composer.json
@@ -12,20 +12,17 @@
],
"require": {
"php": "^7.2 || ^8.0",
- "doctrine/annotations": "^1.12 || ^2.0",
- "doctrine/cache": "^1.10 || ^2.1",
"koriym/attributes": "^1.0",
"koriym/null-object": "^1.0",
"koriym/param-reader": "^1.0",
- "koriym/printo": "^1.0",
- "nikic/php-parser": "^4.5 || ^5.0",
- "ray/aop": "^2.14"
+ "ray/aop": "^2.15",
+ "ray/di": "^2.17.1"
},
"require-dev": {
"ext-pdo": "*",
- "ray/di": "^2.16",
- "phpunit/phpunit": "^8.5.24 || ^9.5",
- "bamarni/composer-bin-plugin": "^1.4"
+ "bamarni/composer-bin-plugin": "^1.4",
+ "doctrine/cache": "^1.0 || ^2.2",
+ "phpunit/phpunit": "^8.5.24 || ^9.5"
},
"config": {
"sort-packages": true,
@@ -35,7 +32,7 @@
},
"autoload": {
"psr-4": {
- "Ray\\Compiler\\": ["src", "src-deprecated"]
+ "Ray\\Compiler\\": ["src", "src-cached-injector-factory", "src-deprecated"]
}
},
"autoload-dev": {
@@ -53,10 +50,11 @@
"cs": ["phpcs --standard=./phpcs.xml src tests"],
"cs-fix": ["phpcbf src tests"],
"clean": ["phpstan clear-result-cache", "psalm --clear-cache", "rm -rf tests/tmp/*.php"],
- "sa": ["psalm -c psalm.compiler.xml --show-info=true", "phpstan analyse -c phpstan.neon --no-progress"],
+ "sa": ["psalm -c psalm.compiler.xml --show-info=false", "phpstan analyse -c phpstan.neon --no-progress"],
"metrics": ["@test", "phpmetrics --report-html=build/metrics --exclude=Exception --log-junit=build/junit.xml --junit=build/junit.xml src"],
- "phpmd": ["phpmd src/di text ./phpmd.xml"],
- "build": ["@cs", "@sa", "@pcov", "@metrics"]
+ "phpmd": ["phpmd src text ./phpmd.xml"],
+ "build": ["@cs", "@sa", "@pcov", "@metrics"],
+ "req-check": ["./vendor/bin/composer-require-checker"]
},
"extra": {
"bamarni-bin": {
diff --git a/docs/exmaple/DevInjectorContext.php b/docs/exmaple/DevInjectorContext.php
index af16204..9f44d5c 100644
--- a/docs/exmaple/DevInjectorContext.php
+++ b/docs/exmaple/DevInjectorContext.php
@@ -2,11 +2,8 @@
declare(strict_types=1);
-use Doctrine\Common\Cache\ApcuCache;
use Doctrine\Common\Cache\CacheProvider;
use Ray\Compiler\AbstractInjectorContext;
-use Ray\Compiler\DiCompileModule;
-use Ray\Compiler\FakeCarModule;
use Ray\Di\AbstractModule;
use Ray\Di\NullCache;
diff --git a/docs/exmaple/ProdInjectorContext.php b/docs/exmaple/ProdInjectorContext.php
index 8324169..38e8e75 100644
--- a/docs/exmaple/ProdInjectorContext.php
+++ b/docs/exmaple/ProdInjectorContext.php
@@ -6,7 +6,6 @@
use Doctrine\Common\Cache\CacheProvider;
use Ray\Compiler\AbstractInjectorContext;
use Ray\Compiler\DiCompileModule;
-use Ray\Compiler\FakeCarModule;
use Ray\Di\AbstractModule;
final class ProdInjectorContext extends AbstractInjectorContext
diff --git a/phpcs.xml b/phpcs.xml
index 9c3efa3..3e16b06 100755
--- a/phpcs.xml
+++ b/phpcs.xml
@@ -28,6 +28,7 @@
+
@@ -39,6 +40,7 @@
+
diff --git a/phpstan.neon b/phpstan.neon
index b6e0c77..c7f9f70 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -6,5 +6,4 @@ parameters:
excludePaths:
- tests/tmp/*
- tests/Fake/*
- checkGenericClassInNonGenericObjectType: false
diff --git a/src/AbstractInjectorContext.php b/src-cached-injector-factory/AbstractInjectorContext.php
similarity index 100%
rename from src/AbstractInjectorContext.php
rename to src-cached-injector-factory/AbstractInjectorContext.php
diff --git a/src/CachedInjectorFactory.php b/src-cached-injector-factory/CachedInjectorFactory.php
similarity index 92%
rename from src/CachedInjectorFactory.php
rename to src-cached-injector-factory/CachedInjectorFactory.php
index d9fdc38..095a31d 100644
--- a/src/CachedInjectorFactory.php
+++ b/src-cached-injector-factory/CachedInjectorFactory.php
@@ -7,14 +7,19 @@
use Doctrine\Common\Cache\CacheProvider;
use Ray\Di\AbstractModule;
use Ray\Di\InjectorInterface;
-use Ray\Di\NullCache;
-
use function assert;
+use function class_exists;
use function serialize;
use function unserialize;
final class CachedInjectorFactory
{
+ public function __construct()
+ {
+ if (! class_exists(CacheProvider::class)) {
+ throw new \RuntimeException('CachedInjectorFactory requires doctrine/cache');
+ }
+ }
/** @var array */
private static $injectors = [];
diff --git a/src/ContextInjector.php b/src-cached-injector-factory/ContextInjector.php
similarity index 100%
rename from src/ContextInjector.php
rename to src-cached-injector-factory/ContextInjector.php
diff --git a/src/InjectorFactory.php b/src-cached-injector-factory/InjectorFactory.php
similarity index 98%
rename from src/InjectorFactory.php
rename to src-cached-injector-factory/InjectorFactory.php
index 07481ed..2d258e1 100644
--- a/src/InjectorFactory.php
+++ b/src-cached-injector-factory/InjectorFactory.php
@@ -9,13 +9,10 @@
use Ray\Di\Exception\Unbound;
use Ray\Di\Injector as RayInjector;
use Ray\Di\InjectorInterface;
-
use function is_dir;
use function mkdir;
-/**
- * @psalm-immutable
- */
+/** @psalm-immutable */
final class InjectorFactory
{
/**
diff --git a/src-cached-injector-factory/NullCache.php b/src-cached-injector-factory/NullCache.php
new file mode 100644
index 0000000..0d45c1d
--- /dev/null
+++ b/src-cached-injector-factory/NullCache.php
@@ -0,0 +1,58 @@
+module = $module;
+ $this->scriptDir = $scriptDir;
+ $this->injector = new AirInjector($scriptDir);
+ $injectorModule = new class ($this->injector) extends AbstractModule {
+ private $injector;
+
+ public function __construct(InjectorInterface $injector)
+ {
+ $this->injector = $injector;
+ }
+
+ protected function configure()
+ {
+ $this->bind(InjectorInterface::class)->toInstance($this->injector);
+ }
+ };
+ $module->install($injectorModule);
+ (new Compiler())->compile($module, $scriptDir);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getInstance($interface, $name = Name::ANY)
+ {
+ return $this->injector->getInstance($interface, $name);
+ }
+
+ /** @deprecated */
+ public function compile(): void
+ {
+ }
+
+ /** @deprecated */
+ public function savePointcuts(): void
+ {
+ }
+
+ /** @deprecated */
+ public function dumpGraph(): void
+ {
+ }
+}
diff --git a/src/GraphDumper.php b/src-deprecated/GraphDumper.php
similarity index 98%
rename from src/GraphDumper.php
rename to src-deprecated/GraphDumper.php
index 4a25138..afb3bbc 100644
--- a/src/GraphDumper.php
+++ b/src-deprecated/GraphDumper.php
@@ -7,7 +7,6 @@
use Koriym\Printo\Printo;
use Ray\Di\Container;
use Ray\Di\Name;
-
use function assert;
use function class_exists;
use function explode;
@@ -16,9 +15,9 @@
use function interface_exists;
use function mkdir;
use function str_replace;
-
use const LOCK_EX;
+/** @deprecated */
final class GraphDumper
{
/** @var Container */
diff --git a/src/IpQualifier.php b/src-deprecated/IpQualifier.php
similarity index 96%
rename from src/IpQualifier.php
rename to src-deprecated/IpQualifier.php
index 39606a7..7459106 100644
--- a/src/IpQualifier.php
+++ b/src-deprecated/IpQualifier.php
@@ -5,9 +5,9 @@
namespace Ray\Compiler;
use ReflectionParameter;
-
use function serialize;
+/** @deprecated */
final class IpQualifier
{
/** @var ReflectionParameter */
diff --git a/src-deprecated/OnDemandCompiler.php b/src-deprecated/OnDemandCompiler.php
new file mode 100644
index 0000000..9b4fc13
--- /dev/null
+++ b/src-deprecated/OnDemandCompiler.php
@@ -0,0 +1,22 @@
+compile($module, $scriptDir);
+ $this->injector = new AirInjector($scriptDir);
+ $this->scriptDir = $scriptDir;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getInstance($interface, $name = Name::ANY)
+ {
+ return $this->injector->getInstance($interface, $name);
+ }
+
+ public function clear(): void
+ {
+ $unlink = static function (string $path) use (&$unlink): void {
+ foreach ((array) glob(rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . '*') as $f) {
+ $file = (string) $f;
+ is_dir($file) ? $unlink($file) : unlink($file);
+ @rmdir($file);
+ }
+ };
+ $unlink($this->scriptDir);
+ }
+}
diff --git a/src/ScriptinjectorModule.php b/src-deprecated/ScriptinjectorModule.php
similarity index 96%
rename from src/ScriptinjectorModule.php
rename to src-deprecated/ScriptinjectorModule.php
index 1008c5a..d062f81 100644
--- a/src/ScriptinjectorModule.php
+++ b/src-deprecated/ScriptinjectorModule.php
@@ -7,6 +7,7 @@
use Ray\Di\AbstractModule;
use Ray\Di\InjectorInterface;
+/** @deprecated */
class ScriptinjectorModule extends AbstractModule
{
/** @var string */
diff --git a/src/AirInjector.php b/src/AirInjector.php
new file mode 100644
index 0000000..fe7146d
--- /dev/null
+++ b/src/AirInjector.php
@@ -0,0 +1,173 @@
+
+ */
+ private $singletons = [];
+
+ /** @var array */
+ private static $scriptDirs = [];
+
+ /**
+ * @param string $scriptDir generated instance script folder path
+ *
+ * @psalm-suppress UnresolvableInclude
+ */
+ public function __construct($scriptDir)
+ {
+ $this->scriptDir = $scriptDir;
+ $this->registerLoader();
+ }
+
+ public function __wakeup()
+ {
+ $this->registerLoader();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @SuppressWarnings(PHPMD.UnusedLocalVariable)
+ * @psalm-suppress UnresolvableInclude
+ */
+ public function getInstance($interface, $name = Name::ANY)
+ {
+ static $prototype;
+ static $singleton;
+ static $injector;
+ static $injectionPoint;
+
+ if ($prototype === null) {
+ $injectionPoint = function (): InjectionPoint {
+ if ($this->ip[0] === '') {
+ throw new InjectionPointUnbound();
+ }
+
+ return new InjectionPoint(
+ new ReflectionParameter([$this->ip[0], $this->ip[1]], $this->ip[2])
+ );
+ };
+
+ $injector = function (): self {
+ return $this;
+ };
+
+ $prototype =
+ /**
+ * @param array{0: string, 1: string, 2: string} $ip
+ *
+ * @return mixed
+ */
+ function (string $dependencyIndex, array $ip = ['', '', '']) use ($injectionPoint, &$prototype, $injector, &$singleton) { // @phpstan-ignore-line
+ $this->ip = $ip; // @phpstan-ignore-line
+
+ return require $this->getInstanceFile($dependencyIndex);
+ };
+ $singleton =
+ /**
+ * @param array{0: string, 1: string, 2: string} $ip
+ *
+ * @return mixed
+ */
+ function (string $dependencyIndex, $ip = ['', '', '']) use ($injectionPoint, $prototype, $injector, &$singleton) { // @phpstan-ignore-line
+ if (isset($this->singletons[$dependencyIndex])) {
+ return $this->singletons[$dependencyIndex];
+ }
+
+ $this->ip = $ip;
+
+ $instance = require $this->getInstanceFile($dependencyIndex);
+ $this->singletons[$dependencyIndex] = $instance;
+
+ return $instance;
+ };
+ $scriptDir = $this->scriptDir;
+ }
+
+ $dependencyIndex = $interface . '-' . $name;
+ if (isset($this->singletons[$dependencyIndex])) {
+ return $this->singletons[$dependencyIndex];
+ }
+
+ /** @psalm-suppress UnresolvableInclude */
+ $instance = require $this->getInstanceFile($dependencyIndex);
+ /** @psalm-suppress UndefinedVariable */
+ $isSingleton = isset($isSingleton) && $isSingleton;
+ if ($isSingleton) {
+ $this->singletons[$dependencyIndex] = $instance;
+ }
+
+ /**
+ * @psalm-var T $instance
+ * @phpstan-var mixed $instance
+ */
+ return $instance;
+ }
+
+ /**
+ * Return compiled script file name
+ */
+ private function getInstanceFile(string $dependencyIndex): string
+ {
+ $file = sprintf('%s/%s.php', $this->scriptDir, str_replace('\\', '_', $dependencyIndex));
+ if (file_exists($file)) {
+ return $file;
+ }
+
+ throw new Unbound($dependencyIndex);
+ }
+
+ private function registerLoader(): void
+ {
+ if (in_array($this->scriptDir, self::$scriptDirs, true)) {
+ return;
+ }
+
+ if (self::$scriptDirs === []) {
+ spl_autoload_register(
+ static function (string $class): void {
+ foreach (self::$scriptDirs as $scriptDir) {
+ $file = sprintf('%s/%s.php', $scriptDir, str_replace('\\', '_', $class));
+ if (file_exists($file)) {
+ require_once $file; // @codeCoverageIgnore
+ }
+ }
+ }
+ );
+ }
+
+ self::$scriptDirs[] = $this->scriptDir;
+ }
+}
diff --git a/src/AopCode.php b/src/AopCode.php
deleted file mode 100644
index e88c9e1..0000000
--- a/src/AopCode.php
+++ /dev/null
@@ -1,93 +0,0 @@
-privateProperty = $privateProperty;
- }
-
- /**
- * Add aop factory code if bindings are given
- *
- * @param array $node
- *
- * @param-out array $node
- */
- public function __invoke(Dependency $dependency, array &$node): void
- {
- $prop = $this->privateProperty;
- /** @var ?NewInstance */
- $newInstance = $prop($dependency, 'newInstance');
- /** @var ?Bind */
- $bind = $prop($newInstance, 'bind');
- /** @var ?Bind */
- $aspectBind = $prop($bind, 'bind');
- /** @var string[][]|null $bindings */
- $bindings = $prop($aspectBind, 'bindings', null);
- if (! is_array($bindings)) {
- return;
- }
-
- $methodBinding = $this->getMethodBinding($bindings);
- $bindingsProp = new Expr\PropertyFetch(new Expr\Variable('instance'), 'bindings');
- $bindingsAssign = new Assign($bindingsProp, new Expr\Array_($methodBinding));
- $this->setBindingAssignAfterInitialization($node, [$bindingsAssign], 1);
- }
-
- /**
- * @param array $array
- * @param array $insertValue
- *
- * @param-out array $array
- */
- private function setBindingAssignAfterInitialization(array &$array, array $insertValue, int $position): void
- {
- $array = array_merge(array_splice($array, 0, $position), $insertValue, $array);
- }
-
- /**
- * @param string[][] $bindings
- *
- * @return Expr\ArrayItem[]
- */
- private function getMethodBinding(array $bindings): array
- {
- $methodBinding = [];
- foreach ($bindings as $method => $interceptors) {
- $items = [];
- foreach ($interceptors as $interceptor) {
- // $singleton('FakeAopInterface-*');
- $dependencyIndex = "{$interceptor}-" . Name::ANY;
- $singleton = new Expr\FuncCall(new Expr\Variable('singleton'), [new Node\Arg(new Scalar\String_($dependencyIndex))]);
- // [$singleton('FakeAopInterface-*'), $singleton('FakeAopInterface-*');]
- $items[] = new Expr\ArrayItem($singleton);
- }
-
- $arr = new Expr\Array_($items);
- $methodBinding[] = new Expr\ArrayItem($arr, new Scalar\String_($method));
- }
-
- return $methodBinding;
- }
-}
diff --git a/src/Code.php b/src/Code.php
deleted file mode 100644
index 46c4f67..0000000
--- a/src/Code.php
+++ /dev/null
@@ -1,37 +0,0 @@
-node = $node;
- $this->isSingleton = $isSingleton;
- $this->qualifiers = $qualifier;
- }
-
- public function __toString(): string
- {
- $prettyPrinter = new Standard();
-
- return $prettyPrinter->prettyPrintFile([$this->node]);
- }
-}
diff --git a/src/CompileInjector.php b/src/CompileInjector.php
index 3badda3..f1d40e2 100644
--- a/src/CompileInjector.php
+++ b/src/CompileInjector.php
@@ -4,209 +4,25 @@
namespace Ray\Compiler;
-use Ray\Compiler\Exception\Unbound;
-use Ray\Di\Annotation\ScriptDir;
-use Ray\Di\Bind;
use Ray\Di\Name;
-use ReflectionParameter;
-
-use function file_exists;
-use function in_array;
-use function rtrim;
-use function spl_autoload_register;
-use function sprintf;
-use function str_replace;
-use function touch;
final class CompileInjector implements ScriptInjectorInterface
{
- public const INSTANCE = '%s/%s.php';
- public const COMPILE_CHECK = '%s/compiled';
-
- /** @var string */
- private $scriptDir;
-
- /**
- * Injection Point
- *
- * [$class, $method, $parameter]
- *
- * @var array{0: string, 1: string, 2: string}
- */
- private $ip = ['', '', ''];
-
- /**
- * Singleton instance container
- *
- * @var array