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

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

 

内置服务

系统内置的服务有:ModelServicePaginatorService 和 ValidateService 类,我们来看看它们是怎么被注册和初始化的。

在 App::initialize() 有这么一段:

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

这里通过循环 App::initializers 的值,并使用容器类的 make 方法获取每个 $initializer 的实例,然后调用实例对应的 init 方法。App::initializers 成员变量的值为:

 protected $initializers = [
Error::class,
RegisterService::class,
BootService::class,
];

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

 

服务注册

执行 $this->make($initializer)->init($this)$initializer 等于 RegisterService::class 时,调用该类中的 init 方法,该方法代码如下:

 public function init(App $app)
{
// 加载扩展包的服务
$file = $app->getRootPath() . 'vendor/services.php'; $services = $this->services; //合并,得到所有需要注册的服务
if (is_file($file)) {
$services = array_merge($services, include $file);
}
// 逐个注册服务
foreach ($services as $service) {
if (class_exists($service)) {
$app->register($service);
}
}
}

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

 protected $services = [
PaginatorService::class,
ValidateService::class,
ModelService::class,
];

这三个服务和扩展包定义的服务将逐一被注册,其注册的方法 register 代码如下:

 public function register($service, bool $force = false)
{
// 比如 think\service\PaginatorService
// getService方法判断服务的实例是否存在于App::$services成员变量中
// 如果是则直接返回该实例
$registered = $this->getService($service);
// 如果服务已注册且不强制重新注册,直接返回服务实例
if ($registered && !$force) {
return $registered;
}
// 实例化该服务
// 比如 think\service\PaginatorService,
// 该类没有构造函数,其父类Service类有构造函数,需要传入一个App类的实例
// 所以这里传入$this(App类的实例)进行实例化
if (is_string($service)) {
$service = new $service($this);
}
// 如果存在「register」方法,则调用之
if (method_exists($service, 'register')) {
$service->register();
}
// 如果存在「bind」属性,添加容器标识绑定
if (property_exists($service, 'bind')) {
$this->bind($service->bind);
}
// 保存服务实例
$this->services[] = $service;
}

详细分析见代码注释。如果服务类定义了 register 方法,在服务注册的时候会被执行,该方法通常是用于将服务绑定到容器;此外,也可以通过定义 bind 属性的值来将服务绑定到容器。

服务逐个注册之后,得到 App::services 的值大概是这样的:

 

每个服务的实例都包含一个 App 类的实例。

 

服务初始化

执行 $this->make($initializer)->init($this)$initializer 等于 BootService::class 时,调用该类中的 init 方法,该方法代码如下:

 public function init(App $app)
{
$app->boot();
}
实际上是执行 App::boot(): public function boot(): void
{
array_walk($this->services, function ($service) {
$this->bootService($service);
});
}

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

 public function bootService($service)
{
if (method_exists($service, 'boot')) {
return $this->invoke([$service, 'boot']);
}
}

这里调用服务实例对应的 boot 方法。接下来,我们以 ModelService 的 boot 方法为例,看看 boot 方法大概可以做哪些工作。ModelService 的 boot 方法代码如下:

 public function boot()
{
// 设置Db对象
Model::setDb($this->app->db);
// 设置Event对象
Model::setEvent($this->app->event);
// 设置容器对象的依赖注入方法
Model::setInvoker([$this->app, 'invoke']);
// 保存闭包到Model::maker
Model::maker(function (Model $model) {
//保存db对象
$db = $this->app->db;
//保存$config对象
$config = $this->app->config;
// 是否需要自动写入时间戳 如果设置为字符串 则表示时间字段的类型
$isAutoWriteTimestamp = $model->getAutoWriteTimestamp(); if (is_null($isAutoWriteTimestamp)) {
// 自动写入时间戳 (从配置文件获取)
$model->isAutoWriteTimestamp($config->get('database.auto_timestamp', 'timestamp'));
}
// 时间字段显示格式
$dateFormat = $model->getDateFormat(); if (is_null($dateFormat)) {
// 设置时间戳格式 (从配置文件获取)
$model->setDateFormat($config->get('database.datetime_format', 'Y-m-d H:i:s'));
} });
}

可以看出,这里都是对 Model 类的静态成员进行初始化。这些静态成员变量的访问属性为 protected,所以,可以在 Model 类的子类中使用这些值。

 

自定义系统服务

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

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

    创建一个文件:app\common\MyServiceDemo.php,写入代码如下:

     <?php
    namespace app\common;
    class MyServiceDemo
    {
    //定义一个静态成员变量
    protected static $myStaticVar = '123';
    // 设置该变量的值
    public static function setVar($value){
    self::$myStaticVar = $value;
    }
    //用于显示该变量
    public function showVar()
    {
    var_dump(self::$myStaticVar);
    }
    }
  • 定义服务提供者

    在项目根目录,命令行执行 php think make:service MyService,将会生成一个 app\service\MyService.php 文件,在其中写入代码:

     <?php
    namespace app\service;
    use think\Service;
    use app\common\MyServiceDemo;
    class MyService extends Service
    {
    // 系统服务注册的时候,执行register方法
    public function register()
    {
    // 将绑定标识到对应的类
    $this->app->bind('my_service', MyServiceDemo::class);
    }
    // 系统服务注册之后,执行boot方法
    public function boot()
    {
    // 将被服务类的一个静态成员设置为另一个值
    MyServiceDemo::setVar('456');
    }
    }
  • 配置系统服务

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

     <?php
    return [
    '\app\service\MyService'
    ];
  • 在控制器中调用
    创建一个控制器文件 app\controller\Demo.php,写入代码:

     <?php
    namespace app\controller;
    use app\BaseController;
    use app\common\MyServiceDemo;
    class Demo extends BaseController
    {
    public function testService(MyServiceDemo $demo){
    // 因为在服务提供类app\service\MyService的boot方法中设置了$myStaticVar=‘456’\
    // 所以这里输出'456'
    $demo->showVar();
    } public function testServiceDi(){
    // 因为在服务提供类的register方法已经绑定了类标识到被服务类的映射
    // 所以这里可以使用容器类的实例来访问该标识,从而获取被服务类的实例
    // 这里也输出‘456’
    $this->app->my_service->showVar();
    }
    }

    执行原理和分析见代码注释。另外说说自定义的服务配置是怎么加载的:App::initialize() 中调用了 App::load() 方法,该方法结尾有这么一段:

     if (is_file($appPath . 'service.php')) {
    $services = include $appPath . 'service.php';
    foreach ($services as $service) {
    $this->register($service);
    }
    }

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

 

在 Composer 扩展包中使用服务

这里以 think-captcha 扩展包为例,该扩展使用了系统服务,其中,服务提供者为 think\captcha\CaptchaService 类,被服务的类为 think\captcha\Captcha

首先,项目根目录先运行 composer require topthink/think-captcha 安装扩展包;安装完成后,我们查看 vendor\services.php 文件,发现新增一行:

 return array (
0 => 'think\\captcha\\CaptchaService', //新增
);

这是怎么做到的呢?这是因为在 vendor\topthink\think-captcha\composer.json 文件配置了:

 "extra": {
"think": {
"services": [
"think\\captcha\\CaptchaService"
]
}
},
而在项目根目录下的 composer.json,有这样的配置: "scripts": {
"post-autoload-dump": [
"@php think service:discover",
"@php think vendor:publish"
]
}

扩展包安装后,会执行这里的脚本,其中,跟这里的添加系统服务配置相关的是:php think service:discover。该指令执行的代码在 vendor\topthink\framework\src\think\console\command\ServiceDiscover.php,相关的代码如下:

 foreach ($packages as $package) {
if (!empty($package['extra']['think']['services'])) {
$services = array_merge($services, (array) $package['extra']['think']['services']);
}
} $header = '// This file is automatically generated at:' . date('Y-m-d H:i:s') . PHP_EOL . 'declare (strict_types = 1);' . PHP_EOL; $content = '<?php ' . PHP_EOL . $header . "return " . var_export($services, true) . ';'; file_put_contents($this->app->getRootPath() . 'vendor/services.php', $content);

可以看出,扩展包如果有配置 ['extra']['think']['services'],也就是系统服务配置,都会被写入到 vendor\services.php 文件,最终,所有服务在系统初始化的时候被加载、注册和初始化。

分析完了扩展包中服务配置的实现和原理,接着我们看看 CaptchaService 服务提供类做了哪些初始化工作。该类只有一个 boot 方法,其代码如下:

 public function boot(Route $route)
{
// 配置路由
$route->get('captcha/[:config]', "\\think\\captcha\\CaptchaController@index");
// 添加一个验证器
Validate::maker(function ($validate) {
$validate->extend('captcha', function ($value) {
return captcha_check($value);
}, ':attribute错误!');
});
}

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

 

总结

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

  • 多PHPer在进阶的时候总会遇到一些问题和瓶颈,业务代码写多了没有方向感,不知道该从那里入手去提升,对此我整理了一些资料,包括但不限于:分布式架构、高可扩展、高性能、高并发、服务器性能调优、TP6,laravel,YII2,Redis,Swoole、Swoft、Kafka、Mysql优化、shell脚本、Docker、微服务、Nginx等多个知识点高级进阶干货需要的可以免费分享给大家,需要的加群(点击→)677079770

ThinkPHP6 核心分析:系统服务的更多相关文章

  1. ThinkPHP6 核心分析之应用程序初始化

    runWithRequest () 方法 在 Http 类的 run() 方法中,得到 think\Request 类的实例后,程序接着执行 $response = $this->runWith ...

  2. ThinkPHP6 核心分析之Http 类跟Request类的实例化

    以下源码分析,我们可以从 App,Http 类的实例化过程,了解类是如何实现自动实例化的,依赖注入是怎么实现的. 从入口文件出发 当访问一个 ThinkPHP 搭建的站点,框架最先是从入口文件开始的, ...

  3. ChIP-seq 核心分析 下游分析

    http://icb.med.cornell.edu/wiki/index.php/Elementolab/ChIPseeqer_Tutorial [怪毛匠子 整理] ChIP-seq[核心分析 下游 ...

  4. Android核心分析之二十二Android应用框架之Activity

    3 Activity设计框架 3.1 外特性空间的Activity    我们先来看看,android应用开发人员接触的外特性空间中的Activity,对于AMS来讲,这个Activity就是客服端的 ...

  5. Android核心分析之二十一Android应用框架之AndroidApplication

    Android Application Android提供给开发程序员的概念空间中Application只是一个松散的表征概念,没有多少实质上的表征.在Android实际空间中看不到实际意义上的应用程 ...

  6. Android核心分析之十六Android电话系统-概述篇

    Android电话系统之概述篇 首先抛开Android的一切概念来研究一下电话系统的最基本的描述.我们的手机首先用来打电话的,随后是需要一个电话本,随后是PIM,随后是网络应用,随后是云计算,随后是想 ...

  7. Android 核心分析之十三Android GWES之Android窗口管理

    Android GWES之Android窗口管理1基本构架原理 Android的窗口管理是C/S模式的.Android中的Window是表示Top Level等顶级窗口的概念.DecorView是Wi ...

  8. KVM,QEMU核心分析

    现在的问题是学习虚拟化软件KVM相关实施原则.处理,的源代码的分析总结,,若有不对的地方,希望大家提出. 因为有一些代码结构图或者是架构图上传比較麻烦.所以博文都放在了自己的个人博客上.麻烦大家移步查 ...

  9. Android核心分析之二十三Andoird GDI之基本原理及其总体框架

     Android GDI基本框架 在Android中所涉及的概念和代码最多,最繁杂的就是GDI相关的代码了.但是本质从抽象上来讲,这么多的代码和框架就干了一件事情:对显示缓冲区的操作和管理. GDI主 ...

随机推荐

  1. Java编程思想——第17章 容器深入研究 读书笔记(三)

    七.队列 排队,先进先出. 除并发应用外Queue只有两个实现:LinkedList,PriorityQueue.他们的差异在于排序而非性能. 一些常用方法: 继承自Collection的方法: ad ...

  2. MySQL从库生成大量小的relay log案例模拟

    最近看到"八怪"写的<MySQL:产生大量小relay log的故障一例>,因之前也遇到类似的情况,一直没搞懂原理及复现,看完此文章后,本着实践是检验真理的唯一标准的原 ...

  3. thinkphp5框架之请求

    又看到请求这一部分,个人认为这部分是算重要的一部分 单独记一篇笔记. 0x01 request请求对象 如果要获取当前的请求信息,可以使用\think\Request类,完全开发手册中也有提到,继承系 ...

  4. spring boot打印sql语句-mybatis

    spring boot打印sql语句-mybatis 概述 当自己编写的程序出现了BUG等等,找了很久 调试运行了几遍到mapper层也进去调试进了源码,非常麻烦 我就想打印出sql语句,好进行解决B ...

  5. vue-property-decorator用法

    vue-property-decorator这个库完全依赖于vue-class-component,所以在使用这个库之前请先阅读它, 不管啥反正都是装饰器而已 vue-property-decorat ...

  6. JAVA NIO 获取udp数据报的 发送方ip

    程序是通了,但是没法转发,获取不到对方ip.nio中 udp使用的是DatagramChannel ,但是SelectorKey.channel()转化之后的DatagramChannel,调用get ...

  7. Mac高效开发之iTerm2、Prezto和Solarized主题

    本文首发于个人网站:Mac高效开发之iTerm2.Prezto和Solarized主题 工欲善其事必先利其器,作为开发,我追求极致的高效,因此会在很多细节上追求效率,例如:命令行窗口敲命令的时候,如果 ...

  8. SQL查询小案例

    这是一篇自学MySQL的小案例,下面是部分数据信息:goods表 1.查询cate_name为‘超级本’的商品名称.价格 SELECT `name`, priceFROM goodsWHERE cat ...

  9. SpringBoot中在除Controller层 使用Validation的方式

    说明:Validation 在Controller层使用Validation应该都使用过了,以下方式可以使用 Validation 在Service层完成对dto的属性校验,避免写一堆的 if els ...

  10. textarea如何实现高度自适应(一)

    转自轩枫阁 - http://www.xuanfengge.com/textarea-on-how-to-achieve-a-high-degree-of-adaptive.html 方法一:div模 ...