前面我们理解依赖注入和服务容器的概念,接下来我们继续理解为什么说接口即契约和服务提供者。
1.强类型与鸭子类型
? ? ? ?强类型:像java,c#,.net,都是强类型,要求函数传参时必须指定参数类型
public int BillUser(User user)
{
this.biller.bill(user.GetId(), this.amount)
}
? ? ? ?鸭子类型:??PHP 是一种鸭子类型语言。说的是一个对象的可用方法取决于其使用方式,而非这个对象继承自谁,或者实现了什么接口。
public function billUser($user)
{
$this->biller->bill($user->getId(), $this->amount);
}
实际上,我们可以传递任何类型的对象到?billUser ?方法,只要这个对象提供了?getId ?方法。这里有个关于鸭子类型的解释:如果一个东西看起来像鸭子,叫起来也像鸭子,那它就是鸭子。换言之,在本例中,如果一个对象看上去像?User ,方法响应也像?User ,那它就是个?User ?对象。
PHP 是弱类型语言,PHP 到底有没有任何强类型功能呢?
当然有!PHP 混合了强类型和鸭子类型(弱类型)结构。
public function billUser(User $user)
{
$this->biller->bill($user->getId(), $amount);
}
给方法签名加上了?User ?类型约束后,我们现在可以确保所有传入billUser ?方法的对象,要么是?User ?类的实例,要么是一个继承自?User ?类的对象实例。
2.接口如同契约
接口如同契约。接口并不包含任何代码实现,只是定义了一个实现该接口的对象必须实现的一系列方法。如果一个对象实现了一个接口,那么我们就能保证这个接口所定义的一系列方法都能在这个对象上调用。由于有接口契约保证特定方法的实现,通过多态也能使类型安全的语言变得更灵活。
强类型语言,写一个简单的酒店客房预订代码
interface ProviderInterface
{
public function getLowestPrice($location);
public function book($location);
}
当用户预订房间时,我们需要将此事记录在系统里。所以在?User ?类里添加如下方法:
class User
{
public function bookLocation(ProviderInterface $provider, $location)
{
$amountCharged = $provider->book($location);
$this->logBookedLocation($location, $amountCharged);
}
}
3.服务提供者
(1)谁是服务提供者?? ? ? ?
????????类里面的注册接口绑定了它的实现类。 还是列举redis的服务提供者,
<?php
namespace Illuminate\Redis;
use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Support\Arr;
use Illuminate\Support\ServiceProvider;
class RedisServiceProvider extends ServiceProvider implements DeferrableProvider
{
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->app->singleton('redis', function ($app) {
$config = $app->make('config')->get('database.redis', []);
return new RedisManager($app, Arr::pull($config, 'client', 'phpredis'), $config);
});
$this->app->bind('redis.connection', function ($app) {
return $app['redis']->connection();
});
}
/**
* Get the services provided by the provider.
*
* @return array
*/
public function provides()
{
return ['redis', 'redis.connection'];
}
}
? ? ? ?在register注册接口绑定了redis的实现类。那么RedisServiceProvider 这个类就是服务提供者,在config/app.php文件中配置就可以使用了。
(2)如何添加和使用?
? ? ? ? 1)先添加测试约束接口:app/Contracts/TestContract
<?php
namespace App\Contracts;
interface TestContract
{
public function callMe($controller);
}
? ? ?
?????????2)新建app/Services/TestServices文件
<?php
namespace App\Services;
use App\Contracts\TestContract;
class TestServices implements TestContract
{
public function callMe($controller){
dump('服务提供者测试:'.$controller);
}
}
? ? ??3).app/Providers目录下创建TestServiceProvider服务提供者,继承ServiceProvider(或用命令:php artisan ?make:provider TestServiceProvider):
<?php
namespace App\Providers;
use App\Services\TestServices;
use Illuminate\Support\ServiceProvider;
class TestServiceProvider extends ServiceProvider
{
/**
* Register services.
* 注册或绑定的意思,注册就是告诉我们应用,我这个函数已经注册了,你可以通过它实现依赖,否则会说不能注入依赖
* @return void
*/
public function register()
{
//使用singleton绑定单例
$this->app->singleton('test',function (){//test是别名
return new TestServices();
});
//使用bind绑定实例到接口以便依赖注入
$this->app->bind('App\Contracts\TestContract',function(){
return new TestServices();
});
}
/**
* Bootstrap services.
* 一般都是写逻辑的引导
* @return void
*/
public function boot()
{
//
}
}
????????4) 将TestServiceProvider服务提供者注册到config/app.php
'providers' => [
...
App\Providers\TestServiceProvider::class,
]
? ? ? ?? ? ? ? 5)TestController使用
<?php
/**
* Notes:
* User: linrongshu
* DateTime: 2021/8/5 18:37
*/
namespace App\Http\Controllers;
use App\Contracts\TestContract;
class TestController extends Controller
{
//依赖注入
public function __construct(TestContract $test){
$this->test = $test;
}
function test(){
// $test = App::make('test');
// $test->callMe('TestController');
app('test')->callMe('TestController'); // "服务提供者测试:TestController"
$this->test->callMe('TestController'); // "服务提供者测试:TestController"
}
}
?????????事实上,Laravel 有好几十个服务提供者,用于管理框架核心组件的容器绑定。几乎框架里每一个组件的容器绑定都是靠服务提供者来完成的。你可以在?config/app.php ?这个配置文件里查看项目目前有哪些服务提供者。
????????一个服务提供者必须至少有一个?register ?方法。你可以在这个方法里将类绑定到容器,就像我们前面做的那样。当一个请求进入应用,框架启动时,所有罗列在配置文件里的服务提供者的?register ?方法就会被调用。这在应用请求生命周期很早的阶段就会发生,所以在我们编写业务逻辑代码时,所有的服务都已经准备好了。
????????register ?Vs.?boot ?方法:永远不要在?register ?方法里面使用任何服务。该方法只是用来将对象绑定到服务容器的地方。所有关于绑定类的解析、交互都要在?boot ?方法(服务提供者的另一个方法)里进行。
????????一些通过 Composer 安装的第三方扩展包也会有服务提供者。这些第三方扩展包的安装说明里一般都会告诉你要在配置文件?config/app.php ?的?providers ?数组里注册其提供的服务提供者(如果支持包自动发现,则不必这么做)。
?4.延迟加载的服务提供者
????????在 Laravel 5 中,我们通过一种新的方式来实现延迟加载服务提供者,在需要延迟加载的服务提供者中将属性?$defer 设置为?true ,并重写?providers ?方法即可,在这个方法中,我们会以数组方式返回该服务提供者注册的服务容器绑定。
<?php
namespace App\Providers;
use Riak\Connection;
use Illuminate\Support\ServiceProvider;
class RiakServiceProvider extends ServiceProvider{
/**
* 服务提供者加是否延迟加载.
*
* @var bool
*/
protected $defer = true;
/**
* 注册服务提供者
*
* @return void
*/
public function register()
{
$this->app->singleton(Connection::class, function ($app) {
return new Connection($app['config']['riak']);
});
}
/**
* 获取由提供者提供的服务.
*
* @return array
*/
public function provides()
{
return [Connection::class];
}
}
5.作为管理者
????????构建一个架构良好的 Laravel 应用的关键就是学习使用服务提供者作为管理工具。
我们先来看个例子吧。也许我们的应用正在使用?Pusher?通过 WebSocket 推送消息给客户端。为了将我们的应用和 Pusher 解耦,最好创建一个新的?EventPusherInterface ?接口和对应的实现类?PusherEventPusher ,这样随着需求变化或应用增长,我们就可以随时轻松切换 WebSocket 提供商:
interface EventPusherInterface
{
public function push($message, array $data = array());
}
<?php
namespace App\Services;
use App\Contracts\EventPusherInterface;
use App\Contracts\PusherSdkInterface;
class PusherEventPusher implements EventPusherInterface
{
public function __construct(PusherSdkInterface $pusher)
{
$this->pusher = $pusher;
}
public function push($message, array $data = array())
{
// 通过 Pusher SDK 推送消息
}
}
????????接下来,我们创建一个服务提供者?EventPusherServiceProvider :
<?php
namespace App\Providers;
use App\Contracts\EventPusherInterface;
use App\Contracts\PusherSdkInterface;
use App\Services\PusherEventPusher;
use Illuminate\Support\ServiceProvider;
use Pusher\Pusher;
class EventPusherServiceProvider extends ServiceProvider
{
public function register()
{
// 在绑定接口实现类时使用 bind 还是 singleton 方法可以这样来考虑:如果在一次请求生命周期中该类只需要有一个实例,就使用 singleton;否则就使用 bind。
$this->app->singleton(PusherSdkInterface::class, function () {
return new Pusher('app-key', 'secret-key', 'app-id');
});
$this->app->singleton(EventPusherInterface::class, PusherEventPusher::class);
}
}
????????很好!现在我们对事件推送进行了清晰的抽象,同时也有了一个很方便的地方注册和绑定其他相关对象到容器里。最后,只需要将?EventPusherServiceProvider ?注册到?config/app.php ?配置文件的providers ?数组就可以了。现在我们就可以将?EventPusherInterface ?注入到应用代码里的任何控制器或类中。
对比下框架其他的服务提供者,如redis:.vendor\laravel\framework\src\Illuminate\Redis\RedisServiceProvider.php
use Illuminate\Support\ServiceProvider;
class RedisServiceProvider extends ServiceProvider implements DeferrableProvider
{
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->app->singleton('redis', function ($app) {
$config = $app->make('config')->get('database.redis', []);
return new RedisManager($app, Arr::pull($config, 'client', 'phpredis'), $config);
});
$this->app->bind('redis.connection', function ($app) {
return $app['redis']->connection();
});
}
/**
* Get the services provided by the provider.
*
* @return array
*/
public function provides()
{
return ['redis', 'redis.connection'];
}
}
'providers' => [
/*
* Laravel Framework Service Providers...
*/
...
Illuminate\Redis\RedisServiceProvider::class,
...
]
6.启动提供者
????????在所有服务提供者都注册以后(register ?方法调用完),它们就进入了「启动」状态。这将会触发每个服务提供者执行各自的?boot ?方法。在使用服务提供者时,一种常见的错误就是在register ?方法里面调用其他提供者注册的服务。由于在某个服务提供者的?register ?方法里,不能保证所有其他服务都已经被注册,在该方法里调用别的服务有可能会出现该服务不可用。因此,调用其它服务的代码应该被定义在服务提供者的?boot ?方法中。register ?方法只能用于注册服务到容器。
在?boot ?方法中,你想做什么都可以:注册事件监听器、引入路由文件、注册过滤器、或者任何其他你能想到的事。再次强调,使用服务提供者作为管理工具的时候,如果你想将几个相关的事件监听器聚合到一起,就将它们放到该服务提供者的?boot ?方法里。
7.框架核心
????????至此,你可能已经注意到,在?config/app.php ?配置文件里面已经有了很多服务提供者,其中每一个都负责引导框架核心的一部分服务。
????????比如MigrationServiceProvider ?负责引导用于运行数据库迁移的类,包括Artisan 迁移命令。EventServiceProvide ?负责引导和注册事件调度类。尽管不同的服务提供者有着不同的复杂度,有些比较大,另一些相对较小,但它们都负责引导核心的一部分功能。
????????提升对 Laravel 核心代码理解的最好方法是去读核心服务提供者的源码。如果你对这些服务提供者的功能以及每个服务提供者注册了什么都很熟悉,那么你将会对Laravel 底层是如何工作的有更加深刻的理解。
????????大部分核心服务提供者是延迟加载的,这意味着不是每次请求都会加载它们;不过,一些用于引导框架基础服务的服务提供者是每一次请求都会被加载的,比如?FilesystemServiceProvide ?和?ExceptionServiceProvider 。核心服务提供者和应用容器将框架的不同部分联系起来,形成一个单一的、内聚的整体。这些核心服务提供者就是框架的构建块。
????????正如之前提到的那样,如果你想深入理解框架是如何运行的,请阅读 Laravel 框架的核心服务提供者的源码。通读之后,你将会对框架如何把各部分功能模块组合在一起,以及每一个服务提供者为应用提供了哪些功能有更加扎实的理解。此外,有了这些更深入的理解,你也可以为更好的 Laravel 生态系统添砖加瓦!
????????以上内容大部分都源自官网,只做摘抄,自己做笔记用,详细内容请移动原文:https://laravelacademy.org/books/laravel-from-appreciate-to-artisan
|