php 分析服务,ThinkPHP6 核心分析:系统服务

什么是系统服务?系统服务是对于程序要用到的类在使用前先进行类的标识的绑定,以便容器能够对其进行解析(通过服务类的?

register

?方法),还有就是初始化一些参数、注册路由等(不限于这些操作,主要是看一个类在使用之前的需要,进行一些配置,使用的是服务类的?

boot

?方法)。以下面要介绍到的?

ModelService

?为例,

ModelService

类提供服务,

ModelService

?类主要对?

Model

?类的一些成员变量进行初始化(在?

boot

?方法中),为后面?

Model

?类的「出场」布置好「舞台」。

下面先来看看系统自带的服务,看看服务是怎么实现的。

?

内置服务

系统内置的服务有:

ModelService

PaginatorService

?和?

ValidateService

?类,我们来看看它们是怎么被注册和初始化的。

在?

App::initialize()

?有这么一段:

1 foreach ($this->initializers as $initializer) {2 $this->make($initializer)->init($this);3 }

?

这里通过循环?

App::initializers

?的值,并使用容器类的?

make

?方法获取每个?

$initializer

?的实例,然后调用实例对应的?

init

?方法。

App::initializers

?成员变量的值为:

1 protected $initializers =[2 Error::class,

3 RegisterService::class,

4 BootService::class,

5 ];

?

这里重点关注后面两个:服务注册和服务初始化。

?

服务注册

执行?

$this->make($initializer)->init($this)

$initializer

?等于?

RegisterService::class

?时,调用该类中的?

init

?方法,该方法代码如下:

1 public function init(App $app)2 {3 //加载扩展包的服务

4 $file = $app->getRootPath() . 'vendor/services.php';5

6 $services = $this->services;7

8 //合并,得到所有需要注册的服务

9 if (is_file($file)) {10 $services = array_merge($services, include $file);11 }12 //逐个注册服务

13 foreach ($services as $service) {14 if (class_exists($service)) {15 $app->register($service);16 }17 }18 }

?

服务注册类中,定义了系统内置服务的值:

1 protected $services =[2 PaginatorService::class,

3 ValidateService::class,

4 ModelService::class,

5 ];

?

这三个服务和扩展包定义的服务将逐一被注册,其注册的方法?

register

?代码如下:

1 public function register($service, bool $force = false)2 {3 //比如 thinkservicePaginatorService4 // getService方法判断服务的实例是否存在于App::$services成员变量中5 // 如果是则直接返回该实例

6 $registered = $this->getService($service);7 //如果服务已注册且不强制重新注册,直接返回服务实例

8 if ($registered && !$force) {9 return $registered;10 }11 //实例化该服务12 // 比如 thinkservicePaginatorService,13 // 该类没有构造函数,其父类Service类有构造函数,需要传入一个App类的实例14 // 所以这里传入$this(App类的实例)进行实例化

15 if (is_string($service)) {16 $service = new $service($this);17 }18 //如果存在「register」方法,则调用之

19 if (method_exists($service, 'register')) {20 $service->register();21 }22 //如果存在「bind」属性,添加容器标识绑定

23 if (property_exists($service, 'bind')) {24 $this->bind($service->bind);25 }26 //保存服务实例

27 $this->services[] = $service;28 }

?

详细分析见代码注释。如果服务类定义了?

register

?方法,在服务注册的时候会被执行,该方法通常是用于将服务绑定到容器;此外,也可以通过定义?

bind

?属性的值来将服务绑定到容器。

服务逐个注册之后,得到?

App::services

?的值大概是这样的:

?

b364dcc9a441897e564d0b64efe7c643.png

?

?

?

每个服务的实例都包含一个?

App

?类的实例。

?

服务初始化

执行?

$this->make($initializer)->init($this)

$initializer

?等于?

BootService::class

?时,调用该类中的?

init

?方法,该方法代码如下:

1 public function init(App $app)2 {3 $app->boot();4 }5 实际上是执行 App::boot():

6

7 public function boot():void8 {9 array_walk($this->services, function ($service) {10 $this->bootService($service);11 });12 }

?

这里是将每个服务实例传入 bootService 方法中。重点关注?

bootService

?方法:

1 public function bootService($service)2 {3 if (method_exists($service, 'boot')) {4 return $this->invoke([$service, 'boot']);5 }6 }

?

这里调用服务实例对应的?

boot

?方法。接下来,我们以?

ModelService

?的?

boot

?方法为例,看看?

boot

?方法大概可以做哪些工作。

ModelService

?的?

boot

?方法代码如下:

1 public functionboot()2 {3 //设置Db对象

4 Model::setDb($this->app->db);5 //设置Event对象

6 Model::setEvent($this->app->event);7 //设置容器对象的依赖注入方法

8 Model::setInvoker([$this->app, 'invoke']);9 //保存闭包到Model::maker

10 Model::maker(function (Model $model) {11 //保存db对象

12 $db = $this->app->db;13 //保存$config对象

14 $config = $this->app->config;15 //是否需要自动写入时间戳 如果设置为字符串 则表示时间字段的类型

16 $isAutoWriteTimestamp = $model->getAutoWriteTimestamp();17

18 if (is_null($isAutoWriteTimestamp)) {19 //自动写入时间戳 (从配置文件获取)

20 $model->isAutoWriteTimestamp($config->get('database.auto_timestamp', 'timestamp'));21 }22 //时间字段显示格式

23 $dateFormat = $model->getDateFormat();24

25 if (is_null($dateFormat)) {26 //设置时间戳格式 (从配置文件获取)

27 $model->setDateFormat($config->get('database.datetime_format', 'Y-m-d H:i:s'));28 }29

30 });31 }

?

可以看出,这里都是对?

Model

?类的静态成员进行初始化。这些静态成员变量的访问属性为?

protected

,所以,可以在?

Model

?类的子类中使用这些值。

?

自定义系统服务

接着,我们自己动手来写一个简单的系统服务。

定义被服务的对象(类)

创建一个文件:

appcommonMyServiceDemo.php

,写入代码如下:

1 <?php2 namespace appcommon;3 classMyServiceDemo4 {5 //定义一个静态成员变量

6 protected static $myStaticVar = '123';7 //设置该变量的值

8 public static function setVar($value){9 self::$myStaticVar = $value;10 }11 //用于显示该变量

12 public functionshowVar()13 {14 var_dump(self::$myStaticVar);15 }16 }

?

定义服务提供者

在项目根目录,命令行执行?

php think make:service MyService

,将会生成一个?

appserviceMyService.php

?文件,在其中写入代码:

1 <?php2 namespace appservice;3 usethinkService;4 useappcommonMyServiceDemo;5 class MyService extendsService6 {7 //系统服务注册的时候,执行register方法

8 public functionregister()9 {10 //将绑定标识到对应的类

11 $this->app->bind('my_service', MyServiceDemo::class);12 }13 //系统服务注册之后,执行boot方法

14 public functionboot()15 {16 //将被服务类的一个静态成员设置为另一个值

17 MyServiceDemo::setVar('456');18 }19 }

?

配置系统服务

在?

appservice.php

?文件(如果没有该文件则创建之),写入:

1 <?php2 return[3 'appserviceMyService'

4 ];

?

在控制器中调用

创建一个控制器文件?

appcontrollerDemo.php

,写入代码:

1 <?php2 namespace appcontroller;3 useappBaseController;4 useappcommonMyServiceDemo;5 class Demo extendsBaseController6 {7 public function testService(MyServiceDemo $demo){8 //因为在服务提供类appserviceMyService的boot方法中设置了$myStaticVar=‘456’9 // 所以这里输出'456'

10 $demo->showVar();11 }12

13 public functiontestServiceDi(){14 //因为在服务提供类的register方法已经绑定了类标识到被服务类的映射15 // 所以这里可以使用容器类的实例来访问该标识,从而获取被服务类的实例16 // 这里也输出‘456’

17 $this->app->my_service->showVar();18 }19 }

?

执行原理和分析见代码注释。另外说说自定义的服务配置是怎么加载的:

App::initialize()

?中调用了?

App::load()

?方法,该方法结尾有这么一段:

1 if (is_file($appPath . 'service.php')) {2 $services = include $appPath . 'service.php';3 foreach ($services as $service) {4 $this->register($service);5 }6 }

?

正是在这里将我们自定义的服务加载进来并且注册。

?

在 Composer 扩展包中使用服务

这里以?

think-captcha

?扩展包为例,该扩展使用了系统服务,其中,服务提供者为?

thinkcaptchaCaptchaService

?类,被服务的类为?

thinkcaptchaCaptcha

首先,项目根目录先运行?

composer require topthink/think-captcha

?安装扩展包;安装完成后,我们查看?

vendorservices.php

?文件,发现新增一行:

1 return array(2 0 => 'think\captcha\CaptchaService', //新增

3 );

?

这是怎么做到的呢?这是因为在?

vendor  opthink hink-captchacomposer.json

?文件配置了:

1 "extra":{2 "think":{3 "services":[4 "think\captcha\CaptchaService"

5 ]6 }7 },

8 而在项目根目录下的 composer.json,有这样的配置:9

10 "scripts":{11 "post-autoload-dump":[12 "@php think service:discover",

13 "@php think vendor:publish"

14 ]15 }

?

扩展包安装后,会执行这里的脚本,其中,跟这里的添加系统服务配置相关的是:

php think service:discover

。该指令执行的代码在?

vendor  opthinkframeworksrc hinkconsolecommandServiceDiscover.php

,相关的代码如下:

1 foreach ($packages as $package) {2 if (!empty($package['extra']['think']['services'])) {3 $services = array_merge($services, (array) $package['extra']['think']['services']);4 }5 }6

7 $header = '// This file is automatically generated at:' . date('Y-m-d H:i:s') . PHP_EOL . 'declare (strict_types = 1);' . PHP_EOL;8

9 $content = '<?php ' . PHP_EOL . $header . "return " . var_export($services, true) . ';';10

11 file_put_contents($this->app->getRootPath() . 'vendor/services.php', $content);

?

可以看出,扩展包如果有配置?

['extra']['think']['services']

,也就是系统服务配置,都会被写入到?

vendorservices.php

?文件,最终,所有服务在系统初始化的时候被加载、注册和初始化。

分析完了扩展包中服务配置的实现和原理,接着我们看看?

CaptchaService

?服务提供类做了哪些初始化工作。该类只有一个?

boot

?方法,其代码如下:

1 public function boot(Route $route)2 {3 //配置路由

4 $route->get('captcha/[:config]', "\think\captcha\CaptchaController@index");5 //添加一个验证器

6 Validate::maker(function ($validate) {7 $validate->extend('captcha', function ($value) {8 return captcha_check($value);9 }, ':attribute错误!');10 });11 }

?

有了以上的先行配置,我们就可以愉快地使用?

Captcha

?类了。

?

总结

使用系统服务有大大的好处和避免了直接修改类的坏处。从以上分析来看,个人觉得,使用系统服务,可以对一个类进行非入侵式的「配置」,如果哪天一个类的某些设定需要修改,我们不用直接修改这个类,只需要修改服务提供类就好了。对于扩展包来说,系统服务使其可以在扩展中灵活配置程序,达到开箱即用的效果。不过,有个缺点是系统服务类都要在程序初始化是进行实例化,如果一个系统的服务类很多,势必影响程序的性能。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值