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 // 比如 think\service\paginatorservice 4 // getservice方法判断服务的实例是否存在于app::$services成员变量中 5 // 如果是则直接返回该实例 6 $registered = $this->getservice($service); 7 // 如果服务已注册且不强制重新注册,直接返回服务实例 8 if ($registered && !$force) { 9 return $registered; 10 } 11 // 实例化该服务 12 // 比如 think\service\paginatorservice, 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
的值大概是这样的:
每个服务的实例都包含一个 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(): void 8 { 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 function boot() 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
类的子类中使用这些值。
自定义系统服务
接着,我们自己动手来写一个简单的系统服务。
-
定义被服务的对象(类)
创建一个文件:
app\common\myservicedemo.php
,写入代码如下:1 <?php 2 namespace app\common; 3 class myservicedemo 4 { 5 //定义一个静态成员变量 6 protected static $mystaticvar = '123'; 7 // 设置该变量的值 8 public static function setvar($value){ 9 self::$mystaticvar = $value; 10 } 11 //用于显示该变量 12 public function showvar() 13 { 14 var_dump(self::$mystaticvar); 15 } 16 }
-
定义服务提供者
在项目根目录,命令行执行
php think make:service myservice
,将会生成一个app\service\myservice.php
文件,在其中写入代码:1 <?php 2 namespace app\service; 3 use think\service; 4 use app\common\myservicedemo; 5 class myservice extends service 6 { 7 // 系统服务注册的时候,执行register方法 8 public function register() 9 { 10 // 将绑定标识到对应的类 11 $this->app->bind('my_service', myservicedemo::class); 12 } 13 // 系统服务注册之后,执行boot方法 14 public function boot() 15 { 16 // 将被服务类的一个静态成员设置为另一个值 17 myservicedemo::setvar('456'); 18 } 19 }
-
配置系统服务
在
app\service.php
文件(如果没有该文件则创建之),写入:1 <?php 2 return [ 3 '\app\service\myservice' 4 ];
-
在控制器中调用
创建一个控制器文件app\controller\demo.php
,写入代码:1 <?php 2 namespace app\controller; 3 use app\basecontroller; 4 use app\common\myservicedemo; 5 class demo extends basecontroller 6 { 7 public function testservice(myservicedemo $demo){ 8 // 因为在服务提供类app\service\myservice的boot方法中设置了$mystaticvar=‘456’\ 9 // 所以这里输出'456' 10 $demo->showvar(); 11 } 12 13 public function testservicedi(){ 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
扩展包为例,该扩展使用了系统服务,其中,服务提供者为 think\captcha\captchaservice
类,被服务的类为 think\captcha\captcha
。
首先,项目根目录先运行 composer require topthink/think-captcha
安装扩展包;安装完成后,我们查看 vendor\services.php
文件,发现新增一行:
1 return array ( 2 0 => 'think\\captcha\\captchaservice', //新增 3 );
这是怎么做到的呢?这是因为在 vendor\topthink\think-captcha\composer.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\topthink\framework\src\think\console\command\servicediscover.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']
,也就是系统服务配置,都会被写入到 vendor\services.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
类了。
总结
使用系统服务有大大的好处和避免了直接修改类的坏处。从以上分析来看,个人觉得,使用系统服务,可以对一个类进行非入侵式的「配置」,如果哪天一个类的某些设定需要修改,我们不用直接修改这个类,只需要修改服务提供类就好了。对于扩展包来说,系统服务使其可以在扩展中灵活配置程序,达到开箱即用的效果。不过,有个缺点是系统服务类都要在程序初始化是进行实例化,如果一个系统的服务类很多,势必影响程序的性能。
上一篇: 如何在ASP中恰当地运用Cookies?
推荐阅读
-
大势至局域网服务器共享文件管理系统和服务器文件管理软件核心功能
-
ThinkPHP6 核心分析:系统服务
-
服务器 建立和删除系统隐藏帐号的方法分析
-
第八天 黑马十次方 十次方的需求分析、十次方的系统设计以及RESTful、项目的前 期准备工作、十次方父模块与公共模块的搭建、基础微服务-标签CRUD的功能
-
-------- ROOTKIT 核心技术——系统服务调度表挂钩调试(PART III) --------
-
Android 系统服务TelecomService启动过程原理分析
-
系统设计和开发中,方法论比技术更重要--兼谈怎样做Java服务器的性能分析和调整
-
Rootkit 核心技术——利用 nt!_MDL(内存描述符链表)突破 SSDT(系统服务描述符表)的只读访问限制 Part I
-
【Android】【源码分析】系统各类异常日志收集服务(DropBoxManagerService)
-
“无处不在” 的系统核心服务 —— ActivityManagerService 启动流程解析