• ThinkPHP6 核心分析:系统服务


    什么是系统服务?系统服务是对于程序要用到的类在使用前先进行类的标识的绑定,以便容器能够对其进行解析(通过服务类的 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     // 比如 thinkservicePaginatorService
     4     // 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 的值大概是这样的:


     

    每个服务的实例都包含一个 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 类的子类中使用这些值。

     

    自定义系统服务

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

    • 定义被服务的对象(类)

      创建一个文件:appcommonMyServiceDemo.php,写入代码如下:

       1 <?php
       2 namespace appcommon;
       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,将会生成一个 appserviceMyService.php 文件,在其中写入代码:

       1 <?php
       2 namespace appservice;
       3 use thinkService;
       4 use appcommonMyServiceDemo;
       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 }
    • 配置系统服务

      在 appservice.php 文件(如果没有该文件则创建之),写入:

      1 <?php
      2     return [
      3         'appserviceMyService'
      4     ];
    • 在控制器中调用
      创建一个控制器文件 appcontrollerDemo.php,写入代码:

       1 <?php
       2 namespace appcontroller;
       3 use appBaseController;
       4 use appcommonMyServiceDemo;
       5 class Demo extends BaseController
       6 {
       7     public function testService(MyServiceDemo $demo){
       8         // 因为在服务提供类appserviceMyService的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 扩展包为例,该扩展使用了系统服务,其中,服务提供者为 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 类了。

     

    总结

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

    • 多PHPer在进阶的时候总会遇到一些问题和瓶颈,业务代码写多了没有方向感,不知道该从那里入手去提升,对此我整理了一些资料,包括但不限于:分布式架构、高可扩展、高性能、高并发、服务器性能调优、TP6,laravel,YII2,Redis,Swoole、Swoft、Kafka、Mysql优化、shell脚本、Docker、微服务、Nginx等多个知识点高级进阶干货需要的可以免费分享给大家,需要的加群(点击→)677079770
  • 相关阅读:
    Python面向对象详解
    使用树莓派搭建LoRaWAN网关并接入腾讯云物联网开发平台
    dajngo
    dajngo 项目目录结构调整
    Django
    nacos的简单使用
    MySQL数据库开发规范
    mabatis的sql标签
    直接插入100w数据报错
    大数据量插入到数据库
  • 原文地址:https://www.cnblogs.com/a609251438/p/11872216.html
Copyright © 2020-2023  润新知