rg / injektor
受google-guice启发的依赖注入容器
Requires
- php: >=8.1
- doctrine/annotations: ^1.12|^2.0.0
- laminas/laminas-code: ^4.13
- psr/log: ^1.0.0
Requires (Dev)
- friendsofphp/proxy-manager-lts: ^1.0
- phpunit/phpunit: ^9.0.0
- roave/better-reflection: ^5.0.0
Suggests
- ocramius/proxy-manager: For lazy loading
This package is auto-updated.
Last update: 2024-09-09 09:57:14 UTC
README
rg\injektor 是一个受 Guice 启发的 PHP 依赖注入容器。与基于反射的其他容器不同,rg\injektor 包含一个工厂类生成器,您可以使用它来防止在生产环境中使用反射。
先决条件
此库需要 PHP 8.1+。
它已在 PHP 8.1 和 PHP 8.2 上进行过测试。
安装
您可以直接使用 composer 安装此库。只需在您的项目目录中运行此命令
$ composer require rg/injektor
用法
安装 rg\injektor 后,您可以使用它如下
$configuration = new \rg\injektor\Configuration($pathToConfigFile, $pathToFactoryDirectory); $dic = new \rg\injektor\DependencyInjectionContainer($configuration); $instance = $dic->getInstanceOfClass('ClassName'); $result = $dic->callMethodOnObject($instance, 'methodName');
有关 rg\injektor 的特定功能的更多详细信息,请参阅下面。
如果您使用某种 MVC 框架,建议将 rg\injektor 包含在您的入口控制器中,以创建控制器对象并在其上调用方法。
生成工厂
默认情况下,rg\injektor 严重依赖反射,这对于您的开发环境来说是可以的,但会不必要地减慢生产环境。因此,您应该使用内置的可能性使用生成的工厂类。为了做到这一点,您必须在部署项目之前生成这些工厂。
首先,您必须在您的代码中使用 \rg\injektor\FactoryDependencyInjectionContainer 类
$configuration = new \rg\injektor\Configuration($pathToConfigFile, $pathToFactoryDirectory); $dic = new \rg\injektor\FactoryDependencyInjectionContainer($configuration);
如果没有工厂,\rg\injektor\FactoryDependencyInjectionContainer 将回退到反射。
要生成工厂,您必须编写一个小脚本,该脚本遍历您的 PHP 文件并为每个文件创建工厂。以下是基于 Symfony Console 组件的此类脚本的示例
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use rg\injektor\WritingFactoryGenerator; class GenerateDependencyInjectionFactories extends \Symfony\Component\Console\Command\Command { /** * @var \rg\injektor\DependencyInjectionContainer */ private $dic; /** * @var \rg\injektor\WritingFactoryGenerator */ private $factoryGenerator; /** * @var string */ private $root; protected function configure() { $this->setDescription('generates factories for dependency injection container'); $this->setHelp('generates factories for dependency injection container'); } /** * @param InputInterface $input * @param OutputInterface $output */ protected function execute(InputInterface $input, OutputInterface $output) { $output->writeln('Generating Factories'); $this->root = '/path/to/your/project'; $factoryPath = $this->root . '/folder/for/generated/factories'; if (!file_exists($factoryPath)) { mkdir($factoryPath, 0777, true); } $pathToConfigFile = '/config/dic.php'; $configuration = new \rg\injektor\Configuration($pathToConfigFile, $factoryPath); $this->dic = new \rg\injektor\FactoryDependencyInjectionContainer($configuration); $this->factoryGenerator = new WritingFactoryGenerator($this->dic->getConfig(), $factoryPath); $this->factoryGenerator->cleanUpGenerationDirectory($factoryPath); $this->processAllDirectories($output); } /** * @param OutputInterface $output */ private function processAllDirectories(OutputInterface $output) { $this->processDirectory($this->root . DIRECTORY_SEPARATOR . 'folderWithPhpClasses', $output); } /** * @param $directory * @param OutputInterface $output */ private function processDirectory($directory, OutputInterface $output) { $output->writeln('Directory: ' . $directory); $directoryIterator = new \RecursiveDirectoryIterator($directory); $iterator = new \RecursiveIteratorIterator($directoryIterator); $regexIterator = new \RegexIterator($iterator, '/^.+\.php$/i', \RecursiveRegexIterator::GET_MATCH); foreach ($regexIterator as $file) { $this->processFile($file[0], $output); } } /** * @param $fullpath * @param OutputInterface $output */ private function processFile($fullpath, OutputInterface $output) { $output->writeln('Process file [' . $fullpath . ']'); require_once $fullpath; $astLocator = (new \Roave\BetterReflection\BetterReflection())->astLocator(); $reflector = new \Roave\BetterReflection\Reflector\DefaultReflector(new Roave\BetterReflection\SourceLocator\Type\SingleFileSourceLocator($fileName, $astLocator)); $classes = $reflector->reflectAllClasses(); foreach ($classes as $class) { $generator->processClass($class->getName()); } } /** * @param \Laminas\Code\Reflection\ClassReflection $class */ private function processClass(\Laminas\Code\Reflection\ClassReflection $class) { if (!$class->isInstantiable()) { return; } $this->factoryGenerator->processFileForClass($class->name); } }
功能
构造函数注入
class Foo { /** * @inject * @param Bar $bar */ public function __construct(Bar $bar) { } } class Bar { } $dic->getInstanceOfClass('Foo');
Bar 的一个实例将被注入为构造函数参数 $bar。当然,Bar 也可以使用依赖注入。容器可以注入任何可注入的类,因为
- 它们在构造函数中有 @inject 注解
- 它们没有参数的构造函数
- 它们没有构造函数
- 参数是可选的
- 参数是配置的(见下文)
构造函数可以是 __construct 方法或如果类配置为单例且 __construct 方法是私有或受保护的,则类的静态 getInstance 方法。
class Foo { /** * @inject * @param Bar $bar */ public function __construct(Bar $bar) { } } /** * @singleton */ class Bar { private function __construct() { } public static function getInstance() { } } $dic->getInstanceOfClass('Foo');
属性注入
class Foo { /** * @inject * @var Bar */ protected $bar; } class Bar { } $dic->getInstanceOfClass('Foo');
字段 $bar 将有一个 Bar 的实例。为了使其工作,该字段不能是私有的,而必须是受保护的或公共的。这也可以与构造函数注入结合使用。
注入具体实现
class Foo { /** * @inject * @var Bar */ protected $bar; } /** * @implementedBy BarImpl */ interface Bar { } class BarImpl implements Bar { } $dic->getInstanceOfClass('Foo');
而不是 Bar,BarImpl 被注入到 $bar 中。您也可以在依赖注入配置中配置此操作,而不是使用注解
'Bar' => array( 'class' => 'BarImpl' )
使用提供者类
class Foo { /** * @inject * @var Bar */ protected $bar; } /** * @providedBy BarProvider */ interface Bar { } class BarImpl implements Bar { } class BarProvider implements rg\injektor\Provider { public function get() { return new BarImpl(); } } $dic->getInstanceOfClass('Foo');
而不是 Bar,BarProvider 的 get 方法(BarImpl)的返回值被注入到 $bar 中。您也可以在依赖注入配置中配置此操作,而不是使用注解
'Bar' => array( 'provider' => array( 'class' => 'BarImpl' ) )
向提供者传递固定数据
class Foo { /** * @inject * @var Bar */ protected $bar; } /** * @providedBy BarProvider {'foo' : 'bar'} */ interface Bar { } class BarImpl implements Bar { } class BarProvider implements rg\injektor\Provider { /** * @inject */ public function __construct(SomeClass $someClass, $foo) { } public function get() { return new BarImpl(); } } $dic->getInstanceOfClass('Foo');
在这里,提供者接收一个 SomeClass 的额外实例注入。变量 $foo 被设置为 'bar'。您也可以在配置中配置此操作
'Bar' => array( 'provider' => array( 'class' => 'BarImpl', 'params' => array( 'foo' => 'bar', ) ) )
作为单例注入
class Foo { /** * @inject * @var Bar */ protected $bar; } /** * @singleton */ class Bar { } $instanceOne = $dic->getInstanceOfClass('Foo'); $instanceTwo = $dic->getInstanceOfClass('Foo');
$instanceOne 和 $instanceTwo 都将具有相同的 Bar 注入实例。
您也可以在依赖注入配置中配置此操作,而不是使用注解
'Bar' => array( 'singleton' => true )
请注意,对于单例 injektor,它会分析注入类的给定参数以确定是否已创建了所需的实例。
这意味着在这个例子中
class Foo { /** * @inject * @var Bar */ protected $bar; } /** * @singleton */ class Bar { public function __construct($arg) { } } $instanceOne = $dic->getInstanceOfClass('Foo', array('arg' => 1)); $instanceTwo = $dic->getInstanceOfClass('Foo', array('arg' => 2));
$instanceOne 和 $instanceTwo 将是不同的实例。虽然这个特性有速度上的优势,但如果您想要无论参数如何都能保持相同的实例,或者总是传递相同的或注入所有参数,请将其标记为服务(见下文)。
作为服务注入
class Foo { /** * @inject * @var Bar */ protected $bar; } /** * @service */ class Bar { } $instanceOne = $dic->getInstanceOfClass('Foo'); $instanceTwo = $dic->getInstanceOfClass('Foo');
$instanceOne 和 $instanceTwo 都将具有相同的 Bar 注入实例。
您也可以在依赖注入配置中配置此操作,而不是使用注解
'Bar' => array( 'service' => true )
与单例模式相比,在这个服务示例中
class Foo { /** * @inject * @var Bar */ protected $bar; } /** * @service */ class Bar { public function __construct($arg) { } } $instanceOne = $dic->getInstanceOfClass('Foo', array('arg' => 1)); $instanceTwo = $dic->getInstanceOfClass('Foo', array('arg' => 2));
将导致 $instanceOne 和 $instanceTwo 是相同的对象实例。
配置参数
您还可以在配置中配置容器应传递给 __construct 或 getInstance 方法的所有或某些参数的内容,而不是让容器从类型提示中猜测它们。
class Foo { /** * @inject */ public function __construct($bar) { } } /** * @singleton */ class Bar { private function __construct() { } /** * @inject */ public static function getInstance($foo, $buzz) { } } $dic->getInstanceOfClass('Foo');
配置
'Foo' => array( 'params' => array( 'bar' => array( 'class' => 'Bar' ) ) ), 'Bar' = array( 'params' => array( 'foo' => array( 'value' => 'fooBar' ), 'buzz' => array( 'value' => true ) ) )
或者,您也可以使用注解来配置
class Foo { /** * @inject * @var Bar {"foo":456,"buzz":"content"} */ protected $propertyInjection; /** * @inject * @param Bar $bar {"foo":123,"buzz":"content"} */ public function __construct(Bar $bar) { } } /** * @singleton */ class Bar { private function __construct() { } /** * @inject */ public static function getInstance($foo, $buzz) { } } $dic->getInstanceOfClass('Foo');
在运行时传递额外的参数
您还可以在运行时向新实例传递一些值。
class Foo { /** * @inject */ public function __construct($val, Bar $bar, Buzz $buzz) { } } class Bar { } class Buzz { } $dic->getInstanceOfClass('Foo', array( 'val' => 123, 'buzz' => new Buzz() ));
这也可以与配置参数结合使用。
命名注入
class Foo { /** * @var Bar * @named barOne */ protected $bar; /** * @inject * @param Bar $one * @param Bar $two * @param Bar $default * @named barOne $one * @named barTwo $two */ public function __construct(Bar $one, Bar $two, Bar $default) { } } interface Bar { } class BarImplDefault implements Bar { } class BarImplOne implements Bar { } class BarImplTwo implements Bar { } $dic->getInstanceOfClass('Foo');
配置
'Bar' => array( 'class' => 'BarImplDefault' 'named' => array( 'barOne' => 'BarImplOne', 'barTwo' => 'BarImplTwo' ) )
您也可以使用注解直接配置此内容
/** * @implementedBy BarImplDefault * @implementedBy barOne BarImplOne * @implementedBy barTwo BarImplTwo */ interface Bar { } class BarImplDefault implements Bar { } class BarImplOne implements Bar { } class BarImplTwo implements Bar { }
您还可以为默认实现命名,这样我们的配置看起来会更加简洁。结果是一样的
/** * @implementedBy default BarImplDefault * @implementedBy barOne BarImplOne * @implementedBy barTwo BarImplTwo */ interface Bar { }
命名提供者
class Foo { /** * @var Bar * @named barOne */ protected $bar; /** * @inject * @param Bar $one * @param Bar $two * @param Bar $default * @named barOne $one * @named barTwo $two */ public function __construct(Bar $one, Bar $two, Bar $default) { } } interface Bar { } $dic->getInstanceOfClass('Foo');
配置
'Bar' => array( 'provider' => array( 'class' => 'BarProvider' ), 'namedProviders' => array( 'barOne' => array( 'class' => 'BarProvider', 'parameters' => array('name' => 'barOne') ), 'barTwo' => array( 'class' => 'BarProvider', 'parameters' => array('name' => 'barTwo') ) ) )
您也可以使用注解直接配置此内容
/** * @providedBy BarProvider * @providedBy barOne BarProvider {"name" : "barOne"} * @providedBy barTwo BarProvider {"name" : "barOne"} */ interface Bar { } class BarProvider implements rg\injektor\Provider { private $name; public function __construct($name) { $this->name = $name; } public function get() { switch ($this->name) { case 'barOne': return new BarImplOne(); case 'barTwo': return new BarImplTwo(); } return new BarImplDefault(); } } class BarImplDefault implements Bar { } class BarImplOne implements Bar { } class BarImplTwo implements Bar { }
您还可以为默认提供者命名,这样我们的配置看起来会更加简洁。结果是一样的
/** * @providedBy default BarProvider * @providedBy barOne BarProvider {"name" : "barOne"} * @providedBy barTwo BarProvider {"name" : "barOne"} */ interface Bar { }
在对象实例上调用方法
容器还可以在实例上调用方法并注入所有方法参数
class Foo { /** * @inject */ public function doSomething(Bar $bar) { } } class Bar { } $foo = new Foo(); $dic->callMethodOnObject($foo, 'doSomething');
当然,您也可以使用命名注入。
您还可以在方法调用中添加额外的值,就像在对象创建时一样
class Foo { /** * @inject */ public function doSomething(Bar $bar, $foo) { } } class Bar { } $foo = new Foo(); $dic->callMethodOnObject($foo, 'doSomething', array('foo' => 'value'));