ray-di / ray.compiler Goto Github PK
View Code? Open in Web Editor NEWA dependency injection compiler for Ray.Di
License: MIT License
A dependency injection compiler for Ray.Di
License: MIT License
Ray.Di
はビルトインモジュールとして以下の3つのモジュールをインストールしています。
ref: https://github.com/ray-di/Ray.Di/blob/2.x/src/di/ContainerFactory.php#L22-L26
上記のモジュールは Ray\Di\Injector
を利用する場合には利用側が意識する必要はありませんが、ScriptInjector
を利用する場合には利用側でモジュールをインストールする必要があります。
ScriptInjector
を利用する際にも通常利用側が意識しないような作りが望ましいと感じましたがどうでしょうか。
Hi. Is there any way to support environment variable at runtime ?
Currently, BEAR.Sunday tutorial too refers PHP's getenv()
function.
https://bearsunday.github.io/manuals/1.0/en/tutorial2.html
new AuraSqlModule(
(string) getenv('TKT_DB_DSN'),
But it is evaluated (compiled) at build system's environment value.
eg. I use Google Cloud Build for build (Docker container build & di compiled), and run application at Cloud Run.
Is there any way like PHP-DI 's env variable evaluation?
https://php-di.org/doc/php-definitions.html#environment-variables
AbstractInjectorContext はコンテキストに応じたモジュール、キャッシュプロバイダなどを提供します。
主にテスト実行時において、コンテキストは再利用しつつもテストケースに応じて束縛を変更したい場合があります。
上記のようなケースに対応するには、束縛を上書きするためのモジュールもしくはコンテキストを外から渡せる必要があります。
以下のようなイメージになります
$context = new TestContext($tmp, new OverrideModule());
$module = $context();
[error] "PHP message: PHP Fatal error: Uncaught TypeError: Argument 2 passed to Ray\Compiler\FunctionCode::__invoke() must implement interface Ray\Di\DependencyInterface, string given
Provider束縛で指定する型がInterfaceでない場合、指定した型の依存解決が必要となりUnbound例外が発生する。
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
class A
{
public function __construct(private B $b)
{}
}
class B
{
public function __construct(private C $c)
{}
}
class C
{
public function __construct(private DInterface $d)
{}
}
interface DInterface {}
class D implements DInterface {}
class Provider implements \Ray\Di\ProviderInterface
{
public function get(): B
{
return new B(new C(new D()));
}
}
class Module extends \Ray\Di\AbstractModule
{
protected function configure()
{
$this->bind(A::class);
$this->bind(B::class)->toProvider(Provider::class);
}
}
$module = new Module();
$compiler = new \Ray\Compiler\DiCompiler($module, __DIR__ . '/../tmp');
$compiler->compile();
このとき、C
はコンパイル対象ではないことを期待していましたが、実際には以下のように Unbound 例外がスローされます。
Fatal error: Uncaught exception 'Ray\Di\Exception\Unbound' with message 'dependency 'DInterface' with name '' used in /Users/tsuchiya/work/github.com/NaokiTsuchiya/Ray.Compiler/tests/Fake/example.php:21 ($d)'
thrown in /Users/tsuchiya/work/github.com/NaokiTsuchiya/Ray.Compiler/src/NodeFactory.php on line 118
PHP Fatal error: Uncaught exception 'Ray\Di\Exception\Unbound' with message 'dependency 'DInterface' with name '' used in /Users/tsuchiya/work/github.com/NaokiTsuchiya/Ray.Compiler/tests/Fake/example.php:21 ($d)'
thrown in /Users/tsuchiya/work/github.com/NaokiTsuchiya/Ray.Compiler/src/NodeFactory.php on line 118
Process finished with exit code 255
上記の Module を文字列評価した結果は以下のとおりです。
A- => (dependency) A
B- => (provider) (dependency) Provider
C- => (dependency) C
利用側でInterfaceを定義して回避することは可能ですが、
Bの依存クラスであるCがコンパイル対象となるのは期待する振る舞いではないように感じました。
Red テストの実装は以下のとおりです。
https://github.com/ray-di/Ray.Compiler/compare/1.x...NaokiTsuchiya:untarget-provider?expand=1
DependencyCompiler
ignore optional=true
.
以下のようなリクエストを同時に2つ出すと module.txt がないとして RuntimeError になることがあります。
curl http://localhost:8080/some/uri
{
"message": "Service Unavailable",
"logref": "4c75224c",
"request": "get page://self/some/uri",
"exceptions": "RuntimeException(/path/to/var/tmp/dev-hal-app/di/module.txt is not readable)",
"file": "/path/to/vendor/ray/compiler/src/ScriptInjector.php(180)"
}
ScriptInjector::__wakeup
と ScriptInjector::saveModule
のファイルアクセスのタイミング問題で発生しているような気はしますが、解決策は何かありますでしょうか?
/**
* @param Cake\Database\Connection $db
*
* @Inject
* @return void
*/
public function setDbConnection(Connection $db = null)
{
$this->db = $db;
}
null
had a priority in this case for compilation even Connection
was bound.
以下のようにbindings
が後になっているとsetTires
でバインディングがあるときに、つまりセッター人ジェクションにAOPが適用されているエッジケースに対応できない。
$instance = new \Ray_Compiler_FakeCar_JhUHY3Q($prototype('Ray\\Compiler\\FakeEngineInterface-'));
$instance->setTires($prototype('Ray\\Compiler\\FakeTyreInterface-'),
$instance->bindings = array('setTires' => array($singleton('Ray\\Compiler\\FakeInterceptor-')));
$return $instance;
$instance->bindings =
の代入をnew
の直後に行う。
$instance = new \Ray_Compiler_FakeCar_JhUHY3Q($prototype('Ray\\Compiler\\FakeEngineInterface-'));
$instance->bindings = array('setTires' => array($singleton('Ray\\Compiler\\FakeInterceptor-')));
$instance->setTires($prototype('Ray\\Compiler\\FakeTyreInterface-'),
$return $instance;
The InjectorFactory
and CachedInjectorFactory
is the injector for when you want to sacrifice a little simplicity and get great performance.
Preparation:
You must have at least two modules, one for base and one for production. You can name them as you wish, but you must install DiCompileModule
for production.
With the CachedInjectorFactory...
Ray.Di
injector is 2X the performance during development and ScriptInjector has 10x more performance. CachedInjectorFactory
creates either injector by context.
The CachedInjectorFactory
caches the injector itself. which can contain bootstrap objects in singleton container.
The cost of the initial object injection becomes almost zero. This is usually the main factor that slows down the framework. CachedInjectorFactory
dramatically improves bootstrap performance.
final class AppInjector
{
/**
* @param 'dev'|'prod' $context
*/
private static function getInstance(string $context) : InjectorInterface
{
if ($context === 'dev') {
return CachedInjectorFactory::getInstance(
'dev',
'/path/to/di/dev',
function () : AbstractModule {
return new Module;
}
);
}
return CachedInjectorFactory::getInstance(
'prod',
'/path/to/di/prod',
function () : AbstractModule {
$module = new Module;
$module->install(new DiCompileModule(true));
return $module;
},
(new PhpFileCache('/path/to/injector_cache')),
[RootObjectInterface::class]
);
}
}
$context = getenv('context');
$injector = AppInjectpr::getInstance($context);
$root = $injector->getInstance(RootObjectInterface::class); // get cached object in the production
InjectorFactoryは単純さが少し犠牲になりますが、大きなパフォーマンスが得られるインジェクターです。
準備:
モジュールを最低2つ、ベースのモジュールとプロダクション用のモジュールを用意します。名前は自由につけれますが、プロダクションではDiCompileModule
をインストールしてください。
CachedInjectorFactoryを使うと…
開発時には速度の速いRay.Di injector、プロダクションで最高のパフォーマンスを発揮するRay.CompilerのScriptInjectorが渡されます。
開発時のRay.Di injectorはScriptInjectorの倍以上のパフォーマンスがあり、プロダクションの時のScriptInjectorは10倍以上のパフォーマンスがあります。(環境によります)
CachedInjectorFactoryはインジェクターをキャッシュします。そのキャッシュされるインジェクターにはブートストラップの時に必要なオブジェクトがシングルトンが含まれます。
アプリケーションブート時のインジェクションコストがほぼゼロになります。通常これはフレームワークの速度を遅くしてる主要因です。CachedInjectorFactoryはブートストラップのパフォーマンスを劇的に改善します。
バインドされていないインターフェイスの依存のコードの作成が間違っている
生成例
<?php
namespace Ray\Di\Compiler;
$instance = new \FakeVendor\HelloWorld\UnboundInterface();
$is_singleton = false;
return $instance;
Unboundをインターフェイスを確認しないでUntarget束縛してるのではないだろうか?
CachedInjectorFactory
からインジェクターを取得した時にコンテナ内の状態変更がリセットされていない。
$injector = ContextInjector::getInstance($context);
$deep = $injector->getInstance(FakeDeep::class);
// $deep状態変更 (FakeDeepはシングルトン)
$injector = ContextInjector::getInstance($context);
$deep2 = $injector->getInstance(FakeDeep::class);
// $deep2は$deepと同じ
現在のScriptInjectorの生成
$injector = new ScriptInjector(
__DIR__ . '/tmp',
static function () {
return new FakeCarModule();
}
);
改善案:
上記に対して、LazyModuleInterface
を実装したモジュールを用意します。
interface LazyModuleInterface
{
public function __invoke(): AbstractModule;
}
class MyLazyModule implemetes LazyModuleInterface
{
public function __invoke(): AbstractModule
{
return new FakeCarModule();
}
}
$injector = new ScriptInjector(
__DIR__ . '/tmp',
new MyLazyModule()
);
MyLazyModule
はシリアライズ可能なオブジェクトです。無名関数と違って、モジュールをデータファイル(_module.txt)に書き出す必要がなくrace conditionの問題が消滅します。
Null bound can be compiled, but when AOP is applied, the AOP part is ignored
Thanks for the reporting issue @jingu.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.