01. phpEnv配置安装
1. 学习基础
- ThinkPHP8.x:
- 前端基础:HTML5/CSS(必须)、JavaScript(可选、但推荐有);
- 后端基础:PHP基础,版本不限,但不能太老,至少PHP5.4以上语法,TP8是兼容PHP8.x的;
- 数据库基础:MySQL数据库,掌握了常规的SQL语句。
- 编码思维基础:MVC、MVVM、设计模式(单例、观察者等)有简单认知即可;
2. 软件获取
- phpEnv:
- 官方网站:https://www.phpenv.cn/ (专业优雅强大的PHP集成环境);
- 下载最新版本,自动安装后(默认C盘,可选),界面如下:
- 简单配置:
- 应用软件->设置->开启CDN加速(方便我们后续安装各种软件);
- 安装 PHP8.2 和 MySQL8 ,选择 Apache (个人向,教学方便);
- 然后点击 启动服务 ,允许各种允许访问即可;
- 在浏览器输入:localhost,即可访问PHP探针。
- 创建站点:
- 点击 主页 下 网站,添加一个虚拟网站,以便后续学习测试;
- 可以选择自定义的目录,我教学方便,就默认了;
- 添加后,会写入hosts,这样我们输入 www.tp.com 域名后就可以访问了;
- 默认会给你一个index.php,显示结果为:php8.2 info
02. ThinkPHP8.x配置安装
1. 8.x安装
- 主要特性:(来自手册)
- 支持PHP8强类型、支持6.x无缝升级、语义化版本策略、升级PSR规范依赖版本;
- 安装方式:
- 仅在windows教学,Linux或其它平台对照手册执行即可;
- 键盘:win + R,输入cmd,输入composer,判断是否已经安装;
- 如果没有安装composer,则:https://getcomposer.org/Composer-Setup.exe (来自手册);
- 切换至你项目的根目录,比如第一节课创建的 www.tp.com 文件夹的上一层 www;
- 输入安装命令:composer create-project topthink/think www.tp.com;
- 注意:被安装的文件夹必须是空的,先要删除里面的内容;
- 各种超时,可以先修改成国内源,比如阿里:
- composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/
- 查看是否成功:composer config -g -l
2. 启动程序
- IDE载入
- 开发工具任意,因为我们在浏览器测试即可,我这里选择了phpStorm,备选如下:
- VS Code
- sublime Text4
- Zend Stduio
- 载入项目后,在IDE的终端输入内置服务器命令:php think run,启动Web服务;
- 访问方式为:www.tp.com:8000 ,默认端口8000,当然也有80端口,不过被占用了;
- 此时,我们发现,phpEnv还没启动呢,怎么就可以访问了呢?
- 官方手册 -> 命令行 -> 启动内置服务器,有讲,这是内置的Web服务器,其它服务没有;
- 所以学习使用时,推荐开启着,比如数据库连接等服务还需要。
- 删除广告
- ThinkPHP8.x目前非常的商业化,打开后,广告糊一脸,我们先把这个去掉。
- 在app/controller/index.php中的index方法,自定义return结果即可。
- 或者执行phpinfo()函数,来检测php版本。
- 配色问题
- 我个人开发学习,都是用的深色背景,明亮代码,一般开发工具都是默认这个样式;
- 也推荐学习者,也用这个默认或差不过的样式,用于保护眼睛和缓解视觉疲劳;
- 但如果讲课,我一般采用的都是亮色背景,原因如下:
- 场景切换:如果是深色,切换到亮色界面,对学习者那么一闪,暴击效果显著;
- 专注力:深色的背景,更加容易让听课者昏睡,注意力不集中;
- 上课时长:每节课10分钟上下,并不会太疲劳,自己长时间开发用深色;
03. 编码规范.目录.配置
1. 编码规范
- 完全版参考手册,这里说重点:
- TP8遵循PSR-2命名规范、PSR-4自动加载规范;
- 类名和文件名保持一致,驼峰式命名(首字母大写)如:Index,BaseController
- 方法和函数名用驼峰式命名(首字母小写)如:index(),hello()
- 数据表和字段用蛇式命名(两个单词中间下划线)如:think_name,user_name
- 更多对照手册
2. 目录结构
- 对于初学者,一个个说是干啥的,没有意义,这里说重点:
- app目录是应用目录,我们开发程序就在这个目录
- config是配置目录,顾名思义,配置程序状态
- public是公共目录,对外访问目录,运行程序默认执行此目录的index.php
- 剩下的,后续学到一个,讲一个。
3. 配置定义
- 默认情况下,程序出错会显示:页面出错!请稍候再试~
- 这种情况,一般是应用部署好后,万一出错给用户看的;
- 如果我们自己在开发阶段,需要开启调试模式,来提示具体的错误信息:
- 在根目录有一个文件:.example.env,改成 .env ,也就是去掉点前面;
- 然后在配置信息的第一行:APP_DEBUG = true 即可,false则不开启。
- 调试模式开启后,可以发现右下角会出现trace调试工具小图标:
- 包含了丰富的调试内容:具体自点查看。
04. URL访问模式
1. 单应用URL
- 注意:这里服务器启动是 php think run 的内置服务器,下节课会探讨外置服务器;
- 结构分析:
- serverName就是我们的:www.tp.com:8000;
- index.php 是入口文件,带上 / ;
- 控制器是app\controller\Index.php中的 Index 这个名称,也就是类名;
- 操作是类里面的方法名,比如:index(默认方法),hello(普通方法);
- 默认方法可以省略,会直接方法,其他普通方法需要键入方法名:
- http://www.tp.com:8000/index.php/Index (默认执行index操作)
- http://www.tp.com:8000/index.php/Index/index (完整路径)
- http://www.tp.com:8000/index.php/Index/test (普通方法,必须完整路径)
- 系统默认自带的hello方法,是针对后续路由课程的,在路由文件设置过导致无效;
- 我们在config/app.php中将路由关闭:“with_route” => false,
- http://www.tp.com:8000/index.php/Index/hello (执行默认参数值)
- http://www.tp.com:8000/index.php/Index/hello/name/World(修改参数值)
- 参数不够直观,尤其多参数的时候,也是支持传统方案的:
- http://www.tp.com:8000/index.php/Index/hello?name=World (问号键值对)
2. 多应用URL
- 如果开启多应用的话,URL比单应用多一个 应用 路径,其它一致;
- 由于我们教学,都在单应用上,暂时不刻意演示,如果后续有机会,放到最后;
05. 内.外置服务器
1. 启动内置
- 在手册命令行 -> 启动内置服务器,有 php think run 启动的服务器介绍;
- 启动后直接输入:127.0.0.1:8000 或 localhost:8000 或 www.tp.com:8000 访问;
- 停止内置服务器,在命令行使用 ctrl + c
2. 启动phpEnv
- 不管是本地的外置服务器集成环境phpEnv,还是以后要部署到远程服务器,都需要配置;
- 这里需要配置的重点就是伪静态,我们不启动内置服务器,只用外置访问试试;
- 注意:这里外置服务器,在第一课我们已经创建了 端口为 80 的 www.tp.com;
- 现在,直接访问试试?但出现了如下图的错误:
- 我们在 www.tp.com 目录下创建一个index.html或index.php索引文件,访问后:
3. 配置目录
- 在第一节课,我们创建的启动目录是 www.tp.com ,但目前知道这个目录下没有 index.php;
- 在手册的 基础 -> 目录结构 中 public 目录 解释为:WEB目录(对外访问目录);
- 并规范到:在实际项目部署中,确保只将 public 对外访问;
- 所以,我们将phpEnv站点配置目录设置为:www.tp.com/pubic 即可;
- 至于http.conf、伪静态和.htaccess等配置信息,默认情况下支持都用,后续需要配置再说;
06. 控制器的定义
1. 定义方式
- 控制器:顾名思义MVC中的C,即逻辑控制定义;
- 默认在app\controller下编写对应的控制器类文件,如果想改默认目录:
- 类名和文件名大小写保持一致,并采用驼峰式(首字母大写);
- User类创建两个方法 index(默认)和 login,访问 URL 如下:
- http://www.tp.com:8000/Index.php/user/
- http://www.tp.com:8000/Index.php/user/login
- 那么如果创建的是双字母组合,比如 class HelloWorld,访问 URL 如下:
- http://www.tp.com:8000/Index.php/helloworld
- http://www.tp.com:8000/Index.php/hello_world
2. URL重写
- URL重写可以省去 index.php 入口文件的键入,默认情况下内置服务器支持重写; http://www.tp.com:8000/User
- 而外置服务器,比如phpEnv,省略了入口文件,则出现如下问题:
- 查看手册,根据它URL重写的修改方案(Apache),需要修改.htaccess最后一行:
- 备注:不同的环境、服务器等等都会有千奇百怪的问题,努力搜索解决即可;
- 如果实在解决不了,请就用 php think run 内置服务器就了,省事。
- 到时候部署到真实服务器环境,再折腾。。。
3. 配置渲染
- 如果你想避免引入同类名时的冲突,可以 config\route.php 设置控制器后缀:
- ThinkPHP 直接采用方法内 return 返回的方式直接就输出了;
- 如果需要使用 json 输出,直接采用 json 函数;
- 不推荐使用 die、exit 等 PHP 方法中断代码执行,推荐助手函数 halt();
07. 基础和空控制器
1. 基础控制器
- 一般来说,创建控制器后,推荐继承基础控制器来获得更多的功能方法;
- 基础控制器仅仅提供了控制器验证功能,并注入了think\App和think\Request;
- 这两个对象后面会有章节详细讲解,下面我们继承并简单使用一下;
2. 空控制器
- 空控制器一般用于载入不存在的控制器时,进行错误提示;
08. 创建数据表及填充
1. 创建数据表
- 我们直接使用phpEnv自带的MySQL操作软件:HeidiSQL,简称HS;
- 登录的时候,注意修改root和密码后,保存,下次方可直接打开;
- 首先,我们创建一个数据库:demo,用于测试:
2. 创建用户表
- 设置好数据库后,我们创建一个user表,包含id、name、age、gender、details等字段;
- 然后,我们填充一些数据,方便查询测试:
- 最后,我们通过SQL语句来测试一下:
09. 连接数据库和查询
1. 连接数据库
- 我们可以在 config\database.php 配置文件中设置与数据库的连接信息:
- 如果是一般性数据库连接,在 ‘’connections‘’ 配置区设置即可;
- 如果是本地测试,它会优先读取 .env 配置,然后再读取 database.php 的配置;
- 如果禁用了 .env 配置,则会读取数据库连接的默认配置:
2. PHP获取数据
- 我们暂时没有详细学习此类语法,可以简单用一些了解一下即可:
10. 构造器之数据查询
1. table方法
- Db类旗下有一个 table 静态调用的方法,参数为完整的表名(前缀都不能省略);
- 如果希望只查询一条数据,可以使用 find() 方法,需指定 where 条件:
- 想要了解执行的原生SQL是什么,可以注释掉 return 直接通过 trace 查看;
- 使用 findOrEmpty() 方法也可以查询一条数据,但在没有数据时返回一个空数组:
- 使用 findOrFail() 方法同样可以查询一条数据,在没有数据时抛出一个异常:
- 想要获取多列数据,可以使用 select() 方法:
- select() 方法默认返回 Collection 对象的数据集,可以通过 toArray() 方法转换成数组:
- 多列数据也可以参与 where() 方法的筛选:
2. 链式查询
- 我们发现通过指向符号 “->” 多次连续调用方法称为:链式查询;
- 当 Db::table(“user”) 时,返回查询对象(Query),即可连缀数据库对应的方法;
- 当返回查询对象(Query)时,就可以继续调用链式方法,where() 也是链式方法;
- 而 where() 被调用后,依旧返回(Query),可以再次链式调用;
- 在手册 数据库 -> 查询构造器 -> 链式操作 可以了解所有可链式的方法:table、where等;
- 直到遇到 find() 或 select() 返回数组或数据集时,结束查询;
11. 表前缀之扩展查询
1. 表前缀
- 一般来说,为了保持表名统一性和防止冲突,都会给表加上一个前缀,以下划线结束;
- 比如:tp_user,这里的 tp_ 就是表前缀,所有;
- 我们修改MySQL中表名,然后刷新程序,报错;
- 当然,你可以传递 Db::table(“tp_user”),但没必要;
- 首先,我们可以来配置统一前缀:
- 在 .env 文件中 添加:DB_PREFIX = tp_
- 如果部署环境 database.php 中 设置
- 然后,使用 Db::name(“user”) 方法即可:
2. 扩展查询
- 通过 value() 方法,可以查询指定字段的值(单个),没有数据返回 null ;
- 通过 colunm() 方法,可以查询指定列的值(多个),没有数据返回空数组;
$user = Db::name(“user”)->column(“name,age”, “id”);
- 另一种处理大量数据:游标查询,为了解决内存开销,每次读取一行,并返回到下一行再读取;
12. 数据的新增方式
1. 单条新增
- 使用 insert() 方法可以向数据表添加一条数据,更多的字段采用默认;
- 如果想强行新增抛弃不存在的字段数据,则使用 strick(false) 方法,忽略异常;
- 如果我们采用的数据库是 mysql,可以支持 replace 写入;
- insert 和 replace insert 写入的区别,前者表示表中存在主键相同则报错,后者则修改;
- 使用 insertGetId() 方法,可以在新增成功后返回当前数据 ID;
2. 多条新增
- 使用 insertAll() 方法,可以批量新增数据,但要保持数组结构一致;
- insertAll() 方法 也支持 replace 写入,如果添加数据量大,可以通过 -> limit() 方法限制添加数量;
13. 更新删除和save方法
1. 数据修改
- 使用 update() 方法来修改数据,修改成功返回影响行数,没有修改返回 0;
- 如果修改数据包含了主键信息,比如 id,那么可以省略掉 where 条件;
- 如果想让一些字段修改时执行 SQL 函数操作,可以使用 exp() 方法实现;
- 如果要自增/自减某个字段,可以使用 inc/dec 方法,并支持自定义步长;
- 使用 Db::raw() 来设置每个字段的特殊需求,灵活且清晰:
- save() 方法是一个通用方法,可以自行判断是新增还是修改(更新)数据;
2. 数据删除
- 极简删除可以根据主键直接删除,删除成功返回影响行数,否则 0;
- 根据主键,还可以删除多条记录;
- 正常情况下,通过 where()方法来删除;
- 清空表以及逻辑软删除,参考手册即可;逻辑软删除,在模型篇单独讲解,这里略过。
14. 查询表达式规则
1. 表达式查询
- 查询表达式支持大部分常用的 SQL 语句,语法格式如下:
- 所有的表达式,查阅手册 -> 查询表达式 中的表格即可;这里列出几个意思一下:
表达式 | 含义 | 快捷方式 |
= | 等于 | |
<= time | 小于等于某个时间 | whereTime |
EXP | SQL表达式查询 | whereExp |
[NOT] LIKE | 模糊查询 | whereLike |
[NOT] IN | [非] IN 查询 | whereIN |
2. 查询示例
- 条件判断类的,id大于4的;
- Like模糊查询,姓王的;
$user = Db::name(“user”)->whereLike(“name”, “王%”)->select();
- NULL查询;
- EXP查询,自定义SQL片段;
- 这里推荐:有快捷方式的用快捷方式,通过Ctrl+方法名得知,快捷方式绕过了很多拼装流程,速度更快;
15. 超多查询都不讲
- 警告:这里罗列了大量的快捷查询的示例、链式查询以及其它非常用的查询方法;
- 介于TP6课程大家的反馈,我这里不再详细讲解每个方法的用法;
- 但我会录入在讲义里,方便后续项目课程时候回头查阅;后续类似的知识点也这么砍;
1. 字符串条件(不讲)
- whereRaw 可以直接写入多条件:
- 包含变量的多条件查询:
2. field()字段筛选(不讲)
- 使用 field() 方法,可以指定要查询的字段;
- 使用 field() 方法,给指定的字段设置别名;
- 在 fieldRaw() 方法里,可以直接给字段设置 MySQL 函数;
- 使用 withoutField() 方法中字段排除,可以屏蔽掉想要不显示的字段;
- 使用 field() 方法在新增时,验证字段的合法性;
3. 常用链式方法(不讲)
- 使用 alias() 方法,给数据库起一个别名;
- 使用 limit() 方法,限制获取输出数据的个数;
- 分页模式,即传递两个参数,比如从第 3 条开始显示 5 条 limit(2,5);
- 使用 order() 方法,可以指定排序方式,没有指定第二参数,默认 asc;
- 支持数组的方式,对多个字段进行排序;
- 使用 group() 方法,给性别不同的人进行 age 字段的总和统计;
- 使用 group() 分组之后,再使用 having() 进行筛选;
4. 时间查询(不讲)
- 可以使用 >、<、>=、<= 来筛选匹配时间的数据;
- 快捷方式 whereTime:
- 区间查询快捷方式 whereBetweenTime:
- 使用 whereYear 查询今年的数据、去年的数据和某一年的数据;
- 使用 whereMonth 查询当月的数据、上月的数据和某一个月的数据;
- 使用 whereDay 查询今天的数据、昨天的数据和某一个天的数据;
- 查询指定时间的数据,比如两小时内的;
- 查询两个时间字段时间有效期的数据,比如活动开始到结束的期间;
- 比如创建两个字段:start_time,end_time,注册后,分别写入对应时间表明它的有效期;
5. 聚合查询(不讲)
- 使用 count() 方法,可以求出所查询数据的数量;
- count() 可设置指定 id,比如有空值(Null)的 details,不会计算数量;
- 使用 max() 方法,求出所查询数据字段的最大值;
- 如果 max() 方法,求出的值不是数值,则通过第二参数强制转换;
- 使用 min() 方法,求出所查询数据字段的最小值,也可以强制转换;
- 使用 avg() 方法,求出所查询数据字段的平均值;
- 使用 sum() 方法,求出所查询数据字段的总和;
6. 子查询(不讲)
- 使用 fetchSql() 方法,传递参数true时,可以设置不执行 SQL,直接返回SQL语句;
- 使用 buildSql() 方法,也是返回 SQL 语句,不需要再执行 select(),且有括号;
- 结合以上方法,我们实现一个子查询;
- 使用闭包的方式执行子查询;
7. 原生查询
- 使用 query() 方法,进行原生 SQL 查询,适用于读取操作,SQL 错误返回 false;
- 使用 execute 方法,进行原生 SQL 更新写入等,SQL 错误返回 false;
8. 列字段快捷查询(不讲)
- 之前用过诸如:whereIn、whereExp、whereLike等等快捷查询;
- 所有快捷查询列表的手册位置:数据库 -> 查询构造器 -> 高级查询中,找到快捷查询表格;
- whereColumn() 方法,比较两个字段的值,符合的就筛选出来;
- whereFieldName() 方法,查询某个字段的值,注意 FileName 是字段名;
- getByFieldName() 方法,查询某个字段的值,注意只能查询一条,不需要 find();
- getFieldByFieldName() 方法,通过查询得到某个指定字段的单一值;
9. 条件查询(不讲)
- when() 可以通过条件判断,执行闭包里的分支查询;
10. 事务(不讲)
- 数据库的表引擎需要是 InnoDB 才可以使用,如果不是调整即可;
- 事务处理,需要执行多个 SQL 查询,数据是关联恒定的;
- 如果成功一条查询,改变了数据,而后一条失败,则前面的数据回滚;
- 比如:银行取钱,银行ATM扣了1000,但入口被卡住,你没拿到,这时需要事务处理;
- 系统提供了两种事务处理的方式,第一种是自动处理,出错自动回滚;
- 手动处理,基本和原生处理类似,可以自行输出错误信息;
11. 获取器(不讲)
- 获取器的意思就是:将数据的字段进行转换处理再进行操作;
- 比如在获取数据列表的时候,将获取到的详情字段全部大写;
- withAttr也是支持JSON字段的,具体参考手册 查询构造器 -> 获取器;
16. 关联索引查询
1. 索引关联
- where 方法的数组查询:
17. 拼装高级查询
1. 拼装查询
- 使用 |(OR) 或 &(AND) 来实现 where 条件的高级查询,where 支持多个连缀;
- 索引数组方式,可以在 where 进行多个字段进行查询;
- 根据之前的课程中,条件字符串复杂组装,比如使用 exp 了,就使用 raw() 方法;
- 如果有多个where,并需要控制优先级,那么在可以在需要的部分加上中括号;
- 如果,条件中有多次出现一个字段,并且需要 OR 来左右筛选,可以用 whereOr;
18. 模型的定义方式
1. 定义模型
- 为了避免被前面课程中控制器的类名干扰,删除或改名都行:
- 定义一个和数据库表相匹配的模型,可在app应用目录下创建model文件夹;
- 模型会自动对应数据表,并且有一套自己的命名规则;
- 模型类需要去除表前缀(tp_),采用驼峰式命名,并且首字母大写;
- 在控制器段创建一个任意名称的类,当然有语义更好,但为了教学理解起名为:TestUser.php;
2. 模型设置
- 系统会自动识别 模型类名 对应 表名,User.php 对应 user 表(不含前缀);
- 但如果你的模型类名不是按照规则对应表名,则需要通过成员字段去设置;
- 系统也会默认id为你的主键名,如果不是id,则需要设置;
- 模型支持初始化功能,需要设置静态方法,并只在第一次实例化的时候执行,且只执行一次;
19. 模型的新增和删除
1. 新增操作
- 用模型新增数据,首先要实例化模型,开发工具会补全use,非集成工具别忘了;
- 使用实例化的方式添加一条数据,并使用 save() 方法进行保存;
- 注意:使用模型时,会自动给时间字段 create_time,update_time 写入当前时间;
- 也可以通过 save() 传递数据数组的方式,来新增数据;
- 使用 allowField() 方法,允许要写入的字段,其它字段就无法写入了;
- 模型新增也提供了 replace() 方法来实现 REPLACE into 新增;
- 当新增成功后,使用 $user->id ,可以获得自增 ID(主键需是 id);
- 使用 saveAll()方法,可以批量新增数据,返回批量新增的数组;
- 使用 ::create() 静态方法,来创建要新增的数据;
2. 删除操作
- 使用 find() 方法,通过主键 (id) 查询到想要删除的数据;
- 然后再通过 delete()方法,将数据删除,返回布尔值;
- 也可以使用静态方法调用 destroy()方法,通过主键(id)删除数据;
- destroy() 方法,使用闭包的方式进行删除;
20. 模型的更新操作
1. 数据更新
- 使用 find()方法获取数据,然后通过 save()方法保存修改,返回布尔值;
- 通过 where()方法结合 find()方法的查询条件获取的数据,进行修改;
- save()方法只会更新变化的数据,如果提交的修改数据没有变化,则不更新;
- 但如果你想强制更新数据,即使数据一样,那么可以使用 force()方法;
- Db::raw()执行 SQL 函数的方式,同样在这里有效;
- 关于验证过滤,后续学习Request再说,手册中 模型 -> 更新 里也有说明:
- 通过 saveAll()方法,可以批量修改数据,返回被修改的数据集合;
- 使用静态方法::update()更新,返回的是对象实例;
21. 模型的查询都一样
1. 模型的查询
- 模型的绝大部分语法基本都来自于 Db::name() 的查询:
- 在手册 模型 -> 查询 中可以查阅,这里就演示几个常用的意思一下:
- find() 单个 和 select() 多个;
- 也可以使用 where()方法进行条件筛选查询数据;
- 模型方法也可以使用 where 等连缀查询,和数据库查询方式一样;
- 模型支持聚合查询:max、min、sum、count、avg 等;
- 模型也支持大量的快捷方式,这里演示一个:
22. 模型的字段设置
1. 字段设置
- 模型的数据字段和表字段是对应关系,默认会自动获取,包括字段的类型;
- 自动获取会导致增加一次查询,如果在模型中配置字段信息,会减少内存开销;
- 可以在模型设置$schema 字段,明确定义字段信息,字段需要对应表写完整;
- 字段类型的定义可以使用PHP类型或者数据库的字段类型都可以,以便自动绑定类型;
- 设置模型字段,只能对模型有效,对于 Db::name() 查询无法作用。
- 要让模型和Db查询都支持字段类型设置,分三步:
- 把上面的$schema先注释掉;
- 在 config/database.php 开启缓存字段;
- 在根目录命令行执行命令:
2. 废弃字段
- 由于历史遗留问题,我们不再想使用某些字段,可以在模型里设置;
- 设置后,我们在查询和写入时将忽略这些字段;
3. 只读字段
- 只读字段用来保护某些特殊的字段值不被更改,这个字段的值一旦写入,就无法更改;
- 然后在控制器端进行修改测试:
23. 获取器和修改器
1. 获取器
- 获取器的作用是对模型实例的数据做出自动处理;
- 一个获取器对应模型的一个特殊方法,该方法为 public;
- 方法名的命名规范为:getFieldAttr();
- 举个例子,数据库表示状态 status 字段采用的是数值;
- 而页面上,我们需要输出 status 字段希望是中文,就可以使用获取器;
- 在 User 模型端,我创建一个对外的方法,如下:
- 控制器端,正常输出数据:
- 如果你定义了获取器,并且想获取原始值,可以使用 getData()方法;
- 使用 withAttr 在控制器端实现动态获取器,比如让年龄+100岁;
2. 修改器
- 模型修改器的作用,就是对模型设置对象的值进行处理;
- 比如,我们要新增数据的时候,对数据就行格式化、过滤、转换等处理;
- 模型修改器的命名规则为:setFieldAttr;
- 我们要设置一个新增,规定输入的年龄都自动+100岁,修改器如下:
- 除了新增,会调用修改器,修改更新也会触发修改器;
- 模型修改器只对模型方法有效,调用数据库的方法是无效的,比如->insert();
24. 搜索器和自动时间戳
1. 搜索器
- 搜索器是用于封装字段(或搜索标识)的查询表达式,类似查询范围;
- 一个搜索器对应模型的一个特殊方法,该方法为 public;
- 方法名的命名规范为:searchFieldAttr();
- 举个例子,我们要封装一个 name 字段的模糊查询,然后封装一个时间限定查询;
- 在 User 模型端,我创建两个对外的方法,如下:
- 在控制器端,通过 withSearch()方法实现模型搜索器的调用;
- withSearch()中第一个数组参数,限定搜索器的字段,第二个则是表达式值;
- 如果想在搜索器查询的基础上再增加查询条件,直接使用链式查询即可;
- 在获取器和修改器都有一个 $data 参数,它的作用是什么?
2. 自动时间戳
- 如果你想全局开启,在 database.php 中,设置为 true;
- 此时,写入操作时,会自动对 create_time 和 update_time 进行写入;
- 如果你只想设置某一个模型开启,需要设置特有字段;
- 自动时间戳只能在模型下有效,数据库方法不可以使用;
- 如果创建和修改时间戳不是默认定义的,也可以自定义;
- 如果业务中只需要 create_time 而不需要 update_time,可以关闭它;
- 也可以动态实现不修改 update_time,具体如下:
25. 软删除和事件
1. 软删除
- 软删除也称为逻辑删除,只是给数据标记 “已删除” 的状态,不是真实的物理删除;
- 为何要对数据进行软删除,因为真实的物理删除,删了就没了呀。
- 在模型端设置软删除的功能,引入 SoftDelete,它是 trait;
- delete_time 默认设置的是 null,如果你想更改这个默认值,可以设置:
- 由于我们之前演示过字段缓存,会导致无法软删除,你可以删除字段缓存,或者重新更新下:
- 删除分为两种:destroy() 和 delete(),具体如下:
- 软删除后,数据库内的数据只是被标记了删除时间,而搜索数据时,会自动屏蔽这些数据;
- 在开启软删除功能的前提下,使用 withTrashed() 方法取消屏蔽软删除的数据;
- 如果只想查询被软删除的数据,使用 onlyTrashed()方法即可;
- 如果想让某一条被软删除的数据恢复到正常数据,可以使用 restore()方法;
- 如何要将软删除后的数据库真实的物理删除,需要先将它恢复,再真实删除;
2. 事件
- 在手册 -> 模型 -> 模型事件中罗列了所有的模型,这里演示常用的意思一下;
- 在模型端使用静态方法调用即可完成事件触发,比如执行了查询方法后调用事件:
26. 关联模型入门
1. 关联表
- 我们已经有了一张 tp_user 表,主键为:id;我们需要一个附属表,来进行关联;
- 附属表:tp_profile,建立两个字段:user_id 和 hobby,外键是 user_id;
2. 关联查询
- 关联模型,顾名思义,就是将表与表之间进行关联和对象化,更高效的操作数据;
- 创建 User 模型和 Profile 模型,均为空模型;如果已有User,改名UserBak备份起来;
- User 模型端,需要关联 Profile,具体方式如下:
- 创建一个控制器用于测试输出:Link.php;
27. 一对一关联查询
1. hasOne 模式
- hasOne 模式,适合主表关联附表,具体设置方式如下:
- 在上一节课,我们了解了表与表关联后,实现的查询方案:
- 使用 save()方法,可以设置关联修改,通过主表修改附表字段的值:
- ->profile 属性方式可以修改数据,->profile()方法方式可以新增数据:
2. belongsTo 模式
- belongsTo 模式,适合附表关联主表,具体设置方式如下:
- 查询方式,和主附查询方式一致:
- 使用 hasOne()也能模拟 belongsTo() 来进行查询,这样就可以不用在 Profile 模型进行设置:
28. 一对多关联查询
1. hasMany 模式
- hasMany 模式,适合主表关联附表,实现一对多查询,具体设置方式如下:
- 查询方案和一对一相同:
- 使用->profile()方法模式,可以进一步进行数据的筛选;
- 使用 has()方法,查询关联附表的主表内容,比如大于等于 2 条的主表记录;
- 使用 hasWhere()方法,查询附表中,可见兴趣的主表记录;
- 使用 save()和 saveAll()进行关联新增和批量关联新增,方法如下:
// 新增附表关联数据
$user->profile()->save([“hobby”=>“测试喜欢1”, “visible”=>1]);
// 批量新增
$user->profile()->saveAll([
[“hobby”=>“测试喜欢2”, “visible”=>1],
[“hobby”=>“测试喜欢3”, “visible”=>1],
]);
- 特别注意:由于外键约束设置问题,默认情况下,关联操作可能会导致1451错误;
- 解决方案:在 profile 表中 设置外键 删除和修改时 为:CASCADE即可,详细阅读如下:
29. 模型预载入和统计
1. 预载入
- 在普通的关联查询下,我们循环数据列表会执行 n+1 次 SQL 查询;
- 上面继续采用普通关联查询的构建方式,打开 trace 调试工具,会得到四次查询;
- 如果采用关联预载入的方式,将会减少到两次,也就是起步一次,循环一次;
- 关联预载入减少了查询次数提高了性能,但是不支持多次调用;
- 如果你有主表关联了多个附表,都想要进行预载入,可以传入多个模型方法即可;
- 上面显示结构中,主表和附表的字段已经非常多了,需要对两个表字段进行筛略:
- 还有一些 预载入缓存、延迟预载入,可以参考手册;
2. 关联统计
- 使用 withCount()方法,可以统计主表关联附表的个数,输出用 profile_count;
- 关联统计的输出采用“关联方法名” _count,这种结构输出;
- 不单单支持 Count,还有如下统计方法,均可支持;
- withMax()、withMin()、withSum()、**withAvg()**等;
- 除了 withCount()不需要指定字段,其它均需要指定统计字段;
- 对于输出的属性,可以自定义:
30. 多对多关联查询
1. 建立三张表
- 复习一下一对一,一个用户对应一个用户档案资料,是一对一关联;
- 复习一下一对多,一篇文章对应多个评论,是一对多关联;
- 多对多怎么理解,分解来看,一个用户对应多个角色,而一个角色对应多个用户;
- 那么这种对应关系,就是多对多关系,最经典的应用就是权限控制;
- tp_user:用户表;tp_role:角色表;tp_access:中间表;
- access 表包含了 user 和 role 表的关联 id,多对多模式;
2. 权限控制
- 在控制器段,我们查询一下id为1的用户,并关联查询它的权限:
- 当我们要给一个用户创建一个角色时,用到多对多关联新增;
- 一般来说,上面的这种新增方式,用于初始化角色比较合适;
- 但是,很多情况下,角色权限是初始化好的,只需要添加中间表,而不是角色表;
- 那么,我们真正需要就是通过用户表新增到中间表关联即可;
- 除了新增,还有直接删除中间表数据的方法:
31. 路由定义入门
1. 路由入门
- 路由的作用就是让 URL 地址更加的规范和优雅,或者说更加简洁;
- 设置路由对 URL 的检测、验证等一系列操作提供了极大的便利性;
- 路由是默认开启的,如果想要关闭路由,在 config/app.php 配置;
- 路由的配置文件在 config/route.php 中,定义文件在 route/app.php;
- 我们还回到最初的 Index 控制器,创建一个 details 带 参数的方法;
- 此时,我们在根目录 route 下的 app.php 里配置路由:
- rule()方法是默认请求是 any,即任何请求类型均可,第三参数可以限制:
- 所有的请求方式均有快捷方式,比如 ::get() 、::post() 等,具体查看手册:路由 -> 路由定义;
- 在路由的规则表达式中,有多种地址的配置规则:
2. 强制路由
- 目前来说,路由访问模式和URL访问模式都可以使用,但我们强制路由访问;
- 开始强制路由,需要在 route.php 里面进行配置:
32. 路由闭包.变量规则
1. 闭包
- 闭包支持我们可以通过 URL 直接执行,而不需要通过控制器和方法;
- 闭包支持也可以传递参数和动态规则:
2. 变量规则
- 系统默认的路由变量规则为\w+,即字母、数字、中文和下划线;
- 如果你想更改默认的匹配规则,可以修改 config/route.php 配置;
- 如果我们需要对于具体的变量进行单独的规则设置,则需要通过 pattern() 方法;
- 将 details 方法里的 id 传值,严格限制必须只能是数字\d+;
- 如果让指定的参数统一限定为数字,比如id和uid,也就是全局设置,在app.php顶部设置:
- 不喜欢斜杠怎么办?能换成减号吗?可以的:
33. 路由参数.域名.MISS
1. 参数
- 设置路由的时候,可以设置相关方法进行,从而实施匹配检测和行为执行;
- ext 方法作用是检测 URL 后缀,比如:我们强制所有 URL 后缀为.html;
- https 方法作用是检测是否为 https 请求,结合 ext 强制 html 如下:
- 如果想让全局统一配置 URL 后缀的话,可以在 config/route.php 中设置;
- 具体值可以是单个或多个后缀,也可以是空字符串(任意后缀),false 禁止后缀;
- denyExt 方法作用是禁止某些后缀的使用,使用后直接报错;
- domain 方法作用是检测当前的域名是否匹配,完整域名和子域名均可;
- 还有ajax/pjax/json检测、filter 额外参数检测、append追加额外参数、option统一管理检测,可参考手册;
2. 域名
- 如果想限定的某个域名下生效的路由,比如 news.tp.com 可以通过域名闭包方式:
- 路由域名也支持:ext、pattern、append 等路由参数方法的操作;
3. MISS
- 全局 MISS,类似开启强制路由功能,匹配不到相应规则时自动跳转到 MISS;
34. 路由分组.URL生成
1. 分组
- 路由分组,即将相同前缀的路由合并分组,这样可以简化路由定义,提高匹配效率;
- 使用 group()方法,来进行分组路由的注册;
- 也可以省去第一参数,让分组路由更灵活一些;
- 使用 prefix()方法,可以省略掉分组地址里的控制器;
2. URL生成
- 使用 url() 助手函数来生成 定义好的路由地址,放在在控制器使用;
- 在手册 -> 路由 -> URL 生成 有 Route::buildUrl() 源方法,只不过助手函数,更方便;
35. 资源路由
1. 创建资源
- 资源路由,采用固定的常用方法来实现简化 URL 的功能;
- 系统提供了一个命令,方便开发者快速生成一个资源控制器;
- 以上的两部操作,创建了一个控制器Blog类,并生成了增删改查操作的方法,而且还实现了全部路由:
标识 | 请求类型 | 路由规则 | 操作方法 |
index | GET | blog | index |
create | GET | blog/create | create |
save | POST | blog | save |
read | GET | blog/:id | read |
edit | GET | blog/:id/edit | edit |
update | PUT | blog/:id | update |
delete | DELETE | blog/:id | delete |
2. 地址URL
- 资源路由注册好后,所有地址都是全自动生成,具体如下:
- 默认的参数采用 id 名称,如果你想别的,比如:blog_id;
- 也可以通过 only() 方法限定系统提供的资源方法:
- 还可以通过 except() 方法排除系统提供的资源方法:
- 使用 rest() 方法,更改系统给予的默认方法,1.请求方式;2.地址;3.操作;
- 支持嵌套资源路由,类似于一对多关联的感觉,实战中用到再操作,详情查看手册:
36. 视图.变量.渲染
1. 视图操作
- 首先,为了方便后续课程学习,先把路由给关闭了;并创建一个用于测试视图的控制器:
- 由于我们不用模板引擎,直接使用php原生,就需要使用 engine() 方法,载入 test 模板;
- 如果希望模板后缀为 .php,方便 php + html5 混编,在 config/view 设置:
- 在 fetch() 方法的第二参数,通过数组方式给模板传递变量:
2. 表单提交
- 先载入一个表单页面:
- 创建一个表单:
- 接受数据:
37. 请求对象.变量.信息
1. 请求对象
- 上一节课中表单提交时,我们接受数据使用了 $this->request->post() 方法,这哪里来的?
- 因为我们的控制器继承了 BaseController 追踪进去,可以看到 $request 成员字段;
- 关于这个知识点的源知识点,可以参考:手册 -> 架构 -> 容器和依赖注入,TP6讲过,8不讲了;
- 在没有继承 BaseController 时,我们需要自己手动注入请求:
- 第二种方式:门面Facade,它相关的知识点在手册 -> 架构 -> 门面:
- 第三种方式:继承 BaseController,其实就是第一种,只不过被封装到基类中去了:
- 第四种方式:终极方法 request() 助手函数:
2. 请求信息
- 在手册 请求 -> 请求信息 里有全部的请求方法模块,这里列举几个意思一下:
方法 | 含义 |
host | 当前访问域名或者IP |
port | 当前访问的端口 |
url | 当前完整URL |
root | URL访问根地址 |
method | 当前请求类型 |
- 我们三种方法演示一遍,最终选一种你喜欢的即可:
3. 请求变量
- Request 对象支持全局变量的检测、获取和安全过滤,支持
- 使用 has() 方法,可以检测全局变量是否已经设置:
- 更多方法,参看手册 请求 -> 输入变量, 这里意思几个:
方法 | 描述 |
param | 获取当前请求变量 |
get | 获取 $_GET 变量 |
post | 获取 $_POST 变量 |
put | 获取 PUT 变量 |
delete | 获取 DELETE 变量 |
session | 获取 SESSION 变量 |
- param() 方法是框架推荐的方法,可以自动识别诸如 get、post等数据信息;
- 可在 app\Request.php 配置过滤器:
- only()、except() 设置允许和排查可接受的变量:
- 以上所有,都封装到助手函数 input() 里了:
38. 请求类型.输出.重定向
1. 请求类型
- Request 对象提供了一个方法 method() 来获取当前请求类型,也提供了判断当前的请求类型:
方法 | 说明 |
method | 获取当前请求类型 |
isGet | 判断是否GET请求 |
isPost | 判断是否POST请求 |
isPut | 判断是否PUT请求 |
isDelete | 判断是否DELETE请求 |
isAjax | 判断是否AJAX请求 |
- 使用请求类型伪装,可以提交 PUT、DELETE 类型:
2. 响应输出
- 响应输出,有好几种:包括 return、json() 和 view() 等等;
- 默认输出方式是以 html 格式输出,如果你发起 json 请求,则输出 json;
- 而背后是 response 对象,可以用 response() 输出达到相同的效果;
- response()方法可以设置第二参数,状态码,或调用 code()方法;
3. 重定向
- 使用 redirect()方法可以实现页面重定向,需要 return 执行;
- 还支持session跳转和记住上一次地址的跳转,实战时再研究;
39. Session和Cookie
1. Session
- TP8不支持原生 $_ SESSION 的获取方式,也不支持 session_开头的函数,默认是关闭的;
- 在使用 Session 之前,需要开启初始化,在中间件文件 middleware.php;
- 使用 ::set() 和 ::get() 方法去设置 Session 的存取:
- ::has() 判断是否赋值,::delete() 删除,::pull() 取值后删除,::clear() 清空整个 session;
- Request 对象可以直接对Session进行存取,自行查看手册,这里再罗列常用的助手函数:
2. Cookie
- Cookie 是客户端存储,默认情况下是开启初始化的,在 config/cookie.php;
- ::has() 判断是否存在,::delete() 删除 cookie;
- 助手函数,更加方便操作:
40. 缓存功能
1. 常规方法
- 系统内置了很多类型的缓存,除了 File,其它均需要结合相关产品;
- 我们这里主要演示 File 文本缓存,其它的需要学习相关产品,比如Redis将单独出;
- 配置文件 cache.php 进行缓存配置,默认生成在 runtime/cache 目录;
- ::inc() 和 ::dec() 实现缓存数据的自增和自减操作:
- ::push() 实现缓存的数组数据追加的功能:
2. 助手函数
- 最常用的创建和读取缓存的助手函数:
41. 验证器的使用
1. 定义方式
- 验证器的使用,我们必须先定义它,系统提供了一条命令直接生成想要的类;
- 验证器定义好了之后,我们需要进行调用测试,创建一个 Verify.php 控制器;
- 默认情况下,出现一个错误就会停止后面字段的验证,我们也可以设置批量验证;
- 手册提供了大量内置的规则,查看 手册 -> 验证 -> 内置规则,但我们也可以自定义规则:
2. 规则.错误信息
- 当规则体量较大时,可以采用数组的方式,让规则的可读性变高;
- 较少的验证时,或想只在控制器独立验证,不想创建验证类,也是支持的;
42. 验证场景.内置规则
1. 验证场景
- 验证场景设置,即特定的场景下是否进行验证,独立验证不存在场景验证;
- 举一个简单的例子,新增数据需要验证用户名,而修改更新时不验证用户名;
- 可以在验证类 User.php 中,设置一个$scene 属性,用来限定场景验证;
- 在控制器端,验证时,根据不同的验证手段,绑定相关场景进行验证即可;
- 在验证类端,可以设置一个公共方法对场景的细节进行定义,方法名为 scene+场景;
2. 内置规则
- 验证器提供了大量的内置规则:格式验证、长度区间、字段比较以及其它等验证;
- 格式验证类:(更多可查看手册 验证 -> 验证规则)
- 可以使用静态 facade 模式单一验证来快捷对字段数据进行判断:
43. Token表单令牌
1. 设置Token
- 表单令牌就是在表单中增加一个隐藏字段,随机生成一串字符,确定不是伪造;
- 这种随机产生的字符和服务器的 session(开启)进行对比,通过则是合法表单;
2. 验证Token
- 一般来说提交跳转有两种形式:路由模式和常规模式,先看下常规模式;
- 路由模式,需要开启路由,并设置路由:
- 当然也支持验证器模式,具体如下:
44. 上传文件
1. 文件上传
- 如果要实现上传功能,首先需要建立一个上传表单:
- 使用 Request::file() 或 request()->file 来获取上传数据:
- 在 config/filesystem.php 里可以修改上次规则和目录;
- 如果要批量上传,表单改成数组形式即可:
2. 上传验证
- 如果要对上传的内容进行验证,比如后缀、大小等,直接用验证器:
- 手册上本身给出的例子是批量上传验证,我这里写一个单一上传验证;
45. 验证码
1. 生成验证码
- 验证码功能不是系统内置的功能了,需要通过 composer 引入进来;
- 引入进来之后,我们在模版中,验证一下验证码是否能正常显示;
- 验证码不显示图片原因是伪静态问题,无法加在404错误:
- 开启路由即可;
- 避免麻烦,可以取消强制路由;
- 手动验证码,需要手动设置点击刷新:
- 在 config/captcha.php 文件中, 可以配置验证码的各种参数:
- 手册提供了自建验证码,具体参看:手册 -> 扩展库 -> 验证码;
2. 验证码验证
- 直接利用验证器功能,对验证码进行验证即可;
- 手册上提供了继承基类后依赖注入的方法,就是将规则和验证合二为一:
- 极简方案 -> 助手函数:
46. 图像处理
1. 生成图像
- 图像处理功能不是系统内置的功能了,需要通过 composer 引入进来;
- 为了更方便的测试,我们在 public/Image.png 放置一张图片,这样不用考虑 url 了;
- 使用 crop() 方法可以裁剪图片,并使用 save() 方法保存到指定路径;
- 使用 thumb() 方法,可以生成缩略图,配合 save() 把缩略图保存下来;
2. 图像参数
- thumb() 方法拥有第三个参数,可以追踪进去查看;
- 使用 rotate()方法,可以旋转图片,默认是 90 度,参数可以设置;
- save(‘路径’,[‘类型’,‘质量’,‘是否隔行扫描’]),追踪到方法查看;
- water()方法,可以给图片增加一个图片水印,默认位置为右下角,可看源码常量;
- text()方法,可以给图片增加一个文字水印;
47. 分页处理
1. 简易分页
- 不管是数据库操作还是模型操作,都使用 paginate()方法来实现;
- 可以通过浏览器的源码检查,或者使用输出html实体的方式来了解 page 构造,方便设计 css;
- paginate() 可以限定总记录数,比如,限定总记录数只有 10 条的页码;
- 可以设置分页的页码为简洁分页,就是没有 1,2,3,4 这种,只有上下页;
2. 分页参数
- 如果想自行制作个性化分页,或获取分页中的各种参数,这里也提供了:
参数 | 描述 |
list_rows | 每页数量 |
page | 当前页 |
path | url路径 |
query | url额外参数 |
fragment | url锚点 |
var_page | 分页变量 |
- 分页提供了 total() 方法,来获取所有总记录:
3. 其它分页
- 重写 Paginate 类,复制 Bootstrap 类,开启重写:
- 手册 提供了 大数据分页 :PaginateX() 方法,用于百万级提高性能的。嗯!
48. 中间件入门
1. 定义中间件
- 中间件的主要用于拦截和过滤 HTTP 请求,并进行相应处理;
- 这些请求的功能可以是 URL 重定向、权限验证等等;
- 为了进一步了解中间件的用法,我们首先定义一个基础的中间件;
- 可以通过命令行模式,在应用目录下生成一个中间件文件和文件夹;
- 然后将这个中间件进行注册,在应用目录下创建 middleware.php 中间件配置;
- 中间件的入口执行方法必须是:handle()方法,第一参数请求,第二参数是闭包;
- 业务代码判断请求的 name 如果等于 index,就拦截住,执行中间件,跳转到首页;
- 但如果请求的 name 是 lee,那需要继续往下执行才行,不能被拦死;
- 那么就需要
- 中间件 handle()方法规定需要返回 response 对象,才能正常使用;
- 而
- 为了测试拦截后,无法继续执行,可以 return response()助手函数测试;
2. 前后置中间件
- 将
- 前置中间件就是请求阶段来进行拦截验证,比如登录判断、跳转、权限等;
- 而后置中间件就是请求完毕之后再进行验证,比如写入日志等等;
49. 中间件操作
1. 路由中间件
- 创建一个给路由使用的中间件,判断路由的 ID 值实现相应的验证;
- 路由方法提供了一个 middleware() 方法,让指定的路由采用指定的中间件;
- 也可以在 config/middleware.php 配置文件加中,配置别名支持;
2. 控制器中间件
- 如果不用路由,怎么用局部化的中间件呢?当然也可以直接在控制器上定义;
- 默认是控制器全体方法有效,如果需要限制,还是 only 和 except;
50. 助手函数和工具库
1. 助手函数
- 前面,我们已经学习了不少实用的助手函数,这里再总结一些:手册 -> 附录 -> 助手函数
助手函数 | 说明 |
env | 支持 Env:: 模式,环境变量 |
config | 支持 Config:: 模式,配置信息 |
app_path | 一组六个,自行参考手册,当前应用目录 |
2. 助手工具库
- 实用助手工具库,需要安装扩展:手册 -> 扩展库 -> think助手工具库
51. sql脚本
52. 案例源码下载地址
链接: https://pan.baidu.com/s/1UzlGcrsOPPa4yzStKpt4hA?pwd=6l45 提取码:6l45