ThinkPHP6入门指南

小黄带你快速入门TP6. 本系列只是快速了解, 只是针对官方文档进行部分讲解. 配合官方文档和实际项目才是最快速的入门方法.

安装框架

自从ThinkPHP5.1之后只能通过Composer安装框架了.

所以在此之前我们需要先安装Composer.

对于Winodws用户,直接下载exe安装即可 地址: https://getcomposer.org/Composer-Setup.exe

对于Linux或Mac os用户使用以下两行命令

curl -sS https://getcomposer.org/installer | php
mv composer.phar /usr/local/bin/composer

安装完成后,我们可以把镜像换成国内源

composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/

这样我们的Composer就安装完成了.下面开始安装我们的Tp6.

composer create-project topthink/think demo

这里的 demo 是目录名.也就是我们的根目录

稍等片刻之后,框架就已经安装完成了. 如何运行起来呢?

方法1:

cd public
php -S localhost:8000

方法2

php think run

随便选一种方式启动即可.这里的两种方式都只适用于开发环境.不可用于线上

之后打开浏览器进入 http://localhost:8000/

会看到欢迎页面,那么现在已经完成ThinkPHP6.0的安装了.

开发规范及目录结构

使用了框架就要遵守一定的规范.

现代PHP框架也都基于PSR规范.下面简单罗列几个点

遵守这些规则可以避免一些不必要的错误发生.

下面说下框架的目录结构

app      应用目录 我们大部分的代码在这里编写
config   配置目录
extend   扩展类库目录
pulic    WEB目录(对外访问目录), 在配置Nginx或Apache的时候root目录指向这里
route    路由定义目录
runtime  应用的运行时目录,里面存放着运行的缓存和日志
vendor   Composer类库目录
view     视图目录
think    环境变量示例文件
.example.env  环境变量示例文件,使用时应拷贝一份命名成.env

TP6安装完成后,默认是单应用模式

再编写类CMS系统的时候,需要前台和后台两部分.可以将目录结构可以参考下面的结构进行调整

.  根目录
├─app           应用目录
│  ├─index           前台目录
│  │  ├─common.php      函数文件
│  │  ├─controller      控制器目录
│  │  ├─model           模型目录
│  │  ├─view            视图目录
│  │  ├─config          配置目录
│  │  ├─route           路由目录
│  │  └─ ...            更多类库目录
│  ├─admin           后台目录
│  │  ├─common.php      函数文件
│  │  ├─controller      控制器目录
│  │  ├─model           模型目录
│  │  ├─view            视图目录
│  │  ├─config          配置目录
│  │  ├─route           路由目录
│  │  └─ ...            更多类库目录
│  ├─common.php         公共函数文件
│  └─event.php          事件定义文件

上面的目录结构就是多应用的布局,其中还可以细分出更多的目录进行代码架构,比如Service、Dao、Manager,再之后的深入中会详细介绍.

如果使用多应用模式,需要安装扩展think-multi-app

composer require topthink/think-multi-app

重点: 多应用模式部署后,记得删除app目录下的controller目录(系统根据该目录作为判断是否单应用的依据)

第一个页面: Hello World!

了解了目录结构后,我们可以开始编写第一行代码了!

再编写前我们先让它运行起来!

直接在命令行中输入 php think run 即可启动 (别告诉我你不知道在哪个目录运行)

打开浏览器输入 http://localhost:8000/

xw81.png

出现这个页面就代表启动成功啦.

下面我们去修改它,让它显示出Hello World!

找到 app/controller下的Index.php文件, 这里就是首页的默认控制器和方法.

index方法里返回的就是我们最开始打开的页面内容,现在我们将他修改成Hello World!

<?php
namespace app\controller;

use app\BaseController;

class Index extends BaseController
{
    public function index()
    {
        return 'Hello,World!';
    }
}

保存代码后,再次访问页面.

出现了我们的 Hello World!

思考: 为什么代码写在app目录下

跳转提示

在编写一些混排项目的时候,我们希望能对用户的一些操作进行反馈.比如登录后台时候输入了无效的账号又或者是删除了某条数据提示用户删除成功.

通常接口开发的时候,只需让前端判断即可. 那么在混排的工程中如何快速实现一个响应的页面呢?

下面是登录的一个页面. 当用户输入错误的账号时

1.png

会给与用户一个提示: 未找到用户

2.png

自带的页面不好看 可以自己自由发挥修改下...

对于这样的一个功能在Tp5中可以直接使用,但需要继承Controller类.在最新的Tp6中已经单独划分成一个包了.

composer require liliuwei/thinkphp-jump

安装之后会在config目录下生成一个jump.php的文件,里面记录了success和error的模板

安装完后还不能直接使用,需要额外配置下.

在app目录下的BaseController.php文件中,引用Jump的命名空间,然后在BaseController类中use Jump即可.

....
use liliuwei\think\Jump;

/**
 * 控制器基础类
 */
abstract class BaseController
{
    use Jump;
	....    
}

好啦这样就可以和Tp5一样使用该方法了.

通过 $this->error('未找到用户') 就可以显示出'未找到用户'页面的提示了.当然你也可以通过 $this->success('登录成功') 来提示用户登录成功

下面贴一段伪代码

use app\BaseController;

class Login extends BaseController
{

	public function login()
    {
    	$userInfo = (....); // 通过用户传入数据从数据库获取账号信息
        if ( !$userInfo ) $this->error('登录失败');
        (....) // 省略
        $this->success('登录成功');
    }

}

渲染模板

在编写完Hello,World后.我们可以试想一下,如何输出一个html页面.

按照之前直接return字符串一样,我们也可以直接返回html代码.

return '<h1>HELLO,WORLD</h1>';

但是这种并不能应用在我们实际的项目中.

我们可以使用框架自带的助手函数view渲染页面. 也可以使用 View::fetch 方法

<?php
namespace app\controller;

use app\BaseController;
use think\facade\View;

class Index extends BaseController {
	public function index() {
		return View::fetch();
	}
    
    public function index2() {
    	return view();
    }
}

这两种方法都能渲染页面.我们运行起来看看效果. php think run

出现了一个 页面错误!请稍后再试~ 的提示. 这里我们得打开调试模式才能看具体报错信息.

我们需要设置一个env

windows命令行下

copy .example.env .env

Mac或Linux

cp .example.env .env

我们打开env可以看一下

APP_DEBUG = true
....

这里已经开启了调试模式. 这个env的作用我们会在后面的文章中做详细介绍.

再次刷新页面

Driver [Think] not supported.

抛出了一个这样的异常. 用过TP5的小伙伴就按耐不住了.这个使用方法还变了? tp5可以直接用的啊.

这里主要是tp6把模板引擎独立成一个扩展了.要自己安装了.

composer require topthink/think-view

当然如果不想安装模板引擎的话可以使用php原生语法渲染. 这里需要改一下配置项

config/view.php

return [
	// 模板引擎类型使用Think
	'type' => 'Think',
    ];

把type类型改为 php 就行了.

好啦.又抛出一个新异常了

模板文件不存在: demo\view\index\index.html

这里我们还没定义模板文件,按照框架约定可以把html放到view目录下对应控制器名的对应方法名文件中.

view\index\index.html = view\控制器名\方法名.html

这里的文件后缀名和目录都是可以更改的.

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>第一个页面</title>
</head>
<body>
	<h1>Hello,World</h1>
</body>
</html>

再次访问,页面中又出现了我们的Hello,World.

获取用户提交参数

我们已经实现了两种Hello,World的输出方式.但是不灵活啊!

现在想让他通过提交的参数输出内容该怎么实现呢? 比如提交一个name参数:小黄 页面上显示出 hello,小黄.

在显示之前.我们需要获取到这些参数.

$_GET 或 $_POST 可以获取到参数. 但是我们在框架中禁止使用!

Tp6给了我们几种获取方式.

<?php
namespace app\controller;

use app\BaseController;
use think\facade\Request;

class Index extends BaseController {
	public function index() {
		var_dump(input('name'));
		var_dump(request()->param('name'));
		var_dump(Request::param('name'));
	}
}

我们打开浏览器,访问http://localhost:8888/?name=小黄 页面打印出3条 小黄,我们已经成功接收参数

input方法详解

input('?get.name'); // 判断 get 请求下name参数是否存在
input('get.'); // 获取所有get请求参数
input('name'); 获取name参数(包括get post请求)

还可以传入参数进行一定修饰,用于简单过滤变量.对于参数的校验最好使用验证器

Request::get('id/d'); // 强制转换为整型类型
input('post.name/s'); // 强制转换为字符串类型
request()->param('ids/a'); // 强制转换为数组类型

还有很多种获取方法.如果想了解可以去官方文档查找. 但是最终都要做到一点. 不相信任何用户传来的数据 .

可能传递过来的是一条sql注入语句,又或者是getshell.一定要严判断!

继续实现我们的需求!

<?php
namespace app\controller;

use app\BaseController;

class Index extends BaseController {
	public function index() {
		$name = input('name/s', 'World');
		return view()->assign('name', $name);
	}
}

input('name/s', 'World'): 接收一个name参数, 如果没有则默认为'World'

view()->assign('name', $name): 模板中的变量(除了一些系统变量外)必须先进行模板赋值后才能使用,可以使用assign方法进行全局模板变量赋值.

为什么我们的模板中不能直接使用php变量呢? (作为一个思考题)

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>第一个页面</title>
</head>
<body>
	<h1>Hello, <?php echo $name; ?></h1>
</body>
</html>

如果安装了模板引擎 可以使用模板方法 {$name} 输出变量.

访问http://localhost:8888/?name=小黄 , 页面中已经显示出 Hello, 小黄 .

我们传递一串js脚本过去: http://localhost:8888/?name=<script>alert('1')</script>

居然执行成了.弹出了一个1 . 这种如果在加以利用是不是可以获取到我们的cookies呢? 细思极恐啊! 所以要做好用户传参验证和过滤!

状态的存储-session

大部分web应用是有状态的.何为有状态呢?

一个用户在某个网站登录后,刷新页面后登录状态还保持着.这就是状态.那么服务端是如何实现的呢?

Session是服务器端技术,服务器在运行时可以为每一个用户创建一个其独享的session文件,所以用户在访问服务器web资源时,可以把各自的数据放在各自的session中,当用于再去访问该服务器中其他web端资源时,其他web端再从session取出用户各自的数据;

继续以上篇的举例.我们通过name参数访问页面.会显示对应的Hello,(name的值), 我们希望访问过一次后服务器会记住上一次传入的name值.也相当与保存状态.

在tp6中使用session需要在全局的中间件定义文件中加上下面的中间件定义

'think\middleware\SessionInit'

然后就可以使用啦

use think\facade\Session;

Session::set('name', 'thinkphp');
Session::get('name');

当然也提供了助手函数

session('name', null); // 删除 name
session('name'); // 取值
session('name', 'a'); // 设置内容

下面开始编码.

<?php
namespace app\controller;

use app\BaseController;

class Index extends BaseController {
	public function index() {
		$lastName = session('name');
		$name = input('name/s', 'World');
		session('name', $name);
		return view()->assign('name', $name)->assign('lastName', $lastName);
	}
}

页面:

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>第一个页面</title>
</head>
<body>
	<h1>Hello, <?php echo $name; ?> [上次传入的name是: ] <?php echo $lastName; ?></h1>
</body>
</html>

我们测试下, 打开浏览器输入 http://localhost:8888/?name=xiaohuang

Hello, xiaohuang [上次传入的name是: ]

由于是第一次打开页面没有上一次的输入所以这里没有显示

$name = input('name/s', 'World');
session('name', $name);

这一次输入的xiaohuang 已经被存入session中. 下次就可以通过 session('name') 获取到上次输入内容.

路由

之前我们一直在首页操作,首页默认指向app/controller/Index类的index方法.

为什么能直接指向呢?在config目录下route.php中定义了这么两行指向了默认的控制器

// 默认控制器名
'default_controller'    => 'Index',
// 默认操作名
'default_action'        => 'index',

当然你也可以通过 http://域名/index/index 访问首页

我们在新增一个方法index2

public function index2() {
	return "index2";
}

打开浏览器访问 http://localhost:8000/index/index2 显示出 index2.

我们如何美化这个url呢? 比如我直接访问 /index2 就能访问到我们上面定义的index2方法呢

这就得用上我们的路由了,

在route目录下有个app.php 这里就是定义整个项目的路由文件. 里面默认提供了两个测试的.

use think\facade\Route;

Route::rule('路由表达式', '路由地址', '请求类型');

我们先定义一个指向index2方法的路由

Route::rule('index2', 'index/index2');

定义之后原来的访问地址会自动失效,我们再次访问 /index2 页面也返回正常了.还可以通过/index2.html来访问.这也是因为route.php的配置文件中有声明伪静态后缀

// URL伪静态后缀
'url_html_suffix'       => 'html',

它还提供了几种其他方法来对应请求类型

Route::get(); // 定义GET请求路由规则
Route::post(); // 定义POST请求路由规则
Route::put(); // 定义PUT请求路由规则
Route::delete(); // 定义DELETE请求路由规则
Route::any(); // 所有请求都支持的路由规则

这里就简单的定义了路由.现在有一套流行的规范-RESTful API. 这里不做过多介绍,外链一篇文章 RESTful API 最佳实践

针对RESTful路由我们可以设置变量

动作 URI 路由
GET /photos/{id} (通过ID查看相片) Route::get('photos/:id','Photos/show');

我们就可以在Photos类中的show方法里接收id参数

public function index2($id) {
	return "接收到参数: " . $id;
}

有些情况下变量用[ ]包含起来后就表示该变量是路由匹配的可选变量。这点不懂的可以动手试试.

中间件

中间件主要用于拦截或过滤应用的HTTP请求.

中间件的本质是一个洋葱模型

1.jpg

图中的顺序为按照 Middleware A -> Middleware B 的顺序组织着

我们可以注意到当中间的横线穿过 Core App,又回到了 Middleware B,为一个嵌套模型,那么实际的顺序其实就是:

Request -> Middleware A -> Middleware B -> Core App -> Middleware B -> Middleware A -> Response

定义中间件

php think make:middleware Check

这个指令会 app/middleware目录下面生成一个Check中间件。

<?php
declare (strict_types = 1);

namespace app\middleware;

class Check
{
    /**
     * 处理请求
     *
     * @param \think\Request $request
     * @param \Closure       $next
     * @return Response
     */
    public function handle($request, \Closure $next)
    {
        //
    }
}

中间件的入口执行方法必须是handle方法,而且第一个参数是Request对象,第二个参数是一个闭包。返回值也必须是一个Response对象

中间件是在请求具体的操作之前还是之后执行,完全取决于中间件的定义本身。

前置中间件 对应于上图的 Request -> Middleware A -> Core App Middleware A 部分

public function handle($request, \Closure $next)
{
	// 添加中间件执行代码
   
	return $next($request);
}

后置中间件 对应于上图的 Core App -> Middleware A -> Response Middleware A 部分

public function handle($request, \Closure $next)
{
	$response = $next($request);
	// 添加中间件执行代码

	return $response;
}

全局中间件 在app目录下面middleware.php文件中定义,我们之前启动session的时候就已经说到了.

通常还会配合路由定义中间件

Route::group('hello', function(){
	Route::rule('hello/:name','hello');
})->middleware('check');

还有控制器中间件只需要在控制器中定义middleware属性 protected $middleware = ['check'];

那么说了这么多,中间件具体有什么实际用途呢?

还有很多实际用途,其根本也是降低了系统的耦合.

数据库

缓存

扩展: 控制反转(IoC)及依赖注入(DI)

总结