安装

ThinkPHP 现在使用composer安装,所以前提需要安装composer并且配置其运行环境,具体不在这里赘述。

安装

  1. composer create-project topthink/think tp

多应用

开启多应用模式

  1. 首先刪除app目录下所有其他文件
  2. 安装多应用模式扩展(要在安装目录下运行):

    1. composer require topthink/think-multi-app
  3. 快速创建两大应用index(前端),admin(后台)

    1. php think build index
    1. php think build admin

    注意:如果没有安装think-multi-app, 使用php think build xx会报错:Command “build” is not defined.

    玩~

    为什么这么说?
    在安装目录下,运行以下命令,可以看到很多有意思的命令参数

    1. php think

    可以看到以下信息:
    一、基本概念 - 图1

    公共函数common.php

    放自己整理的一些公共函数

    多应用的公共函数

    目前已知公共函数分为:助手函数framework/src/helper.php、全局公共函数app/common.php、应用公共函数app/app_name/common.php、以及控制器公共函数app_name/admin/common.php

  • 执行顺序分别为:框架公共函数->全局公共函数->应用公共函数->控制器公共函数
  • 控制器公共函数common.php(admin/common.php) 如果不写function_exists会报错。

    系统服务,这个暂时不太理解

    官方介绍地址:https://www.kancloud.cn/manual/thinkphp6_0/1037490
    官方原话:系统服务的概念是指在执行框架的某些组件或者功能的时候需要依赖的一些基础服务,服务类通常可以继承系统的think\Service类,但并不强制(如果继承think\Service的话可以直接调用this->app获取应用实例)。
    你可以在系统服务中注册一个对象到容器,或者对某些对象进行相关的依赖注入。由于系统服务的执行优先级问题,可以确保相关组件在执行的时候已经完成相关依赖注入。

    服务定义

    命令行生成服务类
    1. php think make:service FileSystemService
    默认生成的服务类会继承系统的think\Service,并且自动生成了系统服务类最常用的两个空方法:registerboot方法。
    命令生成类后,会在app目录下自动建立service目录,并自动生成所建服务类名的php文件,并且会自动生成registerboot方法2个空方法。然后必须再进行服务注册才可以使用。

    register 注册方法

    register方法通常用于注册系统服务,也就是将服务绑定到容器中

    boot 启动方法

    boot方法是在所有的系统服务注册完成之后调用,用于定义启动某个系统服务之前需要做的操作。支持依赖注入,你可以直接使用其它的依赖服务。

    服务注册

    定义好系统服务后,你还需要注册服务到你的应用实例中。
    可以在应用的全局公共文件service.php中定义需要注册的系统服务,系统会自动完成注册以及启动。例如:
    1. return [
    2. '\app\service\ConfigService',
    3. '\app\service\CacheService',
    4. ];

    内置服务

    为了更好的完成核心组件的单元测试,框架内置了一些系统服务类,主要都是用于核心类的依赖注入,包括ModelServicePaginatorServiceValidateService类。这些服务不需要注册,并且也不能卸载。

    多应用的服务

    TODO:中间件分为全局中间件、应用中间件(多应用模式下有效)、路由中间件以及控制器中间件四个组。不确定服务是不是也这样分?

    门面

    门面为容器中的(动态)类提供了一个静态调用接口,相比于传统的静态方法调用, 带来了更好的可测试性和扩展性,你可以为任何的非静态类库定义一个facade类。
    系统已经为大部分核心类库定义了Facade,所以你可以通过Facade来访问这些系统类,当然也可以为你的应用类库添加静态代理。
    啥意思呢?直白点说,就是自己建立的类,正常情况下使用要要实例化(new)以后才能使用,通过facade静态类代理后,可以按照静态类调用——Facade功能可以让类无需实例化而直接进行静态方式调用。

    示例:

    下面是一个示例,假如我们定义了一个app\common\New类,里面有一个show动态方法。
    1. <?php
    2. declare(strict_types=1);
    3. namespace app\common;
    4. class News {
    5. public function show($id) {
    6. //
    7. return "this id is $id";
    8. }
    9. }
    正常的调用方法是:
    1. $news= new \app\common\News;
    2. echo $news->show(1); // 输出 this id is 1
    然后我们定义静态代理类app\facade\News
    1. <?php
    2. namespace app\facade;
    3. use think\Facade;
    4. class News extends Facade
    5. {
    6. protected static function getFacadeClass()
    7. {
    8. return 'app\common\News';
    9. }
    10. }
    然后我们就可以直接引用
    1. // 无需进行实例化 直接以静态方法方式调用show
    2. echo \app\facade\News::show(1);

    核心Facade类库(内置Facade库)

    | (动态)类库 | Facade类 | | :—- | :—- | | :—————- | :—————- | | think\App | think\facade\App | | :—————- | :—————- | | think\Cache | think\facade\Cache | | :—————- | :—————- | | think\Config | think\facade\Config | | :—————- | :—————- | | think\Cookie | think\facade\Cookie | | :—————- | :—————- | | think\Db | think\facade\Db | | :—————- | :—————- | | think\Env | think\facade\Env | | :—————- | :—————- | | think\Event | think\facade\Event | | :—————- | :—————- | | think\Filesystem | think\facade\Filesystem | | :—————- | :—————- | | think\Lang | think\facade\Lang | | :—————- | :—————- | | think\Log | think\facade\Log | | :—————- | :—————- | | think\Middleware | think\facade\Middleware | | :—————- | :—————- | | think\Request | think\facade\Request | | :—————- | :—————- | | think\Response | think\facade\Response | | :—————- | :—————- | | think\Route | think\facade\Route | | :—————- | :—————- | | think\Session | think\facade\Session | | :—————- | :—————- | | think\Validate | think\facade\Validate | | :—————- | :—————- | | think\View | think\facade\View | | :—————- | :—————- |

以上类无需进行实例化就可以很方便的进行方法调用,例如:

  1. use think\facade\Cache;
  2. Cache::set('name','value');
  3. echo Cache::get('name');

中间件

中间件主要用于拦截或过滤应用的HTTP请求,并进行必要的业务处理。
新版部分核心功能使用中间件处理,你可以灵活关闭。包括Session功能、请求缓存和多语言功能。

定义中间件

通过命令快速生成中间件

  1. php think make:middleware Check

该操作会在app目录下生成目录middleware,并在里面生成Check.php中间件
文件内容如下:

  1. <?php
  2. declare (strict_types = 1);
  3. namespace app\middleware;
  4. class Check
  5. {
  6. /**
  7. * 处理请求
  8. *
  9. * @param \think\Request $request
  10. * @param \Closure $next
  11. * @return Response
  12. */
  13. public function handle($request, \Closure $next)
  14. {
  15. //
  16. }
  17. }

中间件的入口执行方法必须是handle方法,而且第一个参数是Request对象,第二个参数是一个闭包。
handle方法必须返回Response对象
Chech.php中间件简单示例

在某些需求下,可以使用第三个参数传入额外的参数。但是$name具体传的是什么内容?Request的变量名还是?

  1. <?php
  2. namespace app\middleware;
  3. class Check
  4. {
  5. public function handle($request, \Closure $next, $name)
  6. {
  7. if ($name == 'think') {
  8. return redirect('index/think');
  9. }
  10. return $next($request);
  11. }
  12. }

TODO:首先能想到的需求:如果是加密话参数,这里在中间件中解密,加密。
TODO:需要验证用户在线验证是否可以在中间件中?用户权限是否可以在中间件中?

结束调度

中间件支持定义请求结束前的回调机制,你只需要在中间件类中添加end方法。

  1. public function end(\think\Response $response)
  2. {
  3. // 回调行为
  4. }

前置/后置中间件

看了半天文档,才搞清楚,被官方的中间件类名误导了,不是因为类名是:Before或者After来确定是前置中间件还是后置中间件的,而是通过$next的位置来确定是前置中间件还是后置中间件,比如:next之后执行,那么就属于后置中间件。
TODO:
问题1:两个的应用区别是什么?
示例:前置中间件

  1. <?php
  2. namespace app\middleware;
  3. class Demo
  4. {
  5. public function handle($request, \Closure $next)
  6. {
  7. // 添加中间件执行代码
  8. // 这里添加的就属于前置中间件
  9. return $next($request);
  10. }
  11. }

示例:后置中间件

  1. <?php
  2. namespace app\middleware;
  3. class Demo
  4. {
  5. public function handle($request, \Closure $next)
  6. {
  7. $response = $next($request);
  8. // 添加中间件执行代码
  9. // 这里添加的就属后置中间件
  10. return $response;
  11. }
  12. }

判断当前环境示例

  1. namespace app\middleware;
  2. /**
  3. * 访问环境检查,是否是微信或支付宝等
  4. */
  5. class InAppCheck
  6. {
  7. public function handle($request, \Closure $next)
  8. {
  9. if (preg_match('~micromessenger~i', $request->header('user-agent'))) {
  10. $request->InApp = 'WeChat';
  11. } else if (preg_match('~alipay~i', $request->header('user-agent'))) {
  12. $request->InApp = 'Alipay';
  13. }
  14. return $next($request);
  15. }
  16. }

注册中间件之后,在controller中可以通过request()->InApp获取相关的值

定义中间件别名

可以直接在应用配置目录下的middleware.php中先预定义中间件(其实就是增加别名标识),例如

  1. return [
  2. 'alias' => [
  3. 'auth' => app\middleware\Auth::class,
  4. 'check' => app\middleware\Check::class,
  5. ],
  6. ];

注意:这里是配置文件的middleware.php,不是注册中间件的middleware.php

注册中间件

新版的中间件分为全局中间件、应用中间件(多应用模式下有效)、路由中间件以及控制器中间件四个组。
执行顺序分别为:全局中间件->应用中间件->路由中间件->控制器中间件

全局中间件

全局中间件在app目录下面middleware.php文件中定义,使用下面的方式:
普通定义:

  1. <?php
  2. return [
  3. \app\middleware\Auth::class,
  4. 'check',
  5. 'Hello',
  6. ];

按组定义:

  1. return [
  2. 'alias' => [
  3. 'check' => [
  4. app\middleware\Auth::class,
  5. app\middleware\Check::class,
  6. ],
  7. ],
  8. ];

中间件的注册应该使用完整的类名,如果已经定义了中间件别名(或者分组)则可以直接使用。
全局中间件的执行顺序就是定义顺序。可以在定义全局中间件的时候传入中间件参数,支持两种方式传入。

  1. <?php
  2. return [
  3. [\app\http\middleware\Auth::class, 'admin'],
  4. 'Check',
  5. ['hello','thinkphp'],
  6. ];

上面的定义表示 给Auth中间件传入admin参数,给Hello中间件传入thinkphp参数。

应用中间件

如果你使用了多应用模式,则支持应用中间件定义,你可以直接在应用目录下面增加middleware.php文件,定义方式和全局中间件定义一样,只是只会在该应用下面生效。

路由中间件

官方原话:最常用的中间件注册方式是注册路由中间件。是这样吗?[TODO]学习路由的时候一起学习这个吧。
官方地址:https://www.kancloud.cn/manual/thinkphp6_0/1037493#%E8%B7%AF%E7%94%B1%E4%B8%AD%E9%97%B4%E4%BB%B6,打开Ctrl+F搜索一下吧,不支持锚点跳转。

控制器中间件

支持为控制器定义中间件,只需要在控制器中定义middleware属性,例如:
这个示例是控制器文件,定义了一个局部变量$middleware,就默认使用中间件了。
app/app_name/controller/Index.php

  1. <?php
  2. namespace app\controller;
  3. class Index
  4. {
  5. protected $middleware = ['auth'];
  6. public function index()
  7. {
  8. return 'index';
  9. }
  10. public function hello()
  11. {
  12. return 'hello';
  13. }
  14. }

当执行index控制器的时候就会调用别名为auth中间件,一样支持使用完整的命名空间定义。
但是以上方法定义出来的中间件是针对当前控制器下所有方法都生效的,如果要局部生效需要传入exceptonlyexcept禁止指定方法使用该中间件,only仅允许指定方法使用该中间件。
app/app_name/controller/Index.php

  1. <?php
  2. namespace app\controller;
  3. class Index
  4. {
  5. protected $middleware = [
  6. 'auth' => ['except' => ['hello'] ],
  7. 'check' => ['only' => ['hello'] ],
  8. ];
  9. public function index()
  10. {
  11. return 'index';
  12. }
  13. public function hello()
  14. {
  15. return 'hello';
  16. }
  17. }

解释:别名为auth的中间件不允许hello方法使用,别名未check的中间件只允许hello使用。

内置中间件

中间件类 描述
think\middleware\AllowCrossDomain 跨域请求支持
think\middleware\CheckRequestCache 请求缓存
think\middleware\LoadLangPack 多语言加载
think\middleware\SessionInit Session初始化
think\middleware\FormTokenCheck 表单令牌

这些内置中间件默认都没有定义,你可以在应用的middleware.php文件中、路由或者控制器中定义这些中间件,如果不需要使用的话,取消定义即可。

事件

新版的事件系统可以看成是5.1版本行为系统的升级版,事件系统相比行为系统强大的地方在于事件本身可以是一个类,并且可以更好的支持事件订阅者。
事件相比较中间件的优势是事件比中间件更加精准定位(或者说粒度更细),并且更适合一些业务场景的扩展。例如,我们通常会遇到用户注册或者登录后需要做一系列操作,通过事件系统可以做到不侵入原有代码完成登录的操作扩展,降低系统的耦合性的同时,也降低了BUG的可能性。
事件系统的所有操作都通过think\facade\Event类进行静态调用

V6.0.3+版本开始,事件机制不能关闭

事件定义

事件系统使用了观察者模式,提供了解耦应用的更好方式。

生成事件类

  1. php think make:event UserLogin

默认会生成一个app\event\UserLogin事件类,也可以指定完整类名生成。
一般事件类无需继承任何其它类。

绑定事件标识(定义事件别名)

你可以给事件类绑定一个事件标识,一般建议直接在应用的event.php事件定义文件中批量绑定。
文件:app/event.php或者app/app_name/event.php,主要取决于事件的应用范围

  1. return [
  2. 'bind' => [
  3. 'UserLogin' => 'app\event\UserLogin',
  4. // 更多事件绑定
  5. ],
  6. ];

事件触发

在你需要监听事件的位置,例如下面我们在用户完成登录操作之后添加如下事件触发代码:

  1. // 触发UserLogin事件 用于执行用户登录后的一系列操作
  2. Event::trigger('UserLogin');

或者使用助手函数

  1. event('UserLogin');

这里UserLogin表示一个事件标识,必须要在event.php文件中定义,如果你定义了单独的事件类,你可以使用事件类名(甚至可以传入一个事件类实例)。

  1. // 直接使用事件类触发
  2. event('app\event\UserLogin');

你可以在event方法中传入一个事件参数

  1. // user是当前登录用户对象实例
  2. event('UserLogin', $user);

如果是定义了事件类,可以直接传入事件对象实例

  1. // user是当前登录用户对象实例
  2. event(new UserLogin($user));

事件监听

事件监听类只需要定义一个handle方法,支持依赖注入。

监听操作

手动注册事件监听

  1. Event::listen('UserLogin', function($user) {
  2. //
  3. });

使用监听类

  1. Event::listen('UserLogin', 'app\listener\UserLogin');

使用监听类,需要自建监听类,可以通过一下命令生成监听类

  1. php think make:listener UserLogin

默认会生成一个app\listener\UserLogin事件监听类,也可以指定完整类名生成。
事件监听类只需要定义一个handle方法,支持依赖注入。

  1. <?php
  2. namespace app\listener;
  3. class UserLogin
  4. {
  5. public function handle($user)
  6. {
  7. // 事件监听处理
  8. }
  9. }

handle方法中如果返回了false,则表示监听中止,将不再执行该事件后面的监听。
一般建议直接在事件定义文件中定义对应事件的监听。即在app/event.php或者app/app_name/event.php中加入监听

  1. return [
  2. 'bind' => [
  3. 'UserLogin' => 'app\event\UserLogin',
  4. // 更多事件绑定
  5. ],
  6. 'listen' => [
  7. 'UserLogin' => ['app\listener\UserLogin'],
  8. // 更多事件监听
  9. ],
  10. ];

事件订阅

可以通过事件订阅机制,在一个监听器中监听多个事件,例如通过命令行生成一个事件订阅者类,

  1. php think make:subscribe User

默认会生成app\subscribe\User类,或者你可以指定完整类名生成。
然后你可以在事件订阅类中添加不同事件的监听方法,例如:

  1. <?php
  2. namespace app\subscribe;
  3. class User
  4. {
  5. public function onUserLogin($user)
  6. {
  7. // UserLogin事件响应处理
  8. }
  9. public function onUserLogout($user)
  10. {
  11. // UserLogout事件响应处理
  12. }
  13. }

监听事件的方法命名规范是on+事件标识(驼峰命名),如果希望统一添加事件前缀标识,可以定义eventPrefix属性。
例如:

  1. <?php
  2. namespace app\subscribe;
  3. class User
  4. {
  5. protected $eventPrefix = 'User';
  6. public function onLogin($user)
  7. {
  8. // UserLogin事件响应处理,因为定义了eventPrefix 属性
  9. }
  10. public function onLogout($user)
  11. {
  12. // UserLogout事件响应处理,因为定义了eventPrefix 属性
  13. }
  14. }

事件注册

在事件定义文件event.php中注册事件订阅者

  1. return [
  2. 'bind' => [
  3. 'UserLogin' => 'app\event\UserLogin',
  4. // 更多事件绑定
  5. ],
  6. 'listen' => [
  7. 'UserLogin' => ['app\listener\UserLogin'],
  8. // 更多事件监听
  9. ],
  10. 'subscribe' => [
  11. 'app\subscribe\User',
  12. // 更多事件订阅
  13. ],
  14. ];

事件小结(不完整,也可能理解有误)

  • 监听listener和订阅者subscribe性质相同,形式不同。
  • 定义的事件是触发就执行的,而监听和事件订阅是事件触发执行完才执行的。

    小结

  • 因为使用的composer,所以文件自动加载要符合psr-4,即:目录名\文件名,对应命名空间:app\目录名,类名必须是文件名,如果是多应用,放在应用目录下的,需要加上对应的目录名。
    eg1:app\vendor\Wechat.php,文件里命名空间:namespace app\subscribe; 类名:Wechat
    eg2:app\app_name\vendor\Wechat.php,文件里命名空间:namespace app\app_name\subscribe; 类名:Wechat

  • 在多应用下服务service、门面facade、中间件middleware和事件event(包括事件监听和事件订阅)都可以放在app根目录下,但是要注意psr-4的自动引入规则。
    服务注册:service.php文件放在app目录下,则是属于全局服务,如果放在app\app_name下则是应用级别的服务,但是都可以use app\service\服务;
    中间件注册:middleware.php文件放在app目录下,则是属于全局中间件,如果放在app\app_name下则是应用级别的中间件,但是都可以use app\middleware\中间件;
    事件注册:event.php文件放在app目录下,则是属于全局事件,如果放在app\app_name下则是应用级别的事件,但是都可以use app\event\事件;