当前位置: 首页 > 后端技术 > PHP

Laravel5.4入门系列13.完结篇:连新手都能看懂的Laravel核心概念讲解

时间:2023-03-29 15:45:22 PHP

AutomaticDependencyInjection是什么DependencyInjection,大白话通过类型提示给函数传递参数。例1首先定义一个类:/routes/web.phpclassBar{}如果我们想在其他地方使用Bar提供的功能(服务),怎么办,直接传入参数即可:/routes/web.phpRoute::get('bar',function(Bar$bar){dd($bar);});访问/bar,显示$barinstance:Bar{#272}即我们不需要先实例化它!如果你学过PHP面向对象,你就知道正常的方式是这样的:classBar{}$bar=newBar();dd($bar);实例2,可以看到稍微复杂一点的例子:classBaz{}classBar{public$baz;公共函数__construct(Baz$baz){$this->baz=$baz;}}$baz=newBaz();$bar=newBar($baz);dd($栏);为了在Bar中使用Baz的功能,我们需要实例化一个Baz,然后在实例化Bar的时候传入Baz实例。在Laravel中,不仅可以自动注入Bar,还可以自动注入Baz:/routes/web.phpclassBaz{}classBar{public$baz;公共函数__construct(Baz$baz){$this->baz=$baz;}}Route::get('bar',function(Bar$bar){dd($bar->baz);});显示结果:Baz{#276}总结通过以上两个例子我们可以看出,在Laravel中,如果我们想在类或函数中为其他类体使用服务,只需要通过类型提示传递参数即可,而Laravel会自动帮我们找到对应的依赖。那么,Laravel是如何完成这项工作的呢?答案是通过服务容器。服务容器什么是服务容器?很容易理解,服务容器是一个包含各种服务实例的特殊类。可以用“去饭店吃饭”打个比方:吃饭——使用服务,也就是叫服务的地方餐——服务盘——盛餐的容器,也就是服务容器服务员——服务provider,负责灌装和上餐的过程在Laravel中如何实现?Rice定义了Rice类:/app/Rice.phpbind('rice',函数(){returnnew\App\Rice();});也可以写成:app()->bind('rice',\App\Rice::class);现在吃,通过make方法提供吃的服务:Route::get('eat',function(){returnapp()->make('rice')->food();//orreturnresolve('米饭')->食物();});make方法可以通过传入我们刚刚定义的变量名来调用服务。访问/eat返回美味的白米饭。为了方便,我们直接在路由文件中实现了流程,相当于自给自足。但服务通常由服务提供商管理。因此,我们可以让服务端AppServiceProvider管理服务:/app/Providers/AppServiceProvider.phpnamespaceApp\Providers;publicfunctionregister(){$this->app->bind('food_container',Rice::class);}更常见的是,我们自己创建一个服务器:$phpartisanmake:providerRiceServiceProviderregistration:/app/Providers/RiceServiceProvider.phpapp->bind('rice',Rice::class);}这里定义了register()方法,但是需要调用这个方法来真正绑定服务到容器,所以需要在providers数组中加入:/config/app.php'providers'=>[App\Providers\RiceServiceProvider::class,],这一步是做什么的?Laravel在启动的时候会访问这个文件,然后在里面调用所有服务提供者的register()方法,这样我们的服务就绑定到容器上了。总结通过上面的例子,你可以基本了解服务容器和服务提供者的使用。当然,我们更常见的是使用类型提示来传递参数:useApp\Rice;Route::get('eat',function(Rice$rice){return$rice->food();});在这种情况下,使用自动依赖注入。无需使用bind手动绑定和make调用服务。那么,为什么需要bind和make呢?make比较好理解,我们有一些Laravel不能提供自动分析的场合,这时候就手动使用make分析,bind的知识稍微多一点,后面会详细讲解。门面是什么?让我们回到刚才“吃”的例子:Route::get('eat',function(Rice$rice){return$rice->food();});在Laravel中,通常可以这样写:Route::get('eat',function(){returnRice::food();});或Route::get('eat',function(){returnrice()->food();});那么,Laravel是如何实现的呢?答案是通过立面。Facade方法实现首先实现Rice::food(),只需要一步:/app/RiceFacade.phpapp->bind('rice',\App\Rice::类);class_alias(\App\RiceFacade::class,'Rice');}这样实现了原来的用法:Route::get('eat',function(){returnRice::food();});看起来是直接调用了Rice类,实际上是调用了RiceFacade类来充当代理。所以,个人认为把Facade翻译成幻觉比较合适。最后,为了方便代理类的命名,Laravel提供了统一命名别名的地方:/config/app.php'aliases'=>['Rice'=>\App\RiceFacade::class,],facade实现过程先分析:Rice::food();因为Rice是一个别名,它实际上执行了:\App\RiceFacade::food()但是我们的RiceFacade类中没有定义静态方法food?怎么做?是不是直接抛出异常?不是的,在PHP中,如果访问到不可访问的静态方法,会先调用__callstatic,所以执行的是:\App\RiceFacade::__callStatic()虽然我们没有在RiceFacade中定义,但是它的父类Facade已经定义好了:/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.phppublicstaticfunction__callStatic($method,$args){//实例化Rice{#270}$instance=static::getFacadeRoot();//实例化失败,抛出异常if(!$instance){thrownewRuntimeException('Afacaderoothasnotbeenset.');}//调用实例的方法return$instance->$method(...$args);}第一步的主要工作是实例化:publicstaticfunctiongetFacadeRoot(){returnstatic::resolveFacadeInstance(static::getFacadeAccessor());//在这个例子中:static::resolveFacadeInstance('rice')}仔细看看resolveFacadeInstance()方法:protectedstaticfunctionresolveFacadeInstance($name){//rice是一个字符串,所以跳过这一步if(is_object($name)){return$name;}//是`rice`实例集if(isset(static::$resolvedInstance[$name])){returnstatic::$resolvedInstance[$name];}returnstatic::$resolvedInstance[$name]=static::$app[$name];}第一步比较好理解,如果我们之前在RiceFacade中这样写:protectedstaticfunctiongetFacadeAccessor(){returnnew\App\Rice;}然后直接返回Rice实例,也是一个实现。主要困难在于最后一行:returnstatic::$resolvedInstance[$name]=static::$app[$name];看起来是访问$app数组,其实是用数组访问对象。PHP提供了这个访问方法接口,Laravel实现了这个接口。换句话说,$app属性实际上是对Laravel容器的引用,所以这实际上是在访问容器上名为rice的对象。之前学习容器的时候,我们把rice绑定到Rice类:publicfunctionregister(){$this->app->bind('rice',\App\Rice::class);//class_alias(\App\RiceFacade::class,'Rice');}所以,它实际上返回了这个类的一个实例。知道了服务容器和服务提供者,就不难理解门面了。辅助方法实现辅助方法实现更简单。不就是为了封装app->make('rice'):/vendor/laravel/framework/src/Illuminate/Foundation/helpers.phpif(!function_exists('rice')){functionrice(){returnapp()->制作('米饭');//相当于returnapp('rice');//相当于returnapp()['rice'];}}然后我们可以使用:Route::get('eat',function(){dd(rice()->food());});总结一下Laravel提供的三种访问类的方式:依赖注入:通过类型提示实现自动依赖注入门面:通过代理访问类辅助方法:通过方法访问类从本质上讲,这三种方法都是通过服务容器实现的和服务提供商。那么,服务容器本身有什么好处呢?接下来我们将重点介绍它。再看一个IOC实现不好的例子(为了测试方便,这个例子写在路由文件中),假设有USB、双孔、三孔三种插座,分别提供插件和充电服务:classUsbsocketService{publicfunctioninsert($deviceName){return$deviceName."InsertingUSBforcharging";}}classDoubleSocketService{publicfunctioninsert($deviceName){return$deviceName."插入双插座充电";}}classThreeSocketService{publicfunctioninsert($deviceName){return$deviceName."插入三孔插座充电";}}设备需要使用socket的服务来充电:classDevice{protected$socketType;//套接字类型publicfunction__construct(){$this->socketType=newUsbSocketService();}publicfunctionpower($deviceName){return$this->socketType->insert($deviceName);}}现在有一个手机要充电:Route::get('/charge',function(){$device=newDevice();return$device->power("mobilephone");});因为Laravel提供了自动依赖注入,所以可以这样写:Route::get('/charge/{device}',function(Device$device){return$device->power("phone");});访问/charge/phone,页面显示phone是插USB充电如果,现在有一台电脑充电,使用的是三孔插座,那么我们需要修改Device类:$this->socketType=newThreeSocketService();这真是一个糟糕的设计,设备类对socket服务类产生依赖。当改变设备类型时,往往需要修改类的内部结构。好的实现为了解决上面的问题,可以参考“IOC”的思路:将依赖转移到外部。让我们看看如何去做。首先定义socket类型接口:interfaceSocketType{publicfunctioninsert($deviceName);}让每个socket实现这个接口:classUsbsocketServiceimplementsSocketType{publicfunctioninsert($deviceName){return$deviceName."插入USB充电";}}classDoubleSocketServiceimplementsSocketType{publicfunctioninsert($deviceName){return$deviceName."插入双插座充电";}}classThreeSocketServiceimplementsSocketType{publicfunctioninsert($deviceName){return$deviceName."插入三孔插座充电";}}最后,在设备中传递的是接口类型,而不是具体的类:classDevice{protected$socketType;//套接字类型publicfunction__construct(SocketType$socketType)//传入接口{$this->socketType=$socketType;}publicfunctionpower($deviceName){return$this->socketType->insert($deviceName);}}在实例化时,决定使用哪种套接字类型,因此依赖转移到外部:Route::get('/charge',function(){$socketType=newThreeSocketService();$device=newDevice($socketType);echo$device->power("计算机");});现在我们可以在不修改类结构的情况下,轻松更换插座来满足不同设备的充电需求:Route::get('/charge',function(){$socketType=newDoubleSocketService();$device=newDevice($socketType);echo$device->power("台灯");});自动依赖注入失效上面的例子,我们可以通过Laravel的自动依赖注入进一步简化:Route::get('/charge',function(Device$device){echo$device->power("Computer");});这里有两种类型提示,一种是Device$device,一种是Device类的内部构造函数。SocketType$sockType第一个没问题,之前也试过。但是第二个SocketType是一个接口,Laravel会把它当成一个类,尝试去匹配SocketType类并实例化,所以在访问/charge的时候会报错:Target[SocketType]isnotinstantiablewhilebuilding[Device].Error原因很明显,Laravel不能自动绑定接口。因此,我们需要前面的bind方法来手动绑定接口:app()->bind('SocketType',ThreeSocketService::class);Route::get('/charge',function(Device$device){echo$设备->电源(“计算机”);});现在,如果我们想改变设备,我们只需要改变绑定值:app()->bind('SocketType',DoubleSocketService::class);Route::get('/charge',function(Device$device){echo$device->power("台灯");});也就是说,我们把依赖转移到外面后,由第三方容器来管理,这就是IOC。合同合同不是一个新概念。其实就是我们在前面例子中定义的接口:interfaceSocketType{publicfunctioninsert($deviceName);}通过契约,我们可以保持松耦合:publicfunction__construct(SocketType$socketType)//传入接口而不是具体的socket类型{$this->socketType=$socketType;}然后服务容器可以根据需要绑定哪个服务:app()->bind('SocketType',UsbSocketService::class);app()->bind('SocketType',DoubleSocketService::class);app()->bind('SocketType',ThreeSocketService::class);Laravel5.4入门系列到此结束,接下来准备学习Vue:)参考:PatrickStephan::简单了解Laravel服务容器解开LaravelFacades–AlanStorm2.7。外观模式—DesignPatternsPHP1.0文档简单了解laravel框架中的服务容器、服务提供者以及如何调用服务-xiake2014博客-博客频道-CSDN。NETlaravel学习笔记-神奇的服务容器-灵感-生活的礼物Laravel的请求生命周期|Laravel5.4中文文档PHP:Overloading-ManualPHP:ArrayAccess-Manual