├── .gitignore ├── 1.1序言.md ├── 1.2关于作者.md ├── 1.3本书源码.md ├── 1.4反馈纠错.md ├── 1.5安全指南.md ├── 1.6捐助作者.md ├── 12.1专有名词索引.md ├── 12.3老读者升级指南.md ├── 2.1编辑器选用.md ├── 2.2命令行工具.md ├── 2.3开发环境搭建.md ├── 2.4浏览器选择.md ├── 2.5第一个应用.md ├── 2.6Git工作流.md ├── 3.1章节说明.md ├── 3.2静态页面.md ├── 3.3Think命令.md ├── 3.4小结.md ├── 4.1章节说明.md ├── 4.2样式美化.md ├── 4.3局部视图.md ├── 4.4路由链接.md ├── 4.5用户注册页面.md ├── 4.6集中视图.md ├── 4.7小结.md ├── 5.1章节说明.md ├── 5.2数据库迁移.md ├── 5.3查看数据表.md ├── 5.4模型文件.md ├── 5.5小结.md ├── 6.1章节说明.md ├── 6.2注册表单.md ├── 6.3用户数据验证.md ├── 6.4注册失败错误信息.md ├── 6.5注册成功.md ├── 6.6小结.md ├── 7.1章节说明.md ├── 7.2会话.md ├── 7.3用户登录.md ├── 7.4退出.md ├── 7.5小结.md ├── 8.1章节说明.md ├── 8.2重构代码.md ├── 8.3更新用户.md ├── 8.4权限系统.md ├── 8.5列出所有用户.md ├── 8.6删除用户.md ├── 8.7访客模式.md ├── 8.8优化前端.md ├── 8.9小结.md ├── 9.1章节说明.md ├── 9.2微博模型.md ├── 9.3显示微博.md ├── 9.4发布微博.md ├── 9.5微博数据流.md ├── README.md ├── SUMMARY.md ├── book.json ├── cover.jpg ├── images ├── 1516383654686.jpg ├── GIF.gif ├── Router-MVC-DB.svg.png ├── TIM截图20180121181857.png ├── TIM截图20180129165617.png └── screenshot_1516529870045.png ├── style └── website.css ├── 第一章.基础信息.md ├── 第七章.会话管理.md ├── 第三章.构建页面.md ├── 第九章.微博CRUD.md ├── 第二章.开发环境布置.md ├── 第五章.用户模型.md ├── 第八章.用户CRUD.md ├── 第六章.用户注册.md └── 第四章.优化页面.md /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode -------------------------------------------------------------------------------- /1.1序言.md: -------------------------------------------------------------------------------- 1 | # ThinkPHP 5.1 入门教程 2 | 3 | 本书将教你使用现代工具流构建一个类似微博的应用。 通过本书,你将会学到如 HTML、CSS、JavaScript、PHP、ThinkPHP 等 Web 开发的相关基础知识。除此之外,编辑器使用、开发环境搭建、Git、Composer、Bootstrap 前端框架在本书中也会使用, 本书代码严格 PSR 规范编写,使用这些知识将会为你以后的 Web 开发生涯奠定结实的基础,无论在大型项目或是个人练手作品都能有一个好的开发习惯和规范。 4 | 5 | ## 安全提示 6 | 截至本文更新日期 2018-12-22,PHP 5.6 及 PHP 7.0 在 2019-01-01 将正式停止维护,请开发时不要使用老版本的 PHP. 7 | 8 | ## 协助交流 9 | 10 | QQ群: 685083718 -------------------------------------------------------------------------------- /1.2关于作者.md: -------------------------------------------------------------------------------- 1 | # 作者: AGD 2 | 3 | AGD 是数据网站 EpicData 的创始人 4 | 浏览我们的开源项目: 5 | * https://github.com/EpicLearn 6 | * https://github.com/OpenEpicData 7 | * https://gamer.epicdata.net 8 | 9 | 通过下列方式找到作者 10 | * Github -- https://github.com/AGDholo 11 | * 邮箱 -- agdholo@gmail.com -------------------------------------------------------------------------------- /1.3本书源码.md: -------------------------------------------------------------------------------- 1 | # 源码地址 2 | 3 | 书籍源码: https://github.com/EpicLearn/ThinkPHP5.1-Web-Actual-combat 4 | 作品源码: https://github.com/EpicLearn/ThinkPHP5.1-Web-Actual-combat-code 5 | 6 | ## 授权协议 7 | 8 | 知识共享许可协议
本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。 9 | 10 | ~~~txt 11 | 本书后端源码基于 MIT 协议发布 12 | Copyright 2019 AGD 13 | 14 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | ~~~ -------------------------------------------------------------------------------- /1.4反馈纠错.md: -------------------------------------------------------------------------------- 1 | # 遇到问题怎么办 2 | 3 | 1. 先检查自己的代码是否有语法、逻辑等基本错误。 4 | 2. 请使用 Google 或者 Bing 搜索,如果中文搜不到解决方案, 进入 Google 翻译 翻译成英文再搜索, 请不要使用百度等其他搜索引擎 5 | 3. 加入 QQ 群:685083718 进行询问 6 | 7 | ## 如何提问 8 | 9 | 1. 请确保你已经在搜索引擎中使用中英文仔细的搜索并且阅读。 10 | 2. 提问时,应该完整的描述问题并提供 相关代码片段 和 错误信息。 11 | 12 | ## 建议反馈 13 | 14 | 加入 QQ 群:685083718 或者发送邮件至 agdholo@gmail.com 反馈 -------------------------------------------------------------------------------- /1.5安全指南.md: -------------------------------------------------------------------------------- 1 | # PHP 版本 2 | 3 | 请在 2019 年使用 PHP 7.2, 并且计划 2019 中期切换到 PHP 7.3 4 | 到目前为止(2018-01-19),PHP 7.2 已经发布快两个月了。 5 | 7.1 以下版本已进入 LTS 养老末期,使用低版本的 PHP 意味着没有官方的安全维护, 6 | 也无法使用新特性和更快的速度(例如 PHP 内置加密操作 PASSWORD_HASH() 仅支持 PHP >= 5.5 , 7 | 和大多数软件一样,PHP 通常会提供 LTS 长期支持和维护, 8 | 但是因为无法知道安全补丁的版本号,不像 Windows 操作系统一样,安全补丁有据可查, 这会使得产品的安全性大大降低。 9 | 因此,如果你是新开的项目,请使用 PHP 最新版本,而过旧的项目也要尝试重构更新。 10 | 11 | ## 依赖管理 12 | 13 | > Composer 安装、配置及使用请参考本书 第二章:2.3 开发环境搭建 14 | 15 | Composer: https://getcomposer.org/ 在 PHP 生态中是重要的一环,大部分的扩展包都是通过 Composer 进行管理。 16 | 如果你是手动下载扩展包并进行手动加载,那么在大型应用上,你无法检测某个框架是否过时, 17 | 也无法知晓某个框架的重大安全漏洞并及时更新到最新版本,过老的版本是非常不安全的。 18 | 19 | ## HTTPS 和浏览器安全 20 | 21 | HTTPS,一种通过计算机网络进行安全通信的传输协议 22 | 在 2018 年,所有现代安全的浏览器已经不在接受 HTTP,用户打开 HTTP 协议的网站时, 23 | 浏览器会警告该网站的连接不安全。 24 | 不过你可以从各大云厂商免费的申请一个 TLS 证书, 或者使用 Let's Encrypt certificate authority: https://letsencrypt.org/ . 25 | 26 | ## 开发安全的 PHP 程序 27 | 28 | 避免 PHP 程序存在 SQL 注入 29 | 如果你是自己编写 SQL 代码, 请确保使用 prepared 语句, 30 | 并且从网络或文件系统提供的信息都作为参数传递, 而不是字符串拼接的形式。 31 | 此外,确保你没有使用 模拟的 prepared 语句: https://stackoverflow.com/questions/134099 . 32 | 33 | ## 文件上传处理 34 | 35 | 接受未知来源的问题,意味着这些文件可能恶意攻击服务器, 36 | 请保证服务器文件权限不超过 775, 37 | 上传的新文件应该是 只读或读写, 38 | 不要在网站根目录保存文件, 39 | 同大多数 PHP 框架一样,不在根目录运行 index.php 目的是为恶意代码提供最少的执行环境, 40 | 例如: 41 | 42 | ` /var/www/example.com-uploaded/ ` 43 | 44 | 或者直接将文件往下移动一个层级 45 | 46 | ` /var/www/example.com/public ` 47 | 48 | ## 跨站请求伪造 49 | 50 | 跨站请求伪造(CSRF)是一种混淆的代理攻击,通过诱导用户的浏览器代表攻击者执行恶意的 HTTP 请求(使用的是该用户的权限). 51 | 首先使用 HTTPS,没有 HTTPS 的话,任何保护都是脆弱的,因为所有数据都是明文传输。 52 | 如果你是用的是现代 PHP 框架,请在框架中开启 CSRF 验证 53 | 如果你是原生用户,只需要: 54 | 55 | * 增加基本的 Challenge-response authentication。 56 | * 为每个表单添加一个隐藏的表单属性。 57 | * 填充一个密码安全的随机值(称为令牌). 58 | * 验证是否提供了隐藏的表单属性,以及是否匹配上期望值。 59 | 60 | ## 密码散列 61 | 62 | >安全的密码存储曾经是一个激烈争论的话题,但现在实现起来相当微不足道 63 | 64 | ~~~~ 65 | $hash = password_hash($password, PASSWORD_DEFAULT); 66 | if (password_verify($password, $hash)) { 67 | // Authenticated. 68 | if (password_needs_rehash($hash, PASSWORD_DEFAULT)) { 69 | // Rehash, update database. 70 | } 71 | } 72 | ~~~~ 73 | 74 | 甚至不需要知道在后台使用什么算法,因为如果你使用最新版本的 PHP ,你也将使用当前最新的技术,用户的密码将会自动进行升级(只要有新的默认算法可用). 75 | 无论你做什么,都不要做 WordPress 所做的事情 76 | WordPress 并不使用 bcrypt 来储存密码,而是使用 MD5, 77 | 要理解为什么,首先查看 这个代码片段 以及 这一个 78 | 如果 $this->portable_hashes 设置为TRUE,则会调用 $this->crypt_private() 79 | 因此,HashPassword 可以大大简化为以下片段: 80 | 81 | ~~~~ 82 | function HashPassword($password) 83 | { 84 | if ( strlen( $password ) > 4096 ) { 85 | return '*'; 86 | } 87 | /* these steps are skipped */ 88 | $random = $this->get_random_bytes(6); 89 | $hash = 90 | $this->crypt_private($password, 91 | $this->gensalt_private($random)); 92 | if (strlen($hash) == 34) 93 | return $hash; 94 | return '*'; 95 | } 96 | ~~~~ 97 | -------------------------------------------------------------------------------- /1.6捐助作者.md: -------------------------------------------------------------------------------- 1 | # 本书为免费书籍,如果你觉得对你的工作学习有帮助,可以请我喝杯咖啡 2 | 3 | ![img](https://box.kancloud.cn/d2f71a0b21cfbef00484bab14fe0afab_990x1502.jpg) 4 | 5 | ## 捐赠列表 6 | 7 | 本书的发展离不开这些小伙伴的帮助,如果您也出了一份力,请发送支付宝账户或订单号及时间至 agdholo@gmail.com,也可以加入QQ群: 685083718 进行交流反馈。 8 | 9 | 捐赠人 | 金额 10 | ------------ | ------------- 11 | 梁子 | 支付宝 ¥66 12 | 土包子_152******01 | 支付宝 ¥50 13 | *海富 | 支付宝 ¥10 14 | [@满满](https://www.kancloud.cn/@alimanman) | 看云 ¥20 15 | [@ihaiyan](https://www.kancloud.cn/@ihaiyan) | 看云 ¥10 16 | [@yhpp](https://www.kancloud.cn/@yhpp) | 看云 ¥10 17 | [@carbon](https://www.kancloud.cn/@carbon) | 看云 ¥10 18 | [@xiaoshaoye](https://www.kancloud.cn/@xiaoshaoye) | 看云 ¥5 19 | [@kwokzc](https://www.kancloud.cn/@kwokzc) | 看云 ¥5 20 | [@ZOMBIE](https://www.kancloud.cn/@zombie2019) | 看云 ¥5 21 | [@jinxun](https://www.kancloud.cn/@jinxun) | 看云 ¥1 -------------------------------------------------------------------------------- /12.1专有名词索引.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AGDholo/ThinkPHP5.1-Web-Actual-combat/45f6b0d02d7785d44581daa01a3d9d84aa1acd70/12.1专有名词索引.md -------------------------------------------------------------------------------- /12.3老读者升级指南.md: -------------------------------------------------------------------------------- 1 | 笔者不建议通过逐步更新文件的方式进行升级, 2 | 0. 请注意,由于 composer.lock 锁定了版本,在 thinkphp 5.x/5.1.x 12 月 9 日之前的版本,存在安全漏洞,使用 composer install 之后请务必使用 composer update 3 | 1. 删除已经存在的书籍程序代码. 4 | 2. 进入 [https://github.com/EpicLearn/ThinkPHP5.1-Web-Actual-combat-code/releases](https://github.com/EpicLearn/ThinkPHP5.1-Web-Actual-combat-code/releases) 下载你当时学到的章节 5 | 3. 解压到前面删除代码的位置 6 | 4. 打开命令行工具, CD 至目录,运行 composer install 以及 composer update 7 | 5. 打开 config/database.php 填写你的数据库信息 8 | 6. 运行迁移 php think migrate:run 9 | 5. 访问你在 Hosts 里设置的链接,即可使用 -------------------------------------------------------------------------------- /2.1编辑器选用.md: -------------------------------------------------------------------------------- 1 | # 现代的编辑器 2 | 3 | > 为什么要选用现代的编辑器 4 | 5 | 对于新手而言,更需要的是一把配件齐全并且及时更新的武器, 方便开发的同时降低使用难度,并且培养现代化的工作环境, 养成一个好的开发习惯远远大于熟练掌握奇淫技巧来使用各类编辑器。 6 | 以下是一些编辑器名词概览: 7 | 8 | * VScode 9 | * Atom 10 | * SublimeText 11 | * Vim 12 | * PHPStorm 13 | 14 | > 本书的代码编写、命令行环境、Git 使用全部通过 VScode 进行,为了你的学习体验,建议与笔者的开发环境一致。 15 | 16 | 本书涉及的软件都在官方网站进行下载安装,请不要使用未知来源的软件,如 百度云 或 各种网站 上下载的版本。 17 | 这对你的应用和开发环境有潜在危害。 18 | 为什么有危害? XcodeGhost 事件: https://baike.baidu.com/item/XcodeGhost 19 | 20 | ## VScode 特点 21 | 22 | * 全中文支持 23 | * 跨平台支持 24 | * 扩展部署在 微软云 上,不需要 科学上网 即可安装 25 | * 由全球最大的操作系统、宇宙级 IDE Visual Studio 开发公司 微软 提供 26 | * 快速的迭代更新 27 | * 对 TypeScript 提供第一方支持 28 | * 版本控制,应用调试一气呵成,几个简单的操作方可使用 29 | * 打开速度极快,在大文件测试下稳居第一 30 | 31 | ## 如何安装 32 | 33 | 前往 VScode: https://code.visualstudio.com/ 官方网站 下载安装。 34 | 安装时请务必勾选如图内容: 35 | 36 | ![img](https://box.kancloud.cn/6b6c5265735e2c264d0308d293005818_503x389.png) 37 | 38 | 完成后重启操作系统。 -------------------------------------------------------------------------------- /2.2命令行工具.md: -------------------------------------------------------------------------------- 1 | # Git 2 | 3 | > 本书使用 Git 管理代码和进行版本控制。 4 | 会涉及一些简单命令,不用担心,如果你完全按照本书的步骤进行,那完全不用担心! 5 | 6 | 本书涉及的软件都在官方网站进行下载安装,请不要使用未知来源的软件,如 百度云 或 各种网站 上下载的版本。 7 | 这对你的应用和开发环境有潜在危害。 8 | 为什么有危害? XcodeGhost 事件: https://baike.baidu.com/item/XcodeGhost 9 | 10 | ## 下载安装 11 | 12 | 进入 Git 官方网站: https://git-scm.com/ 下载安装 13 | 安装时请不要改动安装路径,这可能会导致系统环境变量无法生效。 14 | 15 | ## 验证 Git 配置 16 | 17 | 打开 PowerShell/CMD ,键入 18 | `git --version` 19 | 若成功输出类似 `git version 2.16.0.windows.2,Git` 环境变量已启用 20 | 若未成功输出,请转到本书 2.3 开发环境搭建 参考配置 PHP 环境变量的方式配置 Git 21 | 22 | ## PowerShell/CMD 23 | 24 | Windows 环境中已经自带了 PowerShell/CMD ,不需要额外安装。 -------------------------------------------------------------------------------- /2.3开发环境搭建.md: -------------------------------------------------------------------------------- 1 | # 宝塔面板 2 | 3 | > 警告:本书所用的宝塔面板年代久远,5.x Windows 版本已经停止维护,本书下面的一大堆废话可忽略,但是以下的安装步骤仍然可用(不推荐) 4 | > 你可以参考本书作者编写的另一个环境搭建指南:https://learn.epicdata.net/development-environment-construction-guide/ 5 | > 宝塔面板是非强制性的,你也可以使用你自己喜欢的面板 6 | 7 | 本书所有操作环境都使用 宝塔面板 ,为了避免不一样的麻烦,请和笔者保持一致。 8 | 本书涉及的软件都在官方网站进行下载安装,请不要使用未知来源的软件,如 百度云 或 各种网站 上下载的版本。 9 | 10 | 宝塔面板 是笔者线下和线上都在使用的一键部署及管理工具。 选用它主要有几点: 11 | 12 | * 对新手及其友好,不需要掌握运作原理,也不需要掌握各类文件配置方法,只需要一条命令/一个安装包,即可实现部署 13 | * 全中文化支持,所有设置选项都简单易读 14 | * 商业支持,已经和阿里云达成合作,线上环境稳定,可以投入生产环境 15 | * 更新频率高,各种补丁和新特性都会第一时间发布 16 | 17 | ## 安装宝塔面板 18 | 19 | 1. 进入 宝塔面板官网 ,选择 Windows 版,下载解压安装。 20 | 2. 安装完成后请点击 顶部栏-> 环境 ,安装所需软件(Nginx PHP Mysql). 21 | 22 | ## 配置 PHP 23 | 24 | 找到安装 宝塔面板 的根目录并且进入 `%:\BtSoft >>> %:\BtSoft\WebSoft\php\7.1` 25 | 在本地计算机进入: 控制面板-> 系统和安全-> 系统 26 | 27 | 1. 点击左侧栏: 高级系统设置-> 环境变量 28 | 2. 编辑下方 系统变量 的 Path-> 新建-> 将前面打开的路径复制进去-> 保存并重启计算机 29 | 30 | ## Composer 31 | 32 | 打开 PowerShell/CMD 命令行工具,键入 php -v,出现所示的类似语句表示 php 环境正确配置: 33 | 34 | ~~~~ 35 | PHP 7.1.2 (cli) (built: Feb 14 2017 21:38:39) ( NTS MSVC14 (Visual C++ 2015) x86 ) 36 | Copyright (c) 1997-2017 The PHP Group 37 | Zend Engine v3.1.0, Copyright (c) 1998-2017 Zend Technologies 38 | ~~~~ 39 | 40 | 若出现 `无法将“php”项识别为 cmdlet、函数、脚本文件或可运行程序的名称` 则环境变量没有正确配置,请检查路径并重新修改(保存后请务必重启计算机) 41 | 42 | ## 安装 Composer 43 | 44 | 依次键入以下命令: 45 | 46 | 1. 直接下载 composer.phar,地址:https://dl.laravel-china.org/composer.phar 47 | 2. 把下载的 composer.phar 放到 PHP 安装目录 48 | 3. 新建 composer.bat, 添加如下内容,并保存:@php "%~dp0composer.phar" %* 49 | 4. 键入 `composer --version` 查看是否正确安装 50 | 51 | ## 配置镜像加速 52 | 53 | 众所周知的原因,Composer 在国内使用原生下载链接速度缓慢,键入命令即可使用,国外用户请忽略: 54 | `composer config -g repo.packagist composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/` -------------------------------------------------------------------------------- /2.4浏览器选择.md: -------------------------------------------------------------------------------- 1 | # 现代的浏览器 2 | 3 | 截至本书编写时间(2018年),前端的生产力成几何倍数的增长,使用老旧的浏览器意味着你无法使用更多的新特性,以及可能存在的严重安全漏洞。 4 | 以下是一些浏览器名词概览: 5 | 6 | * Chrome 7 | * Firefox 8 | * ~~IE~~ 9 | * ~~360 浏览器~~ 10 | * ~~QQ 浏览器~~ 11 | 12 | > 如果你的网络支持,请优先选用 Chrome,不要使用 IE 所有版本的浏览器。 -------------------------------------------------------------------------------- /2.5第一个应用.md: -------------------------------------------------------------------------------- 1 | # 创建第一个网站 2 | 3 | 打开 宝塔面板 ,选择顶部栏的 Web 面板,第一次使用会提示安装,并再次点击 4 | 5 | 1. 选择进入内网网址也就是 `http://127.0.0.1:888` 并设置相关账号密码。 6 | 2. 点击左侧栏 “网站” 按钮,并选择 “添加站点” . 7 | 3. 在弹出框内输入你想访问的域名(推荐后缀结尾为 ".test",例如 thinkphp.test),并选择数据库(mysql),PHP(使用最高版本) . 8 | 4. 如图所示: 9 | 10 | ![img](https://box.kancloud.cn/d9f2b5321715e5608fd8784e76e0d3aa_562x581.png) 11 | 12 | ## Hosts 13 | 14 | Hosts 的作用是 将指定域名 转发到特定 IP 上,例如 15 | `thinkphp.test 127.0.0.2` 16 | 当我们访问 thinkphp.test 时,实际上是访问了 127.0.0.2 这个 IP,通过这样,我们就可以在本地实现自定义域名访问。 17 | 18 | ## 修改 Hosts 19 | 20 | 1. 打开 资源管理器 ,在地址栏键入:%SystemRoot%\System32\drivers\etc\ . 21 | 2. 请右键 文件 hosts 并找到 属性 -> 安全,选择你登录的用户名,点击 编辑 ,勾选 写入 . 22 | 3. 再次右键 文件 hosts 选择 Open With Code . 23 | 4. 添加一行:thinkphp.test 127.0.0.2 并保存。 24 | 5. 打开浏览器,在地址栏键入 `http://thinkphp.test` ,此时出现 宝塔面板 的欢迎界面表示成功。 25 | 26 | ## 安装 ThinkPHP 27 | 28 | >禁止将 public/index.php 移动到根目录,这是不安全的,详细内容请参阅本书 1.5 安全指南 . 29 | 30 | 1. 打开 资源管理器 ,找到你创建网站的目录,全选并且删除。 31 | 2. 右键当前目录,选择: Open With Code . 32 | 3. 按下组合键: Ctrl+Shift+` 打开终端。 33 | 34 | 在终端中键入: 35 | `composer create-project topthink/think .` 36 | 37 | 打开浏览器,在地址栏键入 `http://thinkphp.test/public` ,此时出现 ThinkPHP 的欢迎界面表示成功。 38 | 39 | ## 链接优化 40 | 41 | 1. 打开 宝塔 Web 面板。 42 | 2. 点击左侧栏 “网站” 按钮,并点击右侧你创建网站的 “设置” 按钮。 43 | 3. 选择 “网站目录”-> “运行目录”-> “public” 并保存。 44 | 4. 选择 “伪静态” 键入下列代码并保存: 45 | 46 | ~~~~ nginx 47 | location / { 48 | if (!-e $request_filename) { 49 | rewrite ^(.*)$ /index.php?s=/$1 last; 50 | } 51 | } 52 | ~~~~ 53 | 54 | 以上操作的作用: 55 | 56 | 1. 隐藏 /Public 57 | 2. 隐藏 /index.php/ 58 | 59 | 现在,只需要访问 `http://thinkphp.test` 就大功告成! -------------------------------------------------------------------------------- /2.6Git工作流.md: -------------------------------------------------------------------------------- 1 | # Git 是什么 2 | >Git 是 Linux 的创始人 Linus Torvalds 开源的一款分布式版本控制系统,以帮助开发者更好的对项目进行版本管理。 3 | 简单来说,是一套集中进行 提交、回滚、发布 的工具。Git 应该是每一位开发者的标配。 4 | 本书也将使用 Git 进行版本控制,安装过程请参阅章节 2.2 命令行工具 . 5 | 6 | ## 基本设置 7 | 8 | 1. 使用 VScode 打开上个章节创建的 网站目录。 9 | 2. 按下 Ctrl+Shift+G-> 看到顶部栏文字 “源代码管理” 右侧的一个小按钮-> 点击并初始化版本库-> 选择 网站根目录,并确定-> 你现在可以看到 VScode 中有一大排目录出现了 10 | 3. 在顶部消息栏中键入 “初始化 ThinkPHP” 并点击 ✔ 图标 11 | 4. 按下 Ctrl+Shift+P -> 键入 git 12 | 13 | ## Git 与 GitHub 14 | 15 | 1. 进入 GitHub 官方网站: https://github.com/ 并注册账号。 16 | 2. 点击 头像右边 + -> New repository,填写好对应的名称,点击绿色 [Create repository] 按钮完成创建。 17 | 3. 进入到刚刚创建的项目,找到蓝色背景的内容,我们会看到 [Quick setup — if you’ve done this kind of thing before] 一行,并将中间的链接进行复制。 18 | 19 | ## 线下与线上连接 20 | 21 | 返回 VScode 并打开终端,键入 22 | ~~~ 23 | git config --global user.name "xxx" // 输入在 GitHub 创建的用户名 24 | git config --global user.email "xxx" // 输入在 GitHub 创建的邮箱地址 25 | git remote add github https://github.com//xxxxx // 请填写刚刚复制的地址 26 | ~~~ 27 | 28 | 按下 `Ctrl+Shift+G` ,点击顶部栏 “源代码管理” 文字最右边的按钮,选择 “全部提交” ,键入提交内容,然后再次点击这个按钮,选择 “推送到” 选择弹出框下面的远程地址。 29 | 30 | 现在,在浏览器中浏览刚刚创建的项目地址,提交的文件已全部更新。 31 | -------------------------------------------------------------------------------- /3.1章节说明.md: -------------------------------------------------------------------------------- 1 | # 章节说明 2 | >请使用 VScode 打开 第二章 创建的应用,本章的内容基于上一章节 3 | 4 | 从本章开始,我们就进入了 ThinkPHP 开发应用的第一步,构建最简单的静态页面。 -------------------------------------------------------------------------------- /3.2静态页面.md: -------------------------------------------------------------------------------- 1 | # 配置路由 2 | 3 | 当我们进入一条链接时,应用程序做了以下处理: 4 | 1. 路由寻找 URL 对应的控制器。 5 | 2. 控制台将指定的方法进行操作。 6 | 3. 渲染给模板。 7 | 8 | ## ThinkPHP 的路由 9 | 10 | 打开 route/route.php 看到这条语句,将路由做如下修改: 11 | 12 | ~~~ 13 | /* route/route.php */ 14 | ~~Route::get('hello/:name', 'index/hello');~~ 15 | Route::get('hello', 'index/hello'); 16 | ~~~ 17 | 18 | 以上命令的功能: 19 | 20 | * Route:: 注册路由静态方法 21 | * get 请求方式 22 | * 'hello' 请求 URL 23 | * 'index/hello' 响应默认控制器中 hello 方法 (`application/index/controller/Index.php/:function hello`) 24 | 25 | 目录: `application/index/controller/` 为默认控制器的根目录,当我们在路由中书写时,如果只使用默认控制器,只用写入 控制器文件名/方法 26 | > 并不推荐使用省略的方法使用路由语句,这是难以维护的。 27 | 28 | 你应该使用: `'index/index/hello' //模块(index)/控制器(controller)/index.php/:function hello ` 29 | 30 | 打开上述控制器文件,找到 `hello` 方法并修改为: 31 | 32 | ~~~~ 33 | /* application/index/controller/Index.php/:function hello */ 34 | ~~public function hello($name = 'ThinkPHP5')~~ 35 | public function hello() 36 | { 37 | ~~return 'hello,' . $name;~~ 38 | return 'hello world' 39 | } 40 | ~~~~ 41 | 42 | 打开浏览器,访问 `http://thinkphp.test/hello` 则会出现 `hello world`,一个简单的 GET 路由就完成了。 43 | 44 | ## 在路由中直接输出 45 | 46 | 在阅读本小节之前,首先要知道一个概念 闭包: http://php.net/manual/zh/functions.anonymous.php 47 | 稍作了解之后,我们来学习在路由中使用闭包。 48 | 打开路由文件,将刚才的语句进行修改: 49 | 50 | ~~~~ 51 | /* route/route.php */ 52 | ~~Route::get('hello', 'index/index/hello');~~ 53 | Route::get('hello', function() { 54 | return 'hello closures'; 55 | }); 56 | ~~~~ 57 | 58 | 这时候再刷新浏览器,输出语句就变成了 `hello closures` 59 | 这就是路由中闭包的基本用法。 60 | 61 | ## 在控制器中响应模板 62 | 63 | 现在请删除刚才的闭包语句,重新写入: 64 | 65 | ~~~~ 66 | /* route/route.php */ 67 | ~~Route::get('hello', function() { 68 | return 'hello closures'; 69 | });~~ 70 | Route::get('hello', 'index/index/hello'); 71 | ~~~~ 72 | 73 | 进入路由对应的控制器,修改代码: 74 | 75 | ~~~~ 76 | /* application/index/controller/Index.php/:function hello */ 77 | public function hello() 78 | { 79 | ~~return 'hello world';~~ 80 | return view(); 81 | } 82 | ~~~~ 83 | 84 | > return view(); 是一个助手函数,表示响应对应的模板。 85 | 86 | 如果 view() 中未添加任何参数,则会响应到默认路径: 87 | 88 | `当前模块/view/当前控制器名(小写)/当前操作(小写).html` 89 | `application/index(当前模块)/view/index(当前控制器名)/hello.html(当前操作)` 90 | 91 | 1. 我们创建对应路径的对应文件 `application/index/view/index/hello.html` 并写入 `hello view ` 92 | 2. 再次刷新浏览器,输出语句就变成了 `hello view ` 93 | 94 | ## 在路由闭包操作中响应模板 95 | >在实际的开发中,如果我们只是不进行任何操作的响应一个模板文件,那么使用控制器实在是有点大材小用,这时候,就需要在路由中直接输出。 96 | 97 | 打开路由文件,将刚才的语句进行修改 98 | 99 | ~~~~ 100 | /* route/route.php */ 101 | ~~Route::get('hello', 'index/index/hello');~~ 102 | Route::get('hello', function() { 103 | return view('index@index/hello'); 104 | }); 105 | ~~~~ 106 | 107 | 这条语句表示该路由会渲染 `index` 模块下面的 `view/index/hello.html` 模板文件输出。 108 | 1. 进入到对应的模板文件,将 `hello view` 修改为 `hello router view` 109 | 2. 再次刷新浏览器,输出语句就变成了 `hello router view ` 110 | 111 | ## 为路由提供参数 112 | 113 | 我们在第一次对路由文件进行修改的时候,将 `Route::get('hello/:name', 'index/index/hello');` 变为了 `Route::get('hello', 'index/index/hello');` 114 | 其中,删除了 `:name` 这几个字符, `:name` 就是本节提到的 参数 115 | 116 | 将刚才写入的闭包语句改为: 117 | 118 | ~~~~ 119 | /* route/route.php */ 120 | Route::get('hello/:name', function ($name) { 121 | return 'Hello,' . $name; 122 | }); 123 | ~~~~ 124 | 125 | `function($name)` 将 `$name` 赋值为 `:name ` 126 | 如果我们进入 `http://thinkphp/hello/param`,那么就会输出 `hello param` 127 | 128 | ## 创建第一个静态页面 129 | 130 | 请删除所有路由语句和 index 模块 (`application/[index/]`) 131 | 132 | 打开路由文件,写入语句: 133 | 134 | ~~~~ 135 | /* route/route.php */ 136 | Route::get('', 'welcome/index/home'); 137 | Route::get('/help', 'welcome/index/help'); 138 | Route::get('/about', 'welcome/index/about'); 139 | ~~~~ 140 | 141 | 创建控制器文件: `application/welcome/controller/Index.php` 142 | 在创建好的控制器文件中写入: 143 | 144 | ~~~~ 145 | /* application/welcome/controller/Index.php */ 146 | namespace app\welcome\controller; 147 | use think\Controller; 148 | class Index extends Controller 149 | { 150 | public function home() 151 | { 152 | return '主页'; 153 | } 154 | public function help() 155 | { 156 | return '帮助'; 157 | } 158 | public function about() 159 | { 160 | return '关于'; 161 | } 162 | } 163 | ~~~~ 164 | 165 | * `namespace` 代表的是 命名空间,你可以把它理解成路径,不同的 `namespace` 代表着不同的路径, 只有正确路径下的操作才能被找到。 166 | * `use` 是使用 php 各种类的一个操作,如果我们想要使用其他封装好的类,仅仅 `use` 对应的文件就行了。 167 | * `class Index extends Controller` 表示当前的 Index(如果我们创建在控制器下的是 `AnyThing.php`,那么我们就要使用 `AnyThing` 而不是 `Index`) 类继承了 `Controller` 类,如果继承,就可以使用 `Controller` 中的所有公共方法。 168 | 169 | 现在你访问 170 | 171 | ~~~ 172 | http://thinkphp.test 173 | http://thinkphp.test/help 174 | http://thinkphp.test/about 175 | ~~~ 176 | 177 | 那对应就会显示 主页、帮助、关于。 178 | 179 | ## 渲染视图 180 | > 我们在前面的内容中已经学习了 在控制器中响应视图、在路由中响应视图, 现在只需要将 `return ''` 改为 `return view()` 即可 181 | 182 | 现在来创建模板,路径:`application/welcome/view/index`,分别创建: 183 | * home.html 184 | * help.html 185 | * about.html 186 | 187 | ~~~~ 188 | application/welcome/view/index/home.html 189 | 190 | 191 | 192 | thinkphp.test 193 | 194 | 195 |

主页

196 | 197 | 198 | ~~~~ 199 | 200 | ~~~~ 201 | application/welcome/view/index/help.html 202 | 203 | 204 | 205 | thinkphp.test 206 | 207 | 208 |

帮助

209 | 210 | 211 | ~~~~ 212 | 213 | ~~~~ 214 | application/welcome/view/index/about.html 215 | 216 | 217 | 218 | thinkphp.test 219 | 220 | 221 |

关于

222 | 223 | 224 | ~~~~ 225 | 226 | 这时候再刷新浏览器,响应的就是视图文件了。 227 | 228 | ## 公共视图 229 | >如果我们每次都在模板中写入完整且重复的语句,那样的工作量是巨大且低维护性的,那么现在就需要将视图的公共部分拆分出来。 230 | 231 | 1. 现在请创建一个公共模板的文件夹:`application/welcome/view/_layout` 232 | 2. 并在该目录下创建一个 `default.html` 的文件并写入 233 | 234 | ~~~~ 235 | 236 | 237 | 238 | thinkphp.test 239 | 240 | 241 |

{block name="content"}正文{/block}

242 | 243 | 244 | ~~~~ 245 | 246 | 然后点开 `home.html`,将内容修改为: 247 | 248 | ~~~~ 249 | {extend name="_layout/default" /} 250 | {block name="content"} 251 | 主页 252 | {/block} 253 | ~~~~ 254 | 255 | 其他两个页面也像这样修改,只是 content 里的文字变化一下。 256 | 修改完成后,刷新浏览器,现在模板成功的继承并且输出了! -------------------------------------------------------------------------------- /3.3Think命令.md: -------------------------------------------------------------------------------- 1 | # Think 命令 2 | >Think 命令是 ThinkPHP 出品的一款控制台程序,包括不限于进行 生成模块、创建类库、清除缓存、生成缓存、创建模型等操作。 3 | 4 | 以后的章节所有能够使用命令行的都不再手动创建文件。 5 | 详细文档请参考: https://www.kancloud.cn/manual/thinkphp5_1/354138 -------------------------------------------------------------------------------- /3.4小结.md: -------------------------------------------------------------------------------- 1 | # 小结 2 | 3 | 本章为大家介绍了路由的一些基本操作,控制器对视图文件的控制以及视图的继承,也了解到了 Think 命令,我们将在下一章优化应用样式。 4 | 5 | ## 同步代码 6 | 7 | 别忘了 Git 这个工具,按下 Ctrl+Shift+G,键入更新的消息之后点击提交,并在多选栏中选择 推送到,进入 GitHub 查看线上代码是否同步 -------------------------------------------------------------------------------- /4.1章节说明.md: -------------------------------------------------------------------------------- 1 | # 章节说明 2 | > 请使用 VScode 打开 第三章 创建的应用,本章的内容基于上一章节 3 | 4 | 本章将优化第三章创建的页面样式,为之后的开发做准备。 -------------------------------------------------------------------------------- /4.2样式美化.md: -------------------------------------------------------------------------------- 1 | # 使用 Blade 模板 2 | 3 | > Blade 是构建 Laravel 应用所创建的一套模板引擎,众所周知,Laravel 的开发以优雅为名, 我们现在将使用 Blade 进行 ThinkPHP 视图页面的优雅开发。 4 | 5 | 你可以在 Laravel 中文文档: https://laravel-china.org/docs/laravel/5.7/blade/ 查看详细的语法解释 6 | 7 | ## 安装 Blade 8 | 9 | 请按下 Ctrl+Shift+` (请确保在网站根目录 thinkphp.test) 并切换到终端 , 并键入命令: 10 | `composer require luoyy/think-blade` 11 | 12 | 进入 `config/template` , 将 return 里的内容进行替换: 13 | 14 | ~~~~ config 15 | // 模板引擎类型 16 | 'type' => 'Blade', 17 | // 视图基础目录(集中式) 18 | 'view_base' => '', 19 | // 模板起始路径 20 | 'view_path' => '', 21 | // 模板文件名分隔符 22 | 'view_depr' => DIRECTORY_SEPARATOR, 23 | // 模板文件后缀 24 | 'view_suffix' => 'blade.php', 25 | 'cache' => [ 26 | 'cache_subdir' => false, 27 | 'prefix' => '', 28 | ], 29 | 'tpl_replace_string' => [], 30 | ~~~~ 31 | 32 | 进入到上一章所编写的视图层目录 `application/welcome/view`,将所有 `.html` 后缀修改为 `.blade.php` 33 | 34 | ## 测试 blade 35 | 36 | 打开视图层的 `application/welcome/view/index/home.blade.php` 并删除全部内容,键入 {{ time() }} 37 | 进入 `http://thinkphp.test` 若出现 一长串 时间戳,则表示 Blade 模板安装成功. 38 | 39 | ## 重写视图 40 | 41 | >请删除所有视图页面的内容 42 | 43 | 在 `/public/static` 下增加 `/css/app.css`, `/js/app.js`, 并更改 `default.blade.php` 44 | 45 | ~~~~ blade 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | @yield('title') -- ThinkPHP 入门教程 56 | 58 | 59 | 60 | 61 |
62 |
63 | 88 |
89 |
90 |
91 | @yield('content') 92 |
93 | 95 | 97 | 99 | 100 | 101 | 102 | ~~~~ 103 | 104 | 看到代码顶部的 `@yield('title')` 指令是用来显示指定部分的内容. 105 | 同样的,我们在模板继承 `default.blade.php` 之后,只需要使用 `@section('title)` 即可继承并使用. 106 | 找到视图路径 `view/index`, 将下面的模板文件统一加上代码: 107 | 108 | ~~~~ blade 109 | @extends('_layout.default') 110 | // 对应继承路径:_layout/default.blade.php 111 | ~~~~ 112 | 113 | `@extends` 指继承整个模板文件,而在 `Blade` 中,我们不再使用 `/` 来分割路径,而是使用 `.` 114 | 接着,打开 `home.blade.php`, 我们添加一些欢迎界面. 115 | 116 | ~~~~ blade 117 | @extends('_layout.default') 118 | @section('title', '主页') 119 | @section('content') 120 |
121 |
122 |

欢迎你,我的朋友

123 |
124 |
125 | @stop 126 | ~~~~ 127 | 128 | 访问 `http://thinkphp.test`, 一个全新的欢迎页面呈现在眼前. -------------------------------------------------------------------------------- /4.3局部视图.md: -------------------------------------------------------------------------------- 1 | # 局部视图 2 | 3 | 回到 `default.blade.php`, 如果将所有代码都堆积到 `default.blade.php`, 可读性会变得极差,并且为以后的维护增加阻碍. 所以我们需要将 `header` 单独拆分,成为一个独立的模板文件. 4 | 5 | ## 头部视图 6 | 7 | 在 `view/_layout` 下创建 `header.blade.php` 和 `footer.blade.php` 8 | 并将 `default.blade.php` 文件中 `
` `` 的内容全部剪切到 `header.blade.php` `footer.blade.php` 9 | 将 `default.blade.php` 中 `
` 替换为: 10 | 11 | ~~~~ blade 12 | @include('_layout.header') 13 |
14 | @yield('content') 15 |
16 | @include('_layout.footer') 17 | ~~~~ 18 | 19 | `@include` 是 `Blade` 模板引擎中视图引用方法. -------------------------------------------------------------------------------- /4.4路由链接.md: -------------------------------------------------------------------------------- 1 | # 路由链接 2 | 3 | 请打开 `view/_layout/header.blade.php` 4 | 我们找到 `a` 标签中的 `href=""`, `href=""` 中的地址就是用户点击后跳转的地址. 5 | 现在给对应的 主页、帮助、关于添上相应的链接: 6 | 7 | ~~~~ blade 8 | href="/" 9 | href="/help" 10 | href="/about" 11 | ~~~~ 12 | 13 | 现在我们访问 `http://thinkphp.test` 点击顶部栏的文字就可以访问到对应的地址了. 14 | 可是有一个问题,如果我们更改了路由中的链接,那么模板中的所有地址也要一一修改,这在一个大型项目中是不允许出现的,所以使用 `URL 生成`,就能很好的解决这个问题. 15 | 16 | ## URL 生成 17 | 18 | 找到上面修改的链接,分别更改为: 19 | 20 | ~~~~ blade 21 | {{ url('welcome/index/home') }} 22 | {{ url('welcome/index/help') }} 23 | {{ url('welcome/index/about') }} 24 | ~~~~ -------------------------------------------------------------------------------- /4.5用户注册页面.md: -------------------------------------------------------------------------------- 1 | # 注册路由 2 | 3 | > 本章只是书写前端的静态页面,不涉及数据库操作 4 | 打开 `route/router.php`,添加 5 | 6 | ~~~~ php 7 | Route::get('/signup', 'user/auth/create'); 8 | ~~~~ 9 | 10 | 现在我们要创建一个单独的 user 模块来处理用户功能,而 auth 控制器则集中处理验证方面的问题. 11 | 12 | ## 生成控制器 13 | 14 | 请按下 Ctrl+Shift+` 并切换到终端 15 | 使用 Think 命令来生成控制器: 16 | 17 | ~~~~ shell 18 | // 默认生成 资源控制器 19 | php think make:controller user/Auth 20 | ~~~~ 21 | 22 | 打开 `application/user/Auth.php`,并找到 `create()` 方法,键入: 23 | 24 | ~~~~ php 25 | return view(); 26 | ~~~~ 27 | 28 | 现在再创建一个视图文件:`application/user/view/auth/create.blade.php`,如果你不清楚路径为什么会这样,请回到本书 3.2 静态页面 仔细阅读. 29 | 30 | ~~~~ blade 31 | @extends('_layout.default') 32 | @section('title', '注册') 33 | @section('content') 34 |
35 |
36 |

注册

37 |
38 |
39 | @stop 40 | ~~~~ 41 | 42 | 并且把 `welcome/view/_layout` 文件夹复制到 `user/view/` 43 | 现在按下 `Ctrl+Shift+F`,并在第一个框中填写:`注册` 44 | 第二个框中填写 `注册` 45 | 然后点击框框右边的按钮进行全局替换并保存. -------------------------------------------------------------------------------- /4.6集中视图.md: -------------------------------------------------------------------------------- 1 | # 集中视图 2 | 3 | > 在 4.5 小节中我们复制了一遍 _layout 文件夹,这样做对开发其实是百害无一利的,我们现在设置 模板配置文件 让视图单独的保存起来。 4 | 5 | ## 修改配置 6 | 7 | 进入 `/app/config/template.php` 修改代码 8 | 9 | ~~~~ php 10 | // 视图基础目录(集中式) 11 | ~'view_base' => '',~ 12 | 'view_base' => Env::get('ROOT_PATH') . 'resources' . DIRECTORY_SEPARATOR . 'views' . DIRECTORY_SEPARATOR, 13 | // 模板起始路径 14 | ~'view_path' => '',~ 15 | 'view_path' => Env::get('ROOT_PATH') . 'resources' . DIRECTORY_SEPARATOR . 'views' . DIRECTORY_SEPARATOR 16 | ~~~~ 17 | 18 | ## 增加目录 19 | 20 | 创建文件夹 `/resources/views/` 21 | 并在 `views` 目录下创建文件 `_layout` 和 `welcome/index` 复制先前在模块 `view` 中创建的文件到以上目录就行,完成后删除前面章节提到的所有在模块下创建的 `view` 文件. 22 | 23 | 目录结构如图所示: 24 | ![image](https://box.kancloud.cn/fe290b5d617872168c07e10d7bea5eb7_273x329.png) 25 | 26 | 现在打开浏览器,进入 `http://thinkphp.test`,看看是否正常输出了. -------------------------------------------------------------------------------- /4.7小结.md: -------------------------------------------------------------------------------- 1 | # 小结 2 | 3 | 本章使用 `Composer` 安装了第一个模板扩展,同时在模板的基础上学习了 继承、链接优化 等操作,并使用命令行创建控制器,下一章将开始使用模型创建连接数据库开发用户逻辑功能. 4 | 5 | ## 同步代码 6 | 7 | 别忘了 `Git` 这个工具,按下 `Ctrl+Shift+G`,键入更新的消息之后点击提交,并在多选栏中选择 推送到,进入 `GitHub` 查看线上代码是否同步. -------------------------------------------------------------------------------- /5.1章节说明.md: -------------------------------------------------------------------------------- 1 | # 章节说明 2 | 3 | >请使用 VScode 打开 第四章 创建的应用,本章的内容基于上一章节. 4 | 5 | 在前几个章节的开发中,我们只用到了 `MVC` 中的 `VC`,也就是 `控制器-视图`,没有提到 `M-Model(模型)`,本章我们将构建一个基本的 用户模型来实现用户数据的操作,并了解 `ThinkPHP` 如何进行 `CRUD`.在后面的开发中我们还会加上用户注册和登录功能,并进行权限验证,接着我们将通过邮件发送构建最后的激活和找回功能. -------------------------------------------------------------------------------- /5.2数据库迁移.md: -------------------------------------------------------------------------------- 1 | # 数据库迁移 2 | 3 | 在 `ThinkPHP` 中,我们使用 `think-migration` 管理数据表结构,你可以把他看作是在数据库中的版本控制,我们使用该工具每次的操作都会有详细的时间进行记录,同样的,也可以无缝回滚或者修改. 4 | 5 | ## 迁移的好处 6 | 7 | * 协同开发避免出错 8 | * 数据表版本控制 9 | * 部署简单方便 10 | 11 | ## 安装 think-migration 12 | 13 | 请按下 Ctrl+Shift+` 并切换到终端(请确保在网站根目录 thinkphp.test). 14 | 我们将使用 `Composer` 引入扩展 `think-migration`,键入命令: 15 | 16 | ~~~~ shell 17 | composer require topthink/think-migration 18 | ~~~~ 19 | 20 | ## 使用 think-migration 21 | 22 | ~~~~ shell 23 | /* 创建迁移表 */ 24 | php think migrate:create Users 25 | 26 | /* 弹出提示:Create migrations directory?[是否创建新目录?] */ 27 | 键入:y 28 | ~~~~ 29 | 30 | 这时候看到根目录,程序为我们创建了一个 `database/migrations` 的目录,并且创建了一个以时间戳开头的 User 文件. 31 | 进入创建好的 User 文件,修改代码: 32 | 33 | ~~~~ php 34 | use think\migration\Migrator; 35 | use think\migration\db\Column; 36 | class Users extends Migrator 37 | { 38 | /** 39 | * Migrate Up. 40 | */ 41 | public function up() 42 | { 43 | // create the table 44 | $table = $this->table('users'); 45 | $table->addColumn('name', 'string') 46 | ->addColumn('email', 'string') 47 | ->addColumn('password', 'string') 48 | ->addColumn('avatar', 'string', ['null' => true, 'default'=>NULL, 'comment'=>'用户头像']) 49 | ->addColumn('god', 'boolean', ['default'=>FALSE, 'comment'=>'管理员']) 50 | ->addTimestamps('created_at', 'updated_at') 51 | ->addIndex('email', ['unique' => true]) 52 | ->addIndex('god') 53 | ->create(); 54 | } 55 | /** 56 | * Migrate Down. 57 | */ 58 | public function down() 59 | { 60 | $this->dropTable('users'); 61 | } 62 | } 63 | ~~~~ 64 | 65 | 我们来看看这段代码干了什么: 66 | 67 | * addColumn 字面意思,添加一列. 68 | * addColumn('字段名', '值类型', '数组来表示 长度限制、默认值、注释等'). 69 | * $this->dropTable 字面意思,删除表. 70 | * function up() 运行迁移时做的动作. 71 | * function down() 回滚迁移时做的动作. 72 | 73 | 详细文档请查阅:数据库迁移文档: http://docs.phinx.org/ 74 | 75 | ## 运行迁移 76 | 77 | 打开 `/config/database.php` 将对应的 数据库名、密码等 填写为宝塔面板上的值,如果提示权限被拒绝,将数据库密码换成 `root` 密码. 78 | 请按下 Ctrl+Shift+` 并切换到终端(请确保在网站根目录 thinkphp.test). 79 | 键入命令: 80 | 81 | ~~~~ shell 82 | php think migrate:run 83 | ~~~~ 84 | 85 | 这时候再打开 `PHPMyAdmin`,就有会看见迁移的数据表. 86 | 同样的,如果你键入命令: 87 | 88 | ~~~~ shell 89 | php think migrate:rollback 90 | ~~~~ 91 | 92 | 则会回滚到上一步的操作. -------------------------------------------------------------------------------- /5.3查看数据表.md: -------------------------------------------------------------------------------- 1 | # 章节说明 2 | 3 | 在实际的线上环境中,如果我们使用宝塔面板,`PHPMyAdmin` 是最好的选择,面板已经为我们准备好了必要的环境,我们只需要进入 `Web` 面板即可管理. 4 | 5 | 面板中数据库连接方式: 6 | 7 | * Host: 127.0.0.1 8 | * Port: 3306 9 | * database_name: 创建的表名 10 | * database_password: 对应表的密码 11 | 12 | ## PHPMyAdmin 的使用 13 | 14 | 进入 `PHPMyAdmin` 也是非常简单的,只需要进入 `宝塔 Web 面板-> 数据库-> PHPMyAdmin` 即可 15 | 之后,我们再选择对应的数据表,即可看到上一节迁移的结构. 16 | 如果你运行命令再刷新表,此时结构就已经回滚. 17 | 18 | ~~~~ shell 19 | /* 回滚 */ 20 | php think migrate:rollback 21 | 22 | /* 迁移 */ 23 | php think migrate:run 24 | ~~~~ -------------------------------------------------------------------------------- /5.4模型文件.md: -------------------------------------------------------------------------------- 1 | # 模型 2 | 3 | >模型(Model) - 程序员编写程序应有的功能(实现算法等等)、数据库专家进行数据管理和数据库设计(可以实现具体的功能). 4 | 5 | 在 `MVC` 组件的互动中, 6 | `模型(Model)` 用于封装与应用程序的业务逻辑相关的数据以及对数据的处理方法。`Model` 有对数据直接访问的权力,例如对数据库的访问。 `Model` 不依赖 `View` 和 `Controller`,也就是说, `Model` 不关心它会被如何显示或是如何被操作. 但是 `Model` 中数据的变化一般会通过一种刷新机制被公布。为了实现这种机制,那些用于监视此 `Model` 的 `View` 必须事先在此 `Model` 上注册,从而,View 可以了解在数据 `Model` 上发生的改变。 (比较:观察者模式 (软件设计模式)) 7 | 8 | 在本书中,主要使用 `Think-ORM` 与数据库进行交互. 9 | 10 | ## 创建 模型文件 11 | 12 | 键入命令: 13 | 14 | ~~~~ shell 15 | /* make:model 创建 模型文件 */ 16 | php think make:model user/User 17 | ~~~~ 18 | 19 | 完成后,打开新建的 模型文件 `application/user/model/User.php`: 20 | 可以看到默认生成的 模型文件 已经为我们创建好了 命令空间、继承 等操作. 21 | 22 | ## 约定成俗的 表名 23 | 24 | 在实际的模型处理当中,`Think-ORM` 会根据 模型文件名 自动找到 表名 进行操作. 25 | 默认情况下,会使用类的「下划线命名法」与「复数形式名称」来作为数据表的名称生成规则。如: 26 | 27 | * User 对应 users 表 28 | * BlogList 对应 blog_list 表 29 | 30 | 因此 `Think-ORM` 将会假设模型储存在对应的表中,如果你需要手动指定数据表,可以通过 `$table` 来指定: 31 | 32 | ~~~~ php 33 | /* 指定 auths 表 */ 34 | protected $table = 'auths'; 35 | ~~~~ 36 | 37 | ## 约定编程 38 | 39 | >约定编程(convention over configuration),是一种软件设计范式,旨在减少软件开发人员需做决定的数量,获得简单的好处,而又不失灵活性. 40 | 41 | 本质是说,开发人员仅需规定应用中不符约定的部分。例如,如果模型中有个名为 `Sale` 的类,那么数据库中对应的表就会默认命名为 `sales`,只有在偏离这一约定时,例如将该表命名为 `products_sold`, 才需写有关这个名字的配置. 42 | 如果您所用工具的约定与你的期待相符,便可省去配置;反之,你可以配置来达到你所期待的方式. -------------------------------------------------------------------------------- /5.5小结.md: -------------------------------------------------------------------------------- 1 | # 小结 2 | 3 | 通过本章,我们学习到: 4 | 5 | * 模型的创建与 「约定成俗」 的软件设计模式 6 | * 数据库迁移的创建、数据表生成、数据表回滚 7 | 8 | ## 同步代码 9 | 10 | 别忘了 `Git` 这个工具,按下 `Ctrl+Shift+G`,键入更新的消息之后点击提交,并在多选栏中选择 推送到,进入 `GitHub` 查看线上代码是否同步. -------------------------------------------------------------------------------- /6.1章节说明.md: -------------------------------------------------------------------------------- 1 | # 章节说明 2 | 3 | >请使用 VScode 打开 第五章 创建的应用,本章的内容基于上一章节 4 | 5 | 本章开始,我们进入了真正的 M 层操作,我们将通过 表单提交,数据验证,数据保存 来学习到一个完整的表单及数据库操作的流程. -------------------------------------------------------------------------------- /6.2注册表单.md: -------------------------------------------------------------------------------- 1 | # 表单构建 2 | 3 | 更改之前创建好的注册页面 `resources/views/user/auth/create.blade.php`: 4 | 5 | ~~~~ html 6 | @extends('_layout.default') 7 | @section('title', '注册') 8 | @section('content') 9 |
10 |
11 |
12 |

注册

13 |
14 |
15 |
17 | 18 | 19 | 20 |
21 |
22 | 用户名 23 |
24 | 27 |
28 | 29 |
30 |
31 | 邮箱 32 |
33 | 36 |
37 | 38 |
39 |
40 | 密码 41 |
42 | 45 |
46 | 47 |
48 |
49 | 确认密码 50 |
51 | 54 |
55 | 56 | 58 |
59 |
60 |
61 |
62 | @stop 63 | ~~~~ 64 | 65 | 修改控制器代码 `application/user/controller/Auth.php`: 66 | 67 | ~~~~ php 68 | public function create() 69 | { 70 | $token = $this->request->token('__token__', 'sha1'); 71 | $this->assign('token', $token); 72 | return $this->fetch(); 73 | } 74 | ~~~~ 75 | 76 | 这一步的目的是将自定义 CSRF Token 传入模板当中. 77 | 78 | ## CSRF 防御 79 | 80 | > 跨站请求伪造(英语:Cross-site request forgery),也被称为one-click attack 或者session riding,通常缩写为 CSRF 或者 XSRF, 是一种挟制用户在当前已登录的 Web 应用程序上执行非本意的操作的攻击方法. https://zh.wikipedia.org/zh/%E8%B7%A8%E7%AB%99%E8%AF%B7%E6%B1%82%E4%BC%AA%E9%80%A0 81 | > 假如一家银行用以运行转账操作的URL地址如下: `http://www.examplebank.com/withdraw?account=AccoutName&amount=1000&for=PayeeName` 82 | 那么,一个恶意攻击者可以在另一个网站上放置如下代码: `` 83 | 如果有账户名为Alice的用户访问了恶意站点,而她之前刚访问过银行不久,登录信息尚未过期,那么她就会损失1000资金. 84 | 这种恶意的网址可以有很多种形式,藏身于网页中的许多地方.此外,攻击者也不需要控制放置恶意网址的网站.例如他可以将这种地址藏在论坛,博客等任何用户生成内容的网站中.这意味着如果服务端没有合适的防御措施的话,用户即使访问熟悉的可信网站也有受攻击的危险. 85 | 86 | ## 防御措施 87 | 88 | * 添加校验token 89 | 90 | > 由于 `CSRF` 的本质在于攻击者欺骗用户去访问自己设置的地址,所以如果要求在访问敏感数据请求时,要求用户浏览器提供不保存在 `cookie` 中,并且攻击者无法伪造的数据作为校验,那么攻击者就无法再运行 `CSRF` 攻击.这种数据通常是窗体中的一个数据项.服务器将其生成并附加在窗体中,其内容是一个伪随机数.当客户端通过窗体提交请求时,这个伪随机数也一并提交上去以供校验.正常的访问时,客户端浏览器能够正确得到并传回这个伪随机数,而通过CSRF传来的欺骗性攻击中,攻击者无从事先得知这个伪随机数的值,服务端就会因为校验 `token` 的值为空或者错误,拒绝这个可疑请求. 91 | 92 | * 检查 Referer 字段 93 | 94 | > `HTTP` 头中有一个 `Referer` 字段,这个字段用以标明请求来源于哪个地址.在处理敏感数据请求时,通常来说,`Referer` 字段应和请求的地址位于同一域名下.以上文银行操作为例,`Referer` 字段地址通常应该是转账按钮所在的网页地址,应该也位于 `www.examplebank.com` 之下.而如果是 `CSRF` 攻击传来的请求,`Referer` 字段会是包含恶意网址的地址,不会位于 `www.examplebank.com` 之下,这时候服务器就能识别出恶意的访问. 95 | 这种办法简单易行,工作量低,仅需要在关键访问处增加一步校验.但这种办法也有其局限性,因其完全依赖浏览器发送正确的 `Referer` 字段.虽然 `http` 协议对此字段的内容有明确的规定,但并无法保证来访的浏览器的具体实现,亦无法保证浏览器没有安全漏洞影响到此字段.并且也存在攻击者攻击某些浏览器,篡改其 `Referer` 字段的可能. 96 | 97 | 上面这段代码中的 `{:token()}` 是通过添加校验 `token` 来防止 `CSRF` 攻击. -------------------------------------------------------------------------------- /6.3用户数据验证.md: -------------------------------------------------------------------------------- 1 | # 使用资源路由及控制器 2 | 3 | 修改路由路径 `route\route.php`: 4 | 5 | ~~~~ php 6 | ~Route::get('/signup', 'user/auth/create');~ 7 | Route::resource('auth', 'user/auth'); 8 | ~~~~ 9 | 10 | 在 ThinkPHP 的资源路由解析中,以上语句表示注册了一个名称为 `auth` 的资源路由到 `user` 模块的 `auth` 控制器,实际注册的路径就是 `application\user\controller\Auth.php`. 11 | 12 | 打开 `Auth` 控制器 `application\user\controller\Auth.php`,在之前的章节中我们已经使用 `Think` 命令行来创建了一个资源控制器,可以看到系统为我们自动生成了 7个 模块,其中包括: 13 | 14 | 标识 | 请求类型 | 生成路由规则 | 对应操作方法(默认) 15 | ------------ | ------------- | ------------- | ------------- 16 | index | GET | auth | index 17 | create | GET | auth/create | create 18 | save | POST | auth | save 19 | read | GET | auth/:id | read 20 | edit | GET | auth/:id/edit | edit 21 | update | PUT | auth/:id | update 22 | delete | DELETE | auth/:id | delete 23 | 24 | 可以看到,我们在表单注册页面的 `URL`路径是 `http://thinkphp.test/user/auth/create.html`. 25 | 在 6.2 章节中我们写入的 HTML 代码 `
` 其中 `url('save')` 就是对应上面路由规则的 `save` 方法,也就是在 `auth` 控制器中的 `public function save(Request $request)`. 26 | 27 | 在 `save` 方法下添加: 28 | 29 | ~~~~ php 30 | public function save(Request $request) 31 | { 32 | dump($request->post()); 33 | } 34 | ~~~~ 35 | 36 | `$request` 是 ThinkPHP 提供的一个请求方法: https://www.kancloud.cn/manual/thinkphp5_1/353985 ,用于获取所有发送至当前控制器的方法/值. 37 | 而这一段 `save(Request $request)` 则是通过 `依赖注入` 将 `use think\Request;` 注入到此控制器. 38 | 39 | ## 依赖注入 40 | 41 | > 依赖注入其实本质上是指对类的依赖通过构造器完成自动注入,例如在控制器架构方法和操作方法中一旦对参数进行对象类型约束则会自动触发依赖注入,由于访问控制器的参数都来自于 `URL` 请求,普通变量就是通过参数绑定自动获取,对象变量则是通过依赖注入生成. https://www.kancloud.cn/manual/thinkphp5_1/353958 42 | 43 | 依赖注入与控制反转相辅相成,大多数面向对象的编程语言,在调用一个类时都需要先进行实例化 `$class = new class()` 生成一个对象,那么当我们在一个 `function` 中使用 `class()` 类的方法则需要: 44 | 45 | ~~~~ php 46 | class x{ 47 | a = new b() 48 | } 49 | ~~~~ 50 | 51 | 如果要传递参数,则要改为: 52 | 53 | ~~~~ php 54 | class x{ 55 | a = new b(param) 56 | } 57 | ~~~~ 58 | 59 | 如果我们有一个对 `class b` 的改动,那么我们在 `class x` 当中也需要对之修改,这就是 `x` 对 `b` 的依赖,例如现在 `class b` 将接收至少两个参数,在 `class x` 当中我们就要修改为: 60 | 61 | ~~~~ php 62 | class x{ 63 | a = new b(param1, param1) 64 | } 65 | ~~~~ 66 | 67 | 就要分别修改两次,这样的传递参数是高度耦合的,在一个中大型项目中这样的依赖可能多大几十个,如果一个依赖的构造改变,那么其余的几十个也要随之修改,这样的工程繁琐且易错,程序也会极难维护,要解决这个问题,就出现了 `控制反转` 的思想,把 `class b` 从 `class x` 中抽离出来,交给第三方的构造器去控制依赖,ThinkPHP 使用的是: https://www.kancloud.cn/manual/thinkphp5_1/353958, 通过 `构造函数,工厂模式` 等方法,注入到 `class b`,这样就进行了解耦. 68 | 69 | ## 提交表单 70 | 71 | 我们在 6.2 创建的表单上随意填写些字符,并提交,则会出现: 72 | 73 | ~~~~ array 74 | array (size=5) 75 | '__token__' => string '7831e09189d70586bd3145bb486ea556e1f9dd4b' (length=40) 76 | 'name' => string 'test' (length=4) 77 | 'email' => string 'test@test' (length=9) 78 | 'password' => string 'test' (length=4) 79 | 'password_confirmation' => string 'test' (length=4) 80 | ~~~~ 81 | 82 | 可以看到, `$request` 方法已经能够获取到从前端应用发送过来的值,现在再修改控制器: 83 | 84 | ~~~~ php 85 | public function save(Request $request) 86 | { 87 | dump($request->name); 88 | } 89 | ~~~~ 90 | 91 | `$request->name` 就是获取提交表单中 `` `name` 字段的值,也是上面打印出来的 `'name' => string 'test' (length=4)` 的值. 92 | 93 | ## 创建验证器 94 | 95 | 在控制台中键入 `php think make:validate user/Auth` 会创建 `application\user\validate\Auth.php`,打开此文件并键入: 96 | 97 | ~~~~ php 98 | protected $rule = [ 99 | '__token__' => 'token', 100 | 'name|名字' => 'require|max:50', 101 | 'email|邮件' => 'require|email|unique:users|max:255', 102 | 'password|密码' => 'require|confirm|min:6' 103 | ]; 104 | ~~~~ 105 | 106 | 在验证规则中, `name|名字(验证字段|别名)`,别名的作用是在验证出错之后会发送 `别名+错误信息`,在未设置别名之前,错误信息会输出 `name 不能为空`,而设置之后则会输出 `名字不能为空`. 107 | `unique:users` 则是值在 users 表里唯一字段,不得重复, `confirm` 将会自动对比 `密码(name="password")` `确认密码(name="password_confirm")` 字段的值,更多内置的验证规则请查看: https://www.kancloud.cn/manual/thinkphp5_1/354107 108 | 109 | ## 设置验证器 110 | 111 | 打开控制器 `application\user\controller\Auth.php` 并修改: 112 | 113 | ~~~~ php 114 | public function save(Request $request) 115 | { 116 | $requestData = $request->post(); 117 | $result = $this->validate($requestData, 'app\user\validate\Auth'); 118 | if (true !== $result) { 119 | dump($result); 120 | } else { 121 | dump($requestData); 122 | } 123 | } 124 | ~~~~ 125 | 126 | `validate($requestData, 'app\user\validate\Auth')` 的前部分表示传入的值,后半部分表示要使用的验证器. 127 | 现在在重新返回前端页面进行尝试提交不同的表单,则可以看到传入值正确/错误而返回的不同提示/值. -------------------------------------------------------------------------------- /6.4注册失败错误信息.md: -------------------------------------------------------------------------------- 1 | # 优雅的显示错误信息 2 | 3 | 在 6.3 我们已经完成了控制器调用验证器来达到验证的目的,可是在控制器里如果直接输出错误信息 `return $error` 会跳转到一个新的空白页面来单独显示,这样的用户体验非常差,这次我们将在表单上使用 `flash 闪存` 进行一次性的错误信息展示. 4 | 5 | ## 闪存数据 6 | 7 | `flash 闪存` 是 ThinkPHP 中 session 一节: https://www.kancloud.cn/manual/thinkphp5_1/354117 所提供的方法,`flash 闪存` 的数据在下次请求之前有效,意味着这个数据只能输出一次,很符合这一节所要实现的业务场景. 8 | 9 | ~~~~ php 10 | public function save(Request $request) 11 | { 12 | $requestData = $request->post(); 13 | $result = $this->validate($requestData, 'app\user\validate\Auth'); 14 | if (true !== $result) { 15 | return redirect('user/auth/create')->with('validate',$result); 16 | } else { 17 | dump($requestData); 18 | } 19 | } 20 | ~~~~ 21 | 22 | `return redirect('user/auth/create')->with('validate',$result);` 中 `redirect('user/auth/create')` 是跳转到对应的 `控制器/方法` 23 | `with('validate',$result)` 则是 `redirect` 提供的一个快捷 `flash 闪存` 的方法,与 `Session::flash('validate',$result);` 效果一样. 24 | 25 | 详细文档请查看: 26 | 27 | * https://www.kancloud.cn/manual/thinkphp5_1/354117 28 | * https://www.kancloud.cn/manual/thinkphp5_1/353996 29 | 30 | ## 在前端中显示 31 | 32 | 非常简单的,我们只用在之前创建的注册页面 `resources\views\user\auth\create.blade.php` 中添加: 33 | 34 | ~~~~ html 35 |
36 |

注册

37 |
38 | ++++ 39 | @if(session('validate')) 40 | 43 | @endif 44 | ++++ 45 | ~~~~ 46 | 47 | 这下就可以完美的显示出错误信息了. -------------------------------------------------------------------------------- /6.5注册成功.md: -------------------------------------------------------------------------------- 1 | # 存储数据 2 | 3 | 数据的校验已经完成,现在我们进行数据的存储. 4 | 5 | 打开控制器 `application\user\controller\Auth.php` 并修改: 6 | 7 | ~~~~ php 8 | use app\user\model\User; 9 | ... 10 | if (true !== $result) { 11 | return redirect('user/auth/create')->with('validate',$result); 12 | } else { 13 | return User::create($requestData); 14 | } 15 | ~~~~ 16 | 17 | 此时再重新提交数据,系统会抛出错误 `user` 表不存在,ThinkPHP 并未完全遵守前面章节所提到的约定俗称标准,即 `UserModel` 对应 `users` 的约定,我们现在打开 `application\user\model\User.php` 并修改: 18 | 19 | ~~~~ php 20 | class User extends Model 21 | { 22 | // 定义数据表名 23 | protected $table = 'users'; 24 | } 25 | ~~~~ 26 | 27 | `protected $table` 方法,指定表名称. 28 | 29 | 设置完成之后,再次提交,则会返回类似数据: 30 | 31 | ~~~~ text 32 | {"__token__":"884e7770f55ac2d29964499b1ffabb0a35e8cf88","name":"1123","email":"agdholo@gmail.com","password":"protected $table = 'think_user';","password_confirm":"protected $table = 'think_user';","id":"1"} 33 | ~~~~ 34 | 35 | 这表明我们的数据已经添加进数据库,现在我们打开数据库来查看,会发现 `updated_at` 字段并未自动维护,我们则需要手动设置维护字段 36 | 37 | `config\database.php`: 38 | 39 | ~~~~ config 40 | // 自动写入时间戳字段 41 | 'auto_timestamp' => 'timestamp', 42 | ~~~~ 43 | 44 | `application\user\model\User.php`: 45 | 46 | ~~~~ php 47 | // 定义数据表名 48 | protected $table = 'users'; 49 | 50 | // 定义时间戳字段名 51 | protected $createTime = 'created_at'; 52 | protected $updateTime = 'updated_at'; 53 | ~~~~ 54 | 55 | 现在重新添加提交数据,我们的字段已经自动由框架进行维护了. 56 | 可是现在的密码还是明文提交的,我们需要在控制器中对密码进行加密 `application\user\model\User.php`: 57 | 58 | ~~~~ php 59 | // 设置修改器 60 | public function setPasswordAttr($value) 61 | { 62 | return password_hash($value, PASSWORD_DEFAULT); 63 | } 64 | ~~~~ 65 | 66 | 这段代码使用了 `ThinkPHP Model` 提供的修改器功能, `setPasswordAttr($value)` 可以拆分为 `set|Password|Attr`,中部的 `Password` 就是我们要设置的字段值,同样的,如果我们要设置 `name` 字段就写入 `setnameAttr` 就可以了. 67 | 68 | `password_hash` 是 PHP 语言内置的一个 `非对称加密` 算法: http://php.net/manual/zh/function.password-hash.php, 此加密算法不可逆向,只能通过验证取得,请注意,在任何时候都不要使用 `md5` 极其一类的 `摘要算法`,它们属于 `摘要算法` 而不属于 `加密算法`,不可用做加密功能,为了您的生命安全,请牢记此项. 69 | 70 | 同样的,我们只对邮箱做出了 `是否符合邮箱地址规则` 的校验,并没有筛选大小写, `application\user\model\User.php`: 71 | 72 | ~~~~ php 73 | // 设置修改器 74 | public function setPasswordAttr($value) 75 | { 76 | return password_hash($value, PASSWORD_DEFAULT); 77 | } 78 | 79 | public function setEmailAttr($value) 80 | { 81 | return strtolower($value); 82 | } 83 | ~~~~ 84 | 85 | 现在传入的 `email` 字段也会自动转化为小写,对提交的数据已经转换完成了,这时候千万不要已经收工,一个潜在的安全漏洞已经出现 86 | 在控制器 `application\user\controller\Auth.php` 中,我们通过 `User::create($requestData);` 来批量提交所有的字段,在数据表中我们有一项 `god` 字段,目的是判断是否是超级管理员,如果这时候在前端伪造一个 `god` 字段进行提交,后果不堪设想,所以我们在入库的时候需要进行字段过滤. 87 | `application\user\model\User.php`: 88 | 89 | ~~~~ php 90 | // 定义运行操作的字段 91 | protected $field = ['name', 'email', 'password', 'avatar']; 92 | ~~~~ 93 | 94 | 允许用户自主提交的字段就不存在 `god` 等能够手动提权的操作了. 95 | 96 | ## 跳转至成功页面 97 | 98 | 前面的数据操作都进行完成之后,我们还剩最后一项跳转的任务, `application\user\controller\Auth.php`: 99 | 100 | ~~~~ php 101 | public function save(Request $request) 102 | { 103 | $requestData = $request->post(); 104 | $result = $this->validate($requestData, 'app\user\validate\Auth'); 105 | if (true !== $result) { 106 | return redirect('user/auth/create')->with('validate',$result); 107 | } else { 108 | $user = User::create($requestData); 109 | return redirect('user/auth/read')->params(['id' => $user->id]); 110 | } 111 | } 112 | ~~~~ 113 | 114 | `redirect('user/auth/read')->params(['id' => $user->id])` 中 `params(['id' => $user->id])` 是传递的参数,当路由的 `read` 等操作绑定的是 `$id` 时,我们传入 `$id` 将会自动跳转到 `user/auth/read/$id`. 115 | 116 | 可是现在跳转过去的页面任然是空白,所以我们需要添加一些操作 `application\user\controller\Auth.php`: 117 | 118 | ~~~~ php 119 | public function read($id) 120 | { 121 | $user = User::find($id); 122 | $this->assign('user', $user); 123 | return $this->fetch(); 124 | } 125 | ~~~~ 126 | 127 | `User::find($id)` 是模型的一个查询语法,默认查询 `$id(主键值)`. 128 | 接着创建前端文件 `resources\views\user\auth\read.blade.php`: 129 | 130 | ~~~~ html 131 | @extends('_layout.default') 132 | @section('title', $user->name) 133 | @section('content') 134 |
135 |
136 |
137 |

欢迎您 {{ $user->name }}

138 |
139 |
140 |
141 | @stop 142 | ~~~~ 143 | 144 | 现在重新注册个用户,即可自动跳转至欢迎页面了. -------------------------------------------------------------------------------- /6.6小结.md: -------------------------------------------------------------------------------- 1 | # 小结 2 | 3 | 通过本章,我们学习到: 4 | 5 | * 表单提交传值并读取 6 | * 验证器验证数据 7 | * 依赖注入控制反转的简单概念 8 | * 优化用户体验,flash 闪存的使用 9 | * 模型安全字段设置,自动格式化提交值 10 | * 基本的模型插入/读取操作 11 | 12 | ## 同步代码 13 | 14 | 别忘了 `Git` 这个工具,按下 `Ctrl+Shift+G`,键入更新的消息之后点击提交,并在多选栏中选择 推送到,进入 `GitHub` 查看线上代码是否同步. -------------------------------------------------------------------------------- /7.1章节说明.md: -------------------------------------------------------------------------------- 1 | # 章节说明 2 | 3 | >请使用 VScode 打开 第六章 创建的应用,本章的内容基于上一章节 4 | 5 | 本章将会开发出一个完整的用户状态管理功能,实现基本的登入登出相关操作. -------------------------------------------------------------------------------- /7.2会话.md: -------------------------------------------------------------------------------- 1 | # 会话控制 2 | 3 | 在现代 Web 程序中,常见的 `HTTP 会话机制` 有多种: 4 | 5 | * JWT (JSON Web Token): https://jwt.io/ 6 | * Session 7 | * Cookie 8 | * ... 9 | 10 | 在本章的学习中,我们采用 `session` 来控制会话. 11 | 12 | ## 会话控制器 13 | 14 | 在控制器中键入 `php think make:controller user/Session` 创建会话控制器并编辑, 15 | 16 | `application\user\controller\Session.php`: 17 | 18 | ~~~~ php 19 | use app\user\model\User; 20 | ... 21 | public function create() 22 | { 23 | $token = $this->request->token('__token__', 'sha1'); 24 | $this->assign('token', $token); 25 | return $this->fetch(); 26 | } 27 | ~~~~ 28 | 29 | `route\route.php`: 30 | 31 | ~~~~ php 32 | Route::resource('session', 'user/session'); 33 | ~~~~ 34 | 35 | 创建模板 `resources\views\user\session\create.blade.php`: 36 | 37 | ~~~~ html 38 | @extends('_layout.default') 39 | @section('title', '登入') 40 | @section('content') 41 |
42 |
43 |
44 |

登入

45 |
46 | @if(session('validate')) 47 | 50 | @endif 51 |
52 | 54 | 55 | 56 | 57 |
58 |
59 | 邮箱 60 |
61 | 64 |
65 | 66 |
67 |
68 | 密码 69 |
70 | 73 |
74 | 75 | 77 | 78 |
79 |
80 |
81 | @stop 82 | ~~~~ 83 | 84 | 以上创建的内容与上一张所建内容大多类似,不再详细说明. 85 | 现在访问 `http://thinkphp.test/user/session/create` 即可看到登录页面. 86 | 同样的,我们创建验证器,在控制台中键入 `php think make:validate user/Session` 并打开创建好的 87 | 88 | `application\user\validate\Session.php`: 89 | 90 | ~~~~ php 91 | protected $rule = [ 92 | '__token__' => 'token', 93 | 'email|邮件' => 'require|email|max:255', 94 | 'password|密码' => 'require|min:6' 95 | ]; 96 | ~~~~ 97 | 98 | 再打开 `application\user\controller\Session.php`: 99 | 100 | ~~~~ php 101 | public function save(Request $request) 102 | { 103 | $result = $this->validate($request->post(), 'app\user\validate\Session'); 104 | if (true !== $result) { 105 | return redirect('user/session/create')->with('validate',$result); 106 | } else { 107 | $user = User::where('email', $request->email)->find(); 108 | if ($user !== null && password_verify($request->password, $user->password)) { 109 | return 'Password is valid!'; 110 | } else { 111 | return 'Invalid password.'; 112 | } 113 | } 114 | } 115 | ~~~~ 116 | 117 | 在以上 `save` 方法中, `password_verify($request->password, $user->password)` 对应着上一章所用的 `password_hash` 方法,目的是验证 `hash` 之后的数据, `password_verify(请求数据, 待验证的数据)`,如果验证成功则抛出 `true`: http://php.net/manual/zh/function.password-verify.php 118 | 119 | 请注意,在 PHP 中判断 `null`: 120 | 121 | 标识 | empty == null | is_null === null | isset | array_key_exists 122 | ---- | ---- | ---- | ---- | ---- 123 | ϕ | T | T | F | F 124 | null | T | T | F | T 125 | "" | T | F | T | T 126 | [] | T | F | T | T 127 | 0 | T | F | T | T 128 | false | T | F | T | T 129 | true | F | F | T | T 130 | 1 | F | F | T | T 131 | \0 | F | F | T | T 132 | 133 | * `$user != null` == 在 PHP 运算符中不检查类型 134 | * `$user !== null` === 在 PHP 运算符中检查类型 135 | 136 | 意味着如果使用 `==`,PHP 将会转换成一致的类型再做判断,这也是动态弱类型语言的一大弱势,如果无法提前知晓接收的值的类型,也没有类型检查,解释器所转换的类型将不可控制. 137 | 在要求高精度的程序中(例如财务系统),请使用静态强类型语言,在弱类型语言当中,一些高精度的浮点数将会丢失(int to double),例如 `0.9999999999999^2`,甚至也有 `'0.999999999999' * 0.999999999999` 138 | 下面是一些例子: 139 | 140 | * 无类型: 汇编 141 | * 弱类型、静态类型 : C/C++ 142 | * 弱类型、动态类型检查: Perl/PHP 143 | * 强类型、静态类型检查 :Java/C# 144 | * 强类型、动态类型检查 :Python, Scheme 145 | * 静态显式类型 :Java/C 146 | * 静态隐式类型 :Ocaml, Haskell 147 | * ... 148 | 149 | 笔者注: 我经常遇到一些朋友想使用 PHP 来做通讯系统,爬虫系统甚至 GUI 程序,做一个一款语言就能集大成的程序,这是非常不可取的,软件行业没有银弹(Silver Bullet),没有任何一款语言能做完所有的事情,也没有任何一款语言能够让你掌握了就吃到老,对阵下药,不要宰牛用杀猪刀. 150 | 151 | 重新看到 `application\user\controller\Session.php` 并修改: 152 | 153 | ~~~~ php 154 | if ($user !== null && password_verify($request->password, $user->password)) { 155 | return redirect('user/auth/read')->params(['id' => $user->id]); 156 | } else { 157 | return redirect('user/session/create')->with('validate','邮件地址不存在或密码错误'); 158 | } 159 | ~~~~ 160 | 161 | 现在如果验证成功,那么就会跳转到上一章所编写的 `user/auth/read` 方法. 162 | 163 | 虽然现在已经成功验证账户对应的密码,但是还未做到权限的状态管理,用户不管登录是否,都可以 `http://thinkphp.instudy.test/user/auth/read/id/:id` 的地址. 164 | 165 | ## 中间件拦截 166 | 167 | 中间件相当于在 `路由至控制器` 之间修建一道门,如果通过中间件的规则则可以进行下一步的操作,我们现在通过中间件来验证 `是否已经登入`: https://www.kancloud.cn/manual/thinkphp5_1/564279 168 | 169 | 经过笔者两小时的测试,ThinkPHP 的中间件与路由有非常大的逻辑缺陷问题,中间件无法正确挂载至资源路由,资源路由生成规则错误及混乱,URL 绑定在资源路由中不起作用,以下是一些针对这一章节 ThinkPHP 框架的错误: 170 | 171 | * 框架版本: V5.1.28 LTS(2018-10-28) 172 | * PHP 版本: PHP 7.2.11-4+ubuntu18.04.1+deb.sury.org+1 (cli) (built: Nov 4 2018 05:11:49) ( NTS ) 173 | 174 | 定义中间件: 175 | 176 | ~~~~ php 177 | public function handle($request, \Closure $next) 178 | { 179 | $request->mid = 'mid'; 180 | return $next($request); 181 | } 182 | ~~~~ 183 | 184 | 如果中间件不在路由注册: 185 | 186 | ~~~~ php 187 | Route::resource('auth', 'user/auth') 188 | ~->middleware('Auth')~ 189 | ~~~~ 190 | 191 | 那么在控制器不管怎么定义了中间件, dump 都是 null 192 | 193 | ~~~~ php 194 | protected $middleware = [ 195 | 'Auth', 196 | ]; 197 | ~~~~ 198 | 199 | 访问地址: `http://thinkphp.test/user/auth/create.html` 或者 `http://thinkphp.test/auth/create.html` 200 | 都会提示: `/home/vagrant/code/instudy/thinkphp/thinkphp/library/think/Debug.php:226:null` 201 | 202 | 而如果在路由中进行注册中间件: 203 | 204 | ~~~~ php 205 | Route::resource('auth', 'user/auth')->middleware('Auth') 206 | ~~~~ 207 | 208 | 基于完整的控制器路径 `http://thinkphp.test/user/auth/create.html` 仍然输出 `null` 209 | 而进入绑定之后的地址 `http://thinkphp.test/auth/create.html` 却有值 `mid` 210 | 更加神奇的是,如果要使用 URL 生成路由 `url('user/auth/create')`,只会生成到 完整的控制器路径(http://thinkphp.test/user/auth/create.html),而不会生成绑定的地址(http://thinkphp.test/auth/create.html) 211 | 212 | 如果你要强行用绑定的地址,只能写死路径 213 | 214 | `return redirect('/auth/read')->params(['id' => $user->id]);` 215 | 216 | 当写死路径之后,`params()` 方法将不会传值到 `auth/read/:id`,只会跳转到 `/auth/read` 217 | 218 | 关于路由的错误,在前面的章节已经遇到并且不得已才琢磨出了匹配完整路径的方法,可惜 URL 生成:https://www.kancloud.cn/manual/thinkphp5_1/353977 根本不工作,导致了路由中间件挂载无效. 219 | 同时,无论怎么样在资源控制器中挂载中间件,都是无效的,不执行任何东西. 220 | 221 | 基于以上的问题,我们不再采用中间件的方式进行会话拦截. 222 | 223 | ## 手动拦截 224 | 225 | 此方法比较繁琐,代码复用程度极差,是一个临时解决中间件问题的办法. 226 | 227 | `application\user\controller\Session.php`: 228 | 229 | ~~~~ php 230 | use think\facade\Session as SessionFacade; 231 | ... 232 | if ($user !== null && password_verify($request->password, $user->password)) { 233 | SessionFacade::set('user', $user); 234 | return redirect('user/auth/read')->params(['id' => $user->id]); 235 | } 236 | ~~~~ 237 | 238 | 可以看到,我们在顶部引入了 Session 的库,再引入之后,类就会默认挂载 `class Session`,可是这与我们当前 `Session` 的命名出现了冲突,所以使用 `as SessionFacade` 来为 `Session 外部类` 增加别名. 239 | 现在重新进行登录操作,就会重定向至 `'user/auth/read/:id'` 并且附带一个 `user` 的 `session`. 240 | 241 | 现在我们打开 `application\user\controller\Auth.php`: 242 | 243 | ~~~~ php 244 | use think\facade\Session; 245 | ... 246 | public function read($id) 247 | { 248 | if (Session::has('user')) { 249 | $user = User::find($id); 250 | $this->assign('user', $user); 251 | return $this->fetch(); 252 | } else { 253 | return redirect('user/session/create')->with('validate','请先登录'); 254 | } 255 | } 256 | ~~~~ 257 | 258 | `Session::has(param)` 是判断 `param` 的 `session` 值是否存在,现在访问 `http://thinkphp.instudy.test/user/auth/read/id/1.html` 则会跳转至登录页面. -------------------------------------------------------------------------------- /7.3用户登录.md: -------------------------------------------------------------------------------- 1 | # 优化前端 2 | 3 | 虽然现在已经可以正常登录,但是登录之后的页面仍然有注册按钮,未登录的页面没有登录按钮,我们要对前端页面进行一些优化. 4 | 5 | `resources\views\_layout\header.blade.php`: 6 | 7 | ~~~~ html 8 | ~
注册~ 9 | @if(session('user')) 10 | 12 | {{ session('user.name') }} 13 | 14 | @else if 15 | 注册 17 | 登录 19 | @endif 20 | ~~~~ 21 | 22 | 基本的登录之后的前端提示已经完成. 23 | 24 | ## 自动登录 25 | 26 | 非常简单的,和前一章一样,我们只用在注册跳转的地方添加 `session` 即可. 27 | 28 | `application\user\controller\Auth.php`: 29 | 30 | ~~~~ php 31 | public function save(Request $request) 32 | { 33 | $requestData = $request->post(); 34 | $result = $this->validate($requestData, 'app\user\validate\Auth'); 35 | if (true !== $result) { 36 | return redirect('user/auth/create')->with('validate',$result); 37 | } else { 38 | $user = User::create($requestData); 39 | +Session::set('user', $user);+ 40 | return redirect('user/auth/read')->params(['id' => $user->id]); 41 | } 42 | } 43 | ~~~~ 44 | 45 | 现在再访问 `http://thinkphp.test/user/auth/create.html` 进行一遍注册,则会自动登录并跳转至 `http://thinkphp.test/user/auth/read/id/2.html`. 46 | 47 | 可是如果我们访问 `http://thinkphp.test/user/session/create.html` 仍然会出现登录的页面,所以也需要在 `Session` 控制器中进行判断. 48 | 49 | `application\user\controller\Auth.php`: 50 | 51 | ~~~~ php 52 | public function create() 53 | { 54 | if (Session::has('user')) { 55 | $user = Session::get('user'); 56 | return redirect('user/auth/read')->params(['id' => $user->id]); 57 | } else { 58 | $token = $this->request->token('__token__', 'sha1'); 59 | $this->assign('token', $token); 60 | return $this->fetch(); 61 | } 62 | } 63 | ~~~~ 64 | 65 | `application\user\controller\Session.php`: 66 | 67 | ~~~~ php 68 | public function create() 69 | { 70 | if (Session::has('user')) { 71 | $user = Session::get('user'); 72 | return redirect('user/auth/read')->params(['id' => $user->id]); 73 | } else { 74 | $token = $this->request->token('__token__', 'sha1'); 75 | $this->assign('token', $token); 76 | return $this->fetch(); 77 | } 78 | } 79 | ~~~~ 80 | 81 | 可以发现,如果不使用中间件,而是手动进行拦截的话,我们需要在每个控制器的每个方法上都写入 `if (Session::has('user'))`,这样的工程实在太繁琐了,到了后期也非常难以维护. 82 | 83 | -------------------------------------------------------------------------------- /7.4退出.md: -------------------------------------------------------------------------------- 1 | # 退出会话 2 | 3 | 退出会话的原理也非常简单,我们在前面检验是否登录都是通过 `Session` 来判断,现在只需要删除对应的 `Session` 就大功告成了. 4 | 首先修改前端,添加一个退出的按钮 5 | 6 | `resources\views\_layout\header.blade.php`: 7 | 8 | ~~~~ html 9 | ~ 10 | 12 | {{ session('user.name') }} 13 | 14 | ~ 15 | 16 | @if(session('user')) 17 | 43 | @else 44 | 注册 46 | 登录 48 | @endif 49 | ~~~~ 50 | 51 | 此时再刷新页面,会提示 `token` 未定义,我们需要为 `read` 方法添加 `token` 52 | 53 | `application\user\controller\Auth.php`: 54 | 55 | ~~~~ php 56 | public function read($id) 57 | { 58 | if (Session::has('user')) { 59 | $user = User::find($id); 60 | $token = $this->request->token('__token__', 'sha1'); 61 | $this->assign([ 62 | 'user' => $user, 63 | 'token' => $token 64 | ]); 65 | return $this->fetch(); 66 | } else { 67 | return redirect('user/session/create')->with('validate','请先登录'); 68 | } 69 | } 70 | ~~~~ 71 | 72 | 现在再次刷新前端页面,可以正常输出了. 73 | 在前端的 `退出` 按钮里,我们创建了一个 `DELETE` 方法的表单,现在需要实现退出功能. 74 | 75 | `application\user\controller\Session.php`: 76 | 77 | ~~~~ php 78 | public function delete($id) 79 | { 80 | if (SessionFacade::has('user') && $id === SessionFacade::get('user.id')) { 81 | SessionFacade::delete('user'); 82 | return redirect('user/session/create')->with('validate','您已退出'); 83 | } else { 84 | return '非法请求'; 85 | } 86 | } 87 | ~~~~ 88 | 89 | 删除 `Session` 并重定向到登录页面,退出功能已经完成. -------------------------------------------------------------------------------- /7.5小结.md: -------------------------------------------------------------------------------- 1 | # 小结 2 | 3 | 通过本章,我们学习到: 4 | 5 | * 会话的处理及拦截 6 | * Session 控制会话 7 | * 安全的验证加密 8 | 9 | ## 同步代码 10 | 11 | 别忘了 `Git` 这个工具,按下 `Ctrl+Shift+G`,键入更新的消息之后点击提交,并在多选栏中选择 推送到,进入 `GitHub` 查看线上代码是否同步. -------------------------------------------------------------------------------- /8.1章节说明.md: -------------------------------------------------------------------------------- 1 | # 章节说明 2 | 3 | >请使用 VScode 打开 第七章 创建的应用,本章的内容基于上一章节 4 | 5 | ## 准备工作 6 | 7 | 0. 运行 `composer update` 将框架升级至最新的安全版本 8 | 1. 由于之前框架的版本过旧,中间件和 token 操作无法正确挂载的问题在升级之后已经得到解决,我们将先进行代码重构. -------------------------------------------------------------------------------- /8.2重构代码.md: -------------------------------------------------------------------------------- 1 | # 升级框架 2 | 3 | 请运行 `composer update` 升级框架至最新的安全版本. 4 | 5 | ## 中间件拦截 6 | 7 | 在上一章因为中间件失效,我们采用了最原始也是最啰嗦的方法判断用户状态,升级版本之后则可以正常使用中间件. 8 | 之前判断的方式: 9 | 10 | ~~~~ php 11 | if (SessionFacade::has('user')) 12 | ~~~~ 13 | 14 | 现在创建一个新的中间件,键入命令: 15 | 16 | ~~~~ shell 17 | php think make:middleware Auth 18 | ~~~~ 19 | 20 | 打开新建好的中间件文件 `application/http/middleware/Auth.php` 并写入: 21 | 22 | ~~~~ php 23 | public function handle($request, \Closure $next) 24 | { 25 | return $next($request); 26 | } 27 | ~~~~ 28 | 29 | `return $next($request);` 表示返回一个 `response` 对象,可以理解为进行下一步的操作. 30 | 如果我们想往控制器内传参,则只需要编写: 31 | 32 | ~~~~ php 33 | public function handle($request, \Closure $next) 34 | { 35 | $request->hello = 'ThinkPHP'; 36 | 37 | return $next($request); 38 | } 39 | ~~~~ 40 | 41 | 然后在控制器内调用: 42 | 43 | ~~~~ php 44 | public function index(Request $request) 45 | { 46 | return $request->hello; // ThinkPHP 47 | } 48 | ~~~~ 49 | 50 | 当然了,本节是要管理用户的状态,所以请删除前面的代码,并写入: 51 | 52 | ~~~~ php 53 | if (session('?user')) { 54 | return $next($request); 55 | } else { 56 | redirect('user/session/create')->with('validate', '请先登录'); 57 | } 58 | ~~~~ 59 | 60 | 上面代码是标准的流程控制语句,你也可以写成三元运算的方式: 61 | 62 | ~~~~ php 63 | return 64 | session('?user') 65 | ? $next($request) 66 | : redirect('user/session/create')->with('validate', '请先登录'); 67 | ~~~~ 68 | 69 | 这两段代码作用相同,以读者喜好为准. 70 | 请注意,`session()` 方法是 ThinkPHP 框架的助手函数,详细文档请查看: https://www.kancloud.cn/manual/thinkphp5_1/354117 71 | 在本段代码中,`session('?user') 与 Session::has('user')` 等效,`session('user') 与 Session::get('user')` 等效 72 | 73 | ## 使用中间件 74 | 75 | 打开 `application/user/controller/Session.php`: 76 | 77 | ~~~~ php 78 | class Session extends Controller 79 | { 80 | protected $middleware = [ 81 | 'Auth' => [ 82 | 'except' => [ 83 | 'create', 84 | 'save' 85 | ] 86 | ], 87 | ]; 88 | ~~~~ 89 | 90 | `protected $middleware` 则是初始化一个中间件,而 `except` 方法表示,当前控制器下有哪些方法是不使用中间件的. 91 | 92 | 然后看到 `public function create()` 并修改为: 93 | 94 | ~~~~ php 95 | public function create() 96 | { 97 | return $this->fetch(); 98 | } 99 | ~~~~ 100 | 101 | 可以对比之前的代码,我们删除了`如果用户登录则自动跳转` 以及 `token` 输出,后续操作下面再进行说明. 102 | 103 | 再看到 `public function delete($id)` 修改: 104 | 105 | ~~~~ php 106 | public function delete($id) 107 | { 108 | session('user', null); 109 | return redirect('user/session/create')->with('validate', '您已退出'); 110 | } 111 | ~~~~ 112 | 113 | 可以看到,使用中间件之后,代码干净了很多. 114 | 115 | 再打开 `application/user/controller/Auth.php`: 116 | 117 | ~~~~ php 118 | class Auth extends Controller 119 | { 120 | protected $middleware = [ 121 | 'Auth' => [ 122 | 'except' => [ 123 | 'create', 124 | 'save' 125 | ] 126 | ], 127 | ]; 128 | 129 | public function create() 130 | { 131 | return $this->fetch(); 132 | } 133 | 134 | public function read($id) 135 | { 136 | $user = User::find($id); 137 | $this->assign([ 138 | 'user' => $user, 139 | ]); 140 | return $this->fetch(); 141 | } 142 | 143 | ~~~~ 144 | 145 | 146 | 基本的中间件已经使用了,但是现在不管访问什么链接,都会跳出 `未定义变量: token ` 的错误,现在来编写前端页面 `resources/views/_layout/header.blade.php`: 147 | 148 | ~~~~ html 149 | 165 | ~~~~ 166 | 167 | 可以看到,我们将原先的: 168 | `` 169 | 修改为了: 170 | `@php echo token() @endphp` 171 | `token()` 方法是框架自带的 `token` 生成方式. 172 | 173 | 同样的,全局查找: 174 | `` 175 | 并且替换为: 176 | `@php echo token() @endphp` 177 | 178 | 现在 token 输出的问题解决了,还有一项,在用户以及登录的情况下,进入登录/注册页面都会显示输入框,我们再来进行优化 179 | 打开 `resources/views/user/session/create.blade.php` 和 `resources/views/user/auth/create.blade.php`: 180 | 181 | ~~~~ html 182 | ...... 183 | @if(session('validate')) 184 | 187 | @endif 188 | @if(session('user')) 189 | 192 | @endif 193 | ~~~~ 194 | 195 | 现在,在登录的状态下访问 `http://thinkphp.test/user/session/create.html` 看到绿色的已登录提示框. -------------------------------------------------------------------------------- /8.3更新用户.md: -------------------------------------------------------------------------------- 1 | # 创建表单 2 | 3 | 在前面的章节中,我们已经为用户创建了一个 `resource` 路由。 4 | 5 | ~~~php 6 | // route.php 7 | Route::resource('auth', 'user/auth'); 8 | ~~~ 9 | 10 | 所以我们只需要在对应的控制器上进行页面输出即可。 11 | 打开 `application\user\controller\Auth.php`: 12 | 13 | ~~~php 14 | /** 15 | * 显示编辑资源表单页. 16 | * 17 | * @param int $id 18 | * @return \think\Response 19 | */ 20 | public function edit($id) 21 | { 22 | $user_id = Session::get('user.id'); 23 | if ($user_id !== $id) { 24 | return redirect('user/auth/edit', ['id' => $user_id]); 25 | } 26 | $user = User::find($user_id); 27 | $this->assign([ 28 | 'user' => $user, 29 | ]); 30 | return $this->fetch(); 31 | } 32 | ~~~~ 33 | 34 | 可以看到 `edit` 方法代码前三行中先检验了 id 是否是当然登入用户的 id,如果不是,就跳转到当然登入用户的编辑页面,需要注意的是,即使这样进行了跳转,也不是安全的。 35 | 然后进入 blade `resources\views\user\auth\read.blade.php`: 36 | 37 | ~~~html 38 |
39 |

欢迎您 {{ $user->name }}

40 | ++++ 41 | 编辑资料 42 | ++++ 43 |
44 | ~~~~ 45 | 46 | 这时候在用户个人资料页面就会出现 `编辑资料` 的按钮了,不过此时点击进去,会抛出模板文件不存在的错误,所以现在来创建模板。 47 | 新建 blade `resources\views\user\auth\edit.blade.php`: 48 | 49 | ~~~html 50 | @extends('_layout.default') 51 | @section('title', $user->name) 52 | @section('content') 53 |
54 |
55 |
56 |

编辑 {{ $user->name }} 的个人资料

57 |
58 | @if(session('validate')) 59 | 62 | @endif 63 |
64 |
66 | 67 | @php echo token() @endphp 68 | 69 | 70 | 71 |
72 |
73 | 用户名 74 |
75 | 79 |
80 | 81 | 83 |
84 |
85 |
86 |
87 | @stop 88 | ~~~~ 89 | 90 | 在上面这段代码中,我们添加了一行 ``,其中,`value="PUT"` 表示伪装请求类型。 91 | 因为我们是要更新用户资料,所以对应控制器的 `update` 方法,可是在 HTML 标准里,FORM 只有 POST GET 两种方法,所以采用 INPUT 里进行类型伪装的方式,让控制器识别出是更新的操作。 92 | 创建模板完成之后,刷新页面,即可看见刚刚添加好的表单。 93 | 94 | ## 接收数据 95 | 96 | 打开控制器 `application\user\controller\Auth.php`: 97 | 98 | ~~~php 99 | /** 100 | * 保存更新的资源 101 | * 102 | * @param \think\Request $request 103 | * @param int $id 104 | * @return \think\Response 105 | */ 106 | public function update(Request $request, $id) 107 | { 108 | $name = $request->name; 109 | $user_id = Session::get('user.id'); 110 | if ($user_id !== $id) { 111 | return redirect('user/auth/edit', ['id' => $user_id])->with('validate', '非法操作'); 112 | } 113 | User::where('id', $user_id)->update(['name' => $name]); 114 | Session::set('user.name', $name); 115 | return redirect('user/auth/edit', ['id' => $user_id])->with('validate', '修改成功'); 116 | } 117 | ~~~ 118 | 119 | 这段代码中,我们首先将 id 与登入用户的 id 进行对比,以防止模拟表单请求来修改其他用户。 120 | 其次,在数据库更新账户名之后,使用 `session set` 重新设置用户名,保证前端数据的一致性。 121 | 这时候,刷新页面,在输入框中随意填写一些字符,再点击更新按钮,页面上就已经修改成功了。 122 | 123 | ## 验证数据 124 | 125 | 虽然我们现在已经实现了整个更新用户资料的操作逻辑,但是还不算完,由于前端无法对提交数据进行任何限制,所以在后端要进行二次校验。 126 | 键入命令 `php think make:validate app\user\validate\UpdateUser`,并打开刚刚创建好的文件: 127 | 128 | ~~~php 129 | protected $rule = [ 130 | '__token__' => 'token', 131 | 'name|名字' => 'require|max:50', 132 | ]; 133 | ~~~ 134 | 135 | 接着返回控制器 `application\user\controller\Auth.php`: 136 | 137 | ~~~php 138 | public function update(Request $request, $id) 139 | { 140 | $user_id = Session::get('user.id'); 141 | 142 | if ($user_id !== $id) { 143 | return redirect('user/auth/edit', ['id' => $user_id])->with('validate', '非法操作'); 144 | } 145 | $requestData = $request->put(); 146 | $result = $this->validate($requestData, 'app\user\validate\UpdateUser'); 147 | 148 | if (true !== $result) { 149 | return redirect('user/auth/edit', ['id' => $user_id])->with('validate', $result); 150 | } else { 151 | $name = $requestData['name']; 152 | User::where('id', $user_id)->update(['name' => $name]); 153 | Session::set('user.name', $name); 154 | return redirect('user/auth/edit', ['id' => $user_id])->with('validate', '修改成功'); 155 | } 156 | } 157 | ~~~ 158 | 159 | 可以看到这段代码增加了验证数据的步骤,验证唯一 id -> 验证提交数据,最后再进行数据库更新,这样才能提高后端的安全可靠性。 160 | 返回前端页面,尝试不填写任何数据就直接提交,就会看到名字不能为空的警告了,届时,整个更新流程已经书写完毕。 -------------------------------------------------------------------------------- /8.4权限系统.md: -------------------------------------------------------------------------------- 1 | # 优雅的权限 2 | 3 | 我们在上一节的开发当中,使用了大量重复的权限检验方法,例如 `$user_id = Session::get('user.id');`, 4 | 可是每次使用都要进行复制一遍,长期下来代码将会变成又长又臭的面条,改一发而动全身,这样的代码可维护性大大降低,所以我们需要把这些代码抽离,进行解耦。 5 | 6 | ## 权限架构 7 | 8 | 我们前面的章节已经创建过了用户是否登入的中间件 `application\http\middleware\Auth.php`,所以现在只需要判断当前操作用户是否与登入用户一致就行了。 9 | 设计的权限架构如下所示: 10 | 11 | 策略方法进行一致性对比 -> 将行为注册至钩子 -> 控制器验证输出 12 | 13 | 创建文件 `application\behavior\UserPolicy.php`: 14 | 15 | ~~~php 16 | id); 61 | return 62 | $result 63 | ? $next($request) 64 | : redirect('user/session/create')->with('validate', '非法操作'); 65 | } 66 | } 67 | ~~~ 68 | 69 | 接着打开之前创建好的两个控制器: 70 | `application\user\controller\Auth.php` 71 | `application\user\controller\Session.php` 72 | 将里面的中间件名称 `Auth` 更名为 `UserAuthorize` 73 | 74 | ~~~php 75 | 'Auth' => [ 76 | 'except' => [ 77 | 'create', 78 | 'save' 79 | ] 80 | ], 81 | //更改后 82 | 'UserAuthorize' => [ 83 | 'except' => [ 84 | 'create', 85 | 'save' 86 | ] 87 | ], 88 | ~~~ 89 | 90 | 然后删除之前判断用户一致的多余代码 91 | `application\user\controller\Auth.php` 92 | 93 | ~~~php 94 | public function edit($id) 95 | { 96 | // 删除 97 | $user_id = Session::get('user.id'); 98 | if ($user_id !== $id) { 99 | return redirect('user/auth/edit', ['id' => $user_id]); 100 | } 101 | 102 | public function update(Request $request, $id) 103 | { 104 | // 删除 105 | $user_id = Session::get('user.id'); 106 | if ($user_id !== $id) { 107 | return redirect('user/auth/edit', ['id' => $user_id])->with('validate', '非法操作'); 108 | } 109 | ~~~ 110 | 111 | 然后在当前页面下按下 ctrl+f,并在第一行键入 $user_id,第二行键入 $id,之后点击如下图所示的按钮进行全局替换: 112 | [![eEC3Hx.png](https://s2.ax1x.com/2019/07/24/eEC3Hx.png)](https://imgchr.com/i/eEC3Hx) 113 | 114 | 本小节所写内容都是为了避免“面条式代码”,所谓“面条式代码”就是将一行代码多处复制使用,在后续维护中如果需要修改某一项功能,则需要找到所有被复制的代码一一修改,这样不仅毫无观赏性,并且会对后续开发造成极大困扰。 -------------------------------------------------------------------------------- /8.5列出所有用户.md: -------------------------------------------------------------------------------- 1 | # 管理用户 2 | 3 | 本节与下一节联动,通过添加管理员权限然后对所有用户进行查看,删除等操作。 4 | 5 | ## 列出用户 6 | 7 | 非常简单的,我们只需要在控制器里使用模型返回所有值即可。 8 | `application\user\controller\Auth.php` 9 | 10 | ~~~php 11 | public function index() 12 | { 13 | return User::all(); 14 | } 15 | ~~~ 16 | 17 | 打开浏览器访问 http://thinkphp.test/user/auth/index ,却直接跳转到“非法操作”的提示,原因在于我们上一节创建用户一致性策略时,判断了传入 `id` 和 `session id` 的一致性,可是在 index 控制器内,我们并没有传入 `id`,那么则会返回 `false`。 18 | `application\behavior\UserPolicy.php` 19 | 20 | ~~~php 21 | public function run($params) 22 | { 23 | $user_id = Session::get('user.id'); 24 | 25 | if (!is_null($params)) { 26 | // 存在传入 ID $params 27 | return 28 | (int) $user_id === (int) $params 29 | ? true 30 | : false; 31 | } elseif (is_null($user_id)) { 32 | // 用户未登录 33 | return false; 34 | } else { 35 | /** 36 | * 不存在传入 ID 但是用户已登录 37 | * 效果等同于 if(is_null($params) && !is_null($user_id)) 38 | */ 39 | return true; 40 | } 41 | } 42 | ~~~ 43 | 44 | 再访问 http://thinkphp.test/user/auth/index ,即可看到从数据库拿到的 JSON 数据。 45 | 46 | ## 创建 Web 页面 47 | 48 | `resources\views\user\auth\index.blade.php` 49 | 50 | ~~~html 51 | @extends('_layout.default') 52 | @section('title', '查看所有用户') 53 | @section('content') 54 |
55 |

所有用户

56 |
57 | @foreach ($users as $user) 58 | 63 | @endforeach 64 |
65 |
66 | @stop 67 | ~~~ 68 | 69 | 控制器中传入 users 数据 70 | `application\user\controller\Auth.php` 71 | 72 | ~~~php 73 | public function index() 74 | { 75 | $this->assign([ 76 | 'users' => User::all() 77 | ]); 78 | return $this->fetch(); 79 | } 80 | ~~~ 81 | 82 | 访问 http://thinkphp.test/user/auth/index 即可看到刚刚创建好的 Web 页面。 83 | 用户页面中,我们可以看到只有仅仅的几条手动创建的数据,数据太少不利于接下来进行删除操作的测试,所以我们添加一个“批量生成用户”的功能。 84 | 85 | ## 假数据填充 86 | 87 | 我们现在去创建一个 Users 的数据填充。 88 | 1. 键入命令 `php think seed:create Users` 89 | 2. 提示 `Create seeds directory? [y]/n (yes/no) [yes]:` 90 | 3. 键入 y 并回车 91 | 4. 提示创建成功 `created .\database\seeds\Users.php` 92 | 93 | 打开刚刚创建好的填充文件 `database\seeds\Users.php` 94 | 95 | ~~~php 96 | $faker->userName, 117 | 'password' => $faker->password, 118 | 'email' => $faker->email, 119 | ]; 120 | } 121 | 122 | $this->table('users')->insert($data)->save(); 123 | } 124 | } 125 | ~~~ 126 | 127 | 以上方法中,我们使用了 `Faker\Factory` 方法,但是现在项目还未安装此包,我们现在在命令行中键入 128 | ~~~bash 129 | # 由于 Laravel-China 维护的公共镜像过期,之前在 2.3开发环境搭建 这一节使用的镜像地址请更换为 阿里云镜像。键入命令 130 | composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/ 131 | 132 | composer require fzaninotto/faker 133 | ~~~ 134 | 135 | 等待 `faker` 包安装安装完成之后,运行数据填充命令 136 | `php think seed:run` 137 | 再次访问 http://thinkphp.test/user/auth/index 则会看到刚刚生成好的假数据。 138 | 139 | ## 分页 140 | 141 | 打开控制器 `application\user\controller\Auth.php` 142 | 143 | ~~~php 144 | public function index() 145 | { 146 | $this->assign([ 147 | 'users' => User::paginate(10) 148 | ]); 149 | return $this->fetch(); 150 | } 151 | ~~~ 152 | 153 | `paginate` 为分页方法,参数 10 表示每页提取 10 个。 154 | 155 | 再打开模板 `resources\views\user\auth\index.blade.php` 156 | 157 | ~~~html 158 | @extends('_layout.default') 159 | @section('title', '查看所有用户') 160 | @section('content') 161 |
162 |

163 | 所有用户 164 |

165 | 166 |
167 | @foreach ($users as $user) 168 | 173 | @endforeach 174 |
175 | 176 |
177 | {!! $users !!} 178 |
179 |
180 | @stop 181 | ~~~ 182 | 183 | 可以看到,我们在之前的基础上添加了个 `{!! $users !!}`,要说明的是,`{!! !!}` 代表不通过安全过滤直接输出。 184 | 如果我们换成 {{ $users }},则会输出已经过滤好的 HTML 代码 185 | `