热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

ThinkPHP6核心分析:系统服务

什么是系统服务?系统服务是对于程序要用到的类在使用前先进行类的标识的绑定,以便容器能够对其进行解析(通过服务类的 register 方法),还有就是初始化一些参数、注册路由等(不限

什么是系统服务?系统服务是对于程序要用到的类在使用前先进行类的标识的绑定,以便容器能够对其进行解析(通过服务类的 register 方法),还有就是初始化一些参数、注册路由等(不限于这些操作,主要是看一个类在使用之前的需要,进行一些配置,使用的是服务类的 boot 方法)。以下面要介绍到的 ModelService 为例,ModelService类提供服务,ModelService 类主要对 Model 类的一些成员变量进行初始化(在 boot 方法中),为后面 Model 类的「出场」布置好「舞台」。

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

 

内置服务

系统内置的服务有:ModelServicePaginatorService 和 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_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 类了。

 

总结

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

ThinkPHP6 核心分析:系统服务


推荐阅读
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • 基于layUI的图片上传前预览功能的2种实现方式
    本文介绍了基于layUI的图片上传前预览功能的两种实现方式:一种是使用blob+FileReader,另一种是使用layUI自带的参数。通过选择文件后点击文件名,在页面中间弹窗内预览图片。其中,layUI自带的参数实现了图片预览功能。该功能依赖于layUI的上传模块,并使用了blob和FileReader来读取本地文件并获取图像的base64编码。点击文件名时会执行See()函数。摘要长度为169字。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • 本文介绍了C#中数据集DataSet对象的使用及相关方法详解,包括DataSet对象的概述、与数据关系对象的互联、Rows集合和Columns集合的组成,以及DataSet对象常用的方法之一——Merge方法的使用。通过本文的阅读,读者可以了解到DataSet对象在C#中的重要性和使用方法。 ... [详细]
  • Voicewo在线语音识别转换jQuery插件的特点和示例
    本文介绍了一款名为Voicewo的在线语音识别转换jQuery插件,该插件具有快速、架构、风格、扩展和兼容等特点,适合在互联网应用中使用。同时还提供了一个快速示例供开发人员参考。 ... [详细]
  • 本文介绍了通过ABAP开发往外网发邮件的需求,并提供了配置和代码整理的资料。其中包括了配置SAP邮件服务器的步骤和ABAP写发送邮件代码的过程。通过RZ10配置参数和icm/server_port_1的设定,可以实现向Sap User和外部邮件发送邮件的功能。希望对需要的开发人员有帮助。摘要长度:184字。 ... [详细]
  • 本文讲述了作者通过点火测试男友的性格和承受能力,以考验婚姻问题。作者故意不安慰男友并再次点火,观察他的反应。这个行为是善意的玩人,旨在了解男友的性格和避免婚姻问题。 ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • 处理docker容器时间和宿主机时间不一致问题的方法
    本文介绍了处理docker容器时间和宿主机时间不一致问题的方法,包括复制主机的localtime到容器、处理报错情况以及重启容器的步骤。通过这些方法,可以解决docker容器时间和宿主机时间不一致的问题。 ... [详细]
  • 后台获取视图对应的字符串
    1.帮助类后台获取视图对应的字符串publicclassViewHelper{将View输出为字符串(注:不会执行对应的ac ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
  • 动态规划算法的基本步骤及最长递增子序列问题详解
    本文详细介绍了动态规划算法的基本步骤,包括划分阶段、选择状态、决策和状态转移方程,并以最长递增子序列问题为例进行了详细解析。动态规划算法的有效性依赖于问题本身所具有的最优子结构性质和子问题重叠性质。通过将子问题的解保存在一个表中,在以后尽可能多地利用这些子问题的解,从而提高算法的效率。 ... [详细]
  • 本文介绍了Java的集合及其实现类,包括数据结构、抽象类和具体实现类的关系,详细介绍了List接口及其实现类ArrayList的基本操作和特点。文章通过提供相关参考文档和链接,帮助读者更好地理解和使用Java的集合类。 ... [详细]
  • Java验证码——kaptcha的使用配置及样式
    本文介绍了如何使用kaptcha库来实现Java验证码的配置和样式设置,包括pom.xml的依赖配置和web.xml中servlet的配置。 ... [详细]
author-avatar
赤血-魔鬼
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有