다음 튜토리얼 칼럼인 Laravel Framework에서 Laravel-IOC 컨테이너에 대해 자세히 설명하겠습니다. 도움이 필요한 친구들에게 도움이 되길 바랍니다!
IOC(Inversion of Controller)를 제어 모드의 반전이라고 하며, 종속성 주입(Dependency Injection) 모드라고도 합니다. 종속성 주입의 개념을 이해하기 위해 먼저 종속성이 무엇인지 이해합니다
//支付宝支付 class Alipay { public function __construct(){} public function pay() { echo 'pay bill by alipay'; } } //微信支付 class Wechatpay { public function __construct(){} public function pay() { echo 'pay bill by wechatpay'; } } //银联支付 class Unionpay{ public function __construct(){} public function pay() { echo 'pay bill by unionpay'; } } //支付账单 class PayBill { private $payMethod; public function __construct( ) { $this->payMethod= new Alipay (); } public function payMyBill() { $this->payMethod->pay(); } } $pb = new PayBill (); $pb->payMyBill();
위의 코드를 통해 PayBill 클래스의 인스턴스를 생성할 때 PayBill의 생성자에 { $this->payMethod= new Alipay( ); } 즉, Alipay 클래스가 인스턴스화됩니다. 이때 종속성이 생성됩니다. 이는 Alipay로 결제하려면 먼저 Alipay 인스턴스를 획득해야 한다는 의미로 이해할 수 있습니다. Alipay 인스턴스. 함수 지원. new 키워드 사용을 마치면 Alipay 인스턴스를 얻었기 때문에 실제로 종속성은 해결되었습니다.
사실 제가 ioc 개념을 알기 전에는 대부분의 코드가 여기에 있었습니다. 패턴~ _ ~ .이것의 문제점은 무엇입니까? 예를 들어 Alipay 대신 WeChat을 사용하려면 어떻게 해야 합니까? Payment 생성자의 코드를 수정하고 인스턴스화하면 됩니다.
저희 프로그램이 그다지 크지 않으면 아무 느낌이 없을 수도 있지만, 코드가 매우 복잡하고 거대하고 요구 사항이 자주 변경되면 코드를 수정하는 것이 매우 번거로울 것입니다. 따라서 ioc의 아이디어는 Payment 클래스의 종속성을 인스턴스화하고 해결하기 위해 new를 사용하는 것이 아니라 간단히 말하면 내부적으로 새로운 단계가 없으며 결제가 가능하다는 것입니다. 예
종속성이 무엇인지 알았으니 위 코드를 확장해 보겠습니다.
//支付类接口 interface Pay { public function pay(); } //支付宝支付 class Alipay implements Pay { public function __construct(){} public function pay() { echo 'pay bill by alipay'; } } //微信支付 class Wechatpay implements Pay { public function __construct(){} public function pay() { echo 'pay bill by wechatpay'; } } //银联支付 class Unionpay implements Pay { public function __construct(){} public function pay() { echo 'pay bill by unionpay'; } } //付款 class PayBill { private $payMethod; public function __construct( Pay $payMethod) { $this->payMethod= $payMethod; } public function payMyBill() { $this->payMethod->pay(); } } //生成依赖 $payMethod = new Alipay(); //注入依赖 $pb = new PayBill( $payMethod ); $pb->payMyBill();
위 코드에서 다음을 추가합니다. Pay 인터페이스와 모든 결제 방법은 이 인터페이스를 상속하고 결제 기능을 구현합니다. 인터페이스를 사용해야 하는 이유에 대해서는 나중에 설명하겠습니다.
PayBill을 인스턴스화하기 전에 먼저 인스턴스를 생성합니다. 이 단계는 종속성을 생성하는 것입니다. 그런 다음 코드를 통해 이 종속성을 PayBill에 주입해야 합니다. 생성자 이러한 방식으로 PayBill 인스턴스 $pb는 Alipay로 결제할 수 있습니다.
PayBill 클래스를 인스턴스화하기 위해 생성자를 통해 Alipay 클래스의 인스턴스를 주입합니다. Laravel 프레임워크 구현은 수동입니다.
IOC 컨테이너를 소개하기 전에 먼저 리플렉션(reflection)의 개념을 이해해 봅시다. IOC 컨테이너에도 리플렉션을 통해 달성되는 내용이 무엇인지 설명하기 위해 인터넷에서 한 문단을 복사했습니다. "리플렉션은 PHP 실행 상태에서 PHP 프로그램의 분석을 확장하고 주석을 포함하여 클래스, 메소드, 속성, 매개변수 등에 대한 세부 정보를 내보내거나 추출하는 것을 의미합니다." 리플렉션 API는 객체 지향 패러다임에서 메타 모델을 조작하기 위한 API로, 복잡하고 확장 가능한 객체를 구축하는 데 도움이 됩니다. 자동으로 문서를 생성하고 PHP 언어를 확장하는 데에도 사용할 수 있습니다."
간단한 예를 들어
class B{ } class A { public function __construct(B $args) { } public function dosomething() { echo 'Hello world'; } } //建立class A 的反射 $reflection = new ReflectionClass('A'); $b = new B(); //获取class A 的实例 $instance = $reflection ->newInstanceArgs( [ $b ]); $instance->dosomething(); //输出 ‘Hellow World’ $constructor = $reflection->getConstructor();//获取class A 的构造函数 $dependencies = $constructor->getParameters();//获取class A 的依赖类 dump($constructor); dump($dependencies);
덤프를 통해 얻은 $constructor 및 $dependent의 결과는 다음과 같습니다
//constructor ReflectionMethod {#351 +name: "__construct" +class: "A" parameters: array:1 [] extra: array:3 [] modifiers: "public" } //$dependencies array:1 [ 0 => ReflectionParameter {#352 +name: "args" position: 0 typeHint: "B" } ]
다음으로 라라벨 프레임워크에서 서비스 컨테이너는 라라벨 전체의 핵심이 되는 개념을 소개하겠습니다. 전체 시스템 기능과 서비스의 구성 및 호출은 냉장고와 같은 물건을 담는 것입니다. 냉장고에 있는 물건이 필요할 때 바로 가져갈 수 있습니다. 서비스 컨테이너도 이런 식으로 이해할 수 있습니다. 프로그램이 실행되기 시작하면 필요한 서비스를 컨테이너에 넣거나 등록(바인딩)하고 필요할 때 꺼내(만들기) 합니다. 위에서 언급한 make는 등록과 제거의 두 가지 작업입니다.
好了,说了这么多,下面要上一段容器的代码了. 下面这段代码不是laravel 的源码, 而是来自一本书《laravel 框架关键技术解析》. 这段代码很好的还原了laravel 的服务容器的核心思想. 代码有点长, 小伙伴们要耐心看. 当然小伙伴完全可以试着运行一下这段代码,然后调试一下,这样会更有助于理解.
<?php //容器类装实例或提供实例的回调函数 class Container { //用于装提供实例的回调函数,真正的容器还会装实例等其他内容 //从而实现单例等高级功能 protected $bindings = []; //绑定接口和生成相应实例的回调函数 public function bind($abstract, $concrete=null, $shared=false) { //如果提供的参数不是回调函数,则产生默认的回调函数 if(!$concrete instanceof Closure) { $concrete = $this->getClosure($abstract, $concrete); } $this->bindings[$abstract] = compact('concrete', 'shared'); } //默认生成实例的回调函数 protected function getClosure($abstract, $concrete) { return function($c) use ($abstract, $concrete) { $method = ($abstract == $concrete) ? 'build' : 'make'; return $c->$method($concrete); }; } public function make($abstract) { $concrete = $this->getConcrete($abstract); if($this->isBuildable($concrete, $abstract)) { $object = $this->build($concrete); } else { $object = $this->make($concrete); } return $object; } protected function isBuildable($concrete, $abstract) { return $concrete === $abstract || $concrete instanceof Closure; } //获取绑定的回调函数 protected function getConcrete($abstract) { if(!isset($this->bindings[$abstract])) { return $abstract; } return $this->bindings[$abstract]['concrete']; } //实例化对象 public function build($concrete) { if($concrete instanceof Closure) { return $concrete($this); } $reflector = new ReflectionClass($concrete); if(!$reflector->isInstantiable()) { echo $message = "Target [$concrete] is not instantiable"; } $constructor = $reflector->getConstructor(); if(is_null($constructor)) { return new $concrete; } $dependencies = $constructor->getParameters(); $instances = $this->getDependencies($dependencies); return $reflector->newInstanceArgs($instances); } //解决通过反射机制实例化对象时的依赖 protected function getDependencies($parameters) { $dependencies = []; foreach($parameters as $parameter) { $dependency = $parameter->getClass(); if(is_null($dependency)) { $dependencies[] = NULL; } else { $dependencies[] = $this->resolveClass($parameter); } } return (array)$dependencies; } protected function resolveClass(ReflectionParameter $parameter) { return $this->make($parameter->getClass()->name); } }
上面的代码就生成了一个容器,下面是如何使用容器
$app = new Container(); $app->bind("Pay", "Alipay");//Pay 为接口, Alipay 是 class Alipay $app->bind("tryToPayMyBill", "PayBill"); //tryToPayMyBill可以当做是Class PayBill 的服务别名 //通过字符解析,或得到了Class PayBill 的实例 $paybill = $app->make("tryToPayMyBill"); //因为之前已经把Pay 接口绑定为了 Alipay,所以调用pay 方法的话会显示 'pay bill by alipay ' $paybill->payMyBill();
当我们实例化一个Container得到 $app 后, 我们就可以向其中填充东西了
$app->bind("Pay", "Alipay"); $app->bind("tryToPayMyBill", "PayBill");
当执行完这两行绑定码后, $app 里面的属性 $bindings 就已经有了array 值,是啥样的呢,我们来看下
array:2 [ "App\Http\Controllers\Pay" => array:2 [ "concrete" => Closure {#355 class: "App\Http\Controllers\Container" this:Container{[#354](http://127.0.0.4/ioc#sf-dump-254248394-ref2354) …} parameters: array:1 [ "$c" => [] ] use: array:2 [ "$abstract" => "App\Http\Controllers\Pay" "$concrete" => "App\Http\Controllers\Alipay" ] file: "C:\project\test\app\Http\Controllers\IOCController.php" line: "119 to 122" } "shared" => false ] "tryToPayMyBill" => array:2 [ "concrete" => Closure {#359 class: "App\Http\Controllers\Container" this:Container{[#354](http://127.0.0.4/ioc#sf-dump-254248394-ref2354) …} parameters: array:1 [ "$c" => [] ] use: array:2 [ "$abstract" => "tryToPayMyBill" "$concrete" => "\App\Http\Controllers\PayBill" ] file: "C:\project\test\app\Http\Controllers\IOCController.php" line: "119 to 122" } "shared" => false ] ]
当执行 $paybill = $app->make(“tryToPayMyBill”); 的时候, 程序就会用make方法通过闭包函数的回调开始解析了.
解析’tryToPayBill’ 这个字符串, 程序通过闭包函数 和build方法会得到 ‘PayBill’ 这个字符串,该字符串保存在$concrete 上. 这个是第一步. 然后程序还会以类似于递归方式 将$concrete 传入 build() 方法. 这个时候build里面就获取了$concrete = ‘PayBill’. 这个时候反射就派上了用场, 大家有没有发现,PayBill 不就是 class PayBill 吗? 然后在通过反射的方法ReflectionClass(‘PayBill’) 获取PayBill 的实例. 之后通过getConstructor(),和getParameters() 等方法知道了 Class PayBill 和 接口Pay 存在依赖
//$constructor = $reflector->getConstructor(); ReflectionMethod {#374 +name: "__construct" +class: "App\Http\Controllers\PayBill" parameters: array:1 [ "$payMethod" => ReflectionParameter {#371 +name: "payMethod" position: 0 typeHint: "App\Http\Controllers\Pay" } ] extra: array:3 [ "file" => "C:\project\test\app\Http\Controllers\IOCController.php" "line" => "83 to 86" "isUserDefined" => true ] modifiers: "public" } //$dependencies = $constructor->getParameters(); array:1 [ 0 => ReflectionParameter {#370 +name: "payMethod" position: 0 typeHint: "App\Http\Controllers\Pay" } ]
接着,我们知道了有’Pay’这个依赖之后呢,我们要做的就是解决这个依赖,通过 getDependencies($parameters), 和 resolveClass(ReflectionParameter $parameter) ,还有之前的绑定$app->bind(“Pay”, “Alipay”); 在build 一次的时候,通过 return new $concrete;到这里我们得到了这个Alipay 的实例
if(is_null($constructor)) { return new $concrete; }
到这里我们总算结局了这个依赖, 这个依赖的结果就是实例化了一个 Alipay. 到这里还没结束
$instances = $this->getDependencies($dependencies);
上面的$instances 数组只有一个element 那就是 Alipay 实例
array:1 [0 =>Alipay {#380} ]
最终通过 newInstanceArgs() 方法, 我们获取到了 PayBill 的实例。
return $reflector->newInstanceArgs($instances);
到这里整个流程就结束了, 我们通过 bind 方式绑定了一些依赖关系, 然后通过make 方法 获取到到我们想要的实例. 在make中有牵扯到了闭包函数,反射等概念.
好了,当我们把容器的概念理解了之后,我们就可以理解下为什么要用接口这个问题了. 如果说我不想用支付宝支付,我要用微信支付怎么办,too easy.
$app->bind("Pay", "Wechatpay"); $app->bind("tryToPayMyBill", "PayBill"); $paybill = $app->make("tryToPayMyBill"); $paybill->payMyBill();
是不是很简单呢, 只要把绑定从’Alipay’ 改成 ‘Wechatpay’ 就行了,其他的都不用改. 这就是为什么我们要用接口. 只要你的支付方式继承了pay 这个接口,并且实现pay 这个方法,我们就能够通过绑定正常的使用. 这样我们的程序就非常容易被拓展,因为以后可能会出现成百上千种的支付方式.
好了,到这里不知道小伙伴有没有理解呢,我建议大家可以试着运行下这些代码, 这样理解起来会更快.同时推荐大家去看看 《laravel 框架关键技术解析》这本书,写的还是不错的.
위 내용은 Laravel에 대한 자세한 설명 - IOC 컨테이너의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!