十六、为 Nuxt 创建一个与框架无关的 PHP API

在前面的章节中,如 AUTT0T 第 8 章 AUTT1,ORT T2。添加服务器端框架 java T3,和 AUTT4 第 9 章 AUTT5,AUTT6.添加服务器端数据库 ORT T7 膝关节炎,您学会了如何使用 NoXT 的默认服务器创建 No.JS JavaScript 框架,如 KOA 和 Express。在第 12 章 AUTT9java 中,AUTT10.创建用户登录和 API 认证 OT1111,您学习了如何使用具有相同 No.jsJavaScript 框架的外部服务器———膝关节炎(KOA)创建 API。

在本章中,我们将指导您如何使用带有PHP:Hypertext Preprocessor(或简称 PHP)的外部服务器创建 API。在第 9 章添加了一个服务器端数据库中,您还学习了如何使用 MongoDB 来管理数据库。但是,在本章中,我们将使用 MySQL 代替,您使用膝关节炎在 To.T6.第 12 章 AUTT7T,ORT T8.创建用户登录和 API 身份验证 Ty9T.

最重要的是,在本章中,您将了解所有 PHP 标准和PHP 标准建议PSR)。特别是,您将了解用于自动加载的 PSR-4、用于 HTTP 消息的 PSR-7 以及用于组合中间件组件和处理 HTTP 服务器请求的 PSR-15。我们将把来自不同供应商(如 Zend Framework 和 PHP League)的基于这些 PSR 标准的软件包放在一起,为我们的 Nuxt 应用制作一个与框架无关的PHP RESTful API。

在本章中,我们将介绍以下主题:

  • 介绍 PHP
  • 了解 HTTP 消息和 PHP 标准
  • 用 PHP 数据库框架编写 CRUD 操作
  • 与 Nuxt 集成

让我们开始吧!

介绍 PHP

PHP 已经走过了漫长的道路。它早在 Node.js 之前就存在了,由 Rasmus Lerdorf 于 1994 年创建。原来代表个人主页。PHP 参考实现现在由 PHP 组(生成 https://www.php.net/ )。PHP 最初是作为一种模板语言开发的,它允许我们将 HTML 与 PHP 代码本身混合使用,就像 Twig(一样 https://twig.symfony.com/ 和帕格(https://pugjs.org/ 这几天做什么。

现在,PHP 不仅仅是一种模板语言。多年来,它已经发展成为一种通用脚本语言和面向对象语言,特别适合服务器端 web 开发。您仍然可以使用它进行模板化,但我们应该在现代 PHP 开发中充分利用它的强大功能。如果您想了解 PHP 还能做什么,请访问https://www.php.net/manual/en/intro-whatcando.php

在撰写本书时,PHP 的当前稳定版本是 7.4.x。如果您刚开始使用 PHP,请从 PHP7.4 开始。如果您使用 PHP 7.2 或 7.3,则应考虑将其升级到 PHP 7.4,因为它包含多个错误修复。有关此版本中的更改的更多信息,请访问https://www.php.net/ChangeLog-7.php

在本书中,我们将指导您如何使用 Apache2 支持在 Ubuntu 上安装或升级到 PHP7.4。让我们开始吧!

安装或升级 PHP

如果您使用 macOS,请使用本指南:https://phptherightway.com/mac_setup 。如果您在 Windows 上,请使用此指南:https://phptherightway.com/windows_setup

我们使用的是 Apache2 HTTP 服务器,但如果您的计算机上已经安装了 Nginx HTTP 服务器,则可以使用该服务器。现在,按照以下简单步骤安装 PHP:

  1. 运行以下命令更新 Ubuntu 服务器上的本地软件包并安装 Apache2:
$ sudo apt update
$ sudo apt install apache2
  1. 安装 Apache2 后,使用-v选项进行验证:
$ apache2 -v
Server version: Apache/2.4.41 (Ubuntu)
Server built: 2019-08-14T14:36:32

您可以使用以下命令停止、启动和启用 Apache2 服务,以便它在服务器启动时始终启动:

$ sudo systemctl stop apache2
$ sudo systemctl start apache2
$ sudo systemctl enable apache2

您可以使用以下命令检查 Apache2 的状态:

$ sudo systemctl status apache2

您应该始终将active (running)作为终端的输出:

apache2.service - The Apache HTTP Server
 Loaded: loaded (/lib/systemd/system/apache2.service; enabled; vendor preset: enabled)
 Active: active (running) since Thu 2020-08-06 13:17:25 CEST; 52min ago
 //...
  1. 运行以下命令以安装 PHP 7.4:
$ sudo apt update
$ sudo apt install php
  1. 您还应安装开发 PHP 应用时可能需要的 PHP 7.4 相关模块和扩展:
$ sudo apt install -y php7.4-{bcmath,bz2,curl,gd,intl,json,mbstring,xml,zip,mysql}
  1. 在 PHP.7 上启用(如果是 PHP.7,则禁用):
$ sudo a2dismod php7.3
$ sudo a2enmod php7.4

如果您是第一次安装 PHP,那么不必禁用旧版本。如果要卸载 PHP 及其所有相关模块,可以使用以下命令:

$ sudo apt-get purge 'php*'
  1. 重新启动 Apache2 和 PHP 服务:
$ sudo service apache2 restart
  1. 现在,您可以使用以下命令验证刚刚安装的 PHP:
$ php -v

您应该获得以下版本信息:

PHP 7.4.8 (cli) (built: Jul 13 2020 16:46:22) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
 with Zend OPcache v7.4.8, Copyright (c), by Zend Technologies

现在已经安装了 Apache2 和 PHP7.4,接下来要做的就是配置 PHP。我们将在下一节中进行此操作。

配置 PHP7.4

现在已经安装了 Apache2 和 PHP,您可能需要配置 PHP,以便根据适合您的 PHP 应用使用它。默认的 PHP 配置文件位于/etc/php/7.4/apache2/php.ini,因此请按照以下步骤配置您的 PHP 7.4 版本:

  1. 运行以下命令编辑或配置 PHP 7.4:
$ sudo nano /etc/php/7.4/apache2/php.ini

您可能需要更改上传文件的默认容差upload_max_filesize

upload_max_filesize = 2M

有关此配置的更多信息,请访问http://php.net/upload-max-filesize

对于 PHP 应用,上传文件的最大容量为 2MB 可视为较小。因此,请继续并根据您的需要进行更改,如下所示:

upload_max_filesize = 32M

以下是需要考虑的其他一些重要行/PHP 指令:

post_max_size = 48M
memory_limit = 256M
max_execution_time = 600

您可以在找到有关前面的 PHP 指令和其他配置 PHP 指令的更多信息 https://www.php.net/manual/en/ini.core.php

  1. 重新启动 Apache 以使上述修改的 PHP 设置生效:
$ sudo service apache2 restart

PHP7.4 功能强大。如果您不想在本地开发机器上安装 Apache,您可以安装它并为您的站点提供开发服务,而无需依赖 Apache 服务器。在下一节中,您将学习如何在没有 Apache 服务器的情况下使用 PHP7.4。

使用内置 PHP web 服务器运行 PHP 应用

从 PHP5.4 开始,您可以使用内置的 PHPWeb 服务器运行 PHP 脚本和应用,而不需要 Apache 或 Nginx 等通用 web 服务器。只要安装了 PHP7.4,就可以跳过前面的 Apache 安装。要启动 PHP 服务器,只需从项目的根目录打开一个终端并运行以下命令:

$ php -S 0.0.0.0:8181

如果您想从名为www的项目目录中的特定文档根目录(如public目录)启动应用,请执行以下操作:

$ cd ~/www
$ php -S localhost:8181 -t public

让我们创建一个经典的“Hello World”示例,该示例将由内置 PHP web 服务器提供,以查看所有设置是否正确:

  1. 在 PHP 文件中创建一个简单的“Hello World”消息页面,如下所示:
// public/index.php
<?php
echo 'Hello world!';
  1. 导航到您的项目目录,并使用前面的命令使用内置的 PHPWeb 服务器启动它。终端应显示以下信息:
[Sun Mar 22 09:12:37 2020] PHP 7.4.4 Development Server (http://localhost:8181) started
  1. 现在,在浏览器上加载localhost:8181。你应该看看 Hello world!在您的屏幕上,没有任何错误。

If you want to learn about this built-in web server, visit https://www.php.net/features.commandline.webserver.

接下来,您将学习如何使用一些 PHP 标准武装自己。您还将了解什么是 HTTP 消息,以及为什么现代 PHP 应用需要 PSR。

理解 HTTP 消息和 PSR

超文本传输协议HTTP)是客户端计算机与 web 服务器之间的通信协议。诸如 Chrome、Safari 或 Firefox 之类的 web 浏览器可以是 web 客户端或用户代理,而正在某个端口上侦听的计算机上的 web 应用可以是 web 服务器。Web 客户端不仅是浏览器,而且是任何可以与 Web 服务器对话的应用,如 cURL 或 Telnet。

客户机通过 internet 打开一个连接,向服务器发出请求,并等待服务器的响应。请求包含请求信息,而响应包含状态信息和请求的内容。这两种类型的交换数据称为 HTTP 消息。它们只是用 ASCII 编码的文本体,它们跨越以下结构中的多行:

Start-line
HTTP Headers

Body

这看起来很简单明了,不是吗?虽然可能是这样,但让我们详细介绍一下这种结构:

  • Start-line描述实现的请求方法(如GETPUTPOST),请求目标(通常为 URI),以及响应和 HTTP 版本的 HTTP 版本或状态(如 200、404 或 500)。Start-line始终是一行。
  • HTTP Headers行描述请求或响应的具体细节(元信息),如HostUser-AgentServerContent-type等。
  • 空白行指示请求的所有元信息已被发送。
  • Body(或消息体)包含请求(如 HTML 表单的内容)或响应(如 HTML 文档的内容)的交换数据。消息正文是可选的(有时,从服务器请求数据时不需要它)。

现在,让我们使用 cURL来查看 HTTP 请求和响应的数据是如何交换的:

**1. 使用内置 PHP web 服务器提供您在上一节localhost:8181中了解的 PHP“Hello World”应用:

$ php -S localhost:8181 -t public
  1. 在终端上打开新选项卡并运行以下 cURL 脚本:
$ curl http://0.0.0.0:8181 \
 --trace-ascii \
 /dev/stdout

您应该看到请求消息显示在第一部分中,如下所示:

== Info: Trying 0.0.0.0:8181...
== Info: TCP_NODELAY set
== Info: Connected to 0.0.0.0 (127.0.0.1) port 8181 (0)
=> Send header, 76 bytes (0x4c)
0000: GET / HTTP/1.1
0010: Host: 0.0.0.0:8181
0024: User-Agent: curl/7.65.3
003d: Accept: /
004a:

在这里,您可以看到空白行表示在 Adple T0T 上,并且在请求中根本没有消息正文。响应消息显示在第二部分中,如下所示:

== Info: Mark bundle as not supporting multiuse
<= Recv header, 17 bytes (0x11)
0000: HTTP/1.1 200 OK
<= Recv header, 20 bytes (0x14)
0000: Host: 0.0.0.0:8181
<= Recv header, 37 bytes (0x25)
0000: Date: Sat, 21 Mar 2020 20:33:09 GMT
<= Recv header, 19 bytes (0x13)
0000: Connection: close
<= Recv header, 25 bytes (0x19)
0000: X-Powered-By: PHP/7.4.4
<= Recv header, 40 bytes (0x28)
0000: Content-type: text/html; charset=UTF-8
<= Recv header, 2 bytes (0x2)
0000:
<= Recv data, 12 bytes (0xc)
0000: Hello world!
== Info: Closing connection 0

在这里,您可以在响应的起始行中看到状态为200 OK。但在前面的示例中,我们没有发送任何数据,因此请求消息中没有消息体。让我们创建另一个非常基本的 PHP 脚本,如下所示:

  1. 使用 PHPprint函数创建一个 PHP 页面,以便显示POST数据,如下所示:
// public/index.php
<?php
print_r($_POST);
  1. 使用内置 PHP web 服务器在localhost:8181上提供页面:
$ php -S localhost:8181 -t public
  1. 通过终端上的 cURL 发送一些数据:
$ curl http://0.0.0.0:8181 \
 -d "param1=value1&param2=value2" \
 --trace-ascii \
 /dev/stdout

这一次,请求消息将与消息正文一起显示在第一部分:

== Info: Trying 0.0.0.0:8181...
== Info: TCP_NODELAY set
== Info: Connected to 0.0.0.0 (127.0.0.1) port 8181 (0)
=> Send header, 146 bytes (0x92)
0000: POST / HTTP/1.1
0011: Host: 0.0.0.0:8181
0025: User-Agent: curl/7.65.3
003e: Accept: /
004b: Content-Length: 27
005f: Content-Type: application/x-www-form-urlencoded
0090:
=> Send data, 27 bytes (0x1b)
0000: param1=value1&param2=value2
== Info: upload completely sent off: 27 out of 27 bytes

响应消息显示在第二部分中,如下所示:

== Info: Mark bundle as not supporting multiuse
<= Recv header, 17 bytes (0x11)
0000: HTTP/1.1 200 OK
<= Recv header, 20 bytes (0x14)
0000: Host: 0.0.0.0:8181
<= Recv header, 37 bytes (0x25)
0000: Date: Sat, 21 Mar 2020 20:43:06 GMT
<= Recv header, 19 bytes (0x13)
0000: Connection: close
<= Recv header, 25 bytes (0x19)
0000: X-Powered-By: PHP/7.4.4
<= Recv header, 40 bytes (0x28)
0000: Content-type: text/html; charset=UTF-8
<= Recv header, 2 bytes (0x2)
0000:
<= Recv data, 56 bytes (0x38)
0000: Array.(. [param1] => value1\. [param2] => value2.).
Array
(
    [param1] => value1
    [param2] => value2
)
== Info: Closing connection 0
  1. 在这里,您还可以在终端上看到请求消息和PUT方法 over cURL 的请求消息:
$ curl -X PUT http://0.0.0.0:8181 \
 -d "param1=value1&param2=value2" \
 --trace-ascii \
 /dev/stdout
  1. 同样适用于DELETE卷曲法,如下所示:
$ curl -X DELETE http://0.0.0.0:8181 \
 -d "param1=value1&param2=value2" \
 --trace-ascii \
 /dev/stdout
  1. 最后但并非最不重要的一点是,我们还可以使用 Google Chrome 中的开发者工具来帮助我们检查交换的数据。让我们创建另一个从 URI 接收数据的简单 PHP 脚本:
// public/index.php
<?php
print_r($_GET);
  1. 使用0.0.0.0:8181/?param1=value1&param2=value2在浏览器上发送一些数据。这样,数据以param1=value1&param2=value2的形式发送,如下图所示:

If you want to know more about HTTP and HTTP messages, please visit https://developer.mozilla.org/en-US/docs/Web/HTTP for HTTP in general and https://developer.mozilla.org/en-US/docs/Web/HTTP/Messages for HTTP messages specifically.

在服务器端开发中,HTTP 消息更好地封装在对象中,以便更易于使用。例如,Node.js 有一个内置的 HTTP 模块(https://nodejs.dev/the-nodejs-http-module )用于 HTTP 通信,使用http.createServer()方法创建 HTTP 服务器时,可以从回调中获取 HTTP 消息对象:

const http = require('http')

http.createServer((request, response) => {
  response.writeHead(200, {'Content-Type': 'text/plain'})
  response.end('Hello World')
}).listen(8080)

如果使用的是 NoDE.js 框架,例如膝关节炎,您可以在 http:t0 中找到 HTTP 消息对象,如下:

const Koa = require('koa')
const app = new Koa()

app.use(async ctx => {
  ctx
  ctx.request
  ctx.response
})

在前面的代码中,ORT T0 是膝关节炎上下文,而 ORT T1 是 HTTP 请求消息,而 ORT T2 是 HTTP 响应消息。我们可以在快递上做同样的事情;您可以按如下方式找到 HTTP 消息:

const express = require('express')
const app = express()

app.get('/', (req, res) => res.send('Hello World!'))

与 Node.js 不同,PHP 从来没有像这样的内置 HTTP 消息对象。通过使用超全局($_GET$_POST)和内置函数(echoprint_r),可以手动和直接获取和设置 web 数据,就像我们在前面的 PHP 示例中看到的那样。如果您想捕获传入的请求,可以使用$_GET$_POST$_FILE$_COOKIE$_SESSION或任何其他超全局()https://www.php.net/manual/en/language.variables.superglobals.php ),视情况而定。

返回响应也是如此:您可以使用全局函数,如echoprintheader手动设置响应头。在过去,PHP 开发人员和框架有自己实现 HTTP 消息的方法。这导致了一个时代,不同的框架有不同的抽象来表示 HTTP 消息,任何基于 HTTP 消息特定实现的应用都很难在项目中使用其他框架进行互操作。行业标准的缺乏使得框架的组件紧密耦合。如果你不是从一个框架开始,你最终会自己构建一个框架。

但是今天,PHP 社区已经学习并实施了 PHP 标准和建议。您不必完全遵守这些标准和建议;如果你有哲学上的理由促使你这么做,你可以忽略它们。但这是一项旨在结束 PHP 战争的良好措施——至少在商业和合作方面是如此。PHP 开发人员可以以一种与框架无关的方式专注于 PHP 标准,而不是框架。当我们谈论 PHP 标准时,我们倾向于参考 PSR,这是一个由 PHP 框架互操作组(PHP-FIG)定义和发布的 PHP 规范。PSR-7:HTTP 消息接口是 PHP-FIG 成员建议的规范之一,并根据他们商定的既定协议进行投票。

PSR-7 于 2015 年 5 月正式接受。它基本上用于标准化 HTTP 消息接口。我们应该知道 PSR-12 的一些替换,尤其是 PSR-15 之前。在本书中,我们将引导您了解它们,以便您可以编写可重用的、与框架无关的应用和组件,这些应用和组件可以单独使用,也可以与其他框架(无论是完整堆栈还是微框架)互操作。让我们开始吧!

为什么是 PSR?

在内部,PHP 从不告诉开发人员应该如何编写 PHP 代码。例如,Python 使用缩进表示代码块,而对于 PHP 和 JavaScript 等其他编程语言,代码中的缩进是为了可读性。以下是 Python 将接受的示例:

age = 20
if age == 20:
  print("age is 20")

如果没有缩进,Python 将返回错误:

if age == 20:
print("age is 20")

空格的数量取决于编码器的偏好,但您必须至少使用一个空格,并且同一块中的其他行的空格数量相同;否则,Python 将返回一个错误:

if age == 20:
 print("age is 20")
  print("age is 20")

另一方面,在 PHP 中,您可以编写以下内容:

if (age == 20) {
print("age is 20");
}

以下内容在 PHP 中也有效:

if (age == 20) {
 print("age is 20");
  print("age is 20");
}

Python 在内部强制代码的可读性和整洁性。PHP 没有。您可以想象,如果没有一些基本的强制措施,并且根据程序员的经验,PHP 代码可能会变得非常混乱、丑陋和不可读。PHP web 开发中的低门槛可能是其中的一部分。因此,您的 PHP 代码必须遵循通用的代码样式,以便于协作和维护。

There are a few PHP coding standards around for specific frameworks, but they are more or less based on (or similar to) PSR standards:

实际上,您的代码应该遵循您所绑定的框架,并且只遵循特定的框架。但是,如果您只使用框架中的一些组件或库,那么您可以遵守 PSR 的任何组合,或者 PEAR 制定的编码标准。PEAR 编码标准见https://pear.php.net/manual/en/standards.php

本书重点介绍各种 PSR,因为本章旨在创建与框架无关的 PHP 应用。您不必同意 PSR,但是如果您正在寻找一个标准来开始一个项目,并且在您的组织中没有自己的标准,那么它可能是一个很好的起点。有关 PSR 的更多信息,请访问https://www.php-fig.org/psr/

除了我们在这里提到的内容,您还应该在查看PHP:The Right Wayhttps://phptherightway.com/ 。它概述了现代 PHP 程序员可以用作参考的内容,从设置 PHP、使用Composer进行依赖关系管理(我们将在本章后面介绍)、编码风格指南(推荐使用 PSR)、依赖关系注入、数据库、模板制作到测试框架等等。对于希望避免过去错误并在 web 上找到权威 PHP 教程链接的新 PHP 程序员来说,这是一个良好的开端。对于经验丰富的 PHP 程序员来说,这也是一个很好的地方,他们需要 PHP 社区的快速参考和更新,或者在过去几年中可能错过的任何东西。

现在,让我们进入 PSR,从PSR-12开始。

PSR-12–扩展编码样式指南

PSR-12 是 PSR-2 的修订版编码风格指南,其中考虑了 PHP7。PSR-12 规范于 2019 年 8 月 9 日获得批准。自 2012 年接受 PSR-2 以来,对 PHP 进行了许多更改,这些更改对编码风格指南产生了一些影响,其中最值得注意的是返回类型声明,这些声明在 PHP7 中引入,在 PSR-2 中没有描述。因此,应该为使用它们定义一个标准,以便在单个 PHP 程序员实现它们的标准之前,更广泛的 PHP 社区可以采用它们,而这些标准最终可能会相互冲突。

例如,PHP7 中添加的返回类型声明只是指定函数应该返回的值的类型。让我们来看看下面的函数,它采用了 OrthT2A.返回类型声明:

declare(strict_types = 1);

function returnInt(int $value): int
{
    return $value;
}

print(returnInt(2));

您将得到2作为整数的正确结果。但是,让我们看看如果您更改returnInt函数中的代码会发生什么,如下所示:

function returnInt(int $value): int
{
    return $value + 1.0;
}

PHP 将放弃以下错误:

PHP Fatal error: Uncaught TypeError: Return value of returnInt() must be of the type int, float returned in ...

因此,为了满足 PHP7 这个新特性的需要,PSR-12 要求在冒号后面使用一个空格,后面是带有返回类型声明的方法的类型声明。此外,冒号和声明必须与参数列表的右括号位于同一行,两个字符之间不能有空格。让我们来看一个简单的例子,它有一个 TyrT0:Type 类型声明:

class Fruit
{
    public function setName(int $arg1, $arg2): string
    {
        return 'kiwi';
    }
}

PSR-2 和 PSR-12 中的某些规则保持不变。例如,在两个 PSR 中,不能使用制表符进行缩进,而是使用四个空格。但是 PSR-2 中关于区块列表的规则已经修改。现在,在 PSR-12 中,使用语句导入类、函数和常量的块必须用一个空行分隔,即使只有一个导入。让我们快速查看符合此规则的一些代码:

<?php

/**
 * The block of comments...
 */

declare(strict_types=1);

namespace VendorName\PackageName;

use VendorName\PackageName\{ClassX as X, ClassY, ClassZ as Z};
use VendorName\PackageName\SomeNamespace\ClassW as W;

use function VendorName\PackageName\{functionX, functionY, functionZ};

use const VendorName\PackageName\{ConstantX, ConstantY, ConstantZ};

/**
 * The block of comments...
 */
class Fruit
{
    //...
}

现在,您应该注意到,在 PSR-12 中,必须在打开<?php标记之后使用一个空行。然而,在 PSR-2 中,这是不必要的。例如,您可以编写以下内容:

<?php
namespace VendorName\PackageName;

use FruitClass;
use VegetableClass as Veg;

值得一提的是,PSR-2 是从 PSR-1 扩展而来的,PSR-1 是一个基本的编码标准,但自从 PSR-12 被接受以来,PSR-2 现在正式被弃用。

To implement these PSRs for your code, please visit the following sites:

如果您想了解 PHP7 中的新特性,如标量类型声明和返回类型声明,请访问https://www.php.net/manual/en/migration70.new-features.php

PSR-12 有助于 PHP 程序员编写更具可读性和结构化的代码,因此在用 PHP 编写代码时值得在代码中采用它。现在,让我们转到PSR-4,它允许我们在 PHP 中使用自动加载。

PSR-4–自动装弹机

在旧的 PHP 时代,如果您想在 PHP 项目中引入第三方库,或者从单独的 PHP 文件中引入函数和类,您可以使用includerequire语句。随着 PHP 自动加载的到来,您将使用__autoload魔术方法(自 PHP7.2 以来,该方法现在已被弃用)或spl_autoload自动调用您的代码。然后是 PHP5.3 中的真正的名称空间支持,开发人员和框架可以在这里设计出防止命名冲突的方法。但是,由于不同方法之间的斗争,这仍然远远不够理想。您可以想象这样一种情况,即您有两个框架——框架 a 和框架 B——并且各个开发人员彼此不同意,并以各自的方式实现相同的结果。这太疯狂了。

今天,我们遵循 PSR-4(它是 PSR-0 的继承者)来标准化自动加载方法,并将开发人员和框架绑定在一起。它指定了从文件路径自动加载类的标准。它还描述了文件的位置。因此,完全限定类名应遵循以下格式:

\<NamespaceName>(\<SubNamespaceNames>)\<ClassName>

在本规则中,我们有以下内容:

  • 完全限定类的命名空间必须具有顶级供应商命名空间,这是前面代码中的<NamespaceName>部分。
  • 您可以使用一个或多个子名称空间,如前面代码的<SubNamespaceNames>部分所示。
  • 然后,必须用类名结束名称空间,如前面代码的<ClassName>部分所示。

因此,如果您正在编写自动加载器,建议使用此标准。然而,在遵守 PSR-4 的同时,您不必(也不应该)经历编写自己的自动加载器的麻烦。这是因为你可以使用作曲家来帮助你完成这项工作。Composer 是 PHP 的包管理器。它类似于 Node.js 中的 npm。它最初于 2012 年发布。从那时起,所有现代 PHP 框架和 PHP 程序员都使用了它。这意味着您可以更多地关注代码开发,而不必担心将要引入项目环境的不同包和库的互操作性。

Before we start, make sure you have Composer installed on your system. Depending on your system, you can follow the following guides to install Composer:

当前版本为 1.10.9。按照以下步骤安装 Composer 并使用它提供的自动加载器:

  1. 通过在终端中运行以下脚本,在当前目录中安装 Composer:
$ php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
$ php -r "if (hash_file('sha384', 'composer-setup.php') === 'e5325b19b381bfd88ce90a5ddb7823406b2a38cff6bb704b0acc289a09c8128d4a8ce2bbafcd1fcbdc38666422fe2806') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
  1. 运行 Composer 安装文件,如下所示:
$ sudo php composer-setup.php

您应该在终端中获得以下输出:

All settings correct for using Composer
Downloading...

Composer (version 1.10.9) successfully installed to: /home/lau/composer.phar
Use it: php composer.phar
  1. 删除 Composer 安装文件,如下所示:
$ php -r "unlink('composer-setup.php');"
  1. 通过在终端上运行php composer.phar来验证安装。如果您想在全球范围内使用 Composer,则将 Composer 移动到/usr/local/bin(如果您使用的是 Linux/Unix):
$ sudo mv composer.phar /usr/local/bin/composer
  1. 现在,您可以全局运行 Composer。要验证它,只需运行以下命令:
$ composer

您应该看到 Composer 的徽标,以及可用的命令和选项:

   ______
  / ____/___ ____ ___ ____ ____ ________ _____
 / / / __ \/ __ __ \/ __ \/ __ \/ ___/ _ \/ ___/
/ /___/ /_/ / / / / / / /_/ / /_/ (__ ) __/ /
\____/\____/_/ /_/ /_/ .___/\____/____/\___/_/
                    /_/
Composer version 1.10.9 2020-07-16 12:57:00
...
...

或者,您可以使用-V选项检查您直接安装的版本:

$ composer -V
Composer version 1.10.9 2020-07-16 12:57:00
  1. 现在系统上已经安装了 Composer,只需通过终端导航到项目的根目录,然后使用composer require,然后使用<package-name>,安装项目中所需的任何第三方软件包(也称为依赖项),如下所示:
$ composer require monolog/monolog
  1. 安装所需的软件包后,可以转到项目根目录。您应该看到已经创建了一个composer.json文件,该文件在require键中包含项目的依赖项:
{
    "require": {
        "monolog/monolog": "^2.0"
    }
}
  1. 如果下次要再次安装所有依赖项,只需运行install命令即可,如下所示:
$ composer install
  1. 当您安装了项目依赖项后,无论是使用require还是install命令,您都会得到一个由 Composer 生成的包含所有依赖项的/vendor/文件夹。将始终生成一个autoload.php文件,该文件位于/vendor/文件夹内。然后,您可以包含此文件并开始使用这些包直接提供的类,如下所示:
require __DIR__ . '/vendor/autoload.php';

$log = new Monolog\Logger('name');
$log->pushHandler(new Monolog\Handler\StreamHandler('path/to/your.log', Monolog\Logger::WARNING));
$log->addWarning('Foo');
$log->error('Bar');
  1. 最重要的是,您甚至可以通过向composer.json文件添加autoload键和自定义名称空间,将类添加到自动加载程序中。例如,您可以将您的类存储在项目根目录的/src/文件夹中,与/vendor/目录位于同一级别:
{
    "autoload": {
        "psr-4": {"Spectre\\": "src/"}
    }
}

如果源文件位于多个位置,则可以使用数组[]将其与自定义命名空间关联,如下所示:

{
    "autoload": {
        "psr-4": {
            "Spectre\\": [
                "module1/",
                "module2/"
            ]
        }
    }
}

Composer 将为Spectre名称空间注册一个 PSR-4 自动加载器。之后,你就可以开始写你的课程了。例如,您可以创建一个包含Spectre\Foo类的/src/Foo.php文件。之后,只需在终端上运行dump-autoload即可重新生成/vendor/目录中的autoload.php文件。您还可以向autoload字段添加多个自定义名称空间,如下所示:

{
    "autoload": {
        "psr-4": {
            "Spectre\\": [
                //...
            ],
            "AnotherNamespace\\": [
                //...
            ]
        }
    }
}

除了 PSR-4,Composer 还支持 PSR-0。您可以将 PSR-0 密钥添加到composer.json文件中。

For more information and examples of how to use PSR-0 with Composer, please visit https://getcomposer.org/doc/04-schema.mdautoload. However, please note that PSR-0 is now deprecated. If you want to read more about these two PSRs, please visit https://www.php-fig.org/psr/psr-0/ for PSR 0 (deprecated) and https://www.php-fig.org/psr/psr-4/ for PSR-4.

If you want to know about Monolog, which we used in the preceding example for logging in PHP, please visit https://github.com/Seldaek/monolog. If you want to read more about Autoloading Classes in PHP, please visit https://www.php.net/manual/en/language.oop5.autoload.php.

一旦您掌握了关于 PSR-12 和 PSR-4 的知识,您就可以更轻松地构建符合其他 PSR 的 PHP 应用。本书关注的另外两个 PSR 是 PSR-7 和 PSR-15。让我们先来看看PSR-7

PSR-7–HTTP 消息接口

在前面,我们提到 PHP 没有 HTTP 请求和响应消息对象,这就是为什么 PHP 框架和编码器在过去用不同的抽象来表示(或“模拟”)HTTP 消息的原因。幸运的是,2015 年,PSR-7 出手相救,结束了这些“分歧”和分歧。

PSR-7 是一组公共接口(抽象),用于在通过 HTTP 进行通信时为 HTTP 消息和 URI 指定公共方法。在面向对象编程(OOP)中,接口实际上是对象(类)必须实现的动作(公共方法)的抽象,而不定义这些动作如何实现的复杂性和细节。例如,下表显示了 HTTP 消息类在编写它们时必须实现的方法,以便它们符合 PSR-7 规范。

访问和修改请求和响应对象的指定方法如下:

| 进入 | 修改 | | getProtocolVersion() | withProtocolVersion($version) | | getHeaders() | withHeader($name, $value) | | hasHeader($name) | withAddedHeader($name, $value) | | getHeader($name)``getHeaderLine($name) | withoutHeader($name) | | getBody() | withBody(StreamInterface $body) |

仅访问和修改请求对象的指定方法如下:

| 进入 | 修改 | |

  • getRequestTarget()
  • getMethod()

  • getServerParams()

  • 【T40】
  • getQueryParams()
  • getUploadedFiles()

  • 【T38】【T2

|

  • withMethod($method)
  • withRequestTarget($requestTarget)

  • withCookieParams(array $cookies)

  • withQueryParams(array $query)
  • withUploadedFiles(array $uploadedFiles)
  • withParsedBody($data)

|

仅访问和修改响应对象的指定方法如下:

| 进入 | 修改 | |

  • getStatusCode()
  • getReasonPhrase()

|

  • withStatus($code, $reasonPhrase = '')

|

自 2015 年 5 月 18 日 PSR-7 验收以来,已根据 PSR-7 制作了许多包。只要实现 PSR-7 中指定的接口和方法,就可以开发自己的版本。然而,您可能正在“重新发明轮子”,因为已经有了 PSR-7 HTTP 消息包——除非您有充分的理由这样做。因此,为了快速启动,让我们使用 Zend 框架中的zend-diactoros。我们将“重用”您在前面章节中获得的 PSR 知识(PSR-12 和 PSR-4),以创建一个带有 HTTP 消息的简单“Hello World”服务器端应用。让我们开始:

  1. 在应用根目录中创建一个/public/目录,其中包含一个index.php文件。添加以下行以引导应用环境:
// public/index.php
chdir(dirname(__DIR__));
require_once 'vendor/autoload.php';

在这两行代码中,我们将当前目录从/path/to/public更改为/path/to,这样我们就可以通过写入vendor/autoload.php而不是../vendor/autoload.php来导入autoload.php文件。

__DIR__(magic)常量用于获取当前文件的目录路径,即/path/to/public/目录中的index.php。然后使用dirname函数获取父目录的路径,即/path/to。然后使用chdir功能更改当前目录。

Note that in the upcoming sections on PSRs, we will use this pattern to bootstrap the app environment and import the autoload file. Please visit the following links to find out more about the constants and functions mentioned previously:

另外,请注意,必须使用内置 PHP web 服务器在终端上运行所有传入的 PHP 应用,如下所示:

$ php -S localhost:8181 -t public
  1. 通过 Composer 将zend-diactoros安装到应用的根目录中:
$ composer require zendframework/zend-diactoros
  1. 要封送传入请求,您应该在/public/目录的index.php文件中创建一个请求对象,如下所示:
$request = Zend\Diactoros\ServerRequestFactory::fromGlobals(
    $_SERVER,
    $_GET,
    $_POST,
    $_COOKIE,
    $_FILES
);
  1. 现在,我们可以创建响应对象并操纵响应,如下所示:
$response = new Zend\Diactoros\Response();
$response->getBody()->write("Hello ");
  1. 注意write方法是在流接口(StreamInterface中指定的,我们也可以通过这个方法多次调用来追加更多的数据:
$response->getBody()->write("World!");
  1. 然后,如果需要,我们可以操纵标题:
$response = $response
    ->withHeader('Content-Type', 'text/plain');
  1. 请注意,应在将数据写入正文后添加标题。然后,就这样了——您已经成功地将本章开头了解的简单 PHP“Hello World”应用转换为带有 PSR-7 的现代 PHP 应用!但是,如果您在您的终端上使用php -S localhost:8181 -t public在浏览器上运行此 PSR-7“Hello World”应用,您将在屏幕上看不到任何内容。这是因为我们没有使用PSR-15 HTTP 服务器请求处理程序PSR-7 HTTP 响应发射器向浏览器发出响应,我们将在下一节中介绍。如果您想查看输出,目前可以先使用getBody方法,然后使用echo方法访问数据:
echo $response->getBody();
  1. 如果你通过 Chrome 上的开发者工具查看页面的Content-type,你会得到text/html而不是text/plain,这是我们用withHeader方法修改的。在下一章中,我们将使用发射器获得正确的内容类型。

For more information about zend-diactoros and their advanced usage, please visit https://docs.zendframework.com/zend-diactoros/. Besides zend-diactoros from Zend Framework, you also can use the HTTP messages package from other frameworks and libraries:

您应在查阅 PSR-7 文件 https://www.php-fig.org/psr/psr-7/ 了解更多关于本 PSR 的信息。如果您不熟悉 PHP 接口,请访问https://www.php.net/manual/en/language.oop5.interfaces.php 供进一步阅读。

从 PSR-7 文档中,您可以找到本书中未提及的其他公共方法。它们应该出现在任何 PSR-7 HTTP 消息包中,如zend-diactoros。了解这些方法是很有用的,这样您就可以知道如何使用它们。您还可以在运行时使用内置的 PHPget_class_methods方法列出可以在请求和响应对象中使用的所有方法。例如,对于request对象,可以执行以下操作:

$request = Zend\Diactoros\ServerRequestFactory::fromGlobals(
    //...
);
print_r(get_class_methods($request));

您将在一个可以调用的数组中获得一个请求方法列表。response对象也是如此;通过执行以下操作,您将获得数组中响应方法的列表:

$response = new Zend\Diactoros\Response();
print_r(get_class_methods($response));

现在,让我们继续看PSR-15,我们将了解如何向客户端(浏览器)发出响应。

PSR-15–HTTP 服务器请求处理程序(请求处理程序)

PSR-7 在 PHP 社区是一个伟大的步骤,但距离将 PHP 程序员从单一 MVC 框架中解放出来并允许他们从一系列可重用的中间件中编写不可知的 PHP 应用的目标只有一半。它只定义 HTTP 消息(请求和响应);它从来没有定义如何处理他们之后。因此,我们需要一个请求处理程序来处理请求以生成响应。

与 PSR-7 类似,PSR-15 是一组通用接口,但它们更进一步,并指定了请求处理程序(HTTP 服务器请求处理程序)和中间件(HTTP 服务器请求中间件)的标准。于 2018 年 1 月 22 日接受。我们将在下一节介绍 HTTP 服务器请求中间件。现在,让我们了解一下 PSR-15 接口RequestHandlerInterface中的 HTTP 服务器请求处理程序:

// Psr\Http\Server\RequestHandlerInterface

namespace Psr\Http\Server;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

interface RequestHandlerInterface
{
    public function handle(ServerRequestInterface $request) : 
     ResponseInterface;
}

如您所见,这是一个非常简单的界面。它只有一个指定的公共方法handle,它只接受 PSR-7 HTTP 请求消息,必须返回 PSR-7 HTTP 响应消息。我们将使用 Zend Framework 中的zend-httphandlerrunner组件(实现此接口)来提供我们可以用来发出 PSR-7 响应的实用程序。让我们将其连接到应用:

  1. 通过 Composer 安装zend-httphandlerrunner
$ composer require zendframework/zend-httphandlerrunner
  1. 一旦我们在项目环境中安装了它,我们就可以将之前创建的响应发送到浏览器,如下所示:
//...
$response = $response
    ->withHeader('Content-Type', 'text/plain');

(new Zend\HttpHandlerRunner\Emitter\SapiEmitter)->emit($response);

如果您通过 Chrome 上的开发者工具再次检查页面的Content-Type,您将得到正确的内容类型,即text/plain

For more information about zend-httphandlerrunner, visit https://docs.zendframework.com/zend-httphandlerrunner/. For more information about PSR-15, visit https://www.php-fig.org/psr/psr-15/.

除了zend-httphandlerrunner之外,您还可以在使用来自窄火花的 Http 响应发射器 https://github.com/narrowspark/http-emitter 处理请求并发出响应。现在,让我们继续看 PSR-15 的第二个接口MiddlewareInterface

PSR-15–HTTP 服务器请求处理程序(中间件)

PSR-15 中的中间件接口具有以下抽象:

// Psr\Http\Server\MiddlewareInterface

namespace Psr\Http\Server;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;

interface MiddlewareInterface
{
    public function process(
        ServerRequestInterface $request,
        RequestHandlerInterface $handler
    ) : ResponseInterface;
}

同样,您可以看到它是一个非常简单的界面。它只有一个指定的用于中间件实现的公共方法进程。实现此接口的组件(中间件)将只接受 PSR-7 HTTP 请求消息和 PSR-15 HTTP 服务器请求处理程序,然后必须返回 PSR-7 HTTP 响应消息。

我们将使用 Zend Framework 中的zend-stratigility组件实现此接口,以允许我们在应用中创建 PSR-15 中间件。让我们了解如何将其连接到应用:

  1. 通过 Composer 安装zend-stratigility
$ composer require zendframework/zend-stratigility
  1. 一旦我们在我们的项目环境中安装了它,我们将导入middleware函数和MiddlewarePipe类,如下所示:
use function Zend\Stratigility\middleware;

$app = new Zend\Stratigility\MiddlewarePipe();

// Create a request
$request = Zend\Diactoros\ServerRequestFactory::fromGlobals(
    //...
);
  1. 然后,我们可以使用这个middleware函数创建三个中间件并将它们连接到管道上,如下所示:
$app->pipe(middleware(function ($request, $handler) {
    $response = $handler->handle($request);
    return $response
        ->withHeader('Content-Type', 'text/plain');
}));

$app->pipe(middleware(function ($request, $handler) {
    $response = $handler->handle($request);
    $response->getBody()->write("User Agent: " . 
     $request->getHeader('user-agent')[0]);
    return $response;
}));

$app->pipe(middleware(function ($request, $handler) {
    $response = new Zend\Diactoros\Response();
    $response->getBody()->write("Hello world!\n");
    $response->getBody()->write("Request method: " .
     $request->getMethod() . "\n");
    return $response;
}));
  1. 如您所见,我们之前创建的“Hello World”代码块现在是一个与其他中间件堆叠在一起的中间件。最后,我们可以从这些中间件生成一个最终响应,并将其发送到浏览器,如下所示:
$response = $app->handle($request);
(new Zend\HttpHandlerRunner\Emitter\SapiEmitter)->
  emit($response);

在您的浏览器0.0.0.0:8181上,您应该会得到类似于以下内容的结果:

Hello world!
Request method: GET
User Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 
 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36

For more information about zend-stratigility, visit https://docs.zendframework.com/zend-stratigility/.

Besides zend-stratigility, you can also use the following packages to create your middleware:

好了,给你。在几个可互操作组件的帮助下,我们已经启动了一个符合 PSR-12、PSR-7 和 PSR-15 的现代 PHP 应用,这意味着您可以(不可知地)从众多供应商实现的这些标准中自由选择 HTTP 消息、请求处理程序和中间件。但我们还没有完成。正如您可能已经注意到的,我们创建的应用只是一个单页应用,在0.0.0.0:8181的一条“路线”上运行。没有其他路线,如/about/contact等。因此,我们需要一个实现 PSR-15 的路由。我们将在下一节介绍这一点。

PSR-7/PSR-15 路由

我们将使用来自杰出软件包联盟(一个 PHP 开发小组)的路由,这样我们就有了一个 PSR-7 路由系统,并在其上调度我们的 PSR-15 中间件。简而言之,Route 是一个快速的 PSR-7 路由/调度程序包。

它是一个 PSR-15 服务器请求处理程序,可以处理中间件堆栈的调用。建在 FastRoute(https://github.com/nikic/FastRoute 尼基塔·波波夫的

让我们了解如何将其连接到应用:

  1. 通过 Composer 安装league/route
$ composer require league/route
  1. 一旦您安装了它,我们就可以按照如下方式重构“Hello World”组件:
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

$request = Zend\Diactoros\ServerRequestFactory::fromGlobals(
    //...
);

$router = new League\Route\Router;

$router->map('GET', '/', function (ServerRequestInterface $request) : ResponseInterface {
    $response = new Zend\Diactoros\Response;
    $response->getBody()->write('<h1>Hello, World!</h1>');
    return $response;
});
  1. 然后,我们只需从 Route 使用dispatch方法创建一个 PSR-7 HTTP 响应,并将其发送到浏览器:
$response = $router->dispatch($request);
(new Zend\HttpHandlerRunner\Emitter\SapiEmitter)->emit($response);

查看您可以在使用的 HTTP 请求方法列表(getpostputdelete等)https://route.thephpleague.com/4.x/route 。此外,我们还可以将中间件连接到我们的应用。

  1. 如果要锁定整个应用,可以将中间件添加到路由,如下所示:
use function Zend\Stratigility\middleware;

$router = new League\Route\Router;
$router->middleware(<middleware>);
  1. 如果要锁定一组路由,可以将中间件添加到该组,如下所示:
$router
    ->group('/private', function ($router) {
        // ... add routes
    })
    ->middleware(<middleware>)
;
  1. 如果要锁定特定路由,可以将中间件添加到该路由,如下所示:
$router
    ->map('GET', '/secret', <SomeController>)
    ->middleware(<middleware>)
;
  1. 例如,您可以将 Route 与zend-stratigility一起使用:
use function Zend\Stratigility\middleware;

$router = new League\Route\Router;
$router->middleware(middleware(function ($request, $handler) {
    //...
}));
  1. 如果您不想使用middleware功能或根本不想使用zend-stratigility,您可以创建匿名中间件,如下所示:
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

$router = new League\Route\Router;

$router->middleware(new class implements MiddlewareInterface {
    public function process(ServerRequestInterface $request, 
    RequestHandlerInterface $handler) : ResponseInterface
    {
        $response = $handler->handle($request);
        return $response->withHeader('X-Clacks-Overhead', 
        'GNU Terry Pratchett');
    }
});

只要您通过在中间件中实现process方法来遵守 PSR7 和 PSR-15,就根本不需要zend-stratigility。如果您想在单独的 PHP 文件中创建基于类的中间件,请查看中提供的示例 https://route.thephpleague.com/4.x/middleware/

For more information about Route from The League of Extraordinary Packages, visit https://route.thephpleague.com/. You can also check out other packages that have been created by this group of developers at https://thephpleague.com/. Besides Route from The League of Extraordinary, you can also use the following packages for HTTP routers based on PSR-7 and PSR-15:

您可能需要一个调度器来与这些包中的一些一起使用。使用来自特殊包联盟的路由的优点是,它在一个包中提供路由和调度器。

基于此,我们使用 PSR-12、PSR-4、PSR-7 和 PSR-15 编写了一个不可知 PHP 应用。但是我们的 PHP API 还没有完成。还有一项任务要做——我们需要为 CRUD 操作添加一个数据库框架。我们将在下一节中指导您完成此任务。

用 PHP 数据库框架编写 CRUD 操作

正如您在第 9 章中所记得的,添加了一个服务器端数据库CRUD代表create、read、update 和delete。在那一章中,我们使用 MongoDB 创建 CRUD 操作。在本节中,我们将使用 MySQL 创建后端身份验证。我们将在刚刚用 PSR 创建的 PHP 应用中使用 MySQL 和 PHP。因此,让我们从创建 MySQL 数据库中所需的表开始。

创建 MySQL 表

确保您已经在本地计算机上安装了 MySQL 服务器,并创建了一个名为nuxt-php的数据库。完成后,请按照以下步骤完成 API 的第一部分:

  1. 插入以下 SQL 查询以在数据库中创建表:
CREATE TABLE user (
  uuid varchar(255) NOT NULL,
  name varchar(255) NOT NULL,
  slug varchar(255) NOT NULL,
  created_on int(10) unsigned NOT NULL,
  updated_on int(10) unsigned NOT NULL,
  UNIQUE KEY slug (slug)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

您会注意到的第一件事是,我们正在使用uuid而不是id,就像我们在第 12 章中所做的那样,创建用户登录和 API 身份验证。UUID 代表通用唯一标识符。可能有一些原因和好处使您希望选择 UUID 而不是自动递增键来索引数据库表中的记录。例如,您可以在不连接数据库的情况下创建 UUID。它在应用中几乎是独一无二的,因此您可以轻松地组合来自不同数据库的数据,而不会发生冲突。要在 PHP 应用中生成 UUID,我们可以使用 Ben Ramsey 的ramsey/uuid帮助我们生成 RFC 4122(https://tools.ietf.org/html/rfc4122 版本 1、3、4 和 5 UUID。

  1. 那么,让我们通过 Composer 安装ramsey/uuid
$ composer require ramsey/uuid
  1. 现在,您可以使用此包生成 UUID 的版本 1,如下所示:
use Ramsey\Uuid\Uuid;

$uuid1 = Uuid::uuid1();
echo $uuid1->toString();

If you want to find out more information about this package, visit https://github.com/ramsey/uuid.

现在,让我们学习如何使用 PHP 处理 MySQL 数据库,并了解为什么我们需要一个数据库框架来加快 PHP 的开发。

使用 Medoo 作为数据库框架

在 PHP 的旧时代,开发人员使用 MySQL 函数(https://www.php.net/manual/en/ref.mysql.php 管理 MySQL 数据库。然后,MySQLi 扩展(https://www.php.net/manual/en/book.mysqli.php )取代了 MySQL 函数,现在 MySQL 函数已被弃用。但是,现在鼓励开发者使用PHP 数据对象PDO)https://www.php.net/manual/en/book.pdo.php 。PDO 是一个内置的 PHP 接口抽象,就像 PSR-7 和 PSR-15 一样。它是一个数据访问抽象层,为访问和管理数据库(例如,MySQL 和 PostgreSQL)提供了一致的接口(统一的 API),这意味着无论使用哪个数据库,都使用相同的函数来查询和获取数据。它支持以下数据库:

|

  • 立方
  • MS SQL Server
  • 火鸟
  • 国际商用机器公司

|

  • Informix
  • MySQL
  • 神谕
  • ODBC 和 DB2

|

  • PostgreSQL
  • 数据库
  • 4D

|

请注意,PDO 是一个数据访问抽象层,而不是数据库抽象层。因此,根据您使用的数据库,必须安装该数据库的 PDO 驱动程序才能使用 PDO。我们使用的是 MySQL 数据库,因此必须确保安装了PDO_MYSQL驱动程序。在 Ubuntu 中,您可以使用以下命令检查您是否启用了 PDO 扩展,以及您的环境中是否安装了PDO_MYSQL驱动程序:

$ php -m

您应该得到一个 PHP 模块列表。查找PDOpdo_mysql

[PHP Modules]
...
PDO
pdo_mysql
...

可用于检查 PDO 及其驱动程序的另一个更具体的选项如下:

$ php -m|grep -i pdo
PDO
pdo_mysql

如果只想搜索 PDO 驱动程序,请执行以下操作:

$ php -m|grep -i pdo_
pdo_mysql

您还可以使用phpinfo()创建一个 PHP 页面来查找它们。或者,您可以使用getAvailableDrivers方法,如下所示:

print_r(PDO::getAvailableDrivers());

您应该获得 PDO 驱动程序列表,如下所示:

Array
(
    [0] => mysql
)

另外,还有一些内置 PHP 函数可以帮助您:

extension_loaded ('PDO'); // returns boolean
extension_loaded('pdo_mysql'); // returns boolean
get_loaded_extensions(); // returns array

如果没有看到任何 PDO 驱动程序,则必须安装 MySQL 支持的驱动程序。请按照以下步骤执行此操作:

  1. 搜索包名(Ubuntu):
$ apt-cache search php7.4|grep mysql
php7.4-mysql - MySQL module for PHP
  1. 安装php7.4-mysql并重新启动 Apache 服务器:
$ sudo apt-get install php7.4-mysql
$ sudo service apache2 restart

一旦PDO_MYSQL驱动程序就位,就可以立即开始编写 CRUD 操作。例如,我们编写一个insert操作,如下所示:

  1. 创建 MySQL 数据库连接:
$servername = "localhost";
$username = "<username>";
$password = "<password>";
$dbname = "<dbname>";
$connection = new PDO(
    "mysql:host=$servername;dbname=$dbname",
    $username,
    $password
)

Note that <username>, <password>, and <dbname> are placeholders for the actual connection details. You must change them according to your own database settings.

  1. 准备 SQL 查询和bind参数:
$stmt = $connection->prepare("
    INSERT INTO user (
        uuid,
        name,
        slug,
        created_on,
        updated_on
    ) VALUES (
        :uuid,
        :name,
        :slug,
        :created_on,
        :updated_on
    )
");
$stmt->bindParam(':uuid', $uuid);
$stmt->bindParam(':name', $name);
$stmt->bindParam(':slug', $slug);
$stmt->bindParam(':created_on', $createdOn);
$stmt->bindParam(':updated_on', $updatedOn);
  1. 插入新的行:
$uuid = "25769c6c-d34d-4bfe-ba98-e0ee856f3e7a";
$name = "John Doe";
$slug = "john-doe";
$createdOn = (new DateTime())->getTimestamp();
$updatedOn = $createdOn;
$stmt->execute();

这并不理想,因为您必须每次prepare语句并在需要的地方绑定参数,这需要相当多的行来操作。因此,我们应该选择一个 PHP 数据库框架来加速开发。Medoohttps://medoo.in/ 是一种选择。它重量轻,易于集成和使用。

让我们将其安装并连接到我们的应用:

  1. 通过 Composer 安装 Medoo:
$ composer require catfan/medoo
  1. 如果一切都已设置到位,您可以导入 Medoo 并传入一组配置来启动数据库连接,就像我们之前在香草方法中所做的那样:
use Medoo\Medoo;

$database = new Medoo([
  'database_type' => 'mysql',
  'database_name' => '<dbname>',
  'server' => 'localhost',
  'username' => '<username>',
  'password' => '<password>'
]);

这就是通过这个数据库框架建立到 MySQL 数据库的连接。您可以在本书的 GitHub 存储库的/chapter-16/nuxt-php/proxy/backend/core/mysql.php中找到此代码段的实际用法。在下一节中,我们将向您展示如何实现它,但现在,让我们探索如何使用 Medoo 编写一些基本的 CRUD 操作。

插入记录

当您想在表中插入新记录时,可以使用insert方法,如下所示:

$database->insert('user', [
    'uuid' => '41263659-3c1f-305a-bfac-6a7c9eab0507',
    'name' => 'Jane',
    'slug' => 'jane',
    'created_on' => '1568072289'
]);

If you want to find out more details about this method, visit https://medoo.in/api/insert.

查询记录

当您想从表中列出记录时,可以使用select方法,如下所示:

$database->select('user', [
    'uuid',
    'name',
    'slug',
    'created_on',
    'updated_on',
]);

select方法提供记录列表。如果您只想选择某一行,可以使用get方法,如下所示:

$database->get('user', [
    'uuid',
    'name',
    'slug',
    'created_on',
    'updated_on',
    ], [
    'slug' => 'jane'
]);

If you want to find out more details, visit https://medoo.in/api/select for the select method and https://medoo.in/api/get for the get method.

更新记录

当您想要修改表中记录的数据时,可以使用update方法,如下所示:

$database->update('user', [
    'name' => 'Janey',
    'slug' => 'jane',
    'updated_on' => '1568091701'
], [
    'uuid' => '41263659-3c1f-305a-bfac-6a7c9eab0507'
]);

If you want to find out more details about this method, visit https://medoo.in/api/update.

删除记录

当您要从表中删除记录时,可以使用delete方法,如下所示:

$database->delete('user', [
    'uuid' => '41263659-3c1f-305a-bfac-6a7c9eab0507'
]);

如果您想了解有关此方法的更多详细信息,请访问https://medoo.in/api/delete

这就是如何用 Medoo 和 PDO 编写基本 CRUD 操作的问题。

Please check out Medoo's documentation at https://medoo.in/doc for the rest of the methods that you can use. There are other alternatives to Medoo, such as Doctrine DBAL at https://github.com/doctrine/dbal and Eloquent at https://github.com/illuminate/database.

在本节中,您研究了一些 PSR 和 CRUD 操作。接下来,我们将介绍如何将所有这些放在一起,并将它们与 Nuxt 集成。由于 PHP 和 JavaScript 是两种不同的语言,因此它们之间的唯一对话方式是通过 API 中的 JSON。

但在我们编写一个脚本来实现这一点之前,我们应该研究一下这两个程序的跨域应用结构。我们从第 12 章开始就在 Nuxt 应用中使用跨域应用结构,创建用户登录和 API 身份验证,所以您应该很熟悉这一点。让我们开始吧!

构造跨域应用目录

同样,就像构建跨域应用目录一样,下面是我们对 Nuxt 和 PHP API 的整体视图:

// Nuxt app
front-end
├── package.json
├── nuxt.config.js
└── pages
    ├── index.vue
    └── ...

// PHP API
backend
├── composer.json
├── vendor
│ └── ...
├── ...
└── ...

单独而言,Nuxt 的目录结构保持不变。我们只需对 API 目录的结构做一点小小的更改,如下所示:

// PHP API
backend
├── composer.json
├── middlewares.php
├── routes.php
├── vendor
│ └── ...
├── public
│ └── index.php
├── static
│ └── ...
├── config
│ └── ...
├── core
│ └── ...
├── middleware
│ └── ...
└── module
    └── ...

PHPAPI 的目录结构是一个建议。你可以设计一个你喜欢并且最适合你的结构。综上所述,我们有以下几点:

  • /vendor/目录是保存所有第三方包或依赖项的地方。
  • /public/目录只包含一个启动 API 的index.php文件。
  • 静态文件的/static/目录,例如 favicon。
  • /config/目录存储配置文件,如 MySQL 文件。
  • /core/目录存储了我们可以在整个应用中使用的通用对象和函数。
  • /middleware/目录存储我们的 PSR-15 中间件。
  • 目录 T0 目录存储我们稍后创建的自定义模块,正如我们在《ToLt1》第 12 章的 AutoT2A.,Ty3T3 中创建的,用户注册和 API 认证 T4,ToC,用膝关节炎。
  • composer.json文件始终位于根级别。
  • middlewares.php文件是从/middleware/目录导入中间件的核心位置。
  • routes.php文件是从/module/目录导入路由的核心位置。

一旦你准备好了结构,你就可以开始编写顶层代码,将来自不同位置和目录的其他代码粘合到/public/目录的index.php文件中的单个应用中。那么,让我们开始:

  1. foreach循环放入routes.php文件中,以迭代稍后创建的每个模块:
// backend/routes.php
$modules = require './config/routes.php';

foreach ($modules as $module) {
    require './module/' . $module . 'index.php';
}
  1. /config/目录中创建一个routes.php文件,该文件将列出您模块的文件名,如下所示:
// backend/config/routes.php
return [
    'Home/',
    'User/'.
    //...
];
  1. 在此 PHP API 中,middlewares.php文件将导入一段中间件,用于修饰 CRUD 操作的输出:
// backend/middlewares.php
require './middleware/outputDecorator.php';

此装饰器将以 JSON 格式以以下格式打印 CRUD 操作的输出:

{"status":<status code>,"data":<data>}
  1. /middleware/目录中创建一个名为outputDecorator.php的文件,其中包含以下代码。这将以前面的格式包装操作的输出:
// backend/middleware/outputDecorator.php
use function Zend\Stratigility\middleware;

$router->middleware(middleware(function ($request, $handler) {
    $response = $handler->handle($request);
    $existingContent = (string) $response->getBody();
    $contentDecoded = json_decode($existingContent, true);
    $status = $response->getStatusCode();
    $data = [
        "status" => $status,
        "data" => $contentDecoded
    ];
    $payload = json_encode($data);

    $response->getBody()->rewind();
    $response->getBody()->write($payload);

    return $response
        ->withHeader('Content-Type', 'application/json')
        ->withStatus($status);
}));

我们在这里使用zend-stratigility方法来创建组件。然后,我们使用非凡联盟league/route的路由,用这个中间件锁定整个应用。

  1. /core/目录中创建一个mysql.php文件,返回 MySQL 连接的 Medoo 实例:
// backend/core/mysql.php
$dbconfig = require './config/mysql.php';
$mysql = new Medoo\Medoo([
    'database_type' => $dbconfig['type'],
    'database_name' => $dbconfig['name'],
    'server' => $dbconfig['host'],
    'username' => $dbconfig['username'],
    'password' => $dbconfig['password']
]);
return $mysql;
  1. 如前所述,/public/目录只包含一个index.php文件。这是用来启动我们的程序的,因此它包含了您之前了解的关于 PSR 的脚本:
// backend/public/index.php
chdir(dirname(__DIR__));
require_once 'vendor/autoload.php';

$request = Zend\Diactoros\ServerRequestFactory::fromGlobals(
    //...
);

$router = new League\Route\Router;
try {
    require 'middlewares.php';
    require 'routes.php';
    $response = $router->dispatch($request);
} catch(Exception $exception) {
    // handle errors
}

(new Zend\HttpHandlerRunner\Emitter\SapiEmitter)->emit($response);

在这里,您可以看到,middlewares.phproutes.php文件被导入到该文件中,以生成 PSR-7 响应。它们被包装在trycatch块中,以捕获任何 HTTP 错误,例如 404 和 506 错误。因此,模块的任何输出和任何错误都将通过最后一行发送到浏览器。希望这能让您对这个简单的 API 有一个大致的了解。现在,让我们继续深入/module/目录,了解如何创建模块和路由。

创建 API 的公共路由及其模块

创建 API 的公共路由及其模块与您在本书前几章中学习构建的 API 非常相似;主要区别在于语言。我们使用 JavaScript 和 NoDE.JS 框架——膝关节炎,以前,对于本章中的 API,我们使用 PHP 和 PSRS 来创建框架无关 java。那么,让我们开始:

  1. /module/目录中创建两个目录:一个名为Home,另一个名为User。这两个子目录是此 API 中的模块。在每个模块中,创建一个/_routes/目录和一个index.php文件,该文件将从/_routes/目录导入路由,如下所示:
└── module
    ├── Home
    │ ├── index.php
    │ └── _routes
    │ └── hello_world.php
    └── User
        ├── index.php
        └── _routes
           └── ...
  1. Home模块中,输出一条“Hello world!”消息,并将其映射到/路径,如下所示:
// module/Home/_routes/hello_world.php
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

$router->get('/', function (ServerRequestInterface $request) : 
  ResponseInterface {
    return new Zend\Diactoros\Response\JsonResponse(
     'Hello world!');
});
  1. User模块中,编写 CRUD 操作,以便我们可以创建、读取、更新和删除用户。因此,在/_routes/目录中,创建五个名为fetch_user.phpfetch_users.phpinsert_user.phpupdate_user.phpdelete_user.php的文件。在这些文件中,我们将在/Controller/目录中映射每个 CRUD 操作的路径:
└── User
    ├── index.php
    ├── _routes
    │ ├── delete_user.php
    │ ├── fetch_user.php
    │ └── ...
    └── Controller
        └── ...
  1. 例如,在fetch_users.php文件中,我们将定义一个/users路由来列出所有用户,如下所示:
// module/User/_routes/fetch_users.php
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

$router->get('/users', function (ServerRequestInterface $request) : ResponseInterface {
    $database = require './core/mysql.php';
    $users = (new Spectre\User\Controller\
     Fetch\Users($database))->fetch();
    return new Zend\Diactoros\Response\JsonResponse($users);
});

在这里,您可以看到我们将 Medoo 实例导入为$database并将其传递给执行读取操作的控制器,然后调用fetch方法来获取所有可用的用户。

  1. 因此,接下来我们将创建一些 CRUD 目录:InsertFetchUpdateDelete。在每个 CRUD 目录中,我们将 PSR-4 类存储在/Controller/目录中,如下所示:
└── Controller
    ├── Controller.php
    ├── Insert
    │ └── User.php
    ├── Fetch
    │ ├── User.php
    │ └── Users.php
    ├── Update
    │ └── User.php
    └── Delete
        └── User.php
  1. 首先,创建一个可由 CRUD 目录中的类扩展的abstract类。此类只接受其构造函数中的Medoo\Medoo数据库,如下所示:
// module/User/Controller/Controller.php
namespace Spectre\User\Controller;

use Medoo\Medoo;

abstract class Controller
{
    protected $database;

    public function __construct(Medoo $database)
    {
        $this->database = $database;
    }
}
  1. 导入前面的abstract类,并将其扩展到需要连接 MySQL 数据库的任何其他类,如下所示:
// module/User/Controller/Fetch/Users.php
namespace Spectre\User\Controller\Fetch;

use Spectre\User\Controller\Controller;

class Users extends Controller
{
    public function fetch()
    {
        $columns = [
            'uuid',
            'name',
            'slug',
            'created_on',
            'updated_on',
        ];
        return $this->database->select('user', $columns);
    }
}

在这个类中,我们使用select方法从 MySQL 数据库的user表中获取所有用户。Medoo 将返回一个包含用户列表的Array,如果没有用户,则返回一个空Array。然后,使用fetch_users.php文件中的zend-diactoros中的JsonResponse方法将该结果转换为 JSON。

最后由/middleware/目录中的中间件进行修饰。这将产生以下输出:

{"status":200,"data":[{"uuid":"...","name":"Jane","slug":"jane",...},{...},{...}]}

这就是关于 PHP API 的问题。这很容易,不是吗?在本练习中,我们将跳过在 API 端处理 COR 的任务,因为我们将使用 Nuxt Axios 和代理模块在我们将要创建的 Nuxt 应用中无缝、轻松地处理 COR。那么,让我们开始吧!

You can find this PHP API in /chapter-16/nuxt-php/proxy/backend/ and the rest of the CRUD classes of this API in /chapter-16/nuxt-php/proxy/backend/module/User/Controller/ in this book's GitHub repository.

与 Nuxt 集成

@nuxtjs/axios模块与@nuxtjs/proxy模块集成良好,在许多情况下非常有用。防止 CORS 问题是同时使用这两个模块的好处之一。您在第 6 章编写插件和模块中学习了如何安装和使用它们。让我们回顾一下:

  1. 通过 npm 安装@nuxtjs/axios@nuxtjs/proxy模块:
$ npm install @nuxtjs/axios
$ npm install @nuxtjs/proxy
  1. 在 Nuxt 配置文件的modules选项中注册@nuxtjs/axios,如下所示:
// nuxt.config.js
module.exports = {
  modules: [
    '@nuxtjs/axios'
  ],

  axios: {
    proxy: true
  },

  proxy: {
    '/api/': { target: 'http://0.0.0.0:8181', 
     pathRewrite: {'^/api/': ''} }
  }
}

请注意,当您在@nuxtjs/axios中使用@nuxtjs/proxy模块时,无需注册该模块,只要该模块已安装且位于package.json中的dependencies字段中即可。

在前面的配置中,我们使用/api/作为http://0.0.0.0:8181的代理,这是我们运行 PHP API 的地方。因此,每当我们在任何 API 端点请求中使用/api/时,它都在调用0.0.0.0:8181。例如,假设您正在进行 API 调用,如下所示:

$axios.get('/api/users')

@nuxtjs/axios@nuxtjs/proxy模块将该/api/users端点转换为以下内容:

http://0.0.0.0:8181/api/users

但由于我们在 PHP API 的路由中不使用/api/,因此在调用期间,我们在配置中使用pathRewriteremove它。然后,@nuxtjs/axios@nuxtjs/proxy模块发送给 API 的实际 URL 如下:

http://0.0.0.0:8181/users

Once more, visit the following links for more information about these two modules:

安装和配置完成后,我们可以开始创建用于与 PHP API 通信的前端 UI。我们将在下一节中介绍这一点。

创建 CRUD 页面

同样,这对您来说并不是一项全新的任务,因为这几乎与创建您在第 9 章添加服务器端数据库中学习创建的 CRUD 页面相同。让我们回顾一下:

  1. /pages/users/目录中创建以下页面,用于发送和获取数据:
users
├── index.vue
├── _slug.vue
├── add
│ └── index.vue
├── update
│ └── _slug.vue
└── delete
    └── _slug.vue
  1. 例如,使用以下脚本获取所有可用用户:
// pages/users/index.vue
export default {
  async asyncData ({ error, $axios }) {
    try {
      let { data } = await $axios.get('/api/users')
      return {
        users: data.data
      }
    } catch (err) {
      // handle errors.
    }
  }
}

此 Nuxt 应用中的脚本、模板和目录结构与您在第 9 章添加服务器端数据库中学习创建的应用相同。不同的是,在那一章中使用了_id,但在这一章中,我们使用了_slug。现在,您应该能够自己完成其余的 CRUD 页面。但是,您可以随时重温第 9 章添加服务器端数据库中的以下章节,了解更多信息:

  • 创建添加页面
  • 创建更新页面
  • 创建删除页面

创建这些页面后,您可以使用npm run dev运行 Nuxt 应用。您应该可以在localhost:3000看到应用在您的浏览器上运行。

You can find the complete source code for this app in /chapter-16/nuxt-php/proxy/frontend/nuxt-universal/ in this book's GitHub repository.

If you don't want to use the @nuxtjs/axios and @nuxtjs/proxy modules in this Nuxt app, you can find the complete source regarding how to enable CORS in the PHP API for the Nuxt app in /chapter-16/nuxt-php/cors/ in this book's GitHub repository.

You can also find a copy of the database saved as user.sql in /chapter-16/nuxt-php/ in this book's GitHub repository.

现在,让我们总结一下您在这漫长的一章中所学到的知识。我们希望你喜欢这一章,并发现它鼓舞人心。

总结

在本章中,您不仅成功地将 Nuxt 应用与 API 解耦,就像您在第 12 章创建用户登录和 API 身份验证中所做的那样,而且还成功地用不同的语言编写了 API,PHP 是 web 开发中最流行的服务器端脚本语言之一。您学习了如何安装 PHP 和 Apache 以运行 PHP 应用或使用内置 PHP web 服务器进行开发,同时遵守 PSR-12、PSR4、PSR7 和 PSR-15 以构建现代框架无关的应用。您还学习了使用 PHP 数据库框架 Medoo 编写 CRUD 操作,重用第 9 章中的 Nuxt 应用,添加了服务器端数据库,但做了一些修改,并将前端 UI 和后端 API 完美地粘合在一起。现在,您还更详细地了解了 HTTP 消息,并了解了如何使用 PDO 进行现代 PHP 数据库管理。做得好。

在下一章中,您将了解 Nuxt 在实时应用方面还可以做些什么。在这里,您将了解到关于Socket.iodb的内容。我们将引导您完成这两种技术的安装过程。然后,您将学习如何在数据库中执行实时 CRUD 操作,使用 Socket.io 在 JavaScript 中编写实时代码,并将它们与 Nuxt 应用集成。这将是另一个有趣和令人兴奋的章节,我们将引导你通过。所以,请继续关注!****