ThinkPHP 5.2 初体验

5.2版本目前属于测试版本,存在不稳定性,请勿轻易用于正式项目。

安装及入口文件

安装

由于5.2版本完全依赖Composer,因此只能通过composer安装才能使用,不同于5.05.1版本,下载或者Git安装都可以使用。

由于只是测试阶段,所以必须安装dev版本

1
composer create-project topthink/think thinkphp52 5.2.*-dev

启动服务

1
2
cd thinkphp52
php think run

入口文件

新版的入口文件还是位于public目录下面,由于直接使用composer的自动加载机制,因此框架核心已经不再使用Loader类了而是改为加载composerautoload文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------

// [ 应用入口文件 ]
namespace think;

require __DIR__ . '/../vendor/autoload.php';

// 执行应用并响应
(new App())->run()->send();
  • autoload.php
1
2
3
4
5
6
7
<?php

// autoload.php @generated by Composer

require_once __DIR__ . '/composer/autoload_real.php';

return ComposerAutoloaderInit902a9936ccdb3d6e9ddc6502fd4f4a61::getLoader();

注意每一个入口文件在新版里面总是对应一个应用,有意思的是,不同的应用入口文件除了文件名不同外代码可能完全一样(会自动绑定到文件名对应的应用)。

如果你的文件名和应用名不一致,那么可能需要略微调整如下:

1
2
3
4
5
6
7
8
9
10
11
<?php

// [ 应用入口文件 ]
namespace think;

require __DIR__ . '/../vendor/autoload.php';

// 执行应用并响应
(new App())->name('app_name')
->run()
->send();

think\App类还提供了很多的设置方法,都可以在run之前调用。这个有兴趣深入了解的可以通过源码查看。

源码: thinkphp52/vendor/topthink/framework/src/think/App.php

  • App.php

这里展示一些方法和作用, 源码自己去看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?php

namespace think;

class App extends Container
{
// 自动多应用访问
public function autoMulti(array $map = []) {}

// 是否为自动多应用模式
public function isAutoMulti(): bool {}

// 设置应用模式
public function multi(bool $multi) {}

// 是否为多应用模式
public function isMulti(): bool {}

// 设置应用路径
public function path(string $path) {}

// 开启应用调试模式
public function debug(bool $debug = true) {}

// 设置应用名称
public function name(string $name) {}

// 设置控制器层名称
public function controllerLayer(string $layer) {}

// 设置空控制器名称
public function emptyController(string $empty) {}

// 设置应用命名空间
public function setNamespace(string $namespace) {}

// 设置应用根命名空间
public function setRootNamespace(string $rootNamespace) {}

// 设置是否启用应用类库后缀
public function useClassSuffix(bool $suffix = true) {}

// 是否调试模式
public function isDebug(): bool {}

// 是否启用类库后缀
public function hasClassSuffix(): bool {}
}

由于新版本一个入口文件对应一个应用,如果你希望保持5.1版本的URL形式不变,直接访问不同的应用,一个最简单的办法就是设置Apache.htaccess文件。

1
2
3
4
5
6
7
8
<IfModule mod_rewrite.c>
Options +FollowSymlinks -Multiviews
RewriteEngine On

RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^([a-z]+)/(.*)$ $1.php/$2 [QSA,PT,L]
</IfModule>

应用目录及命名空间调整

新版的目录结构其实看起来和5.1并无多大的差异,除了thinkphp框架目录已经纳入vendor之外。另外应用目录从原来的application更改为app,之所以做这个调整是为了让新手更容易理解应用的命名空间对应,不至于产生不必要的困惑。

  • 初始目录 :
1
2
3
4
5
6
7
8
9
10
www
├─app
│ └─controller
├─config
├─extend
├─public
│ └─static
├─route
├─runtime
└─vendor

应用命名空间类似于

1
namespace app\controller;

如果你需要更改根命名空间为top,有两种办法。

  • 一种是修改composer.json文件的autoload,这样可以不改变目录名。
1
2
3
"psr-4": {
"top\\": "app"
},
  • 第二种办法是直接修改你的app目录为top

无论使用哪一种方法,最后在入口文件中设置根命名空间名称。

1
2
3
4
5
6
7
8
9
10
<?php

namespace think;

require __DIR__ . '/../vendor/autoload.php';

// 执行应用并响应
(new App())->setRootNamespace('top')
->run()
->send();

如果你的 admin 应用不在 app 目录下面,有完全独立的命名空间例如

1
\think\admin;

就可以在入口文件admin.php中进行指定

1
2
3
4
5
6
7
8
9
10
<?php

namespace think;

require __DIR__ . '/../vendor/autoload.php';

// 设置当前应用的命名空间
(new App())->setNamespace('\think\admin')
->run()
->send();

这个应用放在什么位置取决于你的命名空间自动加载路径,这一设计让你的应用可以通过composer来加载。

多应用模式

多应用模式下面,除了应用目录的区别外,runtime目录下面会自动创建各个应用的子目录(注意,这个目录同样适用于composer加载的应用),但你只需要通过App::getRuntimePath()方法获取当前应用的runtime目录。

1
2
runtime/home/
runtime/admin/

多个应用的路由定义文件都是独立的,在route目录下面创建对应目录的子目录存放每个应用的路由定义。

1
2
route/home/
route/admin/

多个应用允许加载统一的公共文件。

1
2
3
app/common.php
app/admin/common.php
app/home/common.php

对于配置文件而言,config目录下为应用公共配置,config下子目录则为单个应用的独立配置。

1
2
3
config/
config/admin/
config/home/

可能有人认为现在不同的应用是独立的入口文件,每个应用之间没法互通了,其实这个理解是错误的。不同的应用仍然是可以相互调用的,毕竟类库都是基于命名空间的

单应用模式

上面的目录结构其实是多应用设计,和之前版本一样同样支持单应用模式(之前版本其实是单一模块模式)如果你使用的是单应用的话,目录结构其实就变成了下面的结构。

1
2
3
4
5
6
www  
├─app 应用目录
│ ├─controller 控制器目录
│ ├─model 模型目录
│ ├─view 视图目录
│ └─ ... 更多类库目录

系统的一个默认判断单应用的规则是app目录下面如果存在controller目录,则判断当前为单应用模式。如果你更改了默认的访问控制器层的名称(或者刚好有一个应用名称也叫controller),那么可以在入口文件里面调用 multi(false)来设置为单应用模式。

1
2
3
4
5
6
7
8
9
10
<?php

namespace think;

require __DIR__ . '/../vendor/autoload.php';

// 执行应用并响应
(new App())->multi(false)
->run()
->send();

单应用模式下,如果需要更改命名空间,setNamespacesetRootNamespace 方法是等效的。

数据库和模型的变化

总体来说,在数据库和模型层面,5.2的改动并不大,只是因为强类型变量的约束会导致一些用法的差异,以及一些统一和废弃的方法会导致查询代码的调整。

5.2版本目前尚未正式发布,在正式发布之前可能仍然会存在变化。

新特性

强类型严格模式

新版核心全面启用了严格模式,避免因为方法参数类型模糊而导致的规范不一或者潜在问题。

Db使用门面对象

新版的Db类不再是静态类,需要使用 think\facade\Db 门面进行静态代理。

1
\think\facade\Db::name('user')->find();

重构的fetchSql方法

查询类的fetchSql方法返回了一个新的think\db\Fetch类,获取查询SQL的操作和查询操作完全隔离。另外一个副作用是可以使得查询方法的返回值更加规范。Fetch类目前已经支持动态查询方法。

1
2
Db::name('user')->where('id', 1);  // 返回\think\db\Query对象实例
Db::name('user')->where('id', 1)->fetchSql(); // 返回\think\db\Fetch对象实例

增加乐观锁功能

和软删除一样,增加了一个 think\model\concern\OptimLockTrait用于方便引入扩展模型的乐观锁功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php

namespace app\index\model;

use think\model\conern\OptimiLock;
use think\Model;

class User extends Model
{
use OptimiLock;

protected $optimLock = 'lock_version';
}

增加fetchArray方法

模型的查询可以通过显式调用fetchArray方法使得查询结果返回数组而不是模型对象和数据集,适用于一些图表控件的数据输出。

1
2
3
4
// 返回模型对象
User::find(1);
// 返回数组
User::fetchArray()->find(1);

模型增加Schema定义

模型类增加schema属性,用于完整定义模型对应数据表的字段信息,
可以避免查询的时候获取数据表字段信息而无需生成Schema缓存。并且该属性中定义字段类型支持使用php类型定义。
需要注意和field/type属性的区别,field属性仅用于定义当前模型允许写入的字段,而type属性则仅用于定义数据字段的类型强制转换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php

namespace app\index\model;

use think\Model;

class User extends Model
{
protected $schema = [
'id' => 'int',
'name' => 'string',
'score' => 'float',
'create_time' => 'datetime',
'update_time' => 'datetime',
'delete_time' => 'datetime',
];
}

模型延迟保存机制

模型增加延迟保存方法lazySave,调用该方法后不会马上保存数据,仅仅是保存在内存中,当前模型对象销毁的时候或者下次显式调用save方法的时候都会自动保存到数据库。

1
2
3
4
5
6
$user = User::find(1);
$user->name = 'thinkphp';
$user->lazySave();
...
$user->score = 100;
$user->save();

join系列方法增加参数绑定支持

join方法(包括leftJoin/rightJoin/fullJoin方法)支持手动参数绑定。

增加tableRaw方法

增加tableRaw方法使用表达式方法定义当前查询的数据表

查询缓存支持闭包情况

改进查询缓存方法支持使用闭包查询的情况。

增加 withAttrs 方法

由于参数强制类型的关系,withAttr方法不支持传入数组,必须使用withAttrs方法传入数组批量设置字段获取器。

废弃功能和方法

废弃了一些目前已经很少用到或者不建议使用的功能,以及对容易混淆的一个功能多个用法进行了统一。

取消数据分表功能

取消了数据库分表功能,建议直接使用数据库的分区功能替代,更灵活和性能更好,同时取消了Query类的getPartitionTableNamepartition方法。

取消了get/all方法

无论使用Db类还是模型类查询,全部统一使用find/select方法,取消了之前模型类额外提供的get/all方法。

取消findOrEmpty方法

取消了Query类的findOrEmpty方法,当模型使用find方法查询的时候,数据不存在自动返回一个空模型对象,无需再使用findOrEmpty方法了。

取消relation方法

取消了Query类的relation方法,模型的关联获取直接使用关联属性方式惰性获取。

取消了readMaster方法

取消了Query类和模型类的readMater方法,由于该方法使用了静态属性容易导致Swoole等常驻内存的环境下读取错乱。需要的时候可以用master方法手动切换主库读取。

取消fetchPdo方法

取消了Query类的fetchPdo方法,需要的时候直接使用getPdo方法替代。

取消查询方法传入Query对象

取消Query类的CURD查询方法传入当前对象,如果需要请使用闭包替代。

取消全局查询范围base方法

取消模型类的全局查询范围base方法,改由使用globalScope属性定义(数组)需要全局查询的查询范围方法。或者在db($scope)方法中传入全局查询范围。如果是静态查询,可以使用useGlobalScope($scope)

取消 dbmodel 助手函数

这两个助手函数5.1版本已经不再建议使用了,新版直接废弃掉这两个助手函数,请指教使用\think\facade\Db类和实际的模型类调用。

取消 setInc/setDec方法

取消Query类的setInc/setDec方法,统一使用inc/dec方法替代,同时改进了inc/dec方法支持延时更新

取消setField方法

取消Query类的setField方法,请直接使用data方法或者update方法。

取消__TABLE_NAME__支持

table方法取消__TABLE_NAME__支持,必须明确调用完整表名或者使用name方法。

取消whereOr等方法传入Query对象

除了where方法本身可以传入Query对象外,其它的所有where查询方法(例如whereOr/whereExp等)都不再支持传入Query对象。

取消resultset_type配置参数

数据集查询结果不再受resultset_type配置参数影响,默认情况下,Db查询统一返回数组,模型查询统一返回模型对象和模型数据集对象。如果Db查询的时候也需要返回数据集的话,可以显式调用fetchCollection方法。

with方法和withJoin独立

无需在模型关联方法中定义是否使用JOIN查询,只有在明确调用withJoin的时候才会使用JOIN查询。

路由的调整和改进

ThinkPHP5.2的路由部分,也和其它组件一样,做了精简和优化,主要包括如下方面:

取消路由定义的返回数组形式

因为不利于路由缓存生成,路由定义文件取消了返回数组的方式定义路由,必须采用路由方法注册路由。

例如:

1
2
3
return [
'hello/:name' => 'index/hello',
];

必须改成:

1
Route::get('hello/:name', 'index/hello');

多应用的路由定义文件位置

单应用模式下,路由定义文件和之前一样就在route目录下面,如果你的项目是采用了多应用的话,每个应用的路由定义和匹配都是独立的,也没有模块的概念,路由定义文件的位置应该是在route/应用子目录下面,例如:

1
2
3
route/index/route.php   //  index应用的路由定义文件
route/index/web.php // index应用的第二个路由定义文件
route/admin/route.php // admin应用的路由定义文件

默认的URL规则变成了:

1
http://域名/入口文件(或者应用名)/控制器名/操作名

应用的路由规则其实是定义的入口文件(或者应用名)后面的URL部分,而不包含应用。

自动多应用

最新的5.2版本可以支持在同一个入口文件中访问多个不同的应用(之前必须每个应用添加一个对应的入口文件)。

例如在index.php入口文件中使用:

1
2
3
(new App())->autoMulti()
->run()
->send();

就可以不必创建入口文件自动通过URL访问多个应用

1
http://serverName/index.php/admin

如果你的默认应用不是index(默认为入口文件名),那么可以通过name方法指定默认应用。

1
2
3
4
(new App())->autoMulti()
->name('admin')
->run()
->send();

支持应用名的别名映射,例如:

1
2
3
(new App())->autoMulti([
'think' => 'admin', // 把admin应用映射为think
])->run()->send();

如果需要对某个应用进行自定义,可以使用

1
2
3
4
5
(new App())->autoMulti([
'admin' => function($app) {
$app->debug(true)->useClassSuffix();
}
])->run()->send();

取消别名路由

因为使用场景有限和性能开销问题,取消原来的别名路由功能,建议使用资源路由或者单独的路由替代。

取消快捷路由

因为使用场景有限和不太符合规范,取消了原来的控制器快捷路由功能。

取消空控制器和空操作

原来的空控制器和空操作功能已经取消,请使用MISS路由功能替代,而且可以支持给不同的路由分组设置MISS路由。同时废弃empty_controller配置。

取消控制器自动搜索

由于性能原因,取消了路由的多级控制器自动搜索功能,请在路由规则定义中明确指定要路由的多级控制器。

路由功能独立设计

路由功能不再固定执行,而且设计成为AppInit事件的响应监听,并且可以在项目的事件定义里面配置,系统默认的定义配置如下:

  • app\event.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
return [
'bind' => [
],
'listen' => [
'AppInit' => [
'think\listener\LoadLangPack',
'think\listener\RouteCheck',
],
'AppBegin' => [
'think\listener\CheckRequestCache',
],
'ActionBegin' => [],
'AppEnd' => [],
'LogLevel' => [],
'LogWrite' => [],
'ResponseSend' => [],
'ResponseEnd' => [],
],
'subscribe' => [
],
];

AppInit事件中会执行think\listener\RouteCheck类,如果你的应用完全不需要使用任何的路由功能,可以在配置文件中取消定义即可,系统会执行默认的URL调度(也即是控制器/操作)。

取消注册方法的 optionpattern 参数

取消路由注册方法(包括rule/get/post/put/delete/patch/miss/group等方法)的optionpattern参数,全部改成方法调用形式,例如原来的:

1
Route::get('hello/:name', 'index/hello', [ 'ext'  =>  'html'], [ 'name'  => '\w+']);

需要改成

1
2
3
Route::get('hello/:name', 'index/hello')
->ext('html')
->pattern([ 'name' => '\w+']);

路由分组定义不再支持数组

因为不利于分组的嵌套功能,路由分组定义不再支持数组,只能使用闭包方式定义,例如:

1
2
3
4
Route::group('blog', [
':id' => 'Blog/read',
':name' => 'Blog/read',
])->ext('html')->pattern(['id' => '\d+']);

必须改成

1
2
3
4
Route::group('blog', function() {
Route::get(':id', 'Blog/read');
Route::get(':name', 'Blog/read');
})->ext('html')->pattern(['id' => '\d+']);

如果你需要注册一个虚拟的路由分组,可以直接在第一个参数使用闭包

1
2
3
4
Route::group(function() {
Route::get('blog/:id', 'Blog/read');
Route::get('user/:name', 'User/read');
})->ext('html')->pattern(['id' => '\d+']);

取消了url_controller_layer配置

改为在入口文件中使用controllerLayer方法设置。

1
2
3
(new App())->controllerLayer('Action')
->run()
->send();

取消class_suffix配置

改为在入口文件中使用useClassSuffix方法设置。

1
2
3
(new App())->useClassSuffix(true)
->run()
->send();

同时取消controller_suffixclass_suffix配置参数。

取消 mergeExtraVars 方法和对应参数

改为在路由规则中明确指定变量规则。

header方法参数类型调整

由于强类型约束的原因,header方法改为仅支持数组参数传入。

使用强类型参数

由于全面启用强类型参数,并且使用严格模式,所以一定要注意参数的类型。

参考

Powered by Hexo and Hexo-theme-hiker

Copyright © 2017 - 2023 Keep It Simple And Stupid All Rights Reserved.

访客数 : | 访问量 :