图像处理功能

  1. 图像处理功能不是系统内置的功能了,需要通过composer引入进来;
composer require topthink/think-image
  1. 引入进来之后,首先创建图像处理对象;
$image = Image::open('image.png');

QQ截图20200807161149.png

  1. 获得了图像处理对象后,可以得到这张图片的各种属性;
//图片宽度
echo $image->width();
//图片高度
echo $image->height();
//图片类型
echo $image->type();
//图片 mime
echo $image->mime();
//图片大小
dump($image->size());

QQ截图20200807161234.png

  1. 使用crop()方法可以裁剪图片,并使用save()方法保存到指定路径;
  2. 可以点击追踪方法内部,参看源码参数,了解更多的传值方法;
//裁剪图片
$image->crop(550,400)->save('crop1.png');

QQ截图20200807161430.png

  1. 使用thumb()方法,可以生成缩略图,配合save()把缩略图保存下来;
//生成缩略图
$image->thumb(500,500)->save('thumb1.png');

QQ截图20200807161541.png

  1. 这里要注意一个问题,虽然设置了宽和高,但高度变成了282,说明是等比例的;
  2. 可以点击追踪方法内部,第三个参数默认为:$type = self::THUMB_SCALING;
  3. 而这个常量设置的定义如下:
/* 缩略图相关常量定义 */
const THUMB_SCALING = 1; //常量,标识缩略图等比例缩放类型
const THUMB_FILLED = 2; //常量,标识缩略图缩放后填充类型
const THUMB_CENTER = 3; //常量,标识缩略图居中裁剪类型
...

QQ截图20200807161723.png

  1. 使用rotate()方法,可以旋转图片,默认是 90 度,参数可以设置;
$image->rotate(180)->save('rotate1.png');
  1. save()方法可以配置的参数除了保存文件名的路径,还有以下几个:
  2. save('路径',['类型','质量','是否隔行扫描']),追踪到方法查看;
save($pathname, $type = null, $quality = 80, $interlace = true)
  1. water()方法,可以给图片增加一个图片水印,默认位置为右下角,可看源码常量;
$image->water('mr.lee.png',5,50)->save('water1.png');

QQ截图20200807163443.png

  1. text()方法,可以给图片增加一个文字水印,具体如下:
$image->text('Mr.Lee',getcwd().'/1.ttf',20,'#ffffff')->save('text1.png');

异常处理

  1. 系统输出的异常信息比PHP原生的要人性化的多,但需要开启调试模式;
  2. 如果你想更改异常页面的样式、布局之类的,可以修改这个页面:
vendor/topthink/framework/src/tpl/think_exception.tpl

QQ截图20200808132917.png

  1. 如果你想要直接替换掉异常页面,换成别的,可以在app.php中进行设置:
// 异常页面的模板文件
'exception_tmpl' => app()->getThinkPath() . 'tpl/think_exception.tpl', 

QQ截图20200808132946.png

  1. 系统的异常抛出是自动进行的,并不需要你手动抛出,但也支持手动;
throw new Exception('异常消息', 10086);

QQ截图20200808133027.png

  1. 我们可以使用try{}catch(){}对可能发生的异常进行手动捕获或抛出;
try {
    echo 0/0;
} catch (ErrorException $e)
{
    echo '发生错误:'.$e->getMessage();
}

QQ截图20200808133215.png

  1. 我们可以抛出HTTP异常,所谓HTTP异常比如404错误,500错误之类;
throw new HttpException(404, '页面不存在');

QQ截图20200808133245.png

  1. 系统提供了一个助手函数abort()方法简化HTTP异常抛出;
abort(404, '页面不存在');

QQ截图20200808133303.png

  1. 如果系统关闭了调试模式,进入部署环境下,可以设置HTTP错误页面,比如404;
'http_exception_template' => [
    // 定义 404 错误的模板文件地址
    404 => \think\facade\App::getAppPath() . '404.html',
]

日志处理

  1. 日志处理的操作由Log类完成,它记录着所有程序中运行的错误记录;
  2. config\log.php配置文件,用于设置日志信息;
  3. 我们在runtime目录下后一个log文件夹,里面按照日期排好了每月的日志;
  4. 使用record()方法,记录一条测试日志;
Log::record('测试日志!');

QQ截图20200808134800.png

  1. 我们在log日志文件夹里找到最新生成的日志,可以看到生成的日志信息;
  2. 系统提供了不同日志级别,默认info级别,从低到高排列如下:
  3. debug/info/notice/warning/error/critical/alert/emergency/sql;
  4. 一般记录就是 info 信息,我们也可以指定我们的信息级别;
Log::record('测试日志!', 'error');

QQ截图20200808134820.png

  1. record()方法不是实时记录,需要等待程序完毕后决定是否写入日志;
  2. 如果在写入方法后添加close()关闭写入,那么record()方法则不写入;
Log::close();

QQ截图20200808134916.png

  1. 系统还提供了一个write()方法,进行时时写入,不理会其它限制;
Log::write('测试日志信息', 'error');

QQ截图20200808134939.png

  1. 系统发生异常后,会自动写入error日志,如果你想手动也可以;
try {
    echo 0/0;
} catch (ErrorException $e)
{
    echo '发生错误:'.$e->getMessage();
    Log::record('被除数不得为零', 'error');
}

QQ截图20200808135019.png

  1. 对于各种日志级别,系统提供了一些快捷方式和助手函数,比如:
Log::error('错误日志!'); //Log::record('错误日志!', 'error')
Log::info('信息日志!'); //Log::record('信息日志!', 'info')
trace('错误日志!', 'error');
trace('信息日志!', 'info');

QQ截图20200808135245.png

  1. 系统默认并不记录HTTP异常,因为这种异常容易遭受攻击不断写入日志;
  2. 除了系统提供的几种类型,也可以自己定义日志类型;
Log::diy('自定义日志');

QQ截图20200808135306.png

  1. 在配置文件log.php中,可以设置限定日志文件的级别,不属于的无法写入;
'level' => ['error','info'], 
  1. 在配置文件log.php中,添加转换为json格式;
'json' => true

QQ截图20200808135353.png

  1. 使用::getLog()方法,可以获取写入到内存中的日志;
$logs = Log::getLog();
dump($logs);
  1. 使用::clear()方法,可以清理掉内存中的日志;
Log::clear();
  1. 在配置文件log.php中,可以设置以单独文件存储的方式;
'single' => true,

中间件

定义中间件

  1. 中间件的主要用于拦截和过滤HTTP请求,并进行相应处理;
  2. 这些请求的功能可以是URL重定向,权限验证等等;
  3. 为了进一步了解中间件的用法,我们首先定义一个基础的中间件;
  4. 可以通过命令行模式,在应用目录下生成一个中间件文件和文件夹;
php think make:middleware Check

QQ截图20200808142604.png

namespace app\middleware;
class Check
{
    public function handle($request, \Closure $next)
    {
        if ($request->param('name') == 'index') {
            return redirect('../');
        }
        return $next($request);
    }
}
  1. 然后将这个中间件进行注册,在应用目录下创建middleware.php中间件配置;
return [
    app\middleware\Check::class
];

QQ截图20200808142729.png

  1. 中间件的入口执行方法必须是handle()方法,第一参数请求,第二参数是闭包;
  2. 业务代码判断请求的name如果等于index,就拦截住,执行中间件,跳转到首页;
  3. 但如果请求的namelee,那需要继续往下执行才行,不能被拦死;
  4. 那么就需要$next($request)把这个请求去调用回调函数;
  5. 中间件handle()方法规定需要返回response对象,才能正常使用;
  6. $next($request)执行后,就是返回的respons对象;
  7. 为了测试拦截后,无法继续执行,可以return response()助手函数测试;

前/后置中间件

  1. $next($request)放在方法底部的方式,属于前置中间件;
  2. 前置中间件就是请求阶段来进行拦截验证,比如登录判断,跳转,权限等;
  3. 而后置中间件就是请求完毕之后再进行验证,比如写入日志等等;
public function handle($request, \Closure $next)
{
    //中间件代码,前置
    return $next($request);
}


public function handle($request, \Closure $next)
{
    $response = $next($request);
    //中间件代码,后置
    return $response;
}

QQ截图20200808143003.png

结束调度

  1. 中间件提供了一个end()方法,可以在中间件执行到最后时执行;

注意,在end()方法里面不能有任何的响应输出,因为回调触发的时候请求响应输出已经完成了

public function end(Response $response)
{
    // 注意,在end方法里面不能有任何的响应输出,因为回调触发的时候请求响应输出已经完成了
}

QQ截图20200808154749.png

路由中间件

  1. 创建一个给路由使用的中间件,判断路由的ID值实现相应的验证;
class Check
{
    public function handle($request, \Closure $next)
    {
        if ($request->param('id') == 10) {
            echo '是管理员!';
        }
        return $next($request);
    }
}

QQ截图20200808154917.png

  1. 路由方法提供了一个middleware()方法,让指定的路由采用指定的中间件;
Route::rule('ar/:id', 'Address/read')->middleware(\app\middleware\Auth::class);
Route::rule('ar/:id', 'Address/read')->middleware([Auth::class, Check::class]); //支持多个中间件,这里 use 了

QQ截图20200808155159.png

  1. 也可以在config/middleware.php配置文件加中,配置别名支持;
'alias' => [
    'Auth' => app\middleware\Auth::class,
    'Check' => app\middleware\Check::class,
],
Route::rule('ar/:id', 'Address/read')->middleware(['Auth', 'Check']);
  1. 可以给中间件传递额外参数,通过中间件入口方法的第三个参数接收;
->middleware(Auth::class, 'ok');
public function handle($request, \Closure $next, $param, $param = '')
  1. 中间件也支持分组路由,闭包路由等;
Route::group('ar', function () {
    Route::rule(':id', 'Address/read')
})->middleware(Auth::class);

Route::rule('ar/:id', 'Address/read')->middleware(function ($request, \Closure $next) {
    if ($request->param('id') == 10) {
        echo '是管理员!';
    }
    return $next($request);
});

控制器中间件

  1. 可以让中间件在控制器里注册,让这个控制器执行的时候执行中间件;
protected $middleware = ['Check'];
  1. 默认情况下,控制器中间件对所有操作方法有效,支持做限制;
protected $middleware = [
    'Auth' => ['only' =>['index']],
    'Check' => ['except' =>['read']],
];
  1. 中间件给控制器传递参数,通过Request对象实现;
$request->name = 'Mr.Lee';

服务系统

  1. 服务系统,可以将一个类的对象注册到容器中去,方便调用执行;
  2. 服务的执行优先级较高,在执行主体程序前就已经完成依赖注入;
  3. 它的作用可以做一些初始化,配置一些参数,扩展插件等等均可;
  4. 验证码扩展类就使用了服务系统,我们自己创建一个简单的服务;
  5. common目录下创建一个Shut.php类,这个类是被服务的类;
//定义一个属性字段
protected static $name = 'Mr.Lee';
//设置
public static function setName($name)
{
    self::$name = $name;
}
//获取
public function run()
{
    halt(self::$name.'提醒您,系统已关闭...');
}
  1. 使用命令行,生成一个对Shut.php服务的服务类ShutSerice.php;
php think make:service ShutService
  1. 服务类有两个方法,一个是服务注册register(),一个服务启动boot();
public function register()
{
    //绑定到容器,将被服务的类注册到容器中去
    $this->app->bind('shut', Shut::class);
}
public function boot()
{
    //执行
    Shut::setName('Mr.Wang');
}
  1. 最后一部,将系统服务配置到全局定义文件里,service.php;
return [
    \app\service\ShutService::class,
];
  1. 最后在任意控制器测试即可,可以容易依赖注入或容器标识执行系统服务;
public function index(Shut $shut)
{
    //依赖注入调用
    $shut->run();
    //容器标识调用
    $this->app->shut->run();
    return 'index';
}

QQ截图20200808160828.png

事件

  1. 事件和中间件有一点相似,只不过事件更加的精准定位更细腻的业务场景;
  2. 事件可定义:事件类、事件监听类、事件订阅类;
  3. 我们先手动创建一个测试类;
public function __construct()
{
    //注册监听器
    Event::listen('TestListen', function ($param) {
        echo '我是监听器,我被触发了!'.$param;
    });
}
public function info()
{
    echo '登录前准备!';
    Event::trigger('TestListen', 'ok'); //触发监听器
    event('TestListen'); //助手函数触发
}

QQ截图20200808161838.png

  1. 我们也可以使用监听类来设计监听器,使用命令行创建;
php think make:listener TestListen

QQ截图20200808162241.png

public function info()
{
    echo '登录前准备!';
    Event::listen('TestListen', TestListen::class); //这句可以定义到配置文件
    Event::trigger('TestListen');
}

QQ截图20200808162625.png

  1. app/event.php中,listen是配置监听类的,配置方式如下:
'listen' => [
    'TestListen' => [\app\listener\TestListen::class]
], 

QQ截图20200808162718.png

  1. 而监听类被触发会自动执行handle()方法,实现监听功能;

    public function handle($event)
    {
     echo '我是监听类!'.$event;
    }
  2. 系统还内置了系统触发的事件,只要满足条件就会自动触发;

  3. 事件监听类,可以同时监听多个监听类,只要绑定到一个标识符即可;

    'TestListen' => [
     \app\listener\TestListen::class,
     \app\listener\TestOne::class,
     \app\listener\TestTwo::class
    ]
  4. 对于需要多个监听,监听类不够灵活,而且类会创建很多,可以使用订阅类;

  5. 订阅类就是将监听事件作为内部的方法用on+方法名来实现;

php think make:subscribe UserSub
class UserSub
{
    public function onUserLogin(){
    echo '处理登录后的监听!';
}
    public function onUserLogout(){
    echo '处理退出后的监听!';
}
}
  1. 然后,我们直接去app/event.php注册一下;

    'subscribe' => [
    'UserSub' => \app\subscribe\UserSub::class,
    ], 
  2. 然后,两个方法分别监听两个事件方法,直接调用方法名即可;

public function login(){
    echo '登录成功!';
    Event::trigger('UserLogin');
}
public function logout(){
    echo '退出成功!';
    Event::trigger('UserLogout');
}
  1. 对于事件类,很少有场景需要使用它,毕竟系统提供的各种精确方案较多;
php think make:event UserEvent
class UserEvent
{
    public function __construct()
    {
        echo '我是事件类!';
    }
}
Event::trigger(new UserEvent());

多应用模式

  1. 由于多应用模式属于扩展,我们需要额外安装;
composer require topthink/think-multi-app
  1. 安装后,创建indexadmin两个应用目录文件夹;
  2. 只要将controllermodel 移入即可,修改相应的命名空间;
  3. view也增加indexadmin两个应用目录文件夹,移入相应文件夹;
  4. 默认的应用为index,在app.php修改即可;
// 默认应用
'default_app' => 'index', 
  1. 我们可以做应用映射,比如将admin目录映射为think,admin废弃;
// 应用映射(自动多应用模式有效)
'app_map' => [
    'think' => 'admin'
], 
  1. 我们也可以做域名绑定,比如,后台用域名绑定,直接访问;
// 域名绑定(自动多应用模式有效)
'domain_bind' => [
    'news.abc.com' => 'admin',
    '*' => 'index'
], 
  1. 路由修改:需要在应用目录单独建立路由,内部编码不需要更改;