emrancu / dependency-injection-container
PHP的依赖注入容器
Requires
- php: >=7.0.0
This package is auto-updated.
Last update: 2024-08-29 05:37:21 UTC
README
我们正在用Laravel构建应用程序,但许多人不知道如何使用依赖和参数Laravel初始化控制器和方法。
我们声明路由如下
Route::get('hello', 'TestController@index')
我们的控制器如下
namespace App\Http\Controllers\Test; class TestController extends Controller { private $requests; public function __construct( Request $request) { $this->requests = $request; } /** * Show the application dashboard. */ public function index(TestModel $testModel) { return $testModel->get(); } }
或者路由如下
Route::get('hello', 'TestController')
或者控制器如下
namespace App\Http\Controllers\Test; class TestController extends Controller { private $requests; public function __construct( Request $request) { $this->requests = $request; } /** * Show the application dashboard. */ public function __invoke(TestModel $testModel) { return $testModel->get(); } }
现在的问题
1.1 Laravel如何检测构造函数的依赖并注入?
1.2 Laravel如何使用构造函数的依赖初始化类,并调用带有依赖和参数的'index'方法?
2.1 我们没有传递方法名,但Laravel如何检测 __invoke 方法作为默认方法?
让我们构建一个依赖注入容器来理解前面的问题。
为什么我们应该了解?
- 作为Laravel开发者,我们需要了解Laravel是如何工作的。
- 这有助于我们以自己的方法构建新的框架。
- 我们可以使用这个容器来处理我们的大型项目或模块。
- 最后,我们将开始学习PHP Reflection(很棒!)
让我们开始
我们的步骤
- 创建一个类
- 创建一个用于初始化类以及注入构造函数的依赖和参数的方法(make)
- 创建一个用于提取类和方法以及注入方法依赖和参数的方法(call)
##1. 让我们创建一个类
class DependencyInjectionContainer { /** * The container's instance. * * @var static */ protected static $instance; /** * the class name with namespace * * @var string */ protected $callbackClass; /** * the method name of provided class * * @var string */ protected $callbackMethod; /** * method separator of a class. when pass class and method as string */ protected $methodSeparator = '@'; /** * namespace for class. when pass class and method as string * * @var string */ protected $namespace = "App\\controller\\"; /** * get Singleton instance of the class * * @return static */ public static function instance() { } /** * @param $callable -- string class and method name with separator @ * @param array $parameters */ public function call($callable, $parameters = []) { } /** * separate class and method name * @param $callback */ private function resolveCallback($callback) { } /** * instantiate class with dependency and return class instance * @param $class - class name * @param $parameters (optional) -- parameters as array . If constructor need any parameter */ public function make($class, $parameters = []) { } }
##创建一个用于初始化类以及注入构造函数的依赖和参数的方法(make)
我们将使用PHP PHP Reflection。在本节中,我们使用PHP ReflactionClass 对象。PHP ReflactionClass 有很多方法。在本节中,我们需要创建一个类实例。我们知道,如果我们创建任何类的实例,我们必须传递/注入所有依赖项和参数。
使用PHP ReflactionClass,我们需要检测所有依赖项。
public function make($class, $parameters = []) { $classReflection = new ReflectionClass($class); $constructorParams = $classReflection->getConstructor()->getParameters(); }
在上述代码中,我们将构造函数的所有参数收集到 $constructorParams
变量中。
然后我们需要遍历 $constructorParams
并检测参数和依赖项。
public function make($class, $parameters = []) { $classReflection = new ReflectionClass($class); $constructorParams = $classReflection->getConstructor()->getParameters(); $dependencies = []; /* * loop with constructor parameters or dependency */ foreach ($constructorParams as $constructorParam) { $type = $constructorParam->getType(); if ($type && $type instanceof ReflectionNamedType) { echo "It is a class and we need to instantiate the class and pass to constructor" } else { echo "This is a normal parameter and We need to find parameter value from $parameters . If not found value then need to check is this parameter optional or not. If not optional then through error" } } }
要点
- 如果参数是一个类,我们需要实例化该类并将其传递给构造函数。
- 如果参数不是一个类,我们需要从 $parameters 中查找参数值。如果没有找到值,则需要检查该参数是否为可选参数。如果不是可选参数,则抛出错误。
让我们完善 make 方法
public function make($class, $parameters = []) { $classReflection = new ReflectionClass($class); $constructorParams = $classReflection->getConstructor()->getParameters(); $dependencies = []; /* * loop with constructor parameters or dependency */ foreach ($constructorParams as $constructorParam) { $type = $constructorParam->getType(); if ($type && $type instanceof ReflectionNamedType) { // make instance of this class : $paramInstance = $constructorParam->getClass()->newInstance() // push to $dependencies array array_push($dependencies, $paramInstance); } else { $name = $constructorParam->getName(); // get the name of param // check this param value exist in $parameters if (array_key_exists($name, $parameters)) { // if exist // push value to $dependencies sequencially array_push($dependencies, $parameters[$name]); } else { // if not exist if (!$constructorParam->isOptional()) { // check if not optional throw new Exception("Can not resolve parameters"); } } } } // finally pass dependancy and param to class instance return $classReflection->newInstance(...$dependencies); }
我在代码中解释了所有内容。
好的,我们已经完成了一个用于类的依赖注入容器
让我们创建一个单例实例
public static function instance() { if (is_null(static::$instance)) { static::$instance = new static; } return static::$instance; }
基本用法
$container = DependencyInjectionContainer::instance();
定义一个类
class MyClass { private $dependency; public function __construct(AnotherClass $dependency) { $this->dependency = $dependency; } }
不要使用 new MyClass
,而是使用容器的 make()
方法
$instance = $container->make(MyClass::class);
容器将自动实例化依赖项,因此这在功能上等同于
$instance = new MyClass(new AnotherClass());
实际示例
以下是一个基于 PHP-DI 文档 的更实际的示例 - 将邮件功能从用户注册中分离出来
class Mailer { public function mail($recipient, $content) { // Send an email to the recipient // ... } }
class UserManager { private $mailer; public function __construct(Mailer $mailer) { $this->mailer = $mailer; } public function register($email, $password) { // Create the user account } }
$container = DependencyInjectionContainer::instance(); $userManager = $container->make(UserManager::class); $userManager->register('dave@davejamesmiller.com', 'MySuperSecurePassword!');
##3. 创建一个用于提取类与方法并注入方法依赖和参数的方法(调用)。在本节中,我们将了解Laravel如何解析控制器和方法,以及如何通过依赖/参数来初始化。
步骤
- 分离控制器和方法
- 检测方法依赖/参数并调用方法
- 使用我们的
make()
函数实例化类。
1. 分离控制器和方法
我们的类有一个属性$methodSeparator
,用作分离类名和方法名的分隔符。
private function resolveCallback($callable) { //separate class and method $segments = explode($this->methodSeparator, $callable); // set class name with namespace $this->callbackClass = $this->namespace.$segments[0]; // set method name . if method name not provided then default method __invoke $this->callbackMethod = isset($segments[1]) ? $segments[1] : '__invoke'; }
2. 检测方法依赖/参数并调用方法
public function call($callable, $parameters = []) { // set class name with namespace and method name $this->resolveCallback($callable); // initiate ReflectionMethod with class and method $methodReflection = new ReflectionMethod($this->callbackClass, $this->callbackMethod); // get all dependencies/parameters $methodParams = $methodReflection->getParameters(); $dependencies = []; // loop with dependencies/parameters foreach ($methodParams as $param) { $type = $param->getType(); // check type if ($type && $type instanceof ReflectionNamedType) { /// if parameter is a class $name = $param->getClass()->newInstance(); // create insrance array_push($dependencies, $name); // push to $dependencies array } else { /// Normal parameter $name = $param->getName(); if (array_key_exists($name, $parameters)) { // check exist in $parameters array_push($dependencies, $parameters[$name]); // push to $dependencies array } else { // if not exist if (!$param->isOptional()) { // check if not optional throw new Exception("Can not resolve parameters"); } } } } // make class instance $initClass = $this->make($this->callbackClass, $parameters); // call method with $dependencies/parameters return $methodReflection->invoke($initClass, ...$dependencies); }
现在你可能会问,Laravel是如何收集参数以调用函数的。
当我们声明一个具有动态参数的路由时:
Route::get('hello/{name}', 'TestController@index')
Laravel路由系统会收集所有参数并传递给被调用的函数。
$parameters = ["name" => "AL EMRAN" ]; $container->call("TestController@index", $parameters)
用法
$container->call('TestController@index'); $container->call('TestController@show', ['id' => 4]); $container->call('TestController'); $container->call('TestController', ['id' => 4]);
或者
示例
class TestController { protected $company; public function __construct(Company $company) { $this->company = $company ; } /** * @param Request $request */ public function index(Request $request){ $company = $company->get(); return view('admin.company', compact('company')); } }
我们可以使用
$instance = DependencyInjectionContainer::instance(); $instance->namespace = "App\\Http\Controllers\\Admin\\"; // change namespace $class = $instance->make(CompanyController::class); // make class instance $instance->call(["CompanyController", 'index']); // call method $instance->call([$class, 'index']); // or--call method
安装包
composer require emrancu/dependency-injection-container
免责声明
我不是PHP专家,只是尝试学习。
Laravel依赖注入容器非常庞大,其路由系统也是如此。我只是创建了这个小型类来帮助理解。
这是我的第一篇文章,所以请留下您的宝贵意见或通过“emrancu1@gmail.com”给我发邮件
灵感来源于这里
创始人,Babshaye.com(离线和在线业务解决方案)在Facebook上关注