PHP-入门指南(全)

福利攻略 2026-06-15 06:42:33

PHP 入门指南(全)

原文:zh.annas-archive.org/md5/d36bde355b2574844946c8150420db7b

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

开发网站是当今的优先事项,以便您的业务在互联网上有所存在。设计和开发是任何网站的基础步骤。PHP 通常用于网站和 Web 应用程序开发。PHP 是一种通用的服务器端脚本语言,旨在创建动态页面和应用程序。PHP 作为 Web 开发选项是安全、快速、可靠的,还提供了许多其他优势,使其可以被许多人接受。我们应该考虑是什么使 PHP 成为 Web 行业中最广泛使用的编程语言之一。

本书通过从变量、数据类型、数组和循环等基本概念开始,使您迅速掌握基本概念。然后逐渐进展到更高级的概念,如构建自己的框架和创建应用程序。

本书旨在缩小学习和实施之间的差距。它提供了许多真实的业务案例场景,这将帮助您理解概念,并在完成本书后立即开始编写 PHP 程序。

本书涵盖内容

第一章,“PHP 入门”,介绍了使用 PHP 编程语言的基本知识。在本章中,您将学习基本的 PHP 语法和程序结构。您还将学习如何使用变量、数据类型、运算符和条件语句。

第二章,“数组和循环”,向您展示如何使用流程控制结构。本章将特别介绍循环和数组。

第三章,“函数和类”,教你如何定义和调用函数。我们还将介绍如何创建类,以及如何将类和函数一起使用。

第四章,“数据操作”,教你如何处理用户输入并将结果返回给他们,优雅地处理错误,并学习使用 MySQL 数据库的基础知识。

第五章,“构建 PHP Web 应用程序”,教你在框架中应用面向对象的概念。我们将介绍使用 Whoops 库进行错误报告,并学习如何处理这些错误。我们还将介绍如何在框架中管理和构建我们的应用程序。

第六章,“构建 PHP 框架”,教你从零开始构建一个 MVC 框架。从一个空目录开始,我们将构建一个完整的工作框架,作为更复杂应用程序的起点。

第七章,“身份验证和用户管理”,教你项目的安全方面,即身份验证。我们将构建登录表单,与数据库交互以验证用户的身份。我们还将介绍如何在我们的应用程序中设置密码恢复机制。

第八章,“构建联系人管理系统”,教你构建一个联系人 CRUD(创建、读取、更新和删除)部分,其中将有一个查看页面来查看单个联系人。我们还将为我们的联系人应用程序构建评论系统。

本书所需内容

硬件

最低硬件要求如下:

Windows 7 64 位

处理器:英特尔酷睿处理器

内存:1GB RAM

互联网连接

软件

Windows 的 WAMP 服务器

Linux 的 LAMP 服务器

Mac 的 MAMP 服务器

浏览器:一个或多个浏览器的最新版本(推荐使用 Internet Explorer 11 或 Chrome 54.0.2840 或更新版本)

诸如记事本或 Notepad++之类的文本编辑器

本书适合人群

本书适用于任何有兴趣学习 PHP 编程基础知识的人。为了获得最佳体验,您应该具备 HTML、CSS、JavaScript 和 MySQL 的基本知识。

约定

在本书中,您会发现一些文本样式,用于区分不同类型的信息。以下是一些样式的示例及其含义解释。

文本中的代码词、数据库表名、文件夹名称、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 用户名显示如下:"创建一个新文件并将其命名为 syntax.php。"

文件夹名称、文件名、文件扩展名、路径名、文本中包含的包含文件名显示如下:"要从数组中删除一个元素,请使用 unset 函数。

一段代码设置如下:

echo "Hello World";

?>

任何命令行输入或输出都以以下方式书写:

php syntax.php

新术语和重要词汇以粗体显示。屏幕上看到的词语,例如菜单或对话框中的词语,会以这种方式出现在文本中:"因此,当我们点击提交按钮时,数据将被提交。"

重要的新编程术语以粗体显示。概念术语以斜体显示。

注意

警告或重要提示会以这种方式出现在一个框中。

提示

提示和技巧会以这种方式出现。

安装和设置

在开始阅读本书之前,我们将安装一个 PHP 服务器,比如 WAMP,以及一个文本编辑器,比如 Atom。

在 Windows 上安装 WAMP

在浏览器中访问www.wampserver.com/en/。

点击WAMP SERVER 64 位或 WAMP SERVER 32 位,根据您的系统选择。

接下来,将会弹出一个窗口,其中会出现一些警告。点击直接下载。

下载后打开安装程序。

按照安装程序中的步骤,就完成了!您的 WAMP 服务器已经准备就绪。

在 Linux 上安装 LAMP

在浏览器中访问bitnami.com/stack/lamp/installer。

在Linux下,点击下载按钮。

接下来,将会弹出一个窗口,其中会给您一些登录选项。只需点击不,谢谢,直接带我去下载选项。

下载后打开安装程序。

按照安装程序中的步骤,就完成了!您的 LAMP 服务器已经准备就绪。

在 MAC OS 上安装 MAMP

在浏览器中访问www.mamp.info/en/。

在 MAMP 下,点击下载按钮。

在下一页,点击macOS,然后点击下载按钮。

下载后打开安装程序。

按照安装程序中的步骤,就完成了!您的 MAMP 服务器已经准备就绪。

下载示例代码

您可以从www.packtpub.com的帐户中下载示例代码文件,用于您购买的所有 Packt Publishing 图书。如果您在其他地方购买了本书,您可以访问www.packtpub.com/support并注册,以便文件直接通过电子邮件发送给您。

本书的代码包也托管在 GitHub 上,网址为github.com/TrainingByPackt/Beginning-PHP/。如果代码有更新,将在现有的 GitHub 存储库中进行更新。

第一章:PHP 入门

PHP,或者预处理超文本,是一种用于设计网页应用程序并使网站看起来更直观和有趣的编程语言。多年来,PHP 也在服务器端脚本语言方面获得了很大的流行。PHP 是一种易于使用但功能强大的语言。PHP 可以在多个操作系统上运行,并支持多个服务器。PHP 的所有这些特性使其成为网页设计语言的理想选择。

本书将带你了解 PHP 的基础知识,包括声明语法、声明和使用变量和数据类型、运算符和条件语句。然后,它将涵盖构建 PHP 框架的原则以及构建自己的 PHP 网页应用程序。

在本章中,你将开始学习 PHP 编程语言的基本知识。我们将涵盖语法以及如何在 PHP 中声明和使用变量。我们还将看看如何使用if语句控制执行流程。

在本章结束时,你应该能够使用这些元素编写简单的程序。

在本章结束时,你将能够:

使用 PHP 的基本语法编写简单的程序

使用变量存储不同的数据,并使用不同的运算符进行操作

使用条件语句来控制执行流程

基础知识

我们将从查看 PHP 语法和执行我们的第一个文件开始。让我们开始吧。

在 PHP 中,语法非常重要;你需要适当的语法让你的服务器知道它应该从哪里开始解析 PHP,并且你必须通过开放和关闭 PHP 标签来显示它,如下所示:

?>

通过使用 PHP 标签,你可以在文档的任何地方添加你的代码。这意味着如果你有一个 HTML 网站,你可以添加标签以及一些 PHP 代码,它就会被处理。除了使用开放和关闭 PHP 标签,你还必须在文件中使用.php扩展名。

让我们从一个快速示例开始。

使用 PHP 显示"Hello World"

在本节中,我们将利用我们到目前为止学到的知识来向用户显示一个字符串:

打开你的代码编辑器。

创建一个新文件并命名为syntax.php。

输入以下内容,并保存你的文档:

?>

在终端中打开你的工作目录。

输入以下命令:

php syntax.php

切换回你的文档并输入以下内容:

echo "Hello World";

?>

回到终端并输入以下内容:

php syntax.php

现在你应该在屏幕上看到字符串"Hello World"被打印出来。

变量和数据类型

为了开始学习 PHP,我们必须首先看一下将用于构建每个项目的核心构建块。在我们的应用程序中,我们总是需要一种临时存储数据的方法(在我们的情况下,我们称之为存储方法变量)。

变量定义如下:

$VARIABLENAME = "VALUE";

如你在上面的例子中所见,变量以$符号开头,后面跟着变量名,使用赋值运算符赋值。在这里,我们有一个名为VARIABLENAME的变量,其字符串值为VALUE。

注意

变量名不能以数字或特殊符号开头,除了用于定义变量本身的$符号。

PHP 是少数几种不需要在赋值之前声明数据类型的语言之一。

类型

示例

字符串

"Hello World"

数字

123

浮点数

1.095

布尔值

TRUE 或 FALSE

我们现在将尝试在 PHP 中实现变量。

使用变量

在本节中,我们将举例说明在程序中使用变量的真实例子。我们将首先创建一个变量来存储用户的姓名:

打开你的代码编辑器。

创建一个新文件并命名为variables.php。

输入以下内容,并保存你的文档:

$name = "John Doe";

$age = 25;

$hourlyRate = 10.50;

$hours = 40;

echo $name . " is " . $age . " years old.\n";

echo $name . " makes $" . $hourlyRate . " an hour. \n";

echo $name . " worked " . $hours . " this week. \n";

?>

在终端中打开你的工作目录。

输入以下命令,然后按Enter:

注意

将变量的值插入字符串的另一种方法是使用特殊的语法:

echo "My name is ${$name}.";

?>

运算符

我们现在将看一下 PHP 中可用的各种运算符。

比较运算符

在变量部分,我们看到了=符号,在 PHP 中被称为赋值运算符。这个运算符正如其名,允许您给变量赋值。首先是比较运算符。比较运算符允许您在给定的条件情况下比较两个值。

在比较运算符集合中包括等于、相同、不等、不相同、小于和大于运算符。

用法

名称

描述

$a == $b

等于

如果$a等于$b,则为 TRUE。

$a === $b

相同

如果$a等于$b,并且它们是相同类型,则为 TRUE。

$a!= $b

不等

如果$a不等于$b,则为 TRUE。

$a!== $b

不相同

如果$a不等于$b,或它们不是相同类型,则为 TRUE。

$a < $b

小于

如果$a严格小于$b,则为 TRUE。

$a > $b

大于

如果$a严格大于$b,则为 TRUE。

$a <= $b

小于或等于

如果$a小于或等于$b,则为 TRUE。

$a >= $b

大于或等于

如果$a大于或等于$b,则为 TRUE。

逻辑运算符

接下来是逻辑运算符。逻辑运算符用于一次检查多个情况。逻辑运算符集合包括NOT、AND和OR运算符。

用法

名称

描述

! $a | NOT | 如果$a不为 TRUE,则为 TRUE。

$a && $b

AND

如果$a和$b都为 TRUE,则为 TRUE。

$a || $b

OR

如果$a或$b中的一个为 TRUE,则为 TRUE。

数学运算符

在您的程序中,有时需要进行一些数学运算;这就是数学运算符发挥作用的地方。它们使您能够对两个数进行加法、减法、乘法、除法,并得到两个数相除的余数。

用法

名称

描述

$a + $b

加法

$a和$b的和

$a - $b

减法

$a和$b的差

$a * $b

乘法

$a和$b的乘积

$a / $b

除法

$a和$b的商

$a % $b

模数

$a除以$b的余数

让我们尝试在 PHP 中使用这些运算符。

组合变量和运算符

在本节中,我们将扩展我们之前的示例,计算用户的年薪。以下是步骤:

打开您的代码编辑器。

创建一个新文件,并将其命名为operators.php。

要开始,请复制我们的variables.php文档中的内容。

现在,我们将在文档中添加一个额外的变量,用于保存周数:

$weeks = 52;

接下来,我们将使用乘法运算符来计算我们的周薪,并将其赋值给一个新变量:

$weeklyPay = $hourlyRate * $hours;

现在,有了我们的周薪率,我们可以计算我们的工资:

$salary = $weeks * $weeklyPay;

我们的最后一步是显示我们的最终计算:

echo $name . " will make $" . $salary . " this year.\n";

您的最终文档应如下所示:

$name = "John Doe";

$age = 25;

$hourlyRate = 10.50;

$hours = 40;

echo $name . " is " . $age . " years 01d.\n";

echo $name . " makes $" . $hourlyRate . " an hour. \n";

echo $name . " worked " . $hours . " this week.\n";

$weeks = 52;

$weeklypay = $hourlyRate * $hours;

$salary = $weeks * $weeklyPay;

echo $name . " will make $" . $salary . "this year";

?>

接下来,我们将在我们的终端中打开我们的目录,并运行以下命令:

php operators.php

现在我们应该看到我们的数据被显示出来:

条件语句

现在我们已经掌握了运算符的基础,我们可以开始在所谓的条件语句中使用它们。条件语句允许您控制程序的流程,它们采用if语句的形式。

基本的if语句表示如下:

if (conditional){

}

在括号内,您将保存激活大括号内代码所需的条件。

此外,您可以添加一个else语句,这将允许您在条件不满足时运行替代代码:

if(conditional){

}

else{

}

注意

与条件语句一起使用的一个有用的函数是empty函数。empty函数用于检查变量是否为空

使用条件语句

在本节中,我们将实现条件语句,我们将检查动物的名称,如果匹配,我们将打印特定动物的声音。

打开您的代码编辑器。

创建一个新文件并命名为conditionals.php。

我们将从添加我们的开放和关闭php标签开始:

?>

然后,我们将创建一个新函数来保存我们的动物名称:

$animal = "cat";

?>

现在,我们可以编写我们的第一个条件语句;在这里,我们要检查动物是否是猫,如果是,我们将向用户打印 meow:

$animal = "cat";

if($animal == "cat"){

echo "meow\n";

}

?>

保存文件并在终端中打开您的工作目录。

运行以下命令,查看结果:

php conditionals.php

现在,我们将进一步扩展我们的条件语句,添加其他动物的声音,并将我们的动物更改为狮子:

$animal = "lion";

if($animal == "cat"){

echo "meow\n";

}

else if ($anima == "dog"){

echo "woof\n";

}

else if($animal == "lion"){

echo "roar\n";

}

else {

echo "What does the fox say?\n";

}

?>

现在,让我们再次保存并在终端中运行命令;您应该会得到以下结果:

活动:构建员工工资计算器

想象一下,您是一家百货商店连锁店的 PHP 开发人员,该商店正在为即将到来的黑色星期五大甩卖做准备。在大甩卖期间工作的员工将获得加班工资,以及所有销售额的 10%提成。此外,如果他们的总销售额超过 1000 美元,他们将获得 1000 美元的奖金。管理层希望您创建一个计算器,使员工能够轻松计算他们赚了多少钱。

这项活动的目的是帮助您了解变量和条件语句。

按照以下步骤操作:

创建一个新目录并命名为salary_calculator。

在新目录中创建一个index.php文件。

定义占位符变量:

$hourlyRate = 10.00;

$hoursWorked = 12;

$rateMultiplier = 1.5;

$commissionRate = 0.10;

$grossSales = 25.00;

$bonus = 0;

我们的下一步将是定义我们的计算并将结果分配给它们各自的变量:

$holidayRate = $hourlyRate * $rateMultiplier;

$holidayPay = $holidayRate * $hoursWorked;

$commission = $commissionRate * $grossSales;

$salary = $holidayPay + $commission;

接下来,我们需要检查总销售额变量,看看员工是否超过了 1000 美元,以获得奖金:

if($grossSales >= 1000){

$bonus = 1000;

}

现在我们有了默认费率和计算器,我们可以向用户显示结果:

echo "Salary $" . $salary . "\n";

echo "Bonus +\$" . $commission . "\n";

echo "-------------------------------\n";

echo "Total $" . ($salary + $commission) . "\n";

现在,员工只需更改其小时工资和总销售额的值,并运行程序以获取其总工资金额。

总结

我们现在已经到达了本章的结尾。在本章中,我们从 PHP 语法开始。然后,我们转向变量和在 PHP 中使用的不同运算符。最后,我们看到了如何实现条件语句并控制执行流程。

现在,您应该清楚地了解了变量、数据类型和条件语句,以及它们如何一起使用。在下一章中,您将了解 PHP 中如何实现数组和循环。

第二章:数组和循环

在上一章中,我们讨论了变量和数据类型以及不同的运算符。我们还讨论了如何使用条件控制程序的流程。在本章中,我们将重点讨论如何使用数组存储多个值以及如何使用循环控制流程。数组的基本思想是它是一种变量类型,允许将多个项目存储在一个“容器”中。

例如,如果您想要将公司中所有员工的姓名存储在同一个变量下,数组将帮助您实现这一点。当我们想要多次运行相同的代码块时,使用循环。这通过重用预定义的代码块为开发人员节省了大量工作。这两个概念几乎是今天几乎每个基于 PHP 的 Web 应用程序和网站的核心。

在本章结束时,您将能够:

实现一维和多维数组

识别索引数组和关联数组之间的区别

对数组执行不同的操作

实现各种类型的循环

数组

在本节中,我们将讨论各种类型的数组,然后查看一些常见的操作。

索引数组

索引数组是您将看到的最常见的数组类型,它们可以被定义为预填充或空的。

可以按以下方式定义空数组:

$the_array = array();

?>

注意

请注意,$the_array是一个变量。您可以使用任何其他变量。

使用快捷语法的另一种方法:

$the_array = [];

?>

如果要使用值初始化数组,可以按以下方式执行:

$students = array("Jill", "Michael", "John", "Sally");

?>

当您使用预填充值初始化数组时,每个元素都会获得一个数字索引,从 0 开始。因此,在前面的例子中,Jill 的索引将为 0,Michael 的索引将为 1,John 的索引将为 2,Sally 的索引将为 3。

要打印students数组的第一个索引,可以使用以下代码:

echo $students[0];

?>

通常,当您使用数组时,希望在程序运行过程中添加到数组中。可以通过以下两种方式之一完成:

append快捷方式:

$students[] = "Tom";

?>

或array_push函数:

array_push($students, "Tom", "Joey");

?>

通常,开发人员使用快捷方法;如果要一次将多个记录推送到数组中,可以使用array_push函数。

有时,您可能需要从数组中删除一个元素。要从数组中删除一个元素,使用unset函数。在下面的例子中,我们从数组中删除了“Tom”:

unset($students[4]);

?>

在继续之前,我们将讨论最后一个部分,即更新一个元素。要这样做,请执行以下操作:

$students[0] = "Jessie";

?>

关联数组

接下来是关联数组,也称为键值对。使用关联数组,您可以使用基于文本的键来存储值,这在特定情况下可能会有所帮助。例如,如果我们从前面的例子中取一个学生(在这种情况下是 Michael),我们可以存储他的age,gender和favorite color,如下所示:

$michael = array(

"age" => 20,

"gender" => "male",

"favorite_color" => "blue"

);

?>

如果需要访问数组中的特定值,可以使用key。例如,如果我们想打印 Michael 的年龄,可以这样做:

echo $michael['age'];

?>

向associative数组添加数据与向索引数组添加数据一样简单。您只需使用键并分配一个值。假设我们要向 Michael 的数组添加一个职业:

$michael["occupation"] = "sales associate";

?>

要从关联数组中删除一个元素,请按照我们在indexed数组中所做的相同步骤,但这次使用键。

让我们删除我们在上一步中添加的职业:

unset($michael['occupation']);

?>

使用数组

在本节中,我们将包括name,age,location和education level。请按照以下步骤:

打开您的代码编辑器并创建一个新文件arrays.php。

在新文件中,创建您的php标签:

?>

现在,我们将创建一个新变量,称为$myinfo,并用一个新数组初始化它:

$myinfo = array();

?>

然后,我们将使用我们的name,age,location和education level填充新数组。

接下来,我们将打印我们的数据:

$myinfo = array("John", 25, "USA", "College");

echo "My name is " . $myinfo[0] . "\n";

echo "I am ". $myinfo[1] . " years old. \n";

echo "I live in " . $myinfo[2] . "\n";

echo "My latest education level is " . $myinfo[3];

?>

在终端中打开您的工作目录,并键入以下命令:

php arrays.php

您将获得如下输出所示的结果:

My name is John.

I am 25 years old.

I live in USA.

My latest education level is College.

将字符串转换为数组

有时,在构建基于 PHP 的应用程序时,您不会使用预定义的数据集来实例化数组-例如在构建实用程序脚本时。假设您有一个包含文件名字符串版本的变量,并且希望获取不带扩展名的文件名。通过使用explode函数,可以轻松完成此任务。explode函数接受两个参数:分隔符和要转换为数组的字符串。Explode函数接受两个参数:

$filename = "myexamplefile.txt";

$filename_parts = explode(".", $filename);

echo "Your filename is " . $filename_parts[0];

?>

在前面的例子中,我们定义了一个文件名变量,然后使用 explode 函数,使用句点分隔符将字符串分解为其部分。$filename_parts变量包含两个元素的数组,第一个是字符串myexamplefile,第二个包含字符串txt。

知道这一点,我们可以通过访问字符串部分的 0 索引来打印出文件名。

将数组合并为字符串

除了explode函数之外,PHP 还为我们提供了一个允许我们执行完全相反操作的函数:implode函数。当您想要获取现有数组并将其转换为字符串时,可以使用 implode 函数来定义分隔符并将其传递给数组;您将获得一个单个字符串作为结果。

让我们回到explode的例子。假设我们有一个文件名,并希望在将其保存回字符串之前在其末尾附加一些其他字符串:

$filename = "myexamplefile.txt";

$filename_parts = explode(".", $filename);

$filename_parts[0] .= "_v1";

$filename = implode(".", $filename_parts);

echo "Your new filename is " . $filename;

?>

在前面的代码示例中,我们首先使用 explode 函数将原始文件名分解为其部分。然后,我们访问文件名部分并在其末尾附加字符串 _v1。使用 implode 函数,我们使用其部分重新组合文件名,最后,我们将其打印回屏幕供用户查看。

切片数组

另一个array_slice函数;默认情况下,该函数只需要两个参数,但可以接受四个。两个必需的参数是数组本身和新数组的起始点。两个可选参数是新数组的长度(或要包含的元素数)和保留选项。保留选项允许您决定当前数组元素是保持不变还是在拆分后重新排序。以下是一个基本的使用示例:

$fruit = array("apples","grapes", "oranges", "lemons","limes");

$smallerFruitArray = array_slice($fruit, 2);

?>

在前面的例子中,当我们通过array_slice运行水果数组时,我们将获得一个包含橙子、柠檬和酸橙的数组。

对数组进行排序

排序是构建某些类型的程序的另一个重要工具。您经常在 PHP 中看到的排序函数之一是ksort函数。ksort允许您将数组作为参数传递,然后按升序对其进行排序。

如何使用的示例如下:

$people = array("Jessica" => "35", "April" => "37", "John" => "43", "Tom" => 25);

ksort($people);

?>

在前面的例子中,我们有一个人的数组。一旦您将人们数组通过ksort函数,您应该会看到按字母顺序排列的名称。

多维数组

下一个类型是多维数组。多维数组只是数组中的数组。在我们之前的数组示例中,我们存储了一个学生的名字。当我们想要为特定学生存储多个详细信息时会发生什么?这就是多维数组发挥作用的地方。让我们看看如何定义一个还存储学生的性别和最喜欢的 颜色的学生数组:

注意

要查看完整的代码片段,请打开代码文件中的Lesson 2.php。

$students = array(

"Jill" => array(

"age" => 20,

"gender" => "female",

....

"Amy" => array(

"age" => 25,

"gender" => "female",

"favorite_color" => "green"

),

);

?>

如果我们想要访问学生的信息,我们可以使用以下键:

echo $students['Jill']['age'];

?>

前面的例子将打印出 Jill 的年龄。对于多维数组,我们更新元素值的方式基本上与使用一维数组时相同。

例如,如果我们想将 Jill 的年龄更改为21,我们执行以下操作:

$students['Jill']['age'] = 21;

?>

在我们现有项目中包含一个爱好数组

在这一部分,我们将扩展前面的例子,包括一个爱好数组:

打开你的代码编辑器并创建一个新文件multidimensional.php。

在新文件中,创建你的开启和关闭php标签。

创建一个名为$user的新变量,并用一个新数组进行初始化:

?>

$user = array();

?>

用两个主要部分填充新数组:info和hobbies。在 info 数组中,存储name,age,location和education level,在hobbies数组中,我们将存储三个爱好。

注意

有关完整的代码片段,请参考代码文件夹中的Lesson 2.php文件。

"info" => array(

"name" => "john",

"age" => 27,

...

)

);

?>

接下来我们将打印我们的数据:

注意

有关完整的代码片段,请参考代码文件夹中的Lesson 2.php文件。

"info" => array(

"name" => "john",

"age" => 27,

"location" => "USA",

.....

echo "I live in " . $user["info"]['location'] . ".\n";

echo "My latest education level is " .

$user['info']['education_level']. ".\n";

echo "I enjoy " . $user["hobbies"][0] . ", " .

$user["hobbies"]

[1] . ", " . $user["hobbies"][2].".\n";

?>

在终端中打开你的工作目录,并输入以下命令:

php multidimensional.php

你将得到一个基于我们在前面数组中提供的输入的结果。

循环

循环是任何编程语言中强大的工具。它们允许你编写可以根据给定条件执行特定次数的代码。在本节中,你将学习到各种可用的循环,如 for 循环、foreach 循环、while循环和do-while循环。

for 循环

我们将从for循环开始探索循环。它被认为是最复杂的循环结构,当你知道需要一段代码运行多少次时就会使用它。for 循环的结构如下:

for(initialized counter; test if true; increment counters){

}

?>

创建for循环的步骤如下:

初始化起始计数变量 - 通常会从0开始。

提供一个解析为true或false的评估条件。如果条件为true,循环将继续,如果条件为false,循环将退出(或中断)。

按照特定数字递增值。通常,它会递增1。

这里有一个完整的例子:

for($i = 0; $i < 5; $i++) {

echo "Current Count: " . $i;

}

?>

结合循环和数组

在这一部分,我们将探索如何结合循环和数组。以下是实现它的步骤:

打开你的代码编辑器并创建一个新文件forloop.php。

在新文件中,创建你的开启和关闭php标签:

?>

现在,我们创建一个名为$food的新变量,并用一个新数组进行初始化:

$food = array();

?>

然后,我们用食物名称填充新数组:

?>

接下来,我们循环遍历我们的数组并打印我们的数据:

$food = array("turkey", "milk", "apples");

for($i = 0; $i < count($food); $i++){

echo $food[$i] . "\n";

}

?>

注意

count 函数返回数组中的元素数。

在终端中打开你的工作目录,并输入以下命令:

php forloops.php

while 循环

我们将要探索的下一个循环是 while 循环。当你想要循环执行一段代码直到满足特定条件时,就会使用 while 循环。while 循环的定义如下:

while(condition) {

}

?>

while 循环非常简单,因为它只需要一个条件来运行。循环将一直持续,直到条件为 false。

这里有一个简单的例子:

$count = 1;

while($count < 25){

echo "Count: " . $count . "\n";

$count += 1;

}

?>

我们取前面的count变量并赋值为 1。while条件检查count变量是否小于 25,并且一旦counter变量等于 25,就会中断。在 while 函数内部,我们输出当前计数,然后递增count变量 1。

使用 while 函数

在这一部分,我们将构建一个while函数,它将在counter小于 30 的情况下进行迭代。按照以下步骤进行:

打开你的代码编辑器并创建一个新文件while.php。

在新文件中,创建你的开启和关闭php标签:

?>

接下来,我们定义一个counter函数,并用数字 1 进行初始化:

$count = 1;

?>

然后,我们可以创建一个while循环,它将输出当前计数,然后将counter增加 1:

$count = 1;

while($count <= 30){

echo "Count " . $count . "\n";

$count++;

}

?>

在终端中打开你的工作目录,并输入以下命令:

php while.php

do-while 循环

do-while 循环与 while 循环类似,但它不是在循环开始时运行条件,而是在内部代码块运行后检查条件。其思想是,如果你需要至少运行一次代码块,就使用这个循环而不是 while 循环。

注意

do-while 循环也称为退出控制循环。

do-while 循环的表示如下:

do{

}while(condition);

?>

我们将修改之前的 while 循环:

$count = 1;

do{

echo "Count: " . $count . "\n";

}while($count <= 25);

?>

将 while 循环转换为 do-while 循环

在本节中,我们将复制前面的示例,但将 while 循环转换为 do-while 循环,以便您可以看到它们的功能差异。按照以下步骤进行:

创建一个新文件并将其命名为dowhile.php。

接下来,打开while.php文件,并将内容复制到dowhile.php文件中。

现在,我们将修改while函数以使其类似于以下代码:

$count = 1;

do{

echo "Count " . $count . "\n";

$count++;

}while($count <= 30);

?>

在终端中打开您的工作目录,并输入以下命令:

php dowhile.php

foreach 循环

接下来是 foreach 循环。foreach 循环旨在为程序员提供一种简单的方法来遍历数组。这个循环只能用于数组和对象,这些您将在本书后面学习。这个循环有两种语法:

foreach($array as $value){

}

?>

第一个语法接受一个给定的数组,并遍历数组中的每个元素,将其分配给次要变量。例如:

$students = array("Jill", "John", "Tom", "Amy");

foreach($students as $student){

echo $student . "\n";

}

?>

在前面的示例中,我们定义了一个填充有学生姓名的数组。然后,我们使用 foreach 循环遍历我们的students数组,并echo出每个姓名。

第二种语法写成如下:

foreach($array as $key => $value){

}

?>

在这个版本的for each函数中,给定的数组被遍历,但不是给出单个元素,而是同时给出key和element本身。以下是我们将如何使用它的示例:

注意

有关完整的代码片段,请参阅代码文件夹中的Lesson 2.php文件。

$students = array(

"Jill" => array(

"age" => 20,

"favorite_color" => "blue"

),

.....

);

foreach($students as $name => $info){

echo $name . "'s is " . $info['age'] . " years old\n";

}

?>

在此示例中,我们定义了一个存储每个学生年龄和最喜欢的颜色的多维数组,并使用学生的姓名作为索引。然后,我们使用foreach函数遍历students数组,并将每个元素的键分配给name变量,将学生的信息分配给info变量。在循环内,我们echo出学生的姓名,以及他们的年龄。

活动:使用 foreach 循环

让我们将对于每个循环的理解付诸实践。

您的经理要求您创建一个 PHP 脚本,根据他们的工资计算每个员工每月赚多少钱。

您要做的是:

创建一个新目录并命名为monthly_payment。

在新目录中,创建一个index.php文件。

首先,您将定义一个多维数组,用于存储员工的姓名、职称和工资:

$employees = array(

array(

"name" => "John Doe",

"title" => "Programmer",

"salary" => 60000

....

"title" => "Manager",

"salary" => 132000

)

);

?>

接下来,定义foreach循环,它将遍历employee数组:

foreach($employees as $employee){

}

最后,添加一个echo语句,用于打印姓名、职称和计算出的月薪:

foreach($employees as $employee){

echo $employee['name'] . "(" . $employee['title'] . ") annual salary is $" .

$employee['salary'] . " and earns $" . ($employee['salary'] / 12) . "/mo. \n";

}

计算器脚本现在已经完成。如果您需要添加额外的员工,只需添加一个带有员工信息的额外关联数组即可。

总结

我们已经到了第二章的结尾。在这一章中,我们看到了如何声明和定义数组,并涵盖了不同类型的数组。我们看到了可以对数组执行的各种操作。我们还涵盖了诸如 for 循环、while 循环和 do while 循环之类的控制流语句。

在下一章中,我们将学习如何使用函数和类实现代码的可重用性,让您离构建自己的定制应用程序更近一步。

第三章:函数和类

在上一章中,我们看到了如何声明和定义数组,并涵盖了多种类型的数组,如索引数组、关联数组等。我们还看到了可以对数组执行的各种操作。

在本章中,我们将了解如何定义和调用函数。我们还将学习如何创建类,以及如何将类和函数一起使用。

函数是打包成可重复使用代码的代码块。函数是一段代码,通过进行一些处理,获取一个或多个输出来返回一个值。

类是对象的蓝图。类形成数据的结构和利用信息创建对象的操作。

在本章结束时,您将能够:

定义和调用函数

定义类并使用new关键字创建类的实例

实现和调用public和static类函数

函数

函数就像一个具有固定定义逻辑的机器。在一端,它接受一个参数,对其进行处理,并根据输入和函数定义返回一个值。

函数用于重复使用特定的代码块,而不是在需要时一直定义它:

要定义函数,我们使用关键字function,后跟我们要给函数的名称;在花括号内,我们定义函数的操作。例如,如果我们想创建一个打印“Hello World”的函数,我们写如下:

function HelloWorld(){

echo "Hello World";

}

?>

如果我们将这个函数写在一个新文件中并运行它,它不会显示任何内容,这是因为我们还没有调用函数;我们只是定义了它。

要调用函数,我们添加以下代码行:

HelloWorld();

?>

在创建函数时,有时需要将附加参数传递给您的函数;这可以在定义新函数时完成。

让我们修改前面的示例以接受一个name参数:

function HelloWorld($name){

echo "Hello World, " . $name;

}

?>

要传递名称,我们在函数名后面的括号内定义一个变量。

现在,当我们调用函数时,我们可以通过该变量传递任何值,并且它将被打印出来。

HelloWorld("John");

?>

有时,当我们创建一个函数时,我们知道会有一些情况下我们不会传递一个值。在这些情况下,我们希望自动传递一个默认值。

要设置默认值,您应该在设置变量时将其分配给指定的变量,如下所示:

function displayMessage($message = "World"){

echo "Hello " . $message;

}

displayMessage();

displayMessage("Greg");

?>

函数不仅可以用于将消息打印到屏幕上,还可以返回一个值,该值可以存储在变量中或用于另一个函数。例如,如果您创建一个加法函数,您可能希望返回总和:

function addNumbers($a, $b){

return $a + $b;

}

echo addNumbers(5,6);

?>

现在我们有一个可以返回值的函数,让我们看看如何使用它来存储一个值:

$sum = addNumbers(1,2);

?>

注意

在您的程序中,您有时可能需要以高效的方式动态调用函数。PHP 为您提供了一个有用的工具来做到这一点-call_user_func_array。call_user_func_array函数允许您通过将其名称作为第一个参数传递来调用函数,并通过第二个参数以数组的形式提供参数。

创建一个简单的函数

在本节中,我们将创建一个简单的函数,用于计算给定百分比的折扣。要做到这一点,请按照以下步骤进行:

打开您的代码编辑器并创建一个新文件,function.php。

在新文件中,创建您的打开和关闭 php 标记:

?>

现在,我们将创建两个新变量:$sweaterPrice和$precentOff。它们将存储产品的原始价格以及折扣百分比。

$sweaterPrice = 50;

$percentOff = 0.25;

?>

现在我们有了变量,我们可以定义我们的函数。我们的函数很简单;它接受一个价格和折扣百分比。在函数内部,我们将价格乘以折扣百分比并返回乘积。

$sweaterPrice = 50;

$percentOff = 0.25;

function couponCode($price, $discount){

return $price * $discount;

}

?>

最后,我们可以继续向我们的用户打印关于折扣的消息,使用我们新创建的函数:

$sweaterPrice = 50;

$percentOff = 0.25;

function couponCode($price, $discount){

return $price * $discount;

}

echo "The sweater originally costs $" . $sweaterPrice . " with the discount you'll pay $" . ($sweaterPrice - couponCode($sweaterPrice, $percentOff)) . "\n";

?>

现在您已经了解了函数,应该可以轻松地开发可重用的代码并应用它们。在下一节中,我们将学习有关类的知识。类将使我们更好地理解如何将代码和属性结构化为一个整洁的包。

在本节中,您将学习有关类的知识。类属于一种称为面向对象编程的编程类型,简单地意味着将代码组织成所谓的对象。对象允许您创建一个具有自己变量和方法的基本包,专属于该对象。

注意

把类想象成一个对象的蓝图。只有一个类,但可以有许多实例。这可以与房子的蓝图相比。许多新房子可以根据相同的蓝图建造。

假设我们想创建一个保存学生信息的类。我们可以定义如下:

class Student {

}

?>

这是基本的学生类,以其最简单的形式。我们首先使用关键字class,然后是我们类的名称,这种情况下是Student。接下来,我们创建一个带有开放和关闭括号的代码块。在开放和关闭括号内,我们添加我们类的内容。

这导致了类的下一部分:成员变量。我们在本书的第一章中使用变量。作为一个复习,变量充当一个容器,允许您暂时存储数据。成员变量具有相同的功能,但其作用范围仅限于给定类或类实例的边界内。

我们将扩展我们的Student类来存储学生的姓名、年龄和专业:

class Student {

public $name;

public $age;

public $major;

}

?>

您应该注意到我们在定义变量时使用的public关键字。这很重要,因为它告诉程序如何访问数据。public关键字简单地表示您可以直接访问这些数据。

现在我们的类已经准备好了,我们可以创建一个类的新实例,并将其赋给一个变量,我们可以用这个变量来与类的属性进行交互:

$michael = new Student();

$michael->name = "Michael John";

$michael->age = 27;

$michael->major = "Computer Science";

?>

在这个例子中,我们使用new关键字创建了一个学生类的新实例,并将其赋给一个我们称之为Michael的变量。然后,使用箭头语法,我们可以访问公共值来设置姓名、年龄和专业。

有时候我们想要用值实例化一个类的新实例。我们可以使用一个称为构造函数的函数来实现这一点:

public function __construct(){

}

这个函数是使用new关键字实例化一个新类时调用的默认函数。要传递值,我们将在构造函数中定义这些值。

例如,如果我们想设置学生的信息,我们可以这样做:

class Student {

public $name;

public $age;

public $major;

public function __construct($name, $age, $major){

$this->name = $name;

$this->age = $age;

$this->major = $major;

}

}

?>

现在,我们可以提供学生的信息:

$michael = new Student("Michael John", 27, "Computer Science");

?>

除了public变量,您还可以定义private变量。private关键字使变量只能由方法本身访问。这意味着您只能通过constructor、getter函数和setter函数访问这些类型的变量,这让我们对类函数有了很好的了解。

类函数允许您为类创建本地功能,以set、get和改变类本身中保存的数据。例如,如果我们采用先前的类定义,并用private变量替换public变量,它将如下所示:

class Student {

private $name;

private $age;

private $major;

public function __construct($name, $age, $major){

$this->name = $name;

$this->age = $age;

$this->major = $major;

}

}

?>

如果我们想要更改这些值,或者将这些值放在程序的其他位置,我们当然要定义函数:

注意

有关完整的代码片段,请参考代码文件夹中的Lesson 3.php文件。

class Student {

private $name;

private $age;

private $major;

....

}

public function getName(){

return $this->name;

}

public function getAge(){

return $this->age;

}

public function getMajor(){

return $this->major;

}

}

?>

请记住,在函数的名称中使用set和get并不是必需的;您可以使用任何您想要的名称 - 一些可以让您轻松记住每个函数的作用。正如您在代码示例中所看到的,您可以使用相应的set函数更新private值,并使用相应的get函数检索这些值。

例如,假设 Michael 改变了他的专业:

$michael->setMajor("Engineering");

?>

如果我们想知道他的专业是什么,我们可以使用以下代码:

echo "Michael's major is " . $michael->getMajor();

?>

类是任何类型的编程中非常强大的工具,主要是由于继承的概念。继承允许您创建一个定义一般函数和变量的base类,并将被类的所有子类使用。

举个简单的例子,让我们定义一个Animal类:

class Animal{

public $sound;

public $name;

public function speak(){

echo $this->name . " says " . $this->sound;

}

}

?>

这个基类有一个变量,保存动物的名称和动物发出的声音。此外,它有一个public函数,speak,将打印动物发出的声音。

我们可以从base类中扩展不同类型的动物。

假设我们想定义一个Dog类:

class Dog extends Animal {

public $name = "Dog";

public $sound = "Woof! Woof!";

}

?>

我们只需更改名称和声音变量的值,就可以得到我们的dog类:

$dog = new Dog();

$dog->speak();

?>

在开发子类时,要记住的一件事是,您可以通过以下方式扩展基类构造函数:

class Dog extends Animal {

public $name = "Dog";

public $sound = "Woof! Woof!";

public function __construct(){

parent::__construct();

}

}

?>

另一个有用的部分,当涉及到类时,是static函数。静态函数不需要创建类的实例就可以使用。当您构建一个包含实用函数的类时,这将非常方便。要创建一个static函数,您只需使用static关键字:

class Animal{

public $sound;

public $name;

public function speak(){

echo $this->name . " says " . $this->sound;

}

public static function about(){

echo "This is the animal base class.";

}

}

?>

在上面的例子中,我们创建了一个静态的 about 函数,当调用时会给出类的简短描述。您可以按照以下方式使用此函数:

echo Animal::about();

?>

活动:计算员工的月工资

您被指派计算员工的月工资。工资应该被计算并显示在屏幕上。

这个活动的目的是让您学会如何从给定的百分比中计算折扣。

按照以下步骤执行此活动:

打开您的代码编辑器并创建一个新文件,class.php。

在新文件中,创建您的php标签:

?>

接下来,定义一个基本的员工类:

注意

有关完整的代码片段,请参考代码文件夹中的Lesson 3.php文件。

class BaseEmployee {

private $name;

private $title;

private $salary;

function __construct($name, $title, $salary){

$this->name = $name;

$this->title = $title;

$this->salary = $salary;

}

public function setName($name){

$this->name = $name;

......

}

public function getTitle(){

return $this->title;

}

public function getSalary(){

return $this->salary;

}

}

?>

从这个基类中,我们可以继续创建一个扩展基类的employee类。在这个扩展类中,我们将添加一个额外的函数,用于计算员工的月工资:

注意

有关完整的代码片段,请参考代码文件夹中的Lesson 3.php文件。

class BaseEmployee {

private $name;

private $title;

private $salary;

function __construct($name, $title, $salary){

...

public function getSalary(){

return $this->salary;

}

}

class Employee extends BaseEmployee{

public function calculateMonthlyPay(){

return $this->salary / 12;

}

}

?>

最后,我们将使用新类来打印月工资:

注意

有关完整的代码片段,请参考代码文件夹中的Lesson 3.php文件。

class BaseEmployee {

private $name;

private $title;

private $salary;

......

class Employee extends BaseEmployee{

public function calculateMonthlyPay(){

return $this->salary / 12;

}

}

$markus = new Employee("Markus Gray", "CEO", 100000);

echo "Monthly Pay is " . $markus->calculateMonthlyPay();

?>

摘要

在本章中,我们学习了函数和类。我们讲解了如何定义和调用函数。我们还讲解了如何定义类并将类和函数一起使用。随着我们开始构建更大更复杂的项目,函数和类将帮助我们创建高度组织的代码并保持最佳实践。

在下一章中,我们将涵盖数据操作,如输入和输出数据,使用错误处理捕获和处理错误,我们还将介绍 MySQL 的基础知识。

第四章:数据操作

在上一章中,我们学习了函数和类。我们讨论了如何定义和调用函数。我们还讨论了如何定义类并将类和函数一起使用。

在本章中,我们将专注于处理用户的输入并将结果打印回给他们,优雅地处理错误,并学习使用 MySQL 数据库的基础知识。

在本章结束时,您将能够:

确定如何接受用户输入并将其打印到屏幕上

实现使用 MySQL 的基础知识

输入和输出数据

能够接受用户输入是从使用 PHP 构建网站转向使用 PHP 构建 Web 应用程序时的一个主要要求。通常,输入来自 HTML 表单。

让我们创建一个简单的联系表单:

在上述联系表单中,我们看到了用户姓名、电子邮件和消息的输入。我们将使用的提交此表单的方法称为POST请求。

为了读取正在提交的数据,我们将在表单顶部添加一些 PHP 代码,这些代码将从我们的POST请求中读取并呈现数据:

注意

有关完整的代码片段,请参考代码文件夹中的Lesson 4.php文件。

if($_POST){

echo "Name: " . $_POST['name'] . "\n";

echo "Email: " . $_POST['email'] . "\n";

......

如您所见,接受应用程序用户的输入很容易。在上面的代码示例中,我们使用了一个特殊变量$_POST数组,来访问通过POST请求提交的所有数据。$_POST变量是一个关联数组,可以通过您在输入元素中指定的名称来访问内容。

您可以使用的另一种请求类型是GET请求。GET请求比您想象的更常用;当您导航到网站或在 Google 上进行搜索时,就会使用GET请求。GET请求的输入是通过查询字符串完成的。

查询字符串是附加到 URL 末尾的字符串,以问号开头,如下所示:www.example.com?name=Michael&age=12:

在上面的例子中,您可以看到我们有两个用和号分隔的键。就像在POST方法中一样,GET请求也有一个特殊的变量,那就是$_GET变量(它是一个关联数组)。

如果我们想要从之前的查询字符串中获取名称,可以使用这行代码:

$name = $_GET['name'];

?>

您也可以在表单中使用GET请求。让我们重新访问之前看到的表单元素:

注意

有关完整的代码片段,请参考代码文件夹中的Lesson 4.php文件。

if($_GET){

echo "Name: " . $_GET['name'] . "\n";

echo "Email: " . $_GET['email] . "\n";

......

在表单的方法属性中,我们将其更改为GET,用$_GET变量替换了$_POST变量。

注意

在接受用户输入时,有时需要在对其进行任何操作之前清理输入。某些输入需要清除开头和结尾的任何空格。这就是 PHP 的trim函数发挥作用的地方。trim函数将清除用户输入的两侧的空格和其他类似字符。如果要从左侧或右侧删除,可以分别使用ltrim和rtrim函数。

为我们的用户列表构建一个表单

我们将首先构建一个用户列表的表单。在本节结束时,您将拥有一个表单,可以接受您的firstname、lastname和email。它将在最后有一个提交按钮,用于提交信息:

创建一个名为users_list的新目录。

在新目录中创建一个index.php文件。

在文本编辑器中打开index.php文件,并添加表单代码:

注意

有关完整的代码片段,请参考代码文件夹中的Lesson 4.php文件。

......

现在我们有了表单,我们希望能够查看提交给表单的数据:

注意

有关完整的代码片段,请参考代码文件夹中的Lesson 4.php文件。

if($_POST){

echo "First Name: " . $_POST['firstname'] . "\n";

echo "Last Name: " . $_POST['lastname'] . "\n";

......

id="email"/>


现在,为了看到我们的表单起作用,我们将在终端中打开工作目录并运行以下命令:

php -S localhost:8000 -t

对于任何 Web 应用程序,您都需要一种存储数据的方式。允许您将当前状态持久保存在 MySQL 数据库中的服务称为持久性。如果变量允许您暂时存储数据,持久性允许您长期存储数据在数据库中。

PHP 中主要使用的数据库类型是 MySQL。MySQL 数据库被称为关系型数据库,它们被组织成表格。

在本节中,我们将介绍如何在 PHP 中使用 MySQL 数据库以及如何执行各种操作。

连接到数据库

使用数据库的第一步是连接到数据库。在本章中,我们将专注于使用 PDO(PHP 数据对象)风格的用法。

要连接到数据库,请使用以下代码行:

注意

有关完整的代码片段,请参考代码文件夹中的Lesson 4.php文件。

MySQL 基础知识

$host = "DATABASE_HOST";

$username = "DATABASE_USERNAME";

$password = "DATABASE_PASSWORD";

$database = "DATABASE_NAME";

......

echo "Connected successfully";

}

catch(PDOException $e)

{

echo "Connection failed: " . $e->getMessage();

}

?>

在上述代码中,您可以看到我们有一大块新代码。我们首先定义四个新变量来保存数据库的凭据值:一个用于主机 URL,一个用于用户名,一个用于密码,最后一个用于我们连接的数据库的名称。接下来,我们将数据库连接代码包装在try块中;这将在连接到数据库并运行查询时catch任何错误。在try块中,我们通过使用我们之前定义的凭据变量来初始化 PDO 类的新实例,将其分配给$conn变量。然后,我们设置错误模式以确保如果出现任何错误,它会触发我们的catch块。最后,在try部分,我们echo出一个成功的连接消息。在 try/catch 块的catch部分中,我们只是echo出触发的错误消息。

创建数据库表

我们现在将使用 SQL 查询创建一个表:

注意

有关完整的代码片段,请参考代码文件夹中的Lesson 4.php文件。

$host = "DATABASE_HOST";

$username = "DATABASE_USERNAME";

$password = "DATABASE_PASSWORD";

......

}

catch(PDOException $e)

{

echo "Connection failed: " . $e->getMessage();

}

}

?>

创建表格时,我们使用CREATE TABLE命令,后面跟着表格的名称。然后,在一对括号内,我们定义表格的字段。我们在查询中创建的表将创建一个用户表,其中将保存用户的 ID(此表的主键),并将自动递增用户名称,类型为varchar,最多 60 个字符。表还将保存一个电子邮件地址,类型为varchar,最多 30 个字符。

向数据库插入记录

现在我们的数据库中有一个表,我们可以向其中添加数据。我们使用insert查询添加数据。连接到数据库并设置错误模式后,我们可以定义我们的查询。Insert查询以INSERT INTO命令开始,后面跟着我们要插入数据的表的名称。在一对括号内,我们定义要写入的字段。在字段之后,我们定义要输入表格的值:

注意

有关完整的代码片段,请参考代码文件夹中的Lesson 4.php文件。

$host = "DATABASE_HOST";

$username = "DATABASE_USERNAME";

$password = "DATABASE_PASSWORD";

$database = "DATABASE_NAME";

try {

$conn = new PDO("mysql:host=$host;dbname=$database", $username, $password);

......

}

catch(PDOException $e)

{

echo "Connection failed: " . $e->getMessage();

}

?>

从数据库表中获取单行数据

如果要从数据库中获取用户,可以使用SELECT查询。在这种情况下,我们要获取前一个代码块中插入的新用户。我们将使用以下代码:

注意

有关完整的代码片段,请参考代码文件夹中的Lesson 4.php文件。

$host = "DATABASE_HOST";

$username = "DATABASE_USERNAME";

$password = "DATABASE_PASSWORD";

$database = "DATABASE_NAME";

try {

$conn = new PDO("mysql:host=$host;dbname=$database", $username, $password);

......

}

catch(PDOException $e)

{

echo "Connection failed: " . $e->getMessage();

}

?>

使用$conn变量,我们准备一个SELECT查询,指示我们要从用户表中提取信息;然后我们使用WHERE子句来定义所需信息的条件。最后,我们执行准备好的语句,传递一个带有所需电子邮件地址的array。由于我们希望得到一个关联数组返回给我们,我们将获取模型设置为FETCH_ASSOC,通过使用 fetch 方法获取单个记录。

渲染用户数组,我们在开放和关闭的pre标签之间使用PRINT命令。

注意

pre标签美化了已打印的数组。这通常用于调试数组中包含的内容。

从数据库表中获取多行

如果我们想要获取表中的所有用户,我们可以放弃准备好的语句并直接运行查询。这一次,我们去掉了WHERE子句。我们不再使用 fetch 函数,而是使用fetch_all函数:

注意

有关完整的代码片段,请参考代码文件夹中的Lesson 4.php文件。

$host = "DATABASE_HOST";

$username = "DATABASE_USERNAME";

$password = "DATABASE_PASSWORD";

$database = "DATABASE_NAME";

try {

$conn = new PDO("mysql:host=$host;dbname=$database", $username, $password);

......

echo "";

}

$host = "DATABASE_HOST";

$username = "DATABASE_USERNAME";

$password = "DATABASE_PASSWORD";

$database = "DATABASE_NAME";

try {

$conn = new PDO("mysql:host=$host;dbname=$database", $username, $password);

......

echo "";

}

catch(PDOException $e)

{

echo "Connection failed: " . $e->getMessage();

}

?>

更新数据库表中的记录

现在我们了解了如何向数据库表中添加和获取数据,我们可以开始编辑单个记录。在 MySQL 中,我们使用UPDATE查询来更新数据。要运行UPDATE查询,我们回到我们的准备好的语句,并以UPDATE命令开头,后跟表的名称(在本例中为用户表)。接下来,我们使用SET命令开始定义需要更新的字段和值的过程,然后我们添加WHERE子句来隔离我们希望具有新值的特定记录。为了对查询的执行情况进行一些反馈,通过行计数函数回显计数。

让我们将用户的电子邮件地址更改为test123@email.com:

注意

有关完整的代码片段,请参考代码文件夹中的Lesson 4.php文件。

$host = "DATABASE_HOST";

$username = "DATABASE_USERNAME";

$password = "DATABASE_PASSWORD";

$database = "DATABASE_NAME";

try {

$conn = new PDO("mysql:host=$host;dbname=$database", $username, $password);

......

echo $statement->rowCount() . "(s) rows affected.";

}

catch(PDOException $e)

{

echo "Connection failed: " . $e->getMessage();

}

?>

从数据库表中删除记录

我们在 MySQL 中的最后一部分将是从数据库中删除数据。要删除数据,我们使用DELETE查询。DELETE查询以DELETE FROM开头,后跟您要从中删除数据的表的名称;使用WHERE子句完成查询,以进一步指定要删除的记录。我们将此查询放在准备好的语句中,然后通过传递WHERE子句的值来执行它:

注意

有关完整的代码片段,请参考代码文件夹中的Lesson 4.php文件。

$host = "DATABASE_HOST";

$username = "DATABASE_USERNAME";

$password = "DATABASE_PASSWORD";

$database = "DATABASE_NAME";

try {

$conn = new PDO("mysql:host=$host;dbname=$database", $username, $password);

......

echo $statement->rowCount() . "(s) rows deleted.";

}

catch(PDOException $e)

{

echo "Connection failed: " . $e->getMessage();

}

?>

创建员工表

我们的最终项目将是将我们从用户那里获得的输入存储在数据库表中。在编写将数据添加到数据库的代码之前,我们需要创建一个数据库,如下所示:

打开终端。

使用以下命令连接到 MySQL:

mysql –u root –p

接下来,创建packt_database数据库:

create database packt_database;

告诉 MySQL 使用新创建的数据库:

use packt_database;

最后,创建用户表:

CREATE TABLE users (

id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY,

firstname VARCHAR(30) NOT NULL,

lastname VARCHAR(30) NOT NULL,

email VARCHAR(30) NOT NULL

);

现在,我们可以关闭我们的终端并开始完成我们的应用程序。

向数据库添加用户

在本节中,我们将使用 PHP 向数据库添加用户。然后,我们创建一个表单,接受用户的INSERT查询。

要执行此操作,请执行以下步骤:

在文本编辑器中重新打开users_list目录。

在第二个if语句中,连接到您的数据库:

注意

有关完整的代码片段,请参考代码文件夹中的Lesson 4.php文件。

if($_POST){

if(!$_POST['firstname'] || !$_POST['lastname'] || !$_POST['email']){

exit("All fields are required.");

}

$host = "DATABASE_HOST";

$username = "DATABASE_USERNAME";

$password = "DATABASE_PASSWORD";

$database = "packt_database";

try {

$conn = new PDO("mysql:host=$host;dbname=$database", $username, $password);

......

接下来,继续使用INSERT查询将用户输入添加到数据库中:

注意

有关完整的代码片段,请参考代码文件夹中的Lesson 4.php文件。

if($_POST){

if(!$_POST['firstname'] || !$_POST['lastname'] || !$_POST['email']){

exit("All fields are required.");

}

.......



现在,您已经准备好测试简单的应用程序。在终端中打开user_list目录,并使用以下命令来启动您的应用程序:

php -S localhost:8000 -t .

总结

我们已经到达了本章的结尾。在本章中,我们学习了如何接受用户的输入,以及如何通过 PHP 访问它。最后,我们学习了使用 MySQL 数据库的基础知识,并将所有原则应用到一个通过 Web 表单向数据库添加用户的迷你应用程序中。

在下一章中,我们将介绍使用面向对象编程原则构建 PHP Web 应用程序的基础知识,例如命名空间、使用语句、访问修饰符等。我们还将介绍如何使用 MVC 设计概念正确地构建应用程序。

第五章:构建 PHP Web 应用程序

在上一章中,我们学习了如何接受用户的输入,以及如何通过 PHP 访问它。我们还学习了使用 MySQL 数据库的基础知识,并将之前章节的原则应用到一个小型应用程序中,通过 Web 表单将用户添加到数据库中。

在本章中,我们将学习并应用框架中的面向对象编程概念。我们将使用 Whoops 库来进行错误报告,并学习如何处理这些错误。我们还将介绍如何在框架中管理和构建我们的应用程序。

在本章结束时,您将能够:

在框架环境中应用面向对象编程概念

结构文件和文件夹以构建框架

描述框架如何与数据源交互

使用 MVC 设计模式构建框架

构建一个 CRM 应用程序来管理您的框架上的联系人

构建一个应用程序将需要我们了解底层框架以及如何使用 MVC 架构风格来创建应用程序。PHP 框架是一个设计用来促进代码重用组织的文件夹和文件集合;这些文件夹和文件提供了一个通用的代码基础,用于构建应用程序。通过这些章节,您将学习如何构建这样一个框架。

我们将在本书中使用的一个常见设计模式称为 CRUD - 一个缩写,意思是:

创建:创建一个新的 MySQL 记录

读取:从数据库中读取记录

更新:更新 MySQL 记录

删除:删除 MySQL 记录

CRUD 是在框架中构建的任何实际应用程序的核心。几乎所有内容都可以分解为 CRUD。

CRUD 的示例将涉及创建新内容,读取内容,并提供提示来更新和删除内容。

我们将使用一种称为模型视图控制器(MVC)的设计模式,这是一种用于构建框架的目录和文件结构的方式。将使用 MVC 结构来展示结构和示例:

模型视图控制器(MVC)的表示

PHP 标准建议(PSR)为代码格式设置了一个样式指南,允许与您可能接触到的其他代码最大的兼容性:www.php-fig.org/psr/。

框架环境中的面向对象编程概念

在开始学习框架构建之前,对 PHP 面向对象编程(OOP)概念有一个扎实的理解是一个好主意。所有 PHP 框架共同的一点是,它们首先是建立在 OOP PHP 之上的;本质上,它们只是一种组织文件的方式。

我们将学习以下面向对象编程概念:

命名空间

使用语句

类和对象

方法

访问修饰符

命名空间

命名空间可以与文件夹结构进行比较。命名空间的主要目的是允许类具有相同的名称,但在不同的命名空间下。

命名空间是区分大小写的。命名空间应该以大写字母开头,之后使用驼峰命名法 - 每个单词的开头应该是小写,后面的单词应该是大写。

例如:mySpace

例如,如果您有一个名为Post的类,并且在另一个文件夹中,您有一个名为Post的类。通常情况下,您将无法在同一文件中使用它们,因为这些类会相互冲突;但是,如果每个类都有它们存储的文件夹的命名空间,那么您可以在同一文件中使用它们。

文件 file1.txt 可以存在于目录/home/packt和/home/other中,但是两个 file1.txt 的副本不能同时存在于同一个目录中。此外,要在/home/packt目录之外访问file1.txt,我们必须在文件名之前加上目录名,使用目录分隔符来获取/home/packt/file1.txt。这个原则在编程世界中也适用于命名空间。

您不能在同一个文件中使用两个类,因为它们会互相冲突。为了解决这个问题,您可以给其中一个类起一个别名。将别名视为该类的昵称。

命名空间是对app/controllers目录中的位置的引用:命名空间App\Controllers是其位置的路径。注意在编写命名空间时使用反斜杠字符:

//valid namespace

namespace App\Controllers;

命名空间 App,Controllers 和 Use 语句

Use语句是一种导入类的方式,而不是手动包含它们。use语句与 composer 一起使用。

作为在类中使用use语句的示例,如果我们想要使用Contact模型,可以将以下代码放在类的顶部:

use App\Models\Contact;

当您有一个名为Contact的类,其命名空间为App\Models时,要导入它,您可以使用App\Models\Contact。然后,您可以通过调用Contact来引用这个类;您不必引用其完整的命名空间,因为它已经被导入:

注意

我们使用 composer 根据其命名空间自动加载文件,并将在后面的章节中详细介绍这一点。

//start with the namespace

namespace App\Controllers;

//add the use statement

use App\Models\Contact;

//make the call

$contact = new Contact();

使用命名空间定义类和对象

我们在上一章学习了如何创建类和对象。现在我们将看到如何使用命名空间创建类和对象。

对象是已经实例化的类;例如,如果您看最后一个示例的最后一行,一个名为contact的类已经被实例化,使用了new运算符,后跟类名。这创建了一个新对象;这意味着新对象可以访问类的所有方法和公共属性:

//start with the namespace

namespace App\Controllers;

//add the use statement

use App\Models\Contact;

//make the call

$contact = new Contact();

//make a call to a getContacts method that exists within the contact class

$contact->getContacts();

方法

方法是驻留在类内部的函数。实际上,方法和函数之间唯一的区别是命名约定,以及方法碰巧驻留在类内部。

方法用于检索和传递信息到类中,或从类中传递信息到文件中,该文件中实例化了该类:

//start with a namespace

namespace App\Models;

//here is an example of a method being defined, in the previous example the method was being called.

class Contact

{

public function getContacts()

{

//get data here

}

}

访问修饰符

访问修饰符是授予和限制类的属性和方法访问权限的一种方式。有三种访问修饰符:public,protected和private。这可以与门卫进行比较,允许数据进入或阻止数据进入:

Public

将属性或方法定义为public意味着类,扩展类和实例化类的文件都可以读取和写入该方法或属性。

Protected

protected方法或属性只能被类或扩展类访问。

Private

private属性或方法只能从定义它的类内部访问。

注意

private属性无法从该类的外部访问,也无法从扩展类访问。

以下是如何使用各种访问修饰符的示例。在定义名为$token的属性时,您将看到public,protected和private属性的使用:

public $token;

protected $token;

private $token;

文件夹结构

解释:

这里是我们将在接下来的几章中构建的框架的文件结构:

app文件夹是您的应用程序所在的位置。app包含您的控制器,模型和视图。正如先前提到的,这是 MVC 结构的一部分。

config文件是存储站点名称和数据库凭据的位置。

system文件夹包含框架的核心文件。

vendor目录是一个 composer 目录,包含通过 composer 安装的任何第三方包。它还存储了 composer 本身。

webroot文件夹是您的文档根目录;这是您的浏览器所读取的内容。

在本章的后面,我们将介绍一个名为 MVC 的设计模式。

以下示例使用了这种设计模式,这只是一种组织文件结构的方式。

在此示例中,我们将从已实例化的类中传递单个联系人的详细信息,并在浏览器中显示它们。

目前,请注意每个面向对象编程原则在每个文件中的应用,并看看您是否能识别出来。

注意

此代码将无法作为纯 PHP 工作,因为需要框架的结构。本书将教您这些组件如何(以及为什么)以它们目前的方式一起工作。展示此示例的目的是在框架环境中看到面向对象编程概念的实际应用。

以下是一个控制器的示例:

联系人控制器

首先是namespace,这是 composer 知道如何加载文件的方式。没有 composer,将需要手动包含,使用include或require,称为惰性加载,以防止加载不相关的文件并提高性能。

在namespace和use语句之后是类定义(类的蓝图)。在这里,我们将类命名为Contacts,并扩展了已经存在的BaseController类的功能:

联系人模型

解释:

在这里看到的文件是模型;在前面的示例中实例化了Contact模型。

再次,模型包含了namespace。

在此示例中,不需要use语句,因为数据包含在类定义中。

如果数据存储在数据库或其他数据源中,则该类需要扩展BaseModel:

联系人视图

PHP 视图中使用最少的 PHP;数据通常以数组或变量的形式传递给视图,并且样式是由其决定的:

浏览器视图

注意

作为开发人员的早期阶段,您可能会发现自己经常忘记使用分号;事实上,我们的一位书籍作者大卫经常回忆起他曾经花了将近两天的时间来解决他的第一个项目中的一个错误,结果发现问题是缺少了一个分号。

在框架环境中工作时,忘记使用正确的大小写可能会像忘记使用分号一样麻烦。

这并非一定要这样;您可以利用软件专家附加组件,即 PHP linters,它们将检查问题,如忘记使用正确的大小写。 PHP linters 在运行脚本之前突出显示代码。您将在诸如 PHP Storm 之类的 IDE 中找到此类附加组件,或者在 Sublime Text 或 Atom 等文本编辑器中找到:

www.jetbrains.com/phpstorm/ 由 Jet Brains 制作

www.sublimetext.com/ 由 Sublime HQ 制作

atom.io/ 由 Atom 制作

框架的结构

在其核心,MVC 是关注点分离,因此所有数据源都来自模型或数据库资源。您的控制器控制应用程序的流程,并驻留在控制器目录中。所有标记都位于所谓的视图中。它们一起形成了模型视图控制器(MVC)设计模式。

框架文件夹和文件结构

如果您需要修改数据源,您知道要去模型进行操作;如果您想要更改外观,您知道要去视图;要更改应用程序的控制,您去控制器。

注意

请注意,模型并不局限于从数据库中提取数据;这是一个常见的误解。我们之前的示例突出了这一点。

模型中的其他数据源可能是静态数据或从文件或外部源(如 RSS)读取的数据。

解释:

在使用框架时,您的应用程序的大部分将使用 MVC 设计模式构建。

模型和控制器都将从系统目录中存储的BaseModel和BaseController扩展功能。您可能不经常需要更改这些。建立在框架之上的任何应用程序将主要包含在App目录中存储的模型、控制器和视图目录中:

联系人控制器

在这里,控制器正在与模型通信。模型为控制器提供了数据源。控制器是结构的大脑,在这里,它是一系列指令,用于何时提供数据源以及在什么条件下如何行为。

当用户访问特定 URI 时,联系人类将调用一个函数(这是如何工作以及为什么工作将在后面的章节中介绍);这将启动与模型的联系。

在这个例子中,控制器不关心数据源中包含什么;但是,它可以被编程来检查这些数据。

在控制器获取数据后,数据被存储并传递给视图:

联系人模型

解释:

联系人模型具有包含应用程序知识的数据源,但它本身不能利用这些知识。它只能给出管理知识的指示。CRUD 原则在模型中发挥作用,其中有创建、读取、更新和删除模型知识源的方法。

在这种情况下,数据源是一个名字数组:

联系人视图

在视图文件中,您将看到数据是按原样提供的;在这种情况下,视图被提供了一个名字数组。这些名字是详尽的,并且带有任何标记和样式。

在框架中,视图是一个更广泛结构的一部分,它在您的 Web 应用程序中应用全局元素,如标题和页脚,以及 CSS 和 JavaScript 文件。

可以循环遍历数组,但尽可能在控制器中完成所有处理。

浏览器视图

您现在已经确定了如何在 MVC 框架示例中使用面向对象编程原则。

现在,让我们将这些原则付诸实践。

活动:向目录添加联系人

您需要将联系人添加到您正在创建的目录中,以名字数组的形式存储。当请求时,应用程序应返回一个联系人数组。

这样做的原因是为了更好地全面了解如何在实际应用中使用面向对象编程。

按照以下步骤执行此活动:

创建一个目录结构。

创建一个名为Contacts的目录。

在这个目录中,创建一个名为App的目录。

在App目录中,创建三个更多的目录:

模型

视图

控制器

在模型目录中,创建一个名为Contact.php的文件。

在Contact.php文件中,打开 PHP,并创建一个命名空间:

定义一个名为Contact的类:

class Contact

{

}

在这个类中,定义一个名为getContacts()的公共方法;它应该返回一个名字数组:

class Contact

{

public function getContacts()

{

return ['joe', 'bob', 'kerry', 'dave'];

}

}

在 Controllers 目录中,创建一个名为Contacts.php的文件。

在Contacts.php文件中,打开 PHP,并添加一个namespace:

用use语句导入联系人模型:

use App\Models\Contact;

在这种情况下,可以使用别名,写成如下形式(假设 Contact 的别名为Name):

Use App\Models\Contact as Name;

定义一个名为 Contacts 的类:

class Contacts

{

}

创建一个名为index()的公共函数,在该方法中,创建一个名为contacts的局部变量,并创建contact类的一个新实例(这被称为类的实例化):

class Contacts

{

public function index()

{

$contact = new Contact();

}

}

使用赋值运算符创建一个名为contacts的局部变量,在前一步创建的contacts对象实例后调用getContacts()方法(这被称为箭头符号):

public function index()

{

$contact = new Contact();

$contacts = $contact->getContacts();

}

总结

在本章中,我们创建了一个模型和一个控制器,其中控制器Contacts类实例化了模型Contact类。为了实现这一点,我们创建了一个基本的 MVC 文件夹结构,将控件与数据源分开。我们成功地使用了namespace、use语句、方法、访问修饰符、对象和类。我们现在见识到了框架的力量。

在下一章中,您将创建自己的工作框架。我们将学习如何设置项目开发环境、优雅的错误报告以及使用 Whoops 库进行处理。我们还将实现配置类、默认类以及如何设置路由。

第六章:构建 PHP 框架

在上一章中,我们创建了一个模型和一个控制器,其中控制器Contacts类实例化模型Contact类。我们成功使用了namespace,use语句,方法,访问修饰符,对象和类。我们在上一章中见证了框架的力量。

在本章中,我们将从头开始构建一个 MVC 框架。框架实际上只是一种组织代码和结构代码的方式。从一个空目录开始,我们将构建一个完整的工作框架,作为更复杂应用程序的起点。

注意

在上一章中,我们从数组中检索数据。在本课程中,我们将从数据库中检索数据。

到本章结束时,您将能够:

构建一个基本的 PHP MVC 框架

实施在以前章节中涵盖的面向对象的概念

确定如何将控制器路由到指定的 URI

使用 PHP 数据对象(PDO)与数据库交互

使用 HTML 构建和创建可重用的页面(视图)

我们还将实施我们在以前章节中涵盖的面向对象的概念,包括但不限于命名空间,use语句,对象和类,访问修饰符和方法。

我们将学习如何将控制器路由到指定的 URL,并使用 HTML 构建和创建可重用的页面(视图)。最后,我们将使用 PDO 与数据库交互。

设置项目开发环境

本节涉及设置项目开发环境。

这一切都是关于设置索引,.htaccess文件,创建 Web 根目录,设置 Composer 和设置app目录。

索引是框架的引导文件;这最终是所有请求接收的地方。例如,当用户在地址栏中输入 URL 时,就会发出请求。

.htaccess是 mod rewrite 引擎,将所有请求传递给索引文件。

Web 根目录是公共文件夹,可以被浏览器访问,也用于存储 Web 应用程序的所有资产的索引和.htaccess。这将包括图像,CSS 和 JavaScript 文件。

Composer是用于管理系统依赖库的包管理器。

应用程序目录是您的应用程序;这是您的视图,模型,控制器和助手将存储的地方。助手是帮助开发人员经常遇到的单个常见任务的紧凑方法。开发人员可能发现自己重复执行相同的任务,并将创建一个帮助程序类,其中包含一个或多个方法来帮助完成此任务。这可能是格式化日期,执行特定计算等等。

注意

引导符号象征着设置框架的过程通常被称为引导。这不应与流行的 CSS Grid 命名为 Bootstrap 混淆。这本质上紧密地将框架的所有核心部分联系在一起。

使用 Composer 和 Whoops 进行错误报告

对于这个项目,我们将使用 Whoops 库来处理错误。Whoops 库是用于检查项目中可能发生的错误的工具。这个库已经打包并提供给其他开发人员在他们的项目中使用。

使用 Whoops,当 PHP 发生错误时,您将能够看到显示信息,而不是来自服务器的标准单调的错误报告:

Composer 将管理此依赖的使用,因为它被认为是 PHP 开发人员非常广泛使用和非常受欢迎的包管理器之一。

Composer 是 PHP 中的依赖管理工具。它允许您声明项目所依赖的库,并且会为您管理(安装/更新)它们。要安装 Composer,请访问getcomposer.org/download/。

想象一种情况,您必须为 PHP 安装一个依赖项,为了安装该依赖项,您需要安装其他额外的依赖项。Composer 帮助您处理这个问题。它用于为您处理所有工作,以安装一个库,因为它会一起下载所有库和依赖项。

设置 Composer

我们将在本节中设置 Composer。要做到这一点,请按照以下步骤操作:

创建一个文件夹来存储框架文件。

注意

随意为您的文件夹命名,只要全部小写且没有空格即可。

app保存应用程序文件

system保存核心框架文件

webroot将保存公开访问的文件

接下来,我们将设置 Composer。在框架文件夹的根目录中创建一个名为composer.json的文件。

该文件包含一个 JSON 对象,将根据需要自动加载类。我们将使用 PSR-4 自动加载。

注意

PSR-4 自动加载将根据其命名空间加载类在使用时。例如,新的App\Models\Contact()将告诉 Composer 自动加载名为Contact的文件,该文件存储在文件夹app\Models中。

打开composer.json并创建一个App和System定义。

这将告诉 Composer,我们称为命名空间的一切,无论是App还是System,都要在app或system文件夹中查找类。

我们还加载了一个名为Whoops的第三方包。我们通过在require块中将其包含为依赖项来加载此包:

{

"autoload": {

"psr-4": {

"App\\" : "app/",

"System\\" : "system/"

}

},

"require": {

"filp/whoops": "².1"

}

}

保存composer.json。现在,在webroot中,创建两个文件:index.php和.htaccess。

打开.htaccess。

出于安全原因,如果一个文件夹不包含index文件,我们不希望其内容显示在浏览器中。要禁用目录浏览,请输入:

Options –Indexes

接下来,将检查是否启用了 mod rewrite:

//more code

注意

mod rewrite 提供了一个基于规则的重写引擎,可以实时重写请求的 URL。它有助于使 URL,因此index.php?page可以变成/page。

接下来,打开重写引擎并将基础设置为此文件夹的根目录:

RewriteEngine On

RewriteBase /

要强制使用 HTTPS,可以取消下面的#,但只能在启用了 HTTP 的服务器上这样做。

接下来,定义重写条件。

注意

这是为了忽略尾随斜杠和存在的文件夹和文件。只有动态文件应该被路由,例如,不存在作为物理文件的 URL。

最后的规则将所有请求传递到index.php?$1。$1是请求 URL 中第一个/之后的请求。

RewriteCond基本上意味着“只有在这是真的时候才执行下一个RewriteRule”。

RewriteRule基本上意味着,如果请求匹配^(.+)$(匹配除服务器根目录之外的任何 URL),它将被重写为index.php?$1,这意味着对联系人的请求将被重写为index.php?contact:

RewriteRule ^(.*)$ index.php?$1 [QSA,L]

QSA 表示此标志强制重写引擎将查询字符串部分附加到现有字符串中,而不是替换它。

安全套接字层(SSL)在您的 Web 服务器和 Web 浏览器之间创建加密连接。这样可以阻止任何数据从您的计算机被拦截到 Web 服务器。建议使用 HTTPS。

完整的文件应该如下所示:

# Disable directory snooping

Options -Indexes

# Uncomment the rule below to force HTTPS (SSL)

………..

RewriteRule ^(.*)$ index.php?$1 [QSA,L]

注意

有关完整的代码片段,请参阅代码文件夹中的 Lesson 6.php 文件。

保存文件。现在,打开index.php。

首先,启动 php,然后进行检查以确定vendor/autoload.php是否存在(它尚不存在),并要求该文件。

注意

这是一个重要的步骤。只有在初始化 Composer 后,autoload.php 文件才会存在。在要求文件之前进行检查是一种预防措施,用于避免致命错误。

我们应该通知用户 Composer 正在请求什么以及去哪里获取它。我们通过使用else子句来做到这一点:

if(file_exists('../vendor/autoload.php')){

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

} else {

echo "

Please install via composer.json

";

echo "

Install Composer instructions: https://getcomposer.org/doc/00-intro.md#globally

";

echo "

Once composer is installed navigate to the working directory in your terminal/command prompt and enter 'composer install'

";

exit;

}

接下来,我们将设置我们的环境。

我们将定义一个名为ENVIRONMENT的常量,并给它一个开发的值。当进入production时,将environment设置为production。

注意

在生产环境中,您不希望显示错误。拥有一个环境常量是设置应用程序环境的好方法:

define('ENVIRONMENT', 'development');

现在,根据environment常量,我们可以设置适当的错误报告级别:

if (defined('ENVIRONMENT')){

switch (ENVIRONMENT){

case 'development':

error_reporting(E_ALL);

break;

case 'production':

error_reporting(0);

break;

default:

exit('The application environment is not set correctly.');

}

}

注意

在开发模式下,将显示所有错误,但在生产模式下,将不显示任何错误。

完整的文件看起来像这样:

if(file_exists('../vendor/autoload.php')){

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

} else {

……

error_reporting(0);

break;

default:

exit('The application environment is not set correctly.');

}

}

注意

有关完整的代码片段,请参阅代码文件夹中的 Lesson 6.php 文件。

注意

现在将创建一个名为 vendor 的新文件夹。这个文件夹是 Composer 安装其所需文件和任何第三方依赖项的位置。

现在您可以返回浏览器并重新加载页面。现在应该看到一个空白页面。

注意

这意味着 Composer 正在工作,但我们还没有请求加载任何内容。

当打开 Whoops 包时,视图中的错误将在屏幕上显示代码的完整堆栈跟踪,以显示框架如何执行代码的路径。这可以帮助开发人员通过跟踪其代码的路径来隔离问题。

活动:使用 Composer 安装依赖项

假设您正在开发一个 PHP 项目,并且您的项目需要很多依赖项。您有严格的截止日期,但是在添加这些依赖项之前,您无法继续。您发现可以使用 Composer 自动安装依赖项。现在您需要安装 Composer。

这项活动的目的是让您熟悉 Composer 安装。

要执行此活动,请按照以下步骤进行:

通过打开终端或命令提示符来运行框架。

如果在 Windows 上,导航到framework文件夹并启动 php 服务器:

php –S localhost:8000 –t Webroot

注意

-S表示运行服务器并使用 localhost:8000 作为其地址,-t Webroot将文档root设置为Webroot文件夹。

终端输出将如下所示(您的系统上的某些细节可能会有所不同):

PHP 7.1.4 Development Server started at Wed Nov 29 20:37:27 2017

Listening on http://localhost:8000

Document root is /Users/davidcarr/Dropbox /projects/localsites/framework/webroot

Press Ctrl-C to quit.

现在,转到http://localhost:8000,您将看到我们在index.php的else语句中编写的 Composer 说明。

这是因为我们还没有设置 Composer。我们可以在终端中输入以下内容来完成这一步:

composer install

输出将如下所示:

Loading composer repositories with package information

Updating dependencies (including require-dev)

Package operations: 2 installs, 0 updates, 0 removals

- Installing psr/log (1.0.2) Loading from cache

- Installing filp/whoops (2.1.14) Downloading: 100%

filp/whoops suggests installing symfony/var-dumper (Pretty print complex values better with var-dumper available)

filp/whoops suggests installing whoops/soap (Formats errors as SOAP responses)

Writing lock file

Generating autoload files

请注意,现在将创建一个名为vendor的新文件夹。这个文件夹是 Composer 安装其所需文件和任何第三方依赖项的位置。

现在,返回浏览器并重新加载页面。

注意

现在应该看到一个空白页面。

这意味着 Composer 正在工作,但我们还没有请求加载任何内容。

回到编辑器中的 index.php,在文件底部添加以下行:

//initiate config

$config = App\Config::get();

new System\Route($config);

这将加载我们的config类并设置我们的路由。

保存index.php并在app文件夹中创建一个名为Config.php的新文件。

注意

请注意将文件命名为Config而不是config。在基于 Unix 的系统(如 Mac 和 Linux)上,大小写敏感。

我们已经到达了本节的末尾。我们学会了如何引导应用程序,这允许单一入口点,并且学会了如何使用 Composer 自动加载类。我们讨论了如何处理错误,最后,我们讨论了框架的构建过程。

在下一节中,我们将设置配置类并设置路由。

配置类、默认类和路由

在本节中,我们将学习configuration类,并且我们还将设置路由。

我们将设置config类。这将位于app文件夹的根目录下。config类存储默认控制器,要加载的default方法以及数据库凭据。在index文件的开头,您将把config类传递给route类。route类控制何时加载以及何时加载。现在的重点是configuration类和路由。其他组件将在后面的章节中更详细地讨论。

configuration类是框架选项的数组,包括以下内容:

数据库源凭据

默认控制器的路径

默认方法的路径

在本节中,我们还将创建一个负责加载视图的视图类,这使得可以显示表示层的位置。

在设置路由时,我们正在告知框架在文件系统中查找与 URL 匹配的位置。

在加载正确的文件时,这将是所需的控制器类。我们将激活所需的方法、所需的模型和所需的视图。

我们将做所有这些,以便用户可以在他们的浏览器中看到他们通过简单点击链接请求的内容,这又称为向服务器发出请求。

然后,我们将创建route类,它从 URL 中获取段,以便知道要加载哪个控制器和方法以及要传递的参数。

例如,URL http://localhost:8000/contacts/view/2 表示转到 contacts 控制器的 view 方法。在这种情况下,数字 2 表示传递给 view 方法的参数。

注意

configuration类更常被开发人员称为配置类。

配置是用户可能寻求帮助以了解如何记住其框架项目的重要细节的自然位置。建议开发人员开发一个系统来记住其项目的细节。

如果他们计划将其项目开源,这可能会有所帮助。对于开发人员来说,如果他们需要在以后的某个日期记住项目的细节,这也可能会有所帮助,因为可能会有几个月甚至几年的时间过去,开发人员需要重新访问该项目。

这些可能是什么样的细节?

版本号 - 随着时间的推移,开发人员可能会进行添加和改进,这可能会影响代码基础的核心。知道你正在使用的版本可以帮助你在以后选择适当的编程方法。

鸣谢 - 为使用其他开发人员的工作给予信用是一个好习惯。如果你未能这样做,你可能会收到一个不愉快的未署名开发人员的电子邮件。

作者详情 - 开源项目的用户可能会从原始开发人员的联系方式中受益。不愉快的未署名开发人员需要一个地方发送不愉快的电子邮件。

以下是一个 Config 类的示例

加载视图文件

完成本节后,我们将查看一个示例,以演示加载视图文件的能力。但是,在此阶段尚未创建任何视图,因此使用自定义的 404 页面代替。

本节的示例在浏览器中加载了框架。最初,您将在浏览器中看到一个 404 消息,因为找不到视图。这是因为默认控制器不存在。

views文件夹中存在一个名为404.php的示例文件,其中包含消息“找不到文件”。保存文件并刷新新创建的 404 页面的浏览器。

打开 php 并为文件设置一个namespace为 App。

注意

该类属于 App 命名空间,因为它存储在app文件夹中。

接下来,定义一个名为Config的类,并创建一个名为get的方法。

注意

get方法需要返回一个数组。数组的键将是用于路由和数据库凭据的设置:

有关完整的代码片段,请参阅代码文件夹中的Lesson 6.php文件。

class Config {

……

public static function get()

'db_name' => 'mini',

'db_username' => 'root',

'db_password' => '',

];

}

}

注意

前面的命名空间定义保存了App\Controllers的路径。请注意双反斜杠 - 这是因为反斜杠经常被转义,因此使用双反斜杠可以阻止其被转义。

当我们编写路由时,命名空间定义、默认控制器和默认方法将变得清晰。

最后,设置数据库属性。

设置要使用的数据库类型及其位置的数据库属性,然后是数据库名称以及访问数据库的用户名和密码。

您需要访问 MySQL 数据库以创建数据库。为了设置本地数据库,建议使用 MariaDB。要下载 MariaDB,请按照mariadb.com/downloads/mariadb-tx上的说明进行操作。

注意

在这个例子中,我们有一个名为 mini 的数据库,我的用户名是 root。我们没有密码,所以我们将其留空。

保存Config.php文件。

在routing类设置之前,我们需要创建一个View类。这个类将负责加载view文件,当找不到 URL 时还会显示 404 页面。

在 system 中,创建一个名为View.php的新文件。

打开 php 并将命名空间设置为System.接下来,定义一个名为View的类,并创建一个名为render的方法,该方法接受两个参数,$path和$data。

注意

$path将保存请求文件的路径。

$data将保存要传递给view文件的内容。

$data是可选的;请注意它的默认值是false。这意味着如果只传递一个参数给render方法,那么数据将不会被使用。

在方法 ID 内,一个布尔值检查$data。如果它是false,则忽略;否则,使用foreach循环遍历数据。在每次循环中,数据都会提取到一个本地变量中。

循环结束后,设置视图文件将存储的相对路径,本例中为app/views/,后跟请求的视图。

最后,进行检查以确保view文件存在并且需要它,否则会生成错误:

注意

有关完整的代码片段,请参阅代码文件夹中的Lesson 6.php文件。

namespace System;

/*

* View - load template pages

*

*/

class View {

…….

} else {

die("View: $path not found!");

}

}

}

保存文件并在system文件夹内创建一个名为Route.php的新文件。

打开 php 并将命名空间设置为System.。

我们刚刚创建的View类需要对这个类可用。要导入它,请添加:

use System\View;

注意

这加载了View文件。PHP 之所以知道在哪里找到文件,是因为命名空间,这是 Composer 在起作用。以这种方式导入类非常有帮助。

现在,创建一个名为Route的类和一个名为__construct的方法,该方法期望一个名为$config的参数:

use System\View;

class Route

{

public function __construct($config)

{

现在,设置以下变量:

$url = explode('/', trim($_SERVER['REQUEST_URI'], '/'));

$controller = !empty($url[0]) ? $url[0] : $config['default_controller'];

$method = !empty($url[1]) ? $url[1] : $config['default_method'];

$args = !empty($url[2]) ? array_slice($url, 2) : array();

$class = $config['namespace'].$controller;0

注意

$url将保存请求路由的数组形式,如/page/requested。工作原理是:当运行 explode 时,它会在请求的 URI 中找到一个斜杠,$_SERVER 使其可用。

接下来,$controller方法使用三元运算符来检查$url 的第 0 个索引是否存在,否则使用 Config 类中定义的 default_controller。

$method检查是否存在$url[1],否则从 config 类中读取。

\(args 将获取\)url 的第一个 2 个索引之后的所有其他索引。

$class保存在Config类中设置的控制器的路径。

这些参数的作用是从请求的 URL 中获取控制器、方法和参数。例如:

http://localhost:8000/contacts/view/2

这导致:

联系人 = 联系人类。

视图 = 联系类内的视图方法。

2 = 传递给方法的参数。

如果请求的 URL 是 http:😕/localhost:8000/,则不需要请求控制器或方法,因此将使用默认控制器和方法,如在system\Config.php中设置的那样。

设置这些变量后,进行检查,即如果类不存在,则调用Route类中存在的not_found方法(尚未设置):

//check the class exists

if (! class_exists($class)) {

return $this->not_found();

}

接下来,检查方法以确保它存在:

//check the method exists

if (! method_exists($class, $method)) {

return $this->not_found();

}

接下来,设置一个类的实例:

//create an instance of the controller

$classInstance = new $class;

通过调用call_user_func_array并将类实例和方法的数组以及任何参数作为第二个参数传递来运行该类:

//call the controller and its method and pass in any arguments

call_user_func_array(array($classInstance, $method), $args);

如果调用了一个不存在的route,则需要一个not_found方法。这将调用render方法并将404作为参数传递。这将尝试加载app/view/404.php,如果存在的话:

//class or method not found return a 404 view

public function not_found()

{

$view = new View();

return $view->render('404');

}

完整的类如下所示:

注意

有关完整的代码片段,请参阅代码文件夹中的Lesson 6.php文件。

use System\View;

class Route

…….

{

$view = new View();

return $view->render('404');

}

}

}

操纵输出

本节向您展示了如何操纵上一个示例的输出。以下是操作步骤:

加载框架http://localhost:8000,你会看到以下输出:

View: 404 not found!

注意

这是因为默认控制器还不存在,app/views/404.php 也不存在。

在app文件夹中创建一个views文件夹,并创建一个名为404.php的文件。输入消息,比如'文件找不到。',然后保存文件。

重新加载框架在你的浏览器中,你现在会看到你的消息。

在本节中,我们涵盖了configuration类,我们看到了配置类如何位于root文件夹的顶部。我们还看到了如何设置路由,其中我们执行了view页面的加载。

在下一节中,我们将介绍基础控制器,它定义了 MVC 框架的主要功能。

基础控制器、默认状态和路由

基础控制器类——因为 MVC 框架的性质——需要一个默认状态。

默认视图是由默认控制器类中的默认方法加载的。

从这个默认控制器类中,加载系统中的所有其他控制器。

默认的Controller类和默认方法的创建将是我们在本节中构建的重点。

注意

模型不一定要包含在控制器中,视图也可以独立于数据源工作。

设置基础控制器、默认状态和路由

在本节中,我们将看到如何设置基础控制器、默认状态和路由。以下是步骤:

视图:

现在,让我们设置默认视图。在app\views中创建一个名为default.php的文件,并写入内容Hello from default view或其他消息。

这将在框架的主页上显示。

控制器:

在我们开始构建应用程序控制器之前,我们需要一个所有其他控制器都可以继承的基础控制器。这样做的原因是控制器可以使用基础控制器中定义的任何属性或方法。

创建一个名为BaseController.php的新文件,并将其保存在system文件夹中。

打开 php 并将命名空间设置为System。定义一个名为BaseController的类。

定义两个名为$view和$url的类属性。这两个属性都将具有公共的访问修饰符,这意味着在使用BaseController的任何地方,这些属性都将可用。

接下来,创建一个construct方法,然后设置一个View类的新实例。这样$this->view就可以用来调用extended控制器中的view的render方法。

接下来,将getUrl()方法分配给$this->url属性。这将调用另一个方法来获取当前的 URL。

现在,对环境模式进行检查。如果设置为开发模式,那么将创建一个新的 Whoops 错误处理程序的实例。这个 Whoops 类是由 Composer 引入的,如 composer.json 文件中所定义的。

当在浏览器中运行代码时,Whoops类将提供丰富的错误堆栈跟踪。

最后,定义一个getUrl()方法,它将返回请求的 URL:

注意

有关完整的代码片段,请参考代码文件夹中的Lesson 6.php文件。

use System\View;

class BaseController

{

public $view;

………

$url = isset($_SERVER['REQUEST_URI']) ? rtrim($_SERVER['REQUEST_URI'], '/') : NULL;

$url = filter_var($url, FILTER_SANITIZE_URL);

return $this->url = $url;

}

}

Home 控制器:

在app/Config.php中,我们将default_controller设置为home:

//set default controller

'default_controller' => 'Home',

//set default method

'default_method' => 'index',

现在让我们创建它。在app文件夹中创建一个Controllers文件夹,并创建一个名为Home.php的文件。

注意

所有类都应该以大写字母开头,每个后续的单词都应该大写。

打开 php 并将命名空间设置为App\Controllers。这个命名空间引用了文件夹结构。

接下来,通过调用其命名空间和名称来导入BaseController。

定义一个名为Home的类,并扩展BaseController。

这将允许Home控制器访问$this->view并加载视图。

创建一个名为index的方法,然后返回$this->view->render并传递要加载的文件名。

在这种情况下,传递默认值,将加载app\views\default.php:

namespace App\Controllers;

use System\BaseController;

class Home extends BaseController

{

public function index()

{

return $this->view->render('default');

}

}

活动:探索结果

现在我们将能够看到任务的输出,就像在演示文件中看到的那样。按照以下步骤来做:

在浏览器中打开你的框架http://localhost:8000,你将看到你的默认视图文件被加载。

记得那个 Whoops 类吗?好吧,让我们看看它的作用。打开你的default.php视图文件,在文件末尾添加这段代码。打开 php 并写点东西,但不是在一个字符串中。代码应该如下所示:

Hello from default view.

现在,在浏览器中保存并重新加载页面,你会看到:

这个页面告诉你错误是什么,但也显示了问题所在的代码片段和完整的堆栈跟踪,这样你就可以追踪从执行到失败的过程:

现在,从default.php中删除你的修改,使它只包含你的原始内容,保存并重新加载页面。你将再次看到你的页面正常加载。

现在,让我们看看如何访问一个新的方法。在你的 Home 控制器中,创建一个名为packt的新方法,加载一个名为packt的视图。

在 app\views 中创建一个名为packt.php的新视图文件,并输入文本'Hello from Pack!'

通过转到home/packt http://localhost:8000/home/packt来加载页面。

现在,你将看到你的packt视图文件的内容。

在这一部分,我们对默认状态在我们的项目中扮演的角色有了更好的理解。这个项目需要这些基本方法来最初运行和扩展。

我们通过构建默认状态,包括baseController和baseMethod,获得了经验。

在下一节中,我们将学习 PDO,这是一个轻量级的接口,用于在 PHP 中访问数据库。

使用 PDO

在这一部分,我们将创建 PDO 包装器,并使用数据库作为我们模型的数据源。

从这一部分开始,我们将能够在我们的框架项目中使用数据库。

这里将涵盖六种方法。

我们有一个get方法——这是用于创建与数据库的连接,并确保它是一个单例实例,这意味着它只能有一个实例:

我们有一个raw方法来运行原始的、不安全的查询

一个select方法,用于运行安全查询,从数据库中选择记录

一个insert方法来创建新的记录

一个update方法来更新记录

一个delete方法来删除记录

一个truncate方法来清空一个表

这样做的目的是让你的 CRUD 工作。没有这个类,CRUD 功能将不可能实现。

基本模型是我们使用数据库助手创建数据库连接的地方。

这将允许其他模型类从这个模型扩展并使用数据库连接。这只包括一个单一的方法:

构造:

这个方法负责将配置传递给数据库助手,以创建数据库连接。

现在,我们准备开始使用数据库。

注意

数据库访问:如果你发现你没有访问数据库客户端或 PHP 管理网页界面的权限,那么一个备用选项被包括在内,所有学生都可以使用它来创建数据库和插入数据。

接下来的部分是关于在 apps 文件夹中创建第一个模型的。我们将创建模型contact.php,并讨论最佳实践和命名约定,以及从之前创建的基本模型中扩展,以及设置一个从数据库中显示记录的方法。

接下来,我们将创建一个contact控制器,它继承自基本控制器。在调用索引方法之前导入联系人模型,并将记录从模型传递到视图。在那个视图中,我们将浏览记录并逐行显示它们。

然后我们打开浏览器,转到联系人控制器,看到联系人显示在页面上。

要加载不同的控制器,过程与前一个子主题中描述的相同。创建一个控制器,设置其命名空间,并定义存在于基本控制器中的类,要么有一个在调用控制器名称时加载的索引方法,要么使用不同的名称并通过调用您的控制器名称/方法名称来访问它。

我们几乎已经完成了框架的设置。现在,我们可以创建控制器和方法来加载页面并将数据传递给视图。对于静态站点来说,这很棒 - 它将保持您的代码有组织并且运行速度快 - 但缺少的一个重要组件是使用数据库的能力。

在这一点上,我们将创建一个数据库助手。这是一个存储在名为helpers的公共文件夹中的类的花哨名称。助手是不适合控制器或模型的类,而是独立的类来扩展功能。

数据库助手将有六种方法:

get() - 设置数据库连接

raw() - 运行原始的、不安全的查询

select() - 运行查询以从数据库中选择记录

insert() - 创建新记录

update() - 更新现有记录

delete() - 删除现有记录

truncate() - 清空表

创建联系人控制器并查看记录

在本节中,我们将开始创建我们的联系人控制器。按照以下步骤进行操作:

首先,在app文件夹内创建一个名为Helpers的文件夹,并创建一个名为Database.php的新文件。

打开 php 并将命名空间设置为App\Helpers。

接下来,我们需要导入 PDO。

注意

PDO 是一个数据库抽象层;它是一个支持 12 种不同数据库引擎的包装器,包括 MySQL。这是我们将用来与数据库交互的工具。

要导入 PDO,请使用use语句:

namespace App\Helpers;

use PDO;

接下来,定义一个名为Database的类,它扩展了PDO。在类内部创建一个名为$instances的属性,并将其设置为数组数据类型。

$instances属性将用于确保只有一个数据库连接在使用:

class Database extends PDO

{

protected static $instances = array();

接下来,创建一个名为get()的方法,接受一个名为$config的参数。这将是在app\Config.php中设置的Config。

在这个方法中,设置本地变量以保存数据库凭据。这些值将从$config数组中提取。

然后,创建一个名为$id的变量。这将保存所有数据库本地变量以创建标识符。接下来,执行一个检查,检查$instance属性是否已经有了这个$id。

如果$instances确实有$id,那么它将返回$instance,否则将尝试新的 PDO 连接。

注意

连接到 PDO 时,传递数据库凭据并将字符集设置为 UTF-8。

在下一行,错误模式设置为异常。这意味着任何异常都将被引发和显示。

现在,将$instance设置为当前连接并返回$instance:

注意

有关完整的代码片段,请参考代码文件夹中的Lesson 6.php文件。

GET method

public static function get($config)

{

// Group information

…….

// Setting Database into $instances to avoid duplication

self::$instances[$id] = $instance;

//return the pdo instance

return $instance;

}

RAW 方法

创建一个名为raw的方法。这是一个非常简单的方法。它接受一个参数,即 SQL 语句。$sql传递给$this->query,然后直接运行查询:

注意

这对于执行不需要安全的查询非常有用。如果没有进行检查,查询将按原样执行,并返回结果。

public function raw($sql)

{

return $this->query($sql);

}

SELECT 方法:

接下来,创建一个名为select()的方法。这将接受四个参数:

$sql - SQL 查询

$array - 要绑定到查询的任何键(可选)

$fetchMode - 设置 PDO 提取模式,默认为对象(可选)

$class - 用于指定与提取模式一起使用的类

注意

在方法内部,这样您就不必编写$this->db->select('SELECT * FROM table') we 将向 SQL 查询添加 select,前提是它还没有。这是通过将大小写更改为小写,然后使用substr来检查$sql的前七个字母。如果不等于 select,则在开头添加 select。

接下来,准备查询。这将设置 SQL 查询而不运行它。接下来,循环遍历$array,并将任何值分配给特定的数据类型。如果值是 INT,则使用 PARAM_INT 数据类型,否则数据类型将使用字符串。

最后,执行运行。这将\(SQL 传递到服务器,并将绑定的\)array 键分开,这意味着永远不会发生 SQL 注入,从而产生安全的查询。

查询执行后,然后返回响应。默认情况下,返回一个对象:

注意

有关完整的代码片段,请参阅代码文件夹中的Lesson 6.php文件。

public function select($sql, $array = array(), $fetchMode = PDO::FETCH_OBJ, $class = '')

{

……

return $stmt->fetchAll($fetchMode);

}

}

插入方法

要向数据库插入新记录,需要插入查询。创建一个名为 insert 的新方法,带有两个参数:

$table - 表的名称

\(data - 要插入到\)table 中的键和值的数组

使用 ksort(\(data)对\)data 数组进行排序。

接下来,将所有数组键提取到名为$fieldNames的变量中。这是使用 implode 完成的,设置每个键之间的逗号,并对$data 运行array_keys()。

现在,再做一次相同的操作,这次添加, :作为 implode 选项,并将其保存到名为$fieldValues 的变量中。

然后,使用$this->prepare,可以编写一个 SQL 命令,将$fieldNames设置为$fieldValues的值,用于$table。循环遍历$data并绑定值。

最后,返回最后插入记录的 ID。当您需要主键在记录插入后立即使用时,这将非常有用:

public function insert($table, $data)

{

ksort($data);

$fieldNames = implode(',', array_keys($data));

$fieldValues = ':'.implode(', :', array_keys($data));

$stmt = $this->prepare("INSERT INTO $table ($fieldNames) VALUES ($fieldValues)");

foreach ($data as $key => $value) {

$stmt->bindValue(":$key", $value);

}

$stmt->execute();

return $this->lastInsertId();

}

更新方法

接下来,创建一个名为 update 的方法,带有三个参数:

$table - 表的名称

$data - 要更新的数据的数组

$where - 一个键和值的数组,用于设置条件,例如,['id' => 2],其中 id 等于 2

对$data进行排序,然后提取$data。

在循环内,附加到名为$fieldData的变量。添加$key = :$key。接下来,通过调用trim()删除任何右侧的空格。

接下来,循环遍历$where数组。在每次循环中,分配$key = :$key。这将创建一个占位符列表,以便稍后捕获绑定。

再次,删除任何右侧的空格,然后使用$this->prepare并编写更新 SQL,传递表名,后跟fieldDetails和WhereDetails。

接下来,将键绑定到:$key占位符并执行查询。

最后一步是返回rowCount()。这是已更新的记录数:

注意

有关完整的代码片段,请参阅代码文件夹中的Lesson 6.php文件。

public function update($table, $data, $where)

{

ksort($data);

$fieldDetails = null;

…….

}

$stmt->execute();

return $stmt->rowCount();

}

删除方法

创建一个名为 delete 的新方法,带有三个参数:

$table - 表的名称

$where - 用于确定 where 条件的键值数组

$limit - 要删除的记录数,默认值为 1,传递 null 以删除数字限制

在方法内部,对$where进行排序循环,并从数组的键和值设置占位符。

准备查询并编写 SQL 命令,传递$table,$where和$limit。

最后一步是返回 rowCount()。这是已删除的记录数:

注意

有关完整的代码片段,请参阅代码文件夹中的Lesson 6.php文件。

public function delete($table, $where, $limit = 1)

{

ksort($where);

……..

$stmt->execute();

return $stmt->rowCount();

}

截断方法

最后要创建的方法称为 truncate,它接受一个参数,$table:

$table - 表的名称

在方法内部,调用$this->exec 和 SQL 命令 TRUNCATE TABLE $table。这将清空表,导致没有记录。所有主键将被重置为 0,就好像从未使用过表一样:

public function truncate($table)

{

return $this->exec("TRUNCATE TABLE $table");

}

完整的类如下所示:

注意

有关完整的代码片段,请参阅代码文件夹中的Lesson 6.php文件。

use PDO;

class Database extends PDO

{

/**

* @var array Array of saved databases for reusing

*/

*/

……

{

return $this->exec("TRUNCATE TABLE $table");

}

}

保存这个类。这是一个复杂的类,我们将要编写的其余代码要简单得多。在接下来的几页中,我们将使用数据库助手,并且随着使用方法的目的和用途,它们将变得清晰。

基本模型

我们的下一个任务是创建一个basemodel类,它将使用数据库助手连接到数据库并返回实例。这将允许其他模型类从这个类扩展并使用数据库连接。

在系统文件夹内创建一个名为BaseModel.php的文件。

打开 php 并将命名空间设置为 System。

通过调用它们的命名空间在 use 语句中导入 Config 类和 Database 助手类。

定义类并调用 BaseModel,然后创建一个受保护的属性叫做$db。这是其他模型将用来与数据库交互的。

创建一个__construct()方法。这将在类被实例化时立即运行。在这个方法内,创建一个名为$config的本地变量,并将其赋值为Config::get()。

接下来,创建一个 Database 助手的新实例并调用get方法并传入$config。

这个类看起来像这样:

注意

有关完整的代码片段,请参考代码文件夹中的Lesson 6.php文件。

/*

* model - the base model

*

……..

//connect to PDO here.

$this->db = Database::get($config);

}

}

现在,我们准备开始使用数据库。在继续我们的代码库之前,打开你之前连接的数据库,可以使用 phpmyadmin 或 MySQL 客户端,也可以使用终端。

创建一个数据库——我们将其称为 mini——并创建一个名为contacts的表,其中包含两列:ID 和 name。

注意

如果你没有 MySQL 客户端,你可以通过终端来使用:

mysql -u root

用你的数据库用户名替换 root。Root 是默认值。我默认安装了 MariaDB。没有密码,但如果你需要输入密码,传递密码标志-p:

mysql -u root -p

创建一个新的数据库。

创建数据库mini。

选择那个数据库:

use mini

注意

现在使用名为 mini 的数据库。

数据库是空的,所以让我们创建一个名为 contacts 的表:

create table contacts ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT,

`name` varchar(255) DEFAULT NULL,

PRIMARY KEY (`id`)

) ENGINE=InnoDB DEFAULT CHARSET=latin1;

要查看你的表的列表:

show tables;

+-----------------+

| Tables_in_mini |

+-----------------+

| contacts |

+-----------------+

1 row in set (0.00 sec)

Insert data into the table

insert into contacts (name) values('Dave');

insert into contacts (name) values('Markus');

this will insert the records to see the contents of the table:

select * from contacts;

+----+--------+

| id | name |

+----+--------+

| 1 | Dave |

| 2 | Markus |

+----+--------+

2 rows in set (0.00 sec)

通过这几个命令,数据库已经创建,表也已经被创建,并且已经填充了两条记录。

活动:创建和执行模型

我们已经创建了 contact 控制器并查看了结果。现在我们需要为我们的应用程序实现模型。

这个活动的目的是为我们的应用程序实现模型。

回到框架,我们现在准备创建我们的第一个模型。按照以下步骤进行:

在app文件夹中,创建一个名为Models的新文件夹。这是所有模型将被存储的地方。现在,创建一个名为 Contact.php 的新文件。

注意

将你的模型命名为单数记录是最佳实践,所以在这种情况下,Contact 代表一个联系人表。

在Contact.php中,打开php并将命名空间设置为App\Models。

导入BaseModel并创建一个名为 Contact 的类,并扩展BaseModel。

创建一个名为getContacts()的方法。这个方法将用于获取数据库中存储的所有联系人。

调用$this->db->select()来调用数据库助手的select方法,并编写SQL * FROM contacts。

注意

最佳实践是将命令如SELECT,FROM,WHERE,GROUP BY和ORDER BY写成大写,这样在你的代码中清楚地表明这些命令。

这个模型看起来像这样:

namespace App\Models;

use System\BaseModel;

class Contact extends BaseModel

{

public function getContacts()

{

return $this->db->select('* FROM contacts);

}

}

现在,我们需要运行这个模型。这个最好的地方是在一个控制器内。在app\Controllers文件夹内创建一个名为Contacts的新控制器。

这个类扩展自BaseController,并有一个名为index的方法:

namespace App\Controllers;

use System\BaseController;

class Contacts extends BaseController

{

public function index()

{

}

}

让index方法加载一个名为contacts/index的视图:

public function index()

{

return $this->view->render('contacts/index');

}

在app\views中创建一个名为contacts的文件夹,并创建一个名为index.php的文件。

如果你现在运行这个并转到localhost:8000/contacts,你会得到一个空白页面或者看到contacts/index.php的内容,只要你输入了一些内容。

回到contacts controller,我们需要导入contact模型。我们通过使用use语句并设置命名空间到模型来做到这一点:

use App\Models\Contact;

在index方法内,创建一个contact模型的新实例,并调用getContacts()方法。将其赋值给一个名为$records的变量:

$contacts = new Contact();

$records = $contacts->getContacts();

接下来,将$records传递给view:

return $this->view->render('contacts/index', compact('records'));

注意

使用compact()是一个干净的方法来放置一个表示变量的字符串名称。这将读取$records并将其传递给视图:

在 app\views\contacts\index.php

打开php并检查$records是否存在,然后进行foreach循环并循环遍历每条记录。从$row对象中echo出name键。添加一个包含
标签的字符串-这将导致每次循环都在新行上:

if (isset($records)) {

foreach ($records as $row) {

echo $row->name.'
';

}

}

保存并在浏览器中运行,转到http://localhost:8000/contacts。您将看到数据库中存储的contacts表中的联系人列表。

总结

在本章中,我们对数据库类在项目中的作用有了更好的理解,这在开发人员与数据库交互时被使用。它是 PDO 查询的包装器。他们不需要直接调用它,因为他们是从中扩展的。

我们使用的唯一库叫做 Whoops,它将以可读的格式显示错误。

我们还获得了构建默认状态的经验,包括baseController和baseMethod。

在下一章中,我们将构建一个登录系统和用户登录和退出的身份验证。这将扩展我们到目前为止所涵盖的内容,并引入新的概念。我们还将在下一章中构建密码恢复系统。

第七章:身份验证和用户管理

在上一章中,我们更好地理解了数据库类在项目中的作用,这在开发人员与数据库交互时使用。

我们使用的唯一库叫做 Whoops,它将以可读格式显示错误。我们还获得了构建默认状态的经验,包括baseController和baseMethod。

在本章中,我们将专注于项目的安全方面,即身份验证。我们将构建登录表单,该表单与数据库交互以验证用户的身份。最后,我们将介绍如何在我们的应用程序中设置密码恢复机制。

在本章结束时,您将能够:

为他们的应用程序构建默认视图

构建密码管理和重置系统

为系统应用程序中的模块构建 CRUD

设置路径和包含 Bootstrap

在本节中,我们将继续在框架的基础上构建功能。核心框架系统文件已经就位。这个设置用于在此基础上构建有用的功能。

我们将构建身份验证系统并完成应用程序构建。身份验证是必需的,以防止未经授权的用户访问。这确保只有具有有效用户名和密码的用户才能登录到我们的应用程序中。

注意

在本章中,我们将涵盖身份验证。请注意,本课程中使用的所有示例的登录用户名和密码如下:

用户名:演示

密码:演示

设置路径和创建文件目录的绝对路径

相对路径是相对于当前文件夹路径的路径,例如,./css指向一个相对路径,向上一个文件夹并进入css文件夹。

绝对路径是文件或文件夹的完整路径,例如/user/projects/mvc/css。

这一点很重要,因为这将允许在框架系统的任何地方使用绝对路径包含文件。这是对系统中现有代码的调整。

例如:

$filepath = "../app/views/$path.php";

这变成了:

$filepath = APPDIR."views/$path.php";

这在当前概念的基础上构建,并允许视图组织到子文件夹中。没有这种适应,将无法将任何内容组织到子文件夹中,并且会干扰代码的整洁组织。

没有这些更改也可以继续构建系统,但确保代码整洁有序总是一个好主意。

创建布局文件

布局文件是必需的,以便显示任何错误。

此外,页眉、页脚和导航需要布局文件。创建后,这些文件将提供应该在整个应用程序中引入的元素。这将包括全局元素。

注意

错误用于验证,这将在进一步的子部分中进行介绍,不要与先前看到的错误中的解析错误或类似错误混淆。这些步骤涉及的错误与表单验证相关的错误,其中用户将不正确的信息输入到表单字段中。

包含 Bootstrap

Bootstrap 是一个 HTML、CSS 和 JavaScript 库,将在本章中包含,以提供基本的样式。对开发人员很有用,因为它可以帮助他们在设计师添加设计元素到应用程序之前,原型和可视化他们的应用程序将如何看起来。

在这个项目中,Bootstrap 将作为内容传送网络(CDN)包含在页眉中。CDN 获取在网络上非常常见的资源并对其进行缓存,以帮助提高性能。

注意

这很容易与引导框架混淆。

Bootstrap,HTML、CSS 和 JavaScript 库以及引导概念是两个不同的东西,它们有相似的名称。

您可以通过访问以下链接找到有关 Bootstrap 的更多信息:getbootstrap.com/。

引入 Bootstrap 和 HTML 标记

该部分的目的是实现我们已经实现的通用样式,显示了引入 bootstrap 和 HTML 标记:

尚未解决的问题是路径。到目前为止,我们一直在使用相对路径来包括system/View.php中的视图文件。让我们解决这个问题:

打开webroot/index.php,并在第 9 行后添加以下行:

defined('DS') || define('DS', DIRECTORY_SEPARATOR);

define('APPDIR', realpath(__DIR__.'/../app/') .DS);

define('SYSTEMDIR', realpath(__DIR__.'/../system/') .DS);

define('PUBLICDIR', realpath(__DIR__) .DS);

define('ROOTDIR', realpath(__DIR__.'/../') .DS);

这些是可以在框架中的任何地方调用的常量。第一行定义了目录分隔符,例如/或\,具体取决于机器:

APPDIR – 指向app文件夹

SYSTEMDIR – 指向system文件夹

PUBLICDIR – 指向webroot文件夹

ROOTDIR – 指向root项目路径

每个都创建到其终点的绝对路径。

现在,让我们修复View类。打开system/View.php,在第 24 行,替换:

$filepath = "../app/views/$path.php";

使用:

$filepath = APPDIR."views/$path.php";

注意

这允许视图从父文件夹或子文件夹中包含其他视图,而不会出现问题。

接下来,在app/views文件夹内创建一个名为layouts的文件夹。在app/views/layouts内创建以下文件:

errors.php

footer.php

header.php

nav.php

errors.php

打开errors.php并输入以下代码:

use App\Helpers\Session;

if (isset($errors)) {

foreach($errors as $error) {

echo "

$error
";

}

}

if (Session::get('success')) {

echo "

".Session::pull('success')."
";

}

注意

这包括一个 Session 助手,我们将很快创建。

第一个if语句检查$errors是否存在,如果存在,则退出循环并显示警报。这些类是Bootstrap类(我们将在header.php中有这些)。

接下来的if语句检查是否存在名为success的会话,并且如果存在,则显示其内容。这用于向用户提供反馈。

打开header.php并输入以下代码:

<?=(isset($title) ? $title.' - ' : '');?> Demo</</p> <p>title></p> <p><link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css"></p> <p><link rel="stylesheet" href="/css/style.css"></p> <p><script src="https://code.jquery.com/jquery-2.2.4.min.js"></script></p> <p><script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script></p> <p></head></p> <p><body></p> <p><div class="container"></p> <p>注意</p> <p>这将设置 HTML 文档,并在必要时使用$title,如果存在的话。还包括 Bootstrap CDN CSS 和 JavaScript,以及 jQuery 和位于webroot/css/style.css的自定义 style.css 文件 - 创建此文件。</p> <p>现在,打开footer.php,关闭容器div和body和html标签:</p> <p>注意</p> <p>有关完整的代码片段,请参考代码文件夹中的Lesson 7.php文件。</p> <p></div></p> <p></body></p> <p></html></p> <p>现在,打开nav.php并输入以下代码:</p> <p><nav class="navbar navbar-default"></p> <p>……</p> <p></div><!--/.nav-collapse --></p> <p></div><!--/.container-fluid --></p> <p></nav></p> <p>注意</p> <p>这是 Bootstrap 的导航组件。这是一种为我们的管理页面引入响应式菜单的干净方式。注意两个页面链接,分别是管理和用户。我们还将提供一个注销链接。</p> <p>现在,打开app/views/404.php并包含布局文件:</p> <p><?php include(APPDIR.'views/layouts/header.php');?></p> <p>404!</p> <p><?php include(APPDIR.'views/layouts/footer.php');?></p> <p>注意</p> <p>这将引入页眉并显示页面内容,并以包括页脚结束。</p> <p>不要在这里包括nav。即使用户未登录,也可以显示 404。</p> <p>这样可以以非常干净的方式将常见的布局组织到视图中,这样当您需要更改全局元素时,布局视图就是它们存储的地方。</p> <p>如果框架尚未运行,请在浏览器中打开框架。在根目录时,从终端运行以下命令:</p> <p>php –S localhost:8000 –t webroot</p> <p>注意</p> <p>您不会注意到任何不同,但您将被重定向到一个不存在的页面:http://localhost:8000/example。</p> <p>您将看到一个包含页眉和页脚布局的 404 页面。查看页面源代码 - 右键单击并单击“查看页面源代码”。您应该看到以下输出:</p> <p>注意</p> <p>有关完整的代码片段,请参考代码文件夹中的Lesson 7.php文件。</p> <p><!doctype html></p> <p><html lang="en"></p> <p><head></p> <p>……</p> <p>404!</p> <p></div></p> <p></body></p> <p></html></p> <p>随着我们进入本章的深入,这些布局将变得更加明显。</p> <p>在本节中,我们已经介绍了如何正确设置文件路径。我们介绍了如何正确设置 Bootstrap,并最终为错误和页眉、页脚、导航和错误设置了视图。</p> <p>在下一节中,我们将介绍如何向我们的应用程序添加安全性并设置密码恢复。</p> <p>向项目添加安全性</p> <p>在本节中,我们将继续在框架之上构建功能。核心框架系统文件已经就位。</p> <p>本节的目标是构建功能,以增强项目的安全性。我们将涵盖需要在应用程序中保持良好安全性的各个方面。</p> <p>帮助程序</p> <p>在本小节中,我们将涵盖helpers。</p> <p>我们将创建一个URL helper和一个session helper。这对身份验证以及系统的任何其他方面都很有用,但并不直接相关。</p> <p>会话助手是 PHP 会话的“包装器”,包括对开发人员有用的各种方法,用于处理会话。</p> <p>URL helper在某种意义上与session类似,它是处理 URL 的有用方法。但是,在本书中,它要短得多,只限于单个方法。</p> <p>注意</p> <p>session是一种存储临时数据的方法,比如用户是否已登录。</p> <p>身份验证</p> <p>现在,我们将构建身份验证功能。身份验证是一种只允许具有正确凭据的人访问受限部分的方法。</p> <p>这将涉及创建一个数据库表和一个模型:</p> <p>在数据库中创建用户表</p> <p>在 app 模型中创建一个用户模型</p> <p>添加插入、更新、删除方法</p> <p>然后,我们将创建一个管理员控制器,并导入 URL 和session帮助程序以及user模型。</p> <p>最后,我们将创建相关的视图。</p> <p>仪表板</p> <p>项目将需要一个仪表板;这就像一个需要登录的项目的主页,通常包括指向项目经常访问内容的链接。在这个项目中,我们只需要确保仪表板有一个存在的文件,以便可以将其定向到它。您将创建仪表板视图,并包括布局文件以及页眉、页脚、导航和错误。您将为页面结构添加 HTML。</p> <p>登录</p> <p>创建登录页面也是本节的一部分。</p> <p>在登录视图中,您将创建一个登录表单,并包括布局文件。</p> <p>然后,他们将创建一个登录方法来处理登录过程:</p> <p>部分过程是使用密码哈希和 bcrypt 对密码进行哈希处理</p> <p>使用设计用于返回数据的 Get data 方法</p> <p>除了创建视图和登录方法,我们还将创建logout方法,并修改配置,以便默认情况下主页将是管理员仪表板</p> <p>密码哈希</p> <p>密码哈希使用 bcrypt,这是目前最强大的算法。目前,一台普通计算机需要 12 年才能破解一个密码哈希。</p> <p>部分过程是验证数据,检查用户名和密码是否与数据库中存储的内容匹配。</p> <p>密码哈希是从您的密码创建一个单向哈希的字符串,没有用户应该能够确定哈希的原始内容。</p> <p>注意</p> <p>密码哈希不应与加密混淆。区别在于,在密码哈希中,您可以将哈希密码解密为其原始状态。</p> <p>在 PHP 中实现验证</p> <p>在本节中,我们将看到以下结果。</p> <p>注意</p> <p>本节展示了如何在 PHP 中实现验证,尽管它目前无法正常工作,因为我们尚未创建和提供构成系统知识的数据源。</p> <p>为了解决这个部分,我们将手动创建一个用户。</p> <p>按照以下步骤在 PHP 中实现验证:</p> <p>创建帮助程序:</p> <p>在我们开始构建身份验证之前,我们需要两个新的帮助程序。在app/Helpers中创建一个名为Url.php的新文件,并输入:</p> <p><?php namespace App\Helpers;</p> <p>class Url</p> <p>{</p> <p>public static function redirect($path = '/')</p> <p>{</p> <p>header('Location: '.$path);</p> <p>exit();</p> <p>}</p> <p>}</p> <p>注意</p> <p>这提供了一个名为 redirect 的单一方法,默认情况下为/,当没有传递参数时。这是重定向到我们应用程序的另一个页面的简单方法。</p> <p>在将类包含到页面后,使用:Url::redirect('url/to/redirect/to')</p> <p>要重定向到主页,请使用:</p> <p>Url::redirect()</p> <p>接下来,我们需要一种使用会话的方法。会话是 PHP 跟踪页面数据的一种方式,这非常适合我们的需求,例如能够通过读取会话数据来检测用户是否已登录。</p> <p>我们可以使用普通的$_SESSION 调用,但由于我们正在使用 OOP,让我们利用它来构建一个会话助手。</p> <p>在app/Helpers中创建一个名为Session.php的文件。</p> <p>首先,设置命名空间和类定义:</p> <p>注意</p> <p>需要的第一个方法是确定会话是否已启动。如果更新了sessionStarted参数,它将将其设置为false。这将告诉init方法打开会话:</p> <p><?php namespace App\Helpers;</p> <p>class Session</p> <p>{</p> <p>private static $sessionStarted = false;</p> <p>/**</p> <p>* if session has not started, start sessions</p> <p>*/</p> <p>public static function init()</p> <p>{</p> <p>if (self::$sessionStarted == false) {</p> <p>session_start();</p> <p>self::$sessionStarted = true;</p> <p>}</p> <p>}</p> <p>接下来,创建一个名为set的方法,接受两个参数$key和$value。这用于向会话添加一个$key并将$value设置为$key:</p> <p>public static function set($key, $value = false)</p> <p>{</p> <p>/**</p> <p>* Check whether session is set in array or not</p> <p>* If array then set all session key-values in foreach loop</p> <p>*/</p> <p>if (is_array($key) && $value === false) {</p> <p>foreach ($key as $name => $value) {</p> <p>$_SESSION[$name] = $value;</p> <p>}</p> <p>} else {</p> <p>$_SESSION[$key] = $value;</p> <p>}</p> <p>}</p> <p>接下来,创建一个名为pull的方法,带有一个参数。这将从会话中提取key并在从会话中删除它后返回它,这对于一次性消息非常有用:</p> <p>public static function pull($key)</p> <p>{</p> <p>$value = $_SESSION[$key];</p> <p>unset($_SESSION[$key]);</p> <p>return $value;</p> <p>}</p> <p>接下来,创建一个 get 方法。这将从提供的键返回一个会话:</p> <p>public static function get($key)</p> <p>{</p> <p>if (isset($_SESSION[$key])) {</p> <p>return $_SESSION[$key];</p> <p>}</p> <p>return false;</p> <p>}</p> <p>注意</p> <p>有时,您希望查看会话的内容。创建一个名为display的方法,返回$_SESSION对象:</p> <p>public static function display()</p> <p>{</p> <p>return $_SESSION;</p> <p>}</p> <p>最后一个方法用于在提供$key时销毁会话密钥,否则将销毁整个会话:</p> <p>public static function destroy($key = '')</p> <p>{</p> <p>if (self::$sessionStarted == true) {</p> <p>if (empty($key)) {</p> <p>session_unset();</p> <p>session_destroy();</p> <p>} else {</p> <p>unset($_SESSION[$key]);</p> <p>}</p> <p>}</p> <p>}</p> <p>完整的类如下所示:</p> <p>注意</p> <p>有关完整的代码片段,请参阅代码文件夹中的Lesson 7.php文件。</p> <p><?php namespace App\Helpers;</p> <p>class Session</p> <p>{</p> <p>private static $sessionStarted = false;</p> <p>……..</p> <p>}</p> <p>}</p> <p>现在,我们需要在应用程序运行时自动设置会话。我们通过在app/Config.php中添加Session::init()来实现这一点:</p> <p>注意</p> <p>这使用了一个Use语句,并包括对session's helper类的调用。在这个阶段突出显示这些 OOP 特性可能是有益的。</p> <p>有关完整的代码片段,请参阅代码文件夹中的Lesson 7.php文件。</p> <p><?php namespace App;</p> <p>use App\Helpers\Session;</p> <p>class Config {</p> <p>…….</p> <p>];</p> <p>}</p> <p>}</p> <p>构建身份验证:</p> <p>我们现在准备开始构建 admin Controller 和 users Model,这将是用户登录的入口点。</p> <p>在数据库中创建一个名为 users 的新表:</p> <p>CREATE TABLE `users` (</p> <p>`id` int(11) unsigned NOT NULL AUTO_INCREMENT,</p> <p>`username` varchar(255) DEFAULT NULL,</p> <p>`email` varchar(255) DEFAULT NULL,</p> <p>`password` varchar(255) DEFAULT NULL,</p> <p>`created_at` datetime DEFAULT NULL,</p> <p>`reset_token` varchar(255) DEFAULT NULL,</p> <p>PRIMARY KEY (`id`)</p> <p>) ENGINE=InnoDB DEFAULT CHARSET=utf8;</p> <p>注意</p> <p>ID 是primary键,并将设置为自动递增,这意味着每个记录都将有一个唯一的 ID。</p> <p>reset_token 仅在需要重置密码过程时使用。</p> <p>让我们从 Model 开始。在app/Models中创建一个名为User.php的文件。</p> <p>设置命名空间并导入基本 Model 并设置类定义。</p> <p>注意</p> <p>随着需要,我们将回到这个模型中添加必要的方法。</p> <p>添加用于插入、更新和删除记录的方法:</p> <p>注意</p> <p>有关完整的代码片段,请参阅代码文件夹中的Lesson 7.php文件。</p> <p><?php namespace App\Models;</p> <p>…….</p> <p>{</p> <p>$this->db->delete('users', $where);</p> <p>}</p> <p>}</p> <p>创建 Admin Controller:</p> <p>现在,在app/Controllers中创建一个名为Admin.php的新文件。</p> <p>这将是登录和退出 admin 仪表板的入口点。</p> <p>设置命名空间并导入baseController和Session和URL helpers以及User Model。</p> <p>设置类定义并创建一个名为$user的属性。然后,在__construct方法中,通过调用new User()来初始化User Model。</p> <p>注意</p> <p>这意味着可以使用$this->user来访问 User Model 的任何方法。</p> <p>下一个方法是index()。只要用户已登录,它就会加载仪表板视图。</p> <p>为了确保用户已登录,会运行一个if语句来检查名为logged_jn的会话密钥是否存在,该密钥仅在登录后设置。如果用户未登录,则将其重定向到login方法:</p> <p>注意</p> <p>有关完整的代码片段,请参阅代码文件夹中的Lesson 7.php文件。</p> <p><?php namespace App\Controllers;</p> <p>use System\BaseController;</p> <p>……..</p> <p>$this->view->render('admin/index', compact('title'));</p> <p>}</p> <p>}</p> <p>如果用户已登录,则将加载admin/index视图。创建视图app/views/admin/index.php和入口:</p> <p><?php</p> <p>include(APPDIR.'views/layouts/header.php');</p> <p>include(APPDIR.'views/layouts/nav.php');</p> <p>include(APPDIR.'views/layouts/errors.php');</p> <p>?></p> <p><h1>Dashboard</h1></p> <p><p>This is the application dashboard.</p></p> <p><?php include(APPDIR.'views/layouts/footer.php');?></p> <p>现在,我们需要创建一个login视图。在app/views/admin中创建一个名为auth的文件夹,并创建login.php。</p> <p>首先,包含header布局,然后创建一个调用wrapper和well的div。well类是一个 Bootstrap 类,它提供了灰色方形样式。wrapper类将用于定位div。</p> <p>接下来,包含errors布局以捕获任何错误或消息。</p> <p>现在,我们将创建一个表单,该表单的方法为post,以将其内容发布到ACTION URL,在本例中为/admin/login。</p> <p>然后,为username和password创建两个输入。确保密码的输入类型设置为password。</p> <p>注意</p> <p>将输入类型设置为password可以阻止密码在屏幕上显示。</p> <p>当表单提交时,输入的命名属性是 PHP 如何知道数据是什么的。</p> <p>还需要一个提交按钮来提交表单。一个好的做法是在用户无法记住他们的登录详细信息时提供重置选项。我们将创建一个指向/admin/reset的链接。</p> <p>最后,关闭表单并包含页脚布局:</p> <p>注意</p> <p>有关完整的代码片段,请参考代码文件夹中的Lesson 7.php文件。</p> <p><?php include(APPDIR.'views/layouts/header.php');?></p> <p><div class="wrapper well"></p> <p><?php include(APPDIR.'views/layouts/errors.php');?></p> <p>…….</p> <p>.wrapper h1 {</p> <p>margin-top: 0px;</p> <p>font-size: 25px;</p> <p>}</p> <p>现在,回到 admin 控制器并创建一个login方法:</p> <p>注意</p> <p>设置一个检查,如果用户已登录,则重定向用户。当他们已经登录时,他们不应该能够看到登录页面。</p> <p>在login方法中,创建一个空的$errors数组,并设置页面的$title和load一个调用admin/auth/login的视图,通过使用compact函数传递$title和$errors变量。</p> <p>注意</p> <p>compact()使得可以通过简单输入它们的名称而使用变量,而不需要$:</p> <p>public function login()</p> <p>{</p> <p>if (Session::get('logged_in')) {</p> <p>Url::redirect('/admin');</p> <p>}</p> <p>$errors = [];</p> <p>$title = 'Login';</p> <p>$this->view->render('admin/auth/login', compact('title', 'errors'));</p> <p>}</p> <p>这将加载login视图,并在按下提交时实际上不会执行任何操作。我们需要检查表单是否已提交,但在这之前,我们需要向user模型添加两个方法:</p> <p>public function get_hash($username)</p> <p>{</p> <p>$data = $this->db->select('password FROM users WHERE username = :username', [':username' => $username]);</p> <p>return (isset($data[0]->password) ? $data[0]->password : null);</p> <p>}</p> <p>get_hash($username)将从users表中选择password,其中username与提供的用户名匹配。</p> <p>设置username = :username创建一个占位符。然后,[':username' => $username]将使用该占位符,以便知道值将是什么。</p> <p>然后,检查$data[0]->password是否设置并返回它。否则,返回null。</p> <p>对于get_data(),做同样的事情,只是这次返回的是一个数据数组,而不是单个列:</p> <p>public function get_data($username)</p> <p>{</p> <p>$data = $this->db->select('* FROM users WHERE username = :username', [':username' => $username]);</p> <p>return (isset($data[0]) ? $data[0] : null);</p> <p>}</p> <p>现在,在我们的login方法中,我们可以通过检查$_POST数组是否包含名为submit的对象来检查表单是否已提交。</p> <p>然后,收集表单数据并将其存储在本地变量中。使用htmlspecialchars()是一种安全措施,因为它可以阻止脚本标记能够被执行,并将它们呈现为纯文本。</p> <p>注意</p> <p>接下来,运行一个if语句,调用password_verify(),这是一个内置函数,返回true或false。第一个参数是用户提供的$password,第二个是通过调用$this->user->get_hash($username)从数据库返回的哈希密码。只要password_verify等于false,登录检查就失败了。</p> <p>设置一个$errors变量来包含一个errors消息。接下来,计算$errors,如果等于0,这意味着没有错误,所以从$this->user->get_data($username)获取用户数据。然后,使用会话助手创建一个名为logged_in的会话键,其值为true,以及另一个以用户 ID 作为其值的会话键。</p> <p>最后,将用户重定向到 admin index页面:</p> <p>if (isset($_POST['submit'])) {</p> <p>$username = htmlspecialchars($_POST['username']);</p> <p>$password = htmlspecialchars($_POST['password']);</p> <p>if (password_verify($password, $this->user->get_hash($username)) == false) {</p> <p>$errors[] = 'Wrong username or password';</p> <p>}</p> <p>if (count($errors) == 0) {</p> <p>//logged in</p> <p>$data = $this->user->get_data($username);</p> <p>Session::set('logged_in', true);</p> <p>Session::set('user_id', $data->id);</p> <p>Url::redirect('/admin');</p> <p>}</p> <p>}</p> <p>完整的方法看起来像这样:</p> <p>public function login()</p> <p>{</p> <p>if (Session::get('logged_in')) {</p> <p>Url::redirect('/admin');</p> <p>}</p> <p>……</p> <p>$this->view->render('admin/auth/login', compact('title', 'errors'));</p> <p>}</p> <p>如果框架尚未运行,请运行框架:</p> <p>php –S localhost:8000 –t webroot</p> <p>转到http://localhost:8000/admin/login。</p> <p>注意</p> <p>您将看到一个登录页面。按下登录将显示一个错误消息“用户名或密码错误”,无论您输入什么,因为目前数据库中没有用户。</p> <p>让我们创建我们的登录。我们需要一个哈希密码来存储在数据库中。要在login方法中创建一个哈希密码,请输入:</p> <p>echo password_hash('demo', PASSWORD_BCRYPT);</p> <p>第一个参数是您想要的密码,在本例中是demo。第二个参数是要使用的PASSWORD函数的类型。使用默认的PASSWORD_ BCRYPT意味着 PHP 将使用可能的最强版本。</p> <p>当您刷新页面时,您将看到以下类似的哈希:</p> <p>$2y$10$OAZK6znqAvV2fXS1BbYoVet3pC9dStWVFQGlrgEV4oz2GwJi0nKtC</p> <p>复制这个并将一个新记录插入到数据库客户端中,并将 ID 列留空。它将自动填充。</p> <p>创建一个用户名和电子邮件,并将它们粘贴到hash. F中,对于密码,输入一个有效的datetime,例如 2017-12-04 23:04:00。</p> <p>保存记录。现在,您将能够设置登录。</p> <p>登录后,您将被重定向到/admin。</p> <p>注意</p> <p>记得注释掉或删除echo password_hash('demo', PASSWORD_BCRYPT),,否则哈希将始终显示。</p> <p>趁热打铁,让我们继续添加注销的功能。注销是销毁已登录和user_id会话的情况。在Admin Controller 中,创建一个名为logout的新方法。</p> <p>在方法内部,销毁会话object,然后重定向到login页面:</p> <p>public function logout()</p> <p>{</p> <p>Session::destroy();</p> <p>Url::redirect('/admin/login');</p> <p>}</p> <p>现在,返回应用程序并点击右上角的logout。您将被注销并带回login页面。</p> <p>现在,重新登录。如果您点击Admin链接,您将被带到默认页面。在这种情况下,最好在加载应用程序时立即加载管理员。我们可以通过将Admin Controller 设置为默认app/Config.php来实现这一点。</p> <p>找到以下内容:</p> <p>'default_controller' => 'Home'</p> <p>用以下内容替换它:</p> <p>'default_controller' => Admin,</p> <p>现在,如果您点击Admin(重新加载页面后),您将看到管理员仪表板。</p> <p>注意</p> <p>曾经有一段时间,某些密码哈希标准被认为是互联网安全的最高级别。但是,像大多数技术一样,它不可避免地变得可用,这削弱了其前身的有效性。</p> <p>注意</p> <p>尽一切可能避免以下哈希系统,因为它们不安全:</p> <p>MD5</p> <p>Shar 1</p> <p>Shar 2 56</p> <p>这些密码哈希函数很弱,计算机现在如此强大,只需几秒钟就能破解它们。</p> <p>当开发人员在规划新项目时,建议仔细检查代码,以查找诸如使用这些代码的安全漏洞。</p> <p>在本节中,我们学习了认证过程。我们已经了解了如何进行登录过程。我们已经学习了密码哈希的过程。现在,我们已经有了构建、配置和路由功能到框架的经验。</p> <p>在下一节中,我们将介绍密码恢复的概念,在其中我们将设置在我们的应用程序中重置密码的功能。</p> <p>密码恢复</p> <p>本节是关于设置重置密码的能力。密码重置非常重要,因为可能会出现用户忘记密码的情况。我们现在将构建一个类似以下图片的密码恢复过程:</p> <p>在网上找到的通用密码恢复示例</p> <p>我们将在 admin Controller 中创建一个名为 reset 的方法。该过程会加载一个视图,用户将在其中输入他们的电子邮件地址以请求一封电子邮件。当这个过程被处理时,它将验证电子邮件地址是否有效,并且实际上存在于系统中。</p> <p>这将检查电子邮件,确保它的格式正确,并检查提供的电子邮件地址是否存在于名为 users 的数据库表中。</p> <p>介绍第三方依赖 PHP Mailer</p> <p>PHP Mailer 的图片:https://github.com/PHPMailer</p> <p>我们将通过包含 PHP Mailer 来添加第三方依赖,用于发送电子邮件。</p> <p>PHP Mailer 的工作原理如下:</p> <p>只要验证通过,我们将使用 PHP Mailer 发送带有令牌的电子邮件。令牌稍后将通过电子邮件接收,并作为表单的一部分输入到隐藏字段中,满足验证过程的要求。</p> <p>注意</p> <p>令牌只是一串随机的字母和数字。其想法是为该用户生成一个唯一的东西,以识别该请求来自他们。</p> <p>流程的下一部分是向用户发送电子邮件,当用户点击时,创建一个处理该请求的方法。这涉及创建一个接受电子邮件提供的令牌的更改密码方法,然后在其中显示包含表单的视图。</p> <p>接下来,在视图中,令牌在一个隐藏字段中重新发送。此外,用户可以输入新密码并确认此密码。提交后,控制器将处理数据并验证数据。这涉及确保令牌与用户帐户匹配,并且密码足够长且两个密码匹配。</p> <p>创建后,当更新投入实践时,用户将能够自动登录到管理系统,而无需重新输入密码。</p> <p>这样可以节省用户在重置密码后无需登录。从技术上讲,这是用户体验设计的更新,尽管您可以在这里看到 UX 更改不仅仅局限于设计师领域。</p> <p>注意</p> <p>PHP Mailer 检查格式是否正确。在电子邮件的情况下,这将期望@符号存在。这只是一个验证检查的例子。PHP 内置了方法,以便它可以确定正确的格式是有效的格式。</p> <p>为我们的应用程序构建密码重置机制</p> <p>要完成认证系统,我们需要能够重置密码,以防忘记密码。以下是这样做的步骤:</p> <p>在Admin控制器中创建一个名为reset的新方法。</p> <p>再次检查用户是否已登录,如果是,则将其重定向回管理界面。</p> <p>在加载名为reset:的视图之前,设置一个errors数组并设置页面标题。</p> <p>public function reset()</p> <p>{</p> <p>if (Session::get('logged_in')) {</p> <p>Url::redirect('/admin');</p> <p>}</p> <p>$errors = [];</p> <p>$title = 'Reset Account';</p> <p>$this->view->render('admin/auth/reset', compact('title', 'errors'));</p> <p>}</p> <p>现在,在app/views/admin/auth中创建一个名为reset.php的视图并输入:</p> <p><?php include(APPDIR.'views/layouts/header.php');?></p> <p><div class="wrapper well"></p> <p><?php include(APPDIR.'views/layouts/errors.php');?></p> <p><h1>Reset Account</h1></p> <p><form method="post"></p> <p>…</p> <p>…</p> <p></div></p> <p><?php include(APPDIR.'views/layouts/footer.php');?></p> <p>注意</p> <p>表单将发布到相同的url /admin/reset。我们收集的唯一数据是电子邮件地址。电子邮件地址将用于在继续之前验证用户是否存在。</p> <p>现在,返回到Admin控制器上的重置方法。</p> <p>首先,检查表单是否已使用isset提交,并传递提交按钮名称:</p> <p>if (isset($_POST['submit'])) {</p> <p>接下来,确保电子邮件地址已isset,否则默认为null。检查电子邮件地址是否处于正确的格式中:</p> <p>$email = (isset($_POST['email']) ? $_POST['email'] : null);</p> <p>if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {</p> <p>$errors[] = 'Please enter a valid email address';</p> <p>} else {</p> <p>if ($email != $this->user->get_user_email($email)){</p> <p>$errors[] = 'Email address not found';</p> <p>}</p> <p>}</p> <p>最后,检查电子邮件地址是否属于现有用户。为此,在用户模型中创建一个名为get_user_email($email)的新方法:</p> <p>注意</p> <p>如果存在,这将返回电子邮件地址,否则将返回null。</p> <p>public function get_user_email($email)</p> <p>{</p> <p>$data = $this->db->select('email from users where email = :email', [':email' => $email]);</p> <p>return (isset($data[0]->email) ? $data[0]->email : null);</p> <p>}</p> <p>在前面的控制器中,我们有:</p> <p>if ($email != $this->user->get_user_email($email)){</p> <p>注意</p> <p>检查表单中提供的电子邮件地址是否与数据库中的不匹配,如果是,则创建一个新错误。</p> <p>在验证检查之后,没有错误:</p> <p>if (count($errors) == 0) {</p> <p>保存文件;到目前为止,该方法看起来像这样:</p> <p>注意</p> <p>有关完整的代码片段,请参阅代码文件夹中的Lesson 7.php文件。</p> <p>public function reset()</p> <p>{</p> <p>…….</p> <p>$this->view->render('admin/auth/reset', compact('title', 'errors'));</p> <p>}</p> <p>此时,除其他事项外,需要发送电子邮件。</p> <p>注意</p> <p>最佳做法是不使用 PHP 的内置mail()函数,而是使用诸如phpmailer(github.com/PHPMailer/)之类的库。</p> <p>打开composer.json和phpmailer在要求列表中:</p> <p>{</p> <p>"autoload": {</p> <p>"psr-4": {</p> <p>"App\\" : "app/",</p> <p>"System\\" : "system/"</p> <p>}</p> <p>},</p> <p>"require": {</p> <p>"filp/whoops": "².1",</p> <p>"phpmailer/phpmailer": "~6.0"</p> <p>}</p> <p>}</p> <p>保存文件并在终端中键入composer update。这将拉取phpmailer,使其可用于我们的应用程序。</p> <p>在Admin控制器的顶部,导入phpmailer:</p> <p>use PHPMailer\PHPMailer\PHPMailer;</p> <p>use PHPMailer\PHPMailer\Exception;</p> <p>接下来,转到以下if语句内的reset方法。这是我们将恢复的地方:</p> <p>if (count($errors) == 0) {</p> <p>}</p> <p>现在,我们需要生成一个随机令牌。为此,使用md5,uniqid和rand来生成一个随机令牌。</p> <p>然后,设置一个data和where数组。$data将指定reset_token的值为$token,而$where将是电子邮件地址。将它们传递给用户模型的update()方法以更新用户。</p> <p>这将在数据库中存储$token与用户记录:</p> <p>$token = md5(uniqid(rand(),true));</p> <p>$data = ['reset_token' => $token];</p> <p>$where = ['email' => $email];</p> <p>$this->user->update($data, $where);</p> <p>现在,我们通过创建phpmailer的新实例来设置要发送的电子邮件,然后设置电子邮件的发送者。根据需要更改这一点。</p> <p>传递$email地址,这将被发送到,并通过将 true 传递给 isHTML()来设置 HTML 模式:</p> <p>$mail = new PHPMailer(true);</p> <p>$mail->setFrom('noreply@domain.com');</p> <p>$mail->addAddress($email);</p> <p>$mail->isHTML(true);</p> <p>设置主题和电子邮件正文。我们提供两种正文:HTML 和纯文本。纯文本用于用户的电子邮件客户端无法呈现 HTML 的情况。</p> <p>创建一个指向admin/change/password_token的链接,当使用localhost:时</p> <p>注意</p> <p>重要的是要记住http://localhost:8000的 URL 只适用于您的计算机。</p> <p>$mail->Subject = 'Reset you account';</p> <p>$mail->Body = "<p>To change your password please click <a</p> <p>href='http://localhost:8000/admin/change_password/$token'>this link</a></p>";</p> <p>$mail->AltBody = "To change your password please go to this address: http://localhost:8000/admin/change_password/$token";</p> <p>现在,一切都设置好了。发送电子邮件:</p> <p>$mail->send();</p> <p>创建一个会话来通知用户并重定向管理员/重置:</p> <p>Session::set('success', "Email sent to ".htmlentities($email));</p> <p>Url::redirect('/admin/reset');</p> <p>完成的方法看起来像这样:</p> <p>注意</p> <p>有关完整的代码片段,请参考代码文件夹中的Lesson 7.php文件。</p> <p>public function reset()</p> <p>{</p> <p>if (Session::get('logged_in')) {</p> <p>Url::redirect('/admin');</p> <p>}</p> <p>…….</p> <p>$title = 'Reset Account';</p> <p>$this->view->render('admin/auth/reset', compact('title', 'errors'));</p> <p>}</p> <p>当用户点击电子邮件中的链接时,我们需要处理请求。为此,创建另一个名为change_password的方法,接受一个名为$token的参数:</p> <p>注意</p> <p>该方法获取$token,将其传递给users模型中的一个方法get_user_reset_token($token),并返回用户对象。如果令牌与数据库不匹配,则返回 null。</p> <p>有关完整的代码片段,请参考代码文件夹中的Lesson 7.php文件。</p> <p>$user = $this->user->get_user_reset_token($token);</p> <p>if ($user == null) {</p> <p>$errors[] = 'user not found.';</p> <p>}</p> <p>该方法看起来像这样:</p> <p>注意</p> <p>有关完整的代码片段,请参考代码文件夹中的Lesson 7.php文件。</p> <p>$title = 'Change Password';</p> <p>$this->view->render('admin/auth/change_password', compact('title', 'token', 'errors'));</p> <p>}</p> <p>注意</p> <p>render方法将$title,$token和$errors传递给视图。</p> <p>需要另一个视图。在app/views/admin/auth中创建一个名为change_password.php的视图:</p> <p>注意</p> <p>有关完整的代码片段,请参考代码文件夹中的Lesson 7.php文件。</p> <p><?php include(APPDIR.'views/layouts/header.php');?></p> <p>……</p> <p></div></p> <p><?php include(APPDIR.'views/layouts/footer.php');?></p> <p>注意</p> <p>表单有一个名为$token的隐藏输入。它的值是从控制器传递的$token,这将用于验证请求。</p> <p>还有两个输入:密码和确认密码。这些用于收集所需的密码。</p> <p>当提交表单并收集表单数据时,再次调用get_user_reset_token($token)方法来验证提供的令牌是否有效。</p> <p>此外,密码必须匹配并且长度必须超过三个字符。</p> <p>如果没有错误,则通过将数组传递给$this->user->update来更新数据库中用户的记录以清除reset_token。使用password_hash()对密码进行哈希处理,其中 ID 与用户对象匹配,令牌与提供的令牌匹配:</p> <p>注意</p> <p>有关完整的代码片段,请参考代码文件夹中的Lesson 7.php文件。</p> <p>if (isset($_POST['submit'])) {</p> <p>$token = htmlspecialchars($_POST['token']);</p> <p>……..</p> <p>}</p> <p>}</p> <p>更新后,记录用户并将其重定向到管理员仪表板。</p> <p>完整的方法看起来像这样:</p> <p>注意</p> <p>有关完整的代码片段,请参考代码文件夹中的Lesson 7.php文件。</p> <p>public function change_password($token)</p> <p>{</p> <p>…….</p> <p>$title = 'Change Password';</p> <p>$this->view->render('admin/auth/change_password', compact('title', 'token', 'errors'));</p> <p>}</p> <p>这结束了认证部分。现在我们可以登录、登出,并在忘记密码时重置密码。</p> <p>我们现在已经到达了本节的结束。在这里,我们学会了如何构建密码重置系统,并进一步学习了使用第三方工具的经验。</p> <p>在下一节中,我们将看到如何为用户管理添加 CRUD 功能。</p> <p>为用户管理构建 CRUD</p> <p>CRUD</p> <p>users部分允许创建和管理应用程序的用户。</p> <p>我们将创建 CRUD 以启用:</p> <p>创建用户</p> <p>显示现有用户</p> <p>更新现有用户</p> <p>删除不需要的用户</p> <p>在本节中,我们将创建用户控制器中的不同方法。</p> <p>我们还将在用户模型中创建更多方法,以便检索所有用户或检索特定用户所需的新查询。</p> <p>该过程将如下进行:</p> <p>这个过程的一部分是创建一个construct方法,它允许我们保护所有未经授权的用户的方法。这意味着要能够访问部分内的任何方法,您必须首先登录。index方法列出所有用户,并提供编辑和删除用户的选项。</p> <p>在删除时,首先会出现确认。</p> <p>下一步是创建一个add视图。在这个视图中,将有一个表单,供应用程序的用户创建他们的应用程序的新用户记录。在提交表单时,将收集数据并开始验证过程。</p> <p>这将检查提交的数据是否适合其目的,并且可能是预期的数据。</p> <p>例如,将检查用户名是否超过三个字符的长度,并且在数据库中不存在。</p> <p>注意</p> <p>这个过程对于电子邮件也是一样的,在电子邮件的情况下,要确保它是有效的并且不存在。</p> <p>在验证通过后,用户将被创建,并且成功消息将被记录并显示给用户。应用程序用户然后被重定向到用户视图。</p> <p>然后我们将创建一个update方法和view,这与创建用户的方法和视图非常相似。关键区别在于,表单在加载到页面上时会预先填充用户的详细信息,当表单提交时,会更新特定的用户而不是创建新记录。</p> <p>最后要创建的方法是delete方法,它检查用户的 ID 是否为数字,并且不与已登录用户的 ID 相同,以防止他们删除自己。</p> <p>注意</p> <p>这是开发人员低估用户可能会做的事情。令人惊讶的是,用户可能有意或无意地做的事情,以及如果应用程序不采取措施来防止这种情况,他们可能会删除自己。</p> <p>在记录被删除后,会创建一个成功消息,并将用户重定向回用户页面。</p> <p>构建用户管理的 CRUD</p> <p>在这一部分,我们将看到以下输出显示在我们的屏幕上:</p> <p>注意</p> <p>在读取用户时,要知道在这个表中可以控制显示什么。并不是所有关于该用户的信息都需要显示出来。</p> <p>在这一部分,我们将构建我们的用户部分来创建,读取,更新和删除用户。</p> <p>按照以下步骤构建用户管理的 CRUD:</p> <p>首先,我们需要更多的查询。打开app/Models/User.php.</p> <p>创建以下方法:</p> <p>注意</p> <p>有关完整的代码片段,请参考代码文件夹中的Lesson 7.php文件。</p> <p>get_users() – returns all users ordered by username</p> <p>$data = $this->db->select('username from users where username = :username', [':username' => $username]);</p> <p>return (isset($data[0]->username) ? $data[0]->username : null);</p> <p>}</p> <p>现在,在app/Controllers中创建一个Users控制器。创建Users.php.</p> <p>设置命名空间并导入帮助程序和User模型:</p> <p>use System\BaseController;</p> <p>use App\Helpers\Session;</p> <p>use App\Helpers\Url;</p> <p>use App\Models\User;</p> <p>class Users extends BaseController</p> <p>{</p> <p>接下来,创建一个名为$user的类属性和一个__construct方法。然后,检查用户是否已登录,如果没有,将其重定向到登录页面。</p> <p>创建一个新的用户实例:</p> <p>$this->user = new User()</p> <p>注意</p> <p>在构造函数中进行这个检查意味着这个类的所有方法都将受到未经授权用户的保护。</p> <p>protected $user;</p> <p>public function __construct()</p> <p>{</p> <p>parent::__construct();</p> <p>if (! Session::get('logged_in')) {</p> <p>Url::redirect('/admin/login');</p> <p>}</p> <p>$this->user = new User();</p> <p>}</p> <p>接下来,创建一个index方法。这将调用get_users()并加载一个视图并传入用户对象:</p> <p>public function index()</p> <p>{</p> <p>$users = $this->user->get_users();</p> <p>$title = 'Users';</p> <p>$this->view->render('admin/users/index', compact('users', 'title'));</p> <p>}</p> <p>为了视图,创建app/views/admin/users/index.php.</p> <p>包括布局文件并创建一个表格来显示用户列表:</p> <p>foreach($users as $user)</p> <p>循环遍历所有用户记录。作为安全措施,当从数据库中打印数据时,我们将使用htmlentities()。这将把所有标签转换为它们的 HTML 对应项,这意味着如果任何代码被注入到数据库中,它将简单地被打印为文本,使其无用。</p> <p>注意</p> <p>有关完整的代码片段,请参考代码文件夹中的Lesson 7.php文件。</p> <p><?php</p> <p>include(APPDIR.'views/layouts/header.php');</p> <p>include(APPDIR.'views/layouts/nav.php');</p> <p>……</p> <p></table></p> <p></div></p> <p><?php include(APPDIR.'views/layouts/footer.php');?></p> <p>在循环内部,我们有两个用于编辑和删除的操作链接。请注意,用户的 ID 被传递到href值的末尾。这是为了将 ID 传递到 URL 中。</p> <p>此外,我们有一个指向/users/add的添加用户按钮。让我们创建这个。在你的Users控制器中,创建一个名为add()的新方法:</p> <p>public function add()</p> <p>{</p> <p>$errors = [];</p> <p>$title = 'Add User';</p> <p>$this->view->render('admin/users/add', compact('errors', 'title'));</p> <p>}</p> <p>现在,在app/views/admin/users中创建一个名为add.php的视图。</p> <p>包括布局文件并设置页面标题。接下来,创建一个方法设置为post的表单。</p> <p>您需要username、email、password和confirm password四个输入。确保每个输入都有一个名称。</p> <p>注意</p> <p>粘性表单在出现错误时非常有用。</p> <p>粘性表单是在出现错误时保留其数据的表单。输入仍将显示其中输入的值。</p> <p>要在用户名和电子邮件上实现粘性表单,使用三元运算符:</p> <p>(isset($_POST['username']) ? $_POST['username'] : '')</p> <p>这表示如果$_POST['username']已设置,则打印它,否则打印空字符串:</p> <p>注意</p> <p>有关完整的代码片段,请参考代码文件夹中的Lesson 7.php文件。</p> <p><?php</p> <p>include(APPDIR.'views/layouts/header.php');</p> <p>include(APPDIR.'views/layouts/nav.php');</p> <p>include(APPDIR.'views/layouts/errors.php');</p> <p>……..</p> <p></form></p> <p><?php include(APPDIR.'views/layouts/footer.php');?></p> <p>提交后,表单数据将被发布到/users/add。这需要在Users控制器的add方法中处理。</p> <p>检查表单提交:</p> <p>if (isset($_POST['submit'])) {</p> <p>接下来,收集表单数据:</p> <p>$username = (isset($_POST['username']) ? $_POST['username'] : null);</p> <p>$email = (isset($_POST['email']) ? $_POST['email'] : null);</p> <p>$password = (isset($_POST['password']) ? $_POST['password'] : null);</p> <p>$password_confirm = (isset($_POST['password_confirm']) ? $_POST['password_confirm'] : null);</p> <p>然后,开始验证过程。</p> <p>检查username的长度是否超过 3 个字符:</p> <p>if (strlen($username) < 3) {</p> <p>$errors[] = 'Username is too short';</p> <p>}</p> <p>接下来,通过将$username传递给 Model 上的get_user_username($username)方法来检查$username是否已经存在于数据库中。如果结果与$username相同,则它已经存在,因此创建一个错误:</p> <p>else {</p> <p>if ($username == $this->user->get_user_username($username)){</p> <p>$errors[] = 'Username address is already in use';</p> <p>}</p> <p>}</p> <p>对于电子邮件验证,请使用filter_var和FILTER_VALIDATE_EMAIL检查电子邮件格式是否有效。如果这不返回 true,则创建一个错误。</p> <p>就像username一样,检查$email是否已经存在于数据库中:</p> <p>if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {</p> <p>$errors[] = 'Please enter a valid email address';</p> <p>} else {</p> <p>if ($email == $this->user->get_user_email($email)){</p> <p>$errors[] = 'Email address is already in use';</p> <p>}</p> <p>}</p> <p>对于密码,检查$password是否与$password_confirm匹配或创建错误。否则,检查密码的长度是否超过 3 个字符:</p> <p>if ($password != $password_confirm) {</p> <p>$errors[] = 'Passwords do not match';</p> <p>} elseif (strlen($password) < 3) {</p> <p>$errors[] = 'Password is too short';</p> <p>}</p> <p>如果没有错误,继续并设置一个包含要插入数据库的数据的$data数组。</p> <p>注意</p> <p>注意使用password_hash()函数来存储密码。这是使用 PHP 内置的密码函数,默认情况下将使用bcrypt,这是编写时最安全的哈希技术。</p> <p>通过调用$this->insert($data)创建用户并在重定向回/users 之前设置消息:</p> <p>if (count($errors) == 0) {</p> <p>$data = [</p> <p>'username' => $username,</p> <p>'email' => $email,</p> <p>'password' => password_hash($password, PASSWORD_BCRYPT)</p> <p>];</p> <p>$this->user->insert($data);</p> <p>Session::set('success', 'User created');</p> <p>Url::redirect('/users');</p> <p>}</p> <p>完整的方法如下所示:</p> <p>注意</p> <p>有关完整的代码片段,请参考代码文件夹中的Lesson 7.php文件。</p> <p>public function add()</p> <p>{</p> <p>$errors = [];</p> <p>…….</p> <p>$title = 'Add User';</p> <p>$this->view->render('admin/users/add', compact('errors', 'title'));</p> <p>}</p> <p>要编辑用户,URL 结构是/users/edit/1。末尾的数字是用户的 ID。</p> <p>创建一个名为edit($id)的方法,接受一个名为$id的参数。</p> <p>首先,检查$id是否为数字,否则重定向回/users。</p> <p>通过调用$this>user->get_user($id)获取用户数据,并将 ID 传递给users模型方法。这将返回一个用户对象或null(如果未找到记录)。</p> <p>如果$user等于null,则重定向到404页面。否则,设置一个$errors数组,$title,并加载视图,将用户、错误和标题传递给compact():</p> <p>public function edit($id)</p> <p>{</p> <p>if (! is_numeric($id)) {</p> <p>Url::redirect('/users');</p> <p>}</p> <p>$user = $this->user->get_user($id);</p> <p>if ($user == null) {</p> <p>Url::redirect('/404');</p> <p>}</p> <p>$errors = [];</p> <p>$title = 'Edit User';</p> <p>$this->view->render('admin/users/edit', compact('user', 'errors', 'title'));</p> <p>}</p> <p>现在,在app/views/admin/users中创建一个名为edit.php的视图:</p> <p>注意</p> <p>这几乎与add.php视图相同。主要区别在于用户名和电子邮件输入。它们将使用用户对象进行预填充:</p> <p><?=$user->username;?>是使用$user后的->操作的用户对象。您可以指定要从中获取的列。</p> <p>重要的是不要预先填充密码字段;只有在用户想要更改密码时才应填写。因此,放置一条消息通知用户,只有在他们想要更改现有密码时才应输入密码:</p> <p>有关完整的代码片段,请参考代码文件夹中的Lesson 7.php文件。</p> <p><?php</p> <p>include(APPDIR.'views/layouts/header.php');</p> <p>include(APPDIR.'views/layouts/nav.php');</p> <p>include(APPDIR.'views/layouts/errors.php');</p> <p>……</p> <p></form></p> <p><?php include(APPDIR.'views/layouts/footer.php');?></p> <p>注意</p> <p>提交后,edit($id)方法将处理请求。</p> <p>就像add()方法一样,检查表单提交,收集表单数据,并完善表单验证。</p> <p>这次,我们不会检查用户名或电子邮件是否已经存在于数据库中,只会检查它们是否已提供并且有效:</p> <p>注意</p> <p>有关完整的代码片段,请参考代码文件夹中的Lesson 7.php文件。</p> <p>if (isset($_POST['submit'])) {</p> <p>$username = (isset($_POST['username']) ? $_POST['username'] : null);</p> <p>……</p> <p>$errors[] = 'Password is too short';</p> <p>}</p> <p>}</p> <p>接下来,检查是否有错误:</p> <p>if (count($errors) == 0) {</p> <p>将$data数组设置为更新用户记录。这次,只提供了用户名和电子邮件:</p> <p>$data = [</p> <p>'username' => $username,</p> <p>'email' => $email</p> <p>];</p> <p>如果密码已更新,则将密码添加到$data数组中:</p> <p>if ($password != null) {</p> <p>$data['password'] = password_hash($password, PASSWORD_BCRYPT);</p> <p>}</p> <p>where语句表示 ID 与$id匹配。运行update()并设置消息并重定向到用户页面:</p> <p>$where = ['id' => $id];</p> <p>$this->user->update($data, $where);</p> <p>Session::set('success', 'User updated');</p> <p>Url::redirect('/users');</p> <p>完整的update方法如下:</p> <p>注意</p> <p>有关完整的代码片段,请参考代码文件夹中的Lesson 7.php文件。</p> <p>public function edit($id)</p> <p>{</p> <p>if (! is_numeric($id)) {</p> <p>……</p> <p>}</p> <p>$title = 'Edit User';</p> <p>$this->view->render('admin/users/edit', compact('user', 'errors', 'title'));</p> <p>}</p> <p>完成用户控制器的最后一步是添加删除用户的功能。</p> <p>与编辑一样,URL 结构将在 URL 的格式中传递一个$id,如/users/delete/2。</p> <p>创建一个名为delete($id)的方法。</p> <p>检查$id是否为数字,并检查$id是否与会话$_SESSION['user_id']匹配,否则终止页面。您不希望允许用户删除自己的记录。</p> <p>接下来,通过调用$this->user->get_user($id)来获取用户,并检查$user对象是否不等于null。否则,重定向到404页面。</p> <p>接下来,创建一个$where数组,指出$id与数据库中的 ID 匹配。请注意,我们不使用$data数组。在这种情况下,我们只传递一个$where。这是因为您不能选择列,只能选择行,所以$data将是无意义的。</p> <p>最后,设置消息并重定向回/users:</p> <p>public function delete($id)</p> <p>{</p> <p>if (! is_numeric($id)) {</p> <p>Url::redirect('/users');</p> <p>}</p> <p>if (Session::get('user_id') == $id) {</p> <p>die('You cannot delete yourself.');</p> <p>}</p> <p>$user = $this->user->get_user($id);</p> <p>if ($user == null) {</p> <p>Url::redirect('/404');</p> <p>}</p> <p>$where = ['id' => $user->id];</p> <p>$this->user->delete($where);</p> <p>Session::set('success', 'User deleted');</p> <p>Url::redirect('/users');</p> <p>}</p> <p>现在运行应用程序:</p> <p>php –S localhost:8000 –t webroot</p> <p>转到http://localhost:8000/users,点击Add User,然后填写表单。</p> <p>首先,如果您尝试在没有任何数据的情况下提交表单,您将看到来自在输入上放置了一个必填属性的 HTML 客户端验证。</p> <p>尝试填写与您已经创建的用户名相同的用户,您将看到服务器验证规则正在运行。</p> <p>最后,完整填写表单与新用户详细信息,您将被重定向到/users,并看到新用户,以及确认消息。</p> <p>点击要编辑的用户旁边的Edit。然后,您将看到带有用户名和电子邮件填写的编辑表单。点击提交将带您返回到用户页面。</p> <p>点击delete将立即删除用户(如果用户不是您),而无需确认。让我们修复这个问题!</p> <p>我们的要求规定,当用户按下delete时,应显示确认窗口。如果点击确定,将调用删除 URL,如果点击取消,则不会发生任何事情。</p> <p>打开app/views/admin/users/index.php,并在footer.php代码块之前放置此 JavaScript:</p> <p><script language="JavaScript" type="text/javascript"></p> <p>function del(id, title) {</p> <p>if (confirm("Are you sure you want to delete '" + title + "'?")) {</p> <p>window.location.href = '/users/delete/' + id;</p> <p>}</p> <p>}</p> <p></script></p> <p>这定义了一个 JavaScript 函数,它接受一个 ID 和一个username。当confirm()通过window.location.href时,它将运行,将页面重定向到删除 URL,然后将 ID var传递到 URL 的末尾。</p> <p>在您看到删除链接的循环中:</p> <p><a href="/users/delete/<?=$row->id;?>" class="btn btn-xs btn-danger">Delete</a></p> <p>替换为:</p> <p><a href="javascript:del('<?=$row->id;?>','<?=$row->username;?>')" class="btn btn-xs btn-danger">Delete</a></p> <p>这调用javascript:del(),触发确认弹出窗口并传递用户的ID和username。</p> <p>保存文件并运行页面。当您点击删除时,现在将看到一个确认提示。点击确定将允许删除继续进行,而点击取消将阻止重定向运行。</p> <p>可选活动</p> <p>添加关于用户的其他字段,也许是他们的地址,年龄,爱好,眼睛颜色,或者你选择的任何内容。</p> <p>确保这些在Method和Controller中进行处理,并确保数据库表准备好接受它们。</p> <p>确保这些包含在视图中。</p> <p>在index视图中,学生可以选择他们选择的信息来帮助在表中识别用户。</p> <p>总结</p> <p>在本课程中,我们已经完成了对框架的功能进行构建,允许对用户进行管理。我们已经执行了引入 Bootstrap,为我们的应用程序提供了一些基本级别的样式。我们还在应用程序中实现了密码恢复机制。</p> <p>这完成了联系人应用程序的最基本要求。然而,所有这些都涉及到登录到一个包含应用程序的区域,如果没有正确的用户名和密码凭据,是受限制的。目前,这只是一个空的仪表板页面。一切就绪后,我们现在可以继续构建应用程序来存储用户的联系人。</p> <p>在下一章中,我们将讨论如何在当前应用程序的基础上构建一个联系人管理系统,其中将包括在联系人应用程序中创建、阅读、更新、删除和使用联系人。</p> <p>第八章:构建联系人管理系统</p> <p>在上一章中,我们已经完成了在框架上构建功能,允许管理用户。我们已经执行了引入 Bootstrap,为我们的应用程序提供了一些基本级别的样式。我们还在我们的应用程序中实现了密码恢复机制。</p> <p>在本章中,我们将构建一个联系人 CRUD(创建、读取、更新和删除)部分,其中将有一个查看页面来查看单个联系人。查看页面的评论可以记录在联系人上。我们还将为我们的联系人应用程序构建评论系统。</p> <p>在本章结束时,您将能够:</p> <p>在我们的联系人应用程序中实现 CRUD 功能</p> <p>在我们的联系人应用程序中构建评论系统</p> <p>概述 CMS</p> <p>框架是软件中的抽象,通过编写自定义用户代码,可以提供多个软件。框架中的控制流不像其他库中那样被决定:</p> <p>应用程序的仪表板</p> <p>这是仪表板-用户登录时着陆的页面。从这里,他们可以导航到应用程序的部分,从而能够管理内容:</p> <p>联系人索引页面</p> <p>这是“联系人”索引,用户可以在其中查看联系人表中存储的所有联系人。</p> <p>这是建立在框架之上的应用程序的知识。</p> <p>用户可以看到联系人的姓名以及与每个联系人相关的电子邮件地址和电话号码。</p> <p>用户无法看到联系人 ID,但是该 ID 仍然会生成,并且构成查看、编辑和删除的 URL 的一部分;因此,当通过点击其中一个按钮触发功能时,它已经知道从联系人表中绘制其知识的记录。</p> <p>查看单个联系人</p> <p>这是联系人页面。</p> <p>这个页面显示单个记录中的所有信息。该应用程序中的记录是唯一联系人的数据,由ID表示。该ID是唯一的,因此只会显示一个单个联系人的信息:</p> <p>注意</p> <p>注意</p> <p>这里使用“唯一”一词,因为它是一个单独的ID。如果它被存储为一个单独的ID,那么它被应用程序视为一个唯一的记录。</p> <p>添加联系人页面</p> <p>这是显示表单的页面,允许用户添加一个全新的联系人。</p> <p>这个页面在加载时不需要任何参数,因为它不需要在其字段中加载任何预填充的数据。</p> <p>提交时,假设没有错误,将添加新的联系人:</p> <p>编辑联系人页面</p> <p>这是显示表单的页面,允许用户编辑联系人。</p> <p>这个页面的不同之处在于与联系人记录相关的数据在表单中是预先填充的。</p> <p>之所以可能这样做,是因为编辑页面加载时传递了联系人ID作为参数。这个ID告诉系统应该加载哪个记录到这个页面中。</p> <p>CRUD,联系人应用程序</p> <p>在这一部分,用户将创建 CRUD 联系人应用程序的功能。用户将:</p> <p>创建新的“联系人”记录</p> <p>查看所有“联系人”记录</p> <p>查看单个“联系人”记录</p> <p>更新“联系人”记录</p> <p>删除“联系人”记录</p> <p>注意</p> <p>您可能会发现这与构建用户管理功能非常相似,并且这样做是正确的。</p> <p>CRUD 是所有应用程序的核心,功能从那里扩展。</p> <p>这是分页的例子</p> <p>这是一个筛选的例子</p> <p>在我们的联系人应用程序中插入 CRUD 功能</p> <p>在这一部分,我们将尝试在我们的联系人应用程序中插入 CRUD 功能。</p> <p>看一下以下的屏幕截图:</p> <p>本节的结果</p> <p>以下是在我们的联系人应用程序中插入 CRUD 功能的步骤:</p> <p>在数据库中,我们需要一个contacts表(如果您在之前的章节中有一个,请删除它):</p> <p>CREATE TABLE `contacts` (</p> <p>`id` int(11) unsigned NOT NULL AUTO_INCREMENT,</p> <p>`name` varchar(255) DEFAULT NULL,</p> <p>`email` varchar(255) DEFAULT NULL,</p> <p>`tel` varchar(255) DEFAULT NULL,</p> <p>PRIMARY KEY (`id`)</p> <p>) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;</p> <p>contacts表存储每个联系人的唯一ID,联系人的姓名,电子邮件地址和电话号码。</p> <p>接下来,我们需要一个comments表:</p> <p>CREATE TABLE `comments` (</p> <p>`id` int(11) unsigned NOT NULL AUTO_INCREMENT,</p> <p>`contact_id` int(11) DEFAULT NULL,</p> <p>`user_id` int(11) DEFAULT NULL,</p> <p>`body` text,</p> <p>`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,</p> <p>PRIMARY KEY (`id`)</p> <p>) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;</p> <p>评论必须具有contact_id和user_id字段。这些是外键,用于将评论链接回联系人和发布评论的用户。</p> <p>评论将被添加到正文字段中,并且created_at列用于记录记录创建的时间。此列设置为带有默认CURRENT_TIMESTAMP的时间戳。这意味着在添加新记录时,日期和时间将自动插入:</p> <p>由于我们在之前的章节中已经尝试过联系人,让我们从一些清理开始。删除app/views/contacts文件夹。</p> <p>打开app/views/layouts/nav.php并添加一个指向/contacts的联系人菜单项:</p> <p><nav class="navbar navbar-default"></p> <p><div class="container-fluid"></p> <p>…….</p> <p></div><!--/.container-fluid --></p> <p></nav></p> <p>打开app/Models/Contacts.php。</p> <p>删除这段代码:</p> <p>public function getContacts()</p> <p>{</p> <p>return $this->db->select('* FROM contacts');</p> <p>}</p> <p>然后,用这个替换它:</p> <p>public function get_contacts()</p> <p>{</p> <p>return $this->db->select('* from contacts order by name');</p> <p>}</p> <p>接下来,我们需要一个加载单个联系人的方法,其中ID属于联系人:</p> <p>public function get_contact($id)</p> <p>{</p> <p>$data = $this->db->select('* from contacts where id = :id', [':id' => $id]);</p> <p>return (isset($data[0]) ? $data[0] : null);</p> <p>}</p> <p>我们还需要insert,update和delete方法:</p> <p>public function insert($data)</p> <p>{</p> <p>$this->db->insert('contacts', $data);</p> <p>}</p> <p>public function update($data, $where)</p> <p>{</p> <p>$this->db->update('contacts', $data, $where);</p> <p>}</p> <p>public function delete($where)</p> <p>{</p> <p>$this->db->delete('contacts', $where);</p> <p>}</p> <p>完整的模型如下:</p> <p>注意</p> <p>有关完整的代码片段,请参考代码文件夹中的Lesson 8.php文件。</p> <p><?php</p> <p>namespace App\Models;</p> <p>use System\BaseModel;</p> <p>class Contact extends BaseModel</p> <p>……</p> <p>}</p> <p>}</p> <p>接下来,打开app/Controllers/Contacts.php。</p> <p>导入Session和 URL 助手:</p> <p>Use App\Helpers\Session;</p> <p>Use App\Helpers\Url;</p> <p>替换以下代码:</p> <p>public function index()</p> <p>{</p> <p>$contacts = new Contact();</p> <p>$records = $contacts->getContacts();</p> <p>return $this->view->render('contacts/index', compact('records'));</p> <p>}</p> <p>用这个:</p> <p>protected $contact;</p> <p>public function __construct()</p> <p>{</p> <p>parent::__construct();</p> <p>if (! Session::get('logged_in')) {</p> <p>Url::redirect('/admin/login');</p> <p>}</p> <p>$this->contact = new Contact();</p> <p>}</p> <p>public function index()</p> <p>{</p> <p>$contacts = $this->contact->get_contacts();</p> <p>$title = 'Contacts';</p> <p>return $this->view->render('admin/contacts/index', compact('contacts', 'title'));</p> <p>}</p> <p>注意</p> <p>与我们的Users控制器一样,这将确保您在能够访问联系人之前已登录,并设置$contact模型,收集联系人并加载contacts视图。</p> <p>我们还需要add,edit和delete方法。这与设置Users方法的方式相同。</p> <p>如果表单已经提交,收集表单数据,执行验证,并且如果没有错误,将其插入数据库,设置消息,并重定向:</p> <p>注意</p> <p>有关完整的代码片段,请参考代码文件夹中的Lesson 8.php文件。</p> <p>public function add()</p> <p>{</p> <p>……</p> <p>Session::set('success', 'Contact deleted');</p> <p>Url::redirect('/contacts');</p> <p>}</p> <p>接下来,我们需要为这些方法创建视图。在app/views/admin文件夹内创建一个contacts文件夹,并创建这些视图:</p> <p>注意</p> <p>有关完整的代码片段,请参考代码文件夹中的Lesson 8.php文件。</p> <p>index.php</p> <p><?php</p> <p>include(APPDIR.'views/layouts/header.php');</p> <p>include(APPDIR.'views/layouts/nav.php');</p> <p>…….</p> <p></form></p> <p><?php include(APPDIR.'views/layouts/footer.php');?></p> <p>活动:执行我们的应用程序</p> <p>我们已经将 CRUD 功能实现到我们的联系人应用程序中。让我们通过执行我们的应用程序来尝试一下。</p> <p>这个活动的目的是验证 CRUD 功能在我们的应用程序中是否正常工作。</p> <p>在这一点上,我们可以列出,添加,编辑和删除联系人:</p> <p>为了显示这个,打开你的应用程序:</p> <p>php – S localhost:8000 –t webroot</p> <p>加载http://localhost:8000/contacts。</p> <p>数据库中将列出所有联系人。您可以通过点击“添加联系人”来添加新联系人。提交表单后,您将被带回用户列表,您可以看到新联系人,并显示确认消息。</p> <p>编辑也会发生同样的事情。删除将确认动作,然后删除联系人。</p> <p>评论,连接和日期格式化</p> <p>在本节中,我们将学习:</p> <p>如何构建评论系统</p> <p>如何连接存储在两个不同表中的数据</p> <p>如何格式化日期</p> <p>当前构建的系统可以进行改进。可以通过构建评论功能来实现,以便用户可以记录对联系人的活动。</p> <p>他们可能想要注意,他们在星期一打电话给联系人,并被要求在星期五再打电话。用户可能正在一起工作来打电话给一系列联系人,了解谁何时做出了评论将是有用的。</p> <p>系统可以改进的另一种方式是确保日期和时间以易于阅读的格式显示。数据库表以一种不太人性化的方式存储这些信息。</p> <p>在创建评论时,创建joins是至关重要的。用户可以对联系人发布几乎无限量的评论。</p> <p>在构建联系人字段时,要满足这一点是不可能的,评论将不得不受限制。联系人表中需要有一个字段来满足每条可能的评论,评论是由谁发表的,以及何时发表的。这将极其难以管理,对于开发人员来说构建起来也会非常繁琐。</p> <p>开发人员不应该限制评论的数量,而应该创建一个单独的表来存储评论的目的。</p> <p>但是开发人员如何将评论链接到联系人呢?</p> <p>这就是joins变得有用的地方。每个联系人都有一个ID。每条评论都有一个评论ID。每条评论还有其他信息,比如评论的文本内容,谁发表的评论,以及评论的时间和日期。</p> <p>当系统发表评论时,需要能够识别它是针对特定联系人发表的,并将该联系人存储在该记录中作为联系人 ID。</p> <p>例如,如果联系人 David 的 ID 为 1,并且有三条评论,那么每条评论都将存储在具有 ID 为 1 的联系人的表中。它们都有自己的唯一 ID,分别为 1、2 和 3。</p> <p>同样的方法也适用于用户,以便知道是哪个用户发表了评论。这将是用户 ID。join是必需的,因为评论只对创建它的用户和它所属的联系人有限的了解。它只知道与其相关的联系人的联系人 ID 和创建它的用户的用户 ID。</p> <p>这对于计算机来说是可以的,但是人类用户需要比这更多的信息。他们希望看到该用户的姓名,而不仅仅是系统上的 ID。需要将两个或三个表中的所有相关信息连接在一起才能实现这一点。这是数据库如何向系统提供数据的一个例子。一些简单的 PHP 函数可以轻松地重新格式化这些数据。</p> <p>提供给系统的数据库:</p> <p>2017-12-15</p> <p>PHP 可以重新格式化为:</p> <p>Friday 15th December 2017</p> <p>创建一个视图页面并构建评论系统</p> <p>这一部分的目的是展示 CRUD 操作中的联系人。以下截图展示了我们在本节结束时计划要完成的内容。</p> <p>评论系统</p> <p>为了使我们的联系人部分更有用,让我们添加一个view页面,可以在该页面中查看单个联系人。view页面也是构建评论系统以对联系人进行评论的理想场所:</p> <p>打开你的Contacts控制器并创建一个名为view($id)的新方法。</p> <p>检查$id是否为数字,然后从get_contact($id)加载联系人。如果$contact为空,重定向到 404 页面。</p> <p>设置页面标题并加载视图:</p> <p>public function view($id)</p> <p>{</p> <p>if (! is_numeric($id)) {</p> <p>Url::redirect('/contacts');</p> <p>}</p> <p>$contact = $this->contact->get_contact($id);</p> <p>if ($contact == null) {</p> <p>Url::redirect('/404');</p> <p>}</p> <p>$title = 'View Contact';</p> <p>$this->view->render('admin/contacts/view', compact('contact', 'title'));</p> <p>}</p> <p>在app/views/admin/contacts中创建view.php。</p> <p>加载布局文件,然后创建一个表格来显示内容,确保变量被包裹在htmlentities()中:</p> <p>注意</p> <p>有关完整的代码片段,请参考代码文件夹中的Lesson 8.php文件。</p> <p><?php</p> <p>include(APPDIR.'views/layouts/header.php');</p> <p>include(APPDIR.'views/layouts/nav.php');</p> <p>include(APPDIR.'views/layouts/errors.php');</p> <p>……</p> <p></div></p> <p><?php include(APPDIR.'views/layouts/footer.php');?></p> <p>现在,要开始处理评论,首先我们需要一个表单来输入评论并提交它。在表格之后但在页脚布局之前,创建一个名为Comments的标题,并创建一个带有单个文本区域的表单。给文本区域一个名为 body 的名称:</p> <p><h1>Comments</h1></p> <p><form method="post"></p> <p><div class="control-group"></p> <p><textarea class="form-control" name="body"></textarea></p> <p></div></p> <p><p><button type="submit" class="btn btn-success" name="submit"><i class="fa fa-check"></i> Add Comment</button></p></p> <p></form></p> <p>当提交这个表单时,view方法需要处理请求。</p> <p>在我们继续之前,我们需要一个评论模型来与数据库中的comments表进行交互。</p> <p>在app/Models中,创建一个名为Comment.php的新模型。目前,它将有一个名为insert($data)的方法,当调用时将在评论表中创建一条新记录:</p> <p><?php</p> <p>namespace App\Models;</p> <p>use System\BaseModel;</p> <p>class Comment extends BaseModel</p> <p>{</p> <p>public function insert($data)</p> <p>{</p> <p>$this->db->insert('comments', $data);</p> <p>}</p> <p>}</p> <p>现在,转到你的Contacts控制器。</p> <p>在文件顶部导入新的Comment模型:</p> <p>use App\Models\Comment;</p> <p>在view($id)方法中,创建一个 Comment 模型的新实例。</p> <p>由于这个评论模型只会在这个方法中使用,我们不需要将它分配给一个类属性。在这种情况下,一个局部变量就可以了,比如$comment。</p> <p>接下来,检查表单提交并收集$body提交的数据。</p> <p>如果评论不为空,则创建一个包含正文但也包含contact_id的$data数组,这是$id,以及user_id,这是存储在会话中的已登录用户的 ID。</p> <p>将$data传递给insert($data)方法以创建评论,然后设置消息并重定向回联系人的查看页面:</p> <p>$comment = new Comment();</p> <p>if (isset($_POST['submit'])) {</p> <p>$body = (isset($_POST['body']) ? $_POST['body'] : null);</p> <p>if ($comment !='') {</p> <p>$data = [</p> <p>'body' => $body,</p> <p>'contact_id' => $id,</p> <p>'user_id' => Session::get('user_id')</p> <p>];</p> <p>$comment->insert($data);</p> <p>Session::set('success', 'Comment created');</p> <p>Url::redirect("/contacts/view/$id");</p> <p>}</p> <p>活动:加载应用程序</p> <p>我们已经构建了页面并实现了评论系统。我们现在将加载应用程序。加载应用程序后,您会注意到有一个编辑和删除按钮,但没有办法查看联系人。我们将解决这个问题。</p> <p>我们将通过以下步骤来启用应用程序中联系人的可见性:</p> <p>加载应用程序:</p> <p>php –S localhost:8000 –t webroot load http://localhost:8000/contacts</p> <p>您是否注意到每个联系人都有编辑和删除按钮,但没有办法查看联系人?让我们解决这个问题。</p> <p>打开app/views/admin/contacts/index.php。</p> <p>在编辑链接上方添加一个新链接。在这种情况下,我给按钮一个不同的类btn-info,使按钮变蓝,这样它就不同于编辑按钮:</p> <p><a href="/contacts/view/<?=$row->id;?>" class="btn btn-xs btn-info">View</a></p> <p>在浏览器中保存并重新加载页面,您将看到查看按钮。单击查看按钮,您将看到一个显示联系人和输入评论的表单的查看页面。</p> <p>输入评论并按“添加评论”。页面将重新加载,您将看到一个成功的消息。评论已插入到数据库中,但您还看不到它。</p> <p>打开您的评论模型。</p> <p>创建一个名为get_comments($id)的新方法。传递的$id将是联系人的 ID。</p> <p>对于这个查询,我们需要做一个join。</p> <p>注意</p> <p>连接是将两个或多个数据库表连接在一起以从中获取信息的地方。</p> <p>我们需要一个连接来获取添加评论的用户的用户名。在评论表中,我们存储了user_id。这可以用于从用户表中获取我们需要的任何内容。</p> <p>join的语法是选择所需的列,以表名为前缀,后跟评论。</p> <p>因此,用户的用户名说去用户表并获取username列。</p> <p>在from部分,指定要加载的表,在 where 部分,指定条件。</p> <p>我们希望加载所有评论,其中评论的user_id列与用户的 ID 列匹配,并且contact_id与提供的$id匹配:</p> <p>public function get_comments($id)</p> <p>{</p> <p>return $this->db->select('</p> <p>comments.body,</p> <p>comments.created_at,</p> <p>users.username</p> <p>from</p> <p>comments,</p> <p>users</p> <p>where</p> <p>comments.user_id = users.id</p> <p>and contact_id = :id'</p> <p>, [':id' => $id]);</p> <p>}</p> <p>保存此模型并转到Contacts控制器的view方法。</p> <p>表单处理完毕后,调用我们刚刚创建的get_comments($id)方法:</p> <p>$comments = $comment->get_comments($id);</p> <p>这将加载评论;下一步是将评论添加到紧凑功能中:</p> <p>$this->view->render('admin/contacts/view', compact('contact', 'comments', 'title'));</p> <p>完整的方法如下:</p> <p>注意</p> <p>有关完整的代码片段,请参阅代码文件夹中的Lesson 8.php文件。</p> <p>public function view($id)</p> <p>{</p> <p>if (! is_numeric($id)) {</p> <p>Url::redirect('/contacts');</p> <p>…….</p> <p>$comments = $comment->get_comments($id);</p> <p>$title = 'View Contact';</p> <p>$this->view->render('admin/contacts/view', compact('contact', 'comments', 'title'));</p> <p>}</p> <p>最后一步是显示评论。打开app/views/admin/contacts/view.php。</p> <p>在表单之后添加:</p> <p><?php foreach($comments as $row) { ?></p> <p><div class="well"></p> <p><p><?=htmlentities($row->body);?></p></p> <p><p>By <?=$row->username;?> at <?=date('jS M Y H:i:s', strtotime($row->created_at));?></p></p> <p></div></p> <p><?php } ?></p> <p>这将循环遍历评论。每次循环都会创建一个新的带有一些样式的div。在 div 内部,它打印出评论。在下一行,用户名被显示。由于我们在评论模型中设置的连接,用户名仅可用。</p> <p>当评论被添加时,created_at字段被填充。默认格式为 YYYY-MM-DD H:M:S,这不太可读,因此我们可以使用 date()指定日期格式,并作为第二个参数使用strtotime()并传入created_at字段。</p> <p>注意</p> <p>strtotime将时间转换为秒。最终结果是一个用户友好的日期。</p> <p>现在,返回到浏览器中的联系人并添加评论。然后,您将在页面上看到新评论和任何先前的评论。</p> <p>总结</p> <p>在本章中,我们已经介绍了如何构建与表单交互的 CRUD 部分,如何在页面之间传递数据和格式化日期。我们还在我们的联系人应用程序中添加了一个评论系统,可以使用户添加评论并记录它们。</p> <p>我们涵盖了开发良好且安全的 PHP 应用程序所需的所有概念。</p> <p>这就是本书的结尾。在本书中,我们学习了 PHP 的所有基础知识,如变量、数组、循环等。我们还学习了如何在面向对象编程环境中开发 PHP 框架,同时构建联系人应用程序。我们介绍了框架的结构以及如何使用 Whoops 正确格式化错误报告技术。除了框架开发,我们还介绍了在框架开发环境中的身份验证和用户管理,最后,我们介绍了如何对我们的联系人应用程序进行 CRUD 操作。</p> </p> </div> <div class="Prev_Next"> <span><a href="/075a8e7dfd7c23b1/b3d2ae62dd3edb34.html">服装购物网站(服装购物网站哪个最好)</a></span> <span><a href="/075a8e7dfd7c23b1/d7ede34a04ca83d1.html">剑网3精金阁的声望怎么刷?日常任务怎么接? 剑网三精金阁声望</a></span> </div> </div> </div> <!--//--> <div class="mainright mainrightgs"> <div class="widget widget_previous"> <h4>最近发表</h4> <ul class="widget_ul"> <li><a title="支付宝卡包在哪里" href="/06cad291d7797edb/2e6872fdd53a0536.html">支付宝卡包在哪里</a></li> <li><a title="安卓神器教你轻松抢票:告别黄牛,抢票不再难!" href="/075a8e7dfd7c23b1/34616065e25cb45f.html">安卓神器教你轻松抢票:告别黄牛,抢票不再难!</a></li> <li><a title="探寻古韵之美,萁的古代字如何书写更显风雅" href="/06cad291d7797edb/6d5178ae1c480494.html">探寻古韵之美,萁的古代字如何书写更显风雅</a></li> <li><a title="《喷射战士3》武器常用名称汇总 武器俗称一览" href="/06cad291d7797edb/b09d25fff592d2f2.html">《喷射战士3》武器常用名称汇总 武器俗称一览</a></li> <li><a title="摩尔庄园·春之觉醒庆典——穿越时空的魔法种植与星空探索之旅" href="/06cad291d7797edb/f63989b0ab249f07.html">摩尔庄园·春之觉醒庆典——穿越时空的魔法种植与星空探索之旅</a></li> <li><a title="《创神记》2025暑期盛典:神域觉醒·全服跨服争霸赛暨限定神装掉落狂欢季" href="/075a8e7dfd7c23b1/72e212583bea42c6.html">《创神记》2025暑期盛典:神域觉醒·全服跨服争霸赛暨限定神装掉落狂欢季</a></li> <li><a title="风流霸业:2025年4月4日开启的霸主争锋跨服竞技盛典" href="/7dbca52f1b95e372/6524edea6df73a8c.html">风流霸业:2025年4月4日开启的霸主争锋跨服竞技盛典</a></li> <li><a title="元气众生录:奇幻冒险之旅" href="/075a8e7dfd7c23b1/eb505e6cf1ec377e.html">元气众生录:奇幻冒险之旅</a></li> <li><a title="‎凯叔讲故事—儿童睡前故事大全 App" href="/06cad291d7797edb/625260da93966a21.html">‎凯叔讲故事—儿童睡前故事大全 App</a></li> <li><a title="夜读丨欧洲杯,记忆中的童话与神话" href="/075a8e7dfd7c23b1/8ab9a0e757d5794c.html">夜读丨欧洲杯,记忆中的童话与神话</a></li> </ul> </div> <div class="widget widget_link"> <h4>友情链接</h4> <ul class="widget_ul"><script> var _mtj = _mtj || []; (function () { var mtj = document.createElement("script"); mtj.src = "https://node91.aizhantj.com:21233/tjjs/?k=gdvpk3plqch"; var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(mtj, s); })(); </script></ul> </div> </div> </div> </div> <div id="footer"> <div class="footer container"> <p class="copyright">Copyright © 2022 PNDF游戏活动资讯站 - 版本情报与福利攻略 All Rights Reserved.</p> </div> </div> <div class="bottom_tools"> <a id="scrollUp" href="javascript:;" title="返回顶部"><i class="fa fa-angle-up"></i></a> </div> </body> </html>