使用 PHP 数据库框架编写 CRUD 操作
正如您可能从第九章 “添加服务器端数据库” 中回忆到的,CRUD 代表创建(create)、读取(read)、更新(update)和删除(delete)。在那一章中,我们使用 MongoDB 来创建 CRUD 操作。在本节中,我们将使用 MySQL 来创建后端身份验证。我们将在刚刚使用 PSR 创建的 PHP 应用程序中使用带有 PHP 的 MySQL。因此,让我们首先创建 MySQL 数据库中所需的表。
创建 MySQL 表
请确保您已在本地计算机上安装了 MySQL 服务器并创建了一个名为 nuxt-php
的数据库。完成此操作后,请按照以下步骤完成我们 API 的第一部分:
-
插入以下 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 而不是像我们在第十二章 “创建用户登录和 API 身份验证” 中使用的 id。UUID 代表通用唯一标识符(Universally Unique Identifier)。在数据库表中为记录建立索引时,选择 UUID 而不是自动递增键可能存在一些原因和好处。例如,您可以在不连接到数据库的情况下创建 UUID。它在不同的应用程序中几乎是唯一的,因此您可以轻松地合并来自不同数据库的数据,而永远不会发生冲突。要在 PHP 应用程序中生成 UUID,我们可以使用 Ben Ramsey 的 ramsey/uuid 来帮助我们生成 RFC 4122 (https://tools.ietf.org/html/rfc4122) 版本 1、3、4 和 5 的 UUID。
-
因此,让我们通过 Composer 安装
ramsey/uuid
:$ composer require ramsey/uuid
-
现在,您可以按如下方式使用此包生成版本 1 的 UUID:
use Ramsey\Uuid\Uuid; $uuid1 = Uuid::uuid1(); echo $uuid1->toString();
如果您想了解有关此包的更多信息,请访问 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 函数。然而,现在,开发者被鼓励使用 PHP 数据对象 (PDO) (https://www.php.net/manual/en/book.pdo.php)。PDO 是一个内置的 PHP 接口抽象,就像 PSR-7 和 PSR-15 一样。它是一个数据访问抽象层,为访问和管理数据库(例如 MySQL 和 PostgreSQL)提供一致的接口(统一的 API),这意味着无论您使用哪个数据库,您都使用相同的函数来查询和获取数据。它支持以下数据库:
|
|
|
请注意,PDO 是一个数据访问抽象层,而不是数据库抽象层。因此,根据您使用的数据库,必须安装该数据库的 PDO 驱动程序才能使用 PDO。我们正在使用 MySQL 数据库,因此我们必须确保已安装 PDO_MYSQL 驱动程序。在 Ubuntu 中,您可以使用以下命令检查是否启用了 PDO 扩展并且您的环境中安装了 PDO_MYSQL 驱动程序:
$ php -m
您应该会看到 PHP 模块列表。查找 PDO
和 pdo_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'); // 返回布尔值
extension_loaded('pdo_mysql'); // 返回布尔值
get_loaded_extensions(); // 返回数组
如果您没有看到任何 PDO
驱动程序,则必须安装 MySQL
支持的驱动程序。请按照以下步骤操作:
-
搜索软件包名称(Ubuntu):
$ apt-cache search php7.4|grep mysql php7.4-mysql - MySQL module for PHP
-
安装
php7.4-mysql
并重新启动您的Apache
服务器:$ sudo apt-get install php7.4-mysql $ sudo service apache2 restart
一旦您安装了 PDO_MYSQL
驱动程序,就可以立即开始编写 CRUD
操作。例如,让我们编写一个插入操作,如下所示:
-
创建
MySQL
数据库连接:$servername = "localhost"; $username = "<username>"; $password = "<password>"; $dbname = "<dbname>"; $connection = new PDO( "mysql:host=$servername;dbname=$dbname", $username, $password );
请注意,<username>、<password> 和 <dbname> 是实际连接详细信息的占位符。您必须根据自己的数据库设置更改它们。
-
准备 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);
-
插入新行:
$uuid = "25769c6c-d34d-4bfe-ba98-e0ee856f3e7a"; $name = "John Doe"; $slug = "john-doe"; $createdOn = (new DateTime())->getTimestamp(); $updatedOn = $createdOn; $stmt->execute();
这不是很理想,因为您每次都必须准备语句并在需要的地方绑定参数,这需要相当多的代码行才能操作。因此,我们应该选择一个 PHP 数据库框架来加速开发。Medoo (https://medoo.in/) 是一个不错的选择。它轻巧且非常易于集成和使用。
让我们安装它并将其连接到我们的应用程序:
-
通过
Composer
安装Medoo
:$ composer require catfan/medoo
-
如果一切设置就绪,您可以导入
Medoo
并传入一个配置数组来启动数据库连接,就像我们之前在原生方法中所做的那样:use Medoo\Medoo; $database = new Medoo([ 'database_type' => 'mysql', 'database_name' => '<dbname>', 'server' => 'localhost', 'username' => '<username>', 'password' => '<password>' ]);
这就是通过这个数据库框架建立与 MySQL 数据库连接的全部内容。您可以在本书的 GitHub 存储库中的 /chapter-16/nuxtphp/proxy/backend/core/mysql.php
中找到此代码片段的实际用法。我们将在下一节中向您展示如何实现它,但现在,让我们探索如何使用 Medoo 编写一些基本的 CRUD 操作。
插入记录
当您想向表中插入新记录时,可以使用 insert
方法,如下所示:
$database->insert('user', [
'uuid' => '41263659-3c1f-305a-bfac-6a7c9eab0507',
'name' => 'Jane',
'slug' => 'jane',
'created_on' => '1568072289'
]);
如果您想了解有关此方法的更多详细信息,请访问 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']);
如果您想了解更多详细信息,请访问 https://medoo.in/api/select 以获取 select 方法的信息,以及 https://medoo.in/api/get 以获取 get 方法的信息。 |
更新记录
当您想修改表中记录的数据时,可以使用 update
方法,如下所示:
$database->update('user', [
'name' => 'Janey',
'slug' => 'jane',
'updated_on' => '1568091701'
], [
'uuid' => '41263659-3c1f-305a-bfac-6a7c9eab0507'
]);
如果您想了解有关此方法的更多详细信息,请访问 https://medoo.in/api/update。 |
删除记录
当您想从表中删除记录时,可以使用 delete
方法,如下所示:
$database->delete('user', [
'uuid' => '41263659-3c1f-305a-bfac-6a7c9eab0507'
]);
如果您想了解有关此方法的更多详细信息,请访问 https://medoo.in/api/delete。
这就是关于如何使用 Medoo 和 PDO 编写基本 CRUD 操作的全部内容。
请查看 Medoo 的文档 https://medoo.in/doc 以了解您可以使用的其余方法。Medoo 还有其他替代方案,例如 Doctrine DBAL (https://github.com/doctrine/dbal) 和 Eloquent (https://github.com/illuminate/database)。 |
在本节中,您学习了一些 PSR 和 CRUD 操作。接下来,我们将介绍如何将所有这些组合在一起并将它们与 Nuxt
集成。由于 PHP 和 JavaScript 是两种不同的语言,它们相互通信的唯一方式是通过 API 中的 JSON。
但是在编写启用此功能的脚本之前,我们应该研究这两个程序的跨域应用程序结构。自第十二章 “创建用户登录和 API 身份验证” 以来,我们的 Nuxt
应用程序一直使用跨域应用程序结构,因此您应该对此很熟悉。让我们开始吧!
组织跨域应用目录
同样,就像构建跨域应用程序目录一样,以下是我们对 Nuxt
和我们的 PHP API 的整体视图:
// Nuxt 应用
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
└── ...
PHP API 的目录结构只是一个建议。您始终可以设计一个您更喜欢且最适合您的结构。因此,概括来说,我们有以下内容:
-
/vendor/ 目录用于存放所有第三方软件包或依赖项。
-
/public/ 目录仅包含一个用于启动我们 API 的 index.php 文件。
-
/static/ 目录用于存放静态文件,例如网站图标。
-
/config/ 目录存储配置文件,例如 MySQL 文件。
-
/core/ 目录存储我们可以在整个应用程序中使用的通用对象和函数。
-
/middleware/ 目录存储我们的 PSR-15 中间件。
-
/module/ 目录存储我们稍后将创建的自定义模块,就像我们在第十二章“创建用户登录和 API 身份验证”中使用 Koa 所做的那样。
-
composer.json 文件始终位于根级别。
-
middlewares.php 文件是导入来自 /middleware/ 目录的中间件的核心位置。
-
routes.php 文件是导入来自 /module/ 目录的路由的核心位置。
准备好结构后,您可以开始编写顶级代码,该代码将把来自不同位置和目录的其他代码粘合到 /public/
目录的 index.php
文件中的单个应用程序中。因此,让我们开始吧:
-
将
foreach
循环放入routes.php
文件中,以迭代您稍后将创建的每个模块:// backend/routes.php $modules = require './config/routes.php'; foreach ($modules as $module) { require './module/' . $module . 'index.php'; }
-
在
/config/
目录中创建一个routes.php
文件,该文件将列出您的模块的文件名,如下所示:// backend/config/routes.php return [ 'Home/', 'User/', // ... ];
-
在这个 PHP API 中,
middlewares.php
文件将会导入一个中间件,// backend/middlewares.php require './middleware/outputDecorator.php';
这个装饰器将会把 CRUD 操作的输出打印成以下 JSON 格式:
{"status":<status code>,"data":<data>}
-
在
/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 组件的 middleware 方法来创建装饰器中间件。然后,我们通过使用来自 The League of Extraordinary 的 league/route 的 router 来将整个应用程序锁定在这个中间件中。
-
在 /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;
-
正如我们之前提到的,/public/ 目录只包含一个 index.php 文件。这个文件用于启动我们的程序,因此它包含您之前学习过的关于 PSRs 的脚本:
// 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.php 和 routes.php 文件被导入到这个文件中以生成一个 PSR-7 响应。它们被包裹在 try 和 catch 代码块中,以捕获任何 HTTP 错误,例如 404 和 506 错误。因此,来自模块的任何输出和任何错误都将通过最后一行发送到浏览器。希望这已经为您提供了这个简单 API 的概览。现在,让我们继续深入 /module/ 目录,更详细地了解如何创建模块和路由。
创建 API 的公共路由及其模块
创建 API 的公共路由及其模块与您在本书前几章中学习构建的 API 非常相似;主要的区别在于语言。之前我们使用 JavaScript 和 Node.js 框架 Koa,而本章的 API,我们使用 PHP 和 PSR 来创建一个与框架无关的 API。让我们开始吧:
-
在 /module/ 目录中创建两个目录:一个名为 Home,另一个名为 User。这两个子目录是此 API 中的模块。在每个模块中,创建一个 /_routes/ 目录和一个 index.php 文件,该文件将导入来自 /_routes/ 目录的路由,如下所示:
└── module ├── Home │ ├── index.php │ └── _routes │ └── hello_world.php └── User ├── index.php └── _routes └── ...
-
在 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!' ); });
-
在 User 模块中,编写 CRUD 操作,以便我们可以创建、读取、更新和删除我们的用户。因此,在 /_routes/ 目录中,创建五个文件,分别名为 fetch_user.php、fetch_users.php、insert_user.php、update_user.php 和 delete_user.php。在每个文件中,我们将为 /Controller/ 目录中的每个 CRUD 操作映射路由:
└── User ├── index.php ├── _routes │ ├── delete_user.php │ ├── fetch_user.php │ └── ... └── Controller └── ...
-
例如,在 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 方法来获取所有可用用户。
-
因此,接下来我们将创建一些 CRUD 目录:Insert、Fetch、Update 和 Delete。在每个 CRUD 目录中,我们将 PSR-4 类存储在 /Controller/ 目录中,如下所示:
└── Controller ├── Controller.php ├── Insert │ └── User.php ├── Fetch │ ├── User.php │ └── Users.php ├── Update │ └── User.php └── Delete └── User.php
-
首先,创建一个抽象类,CRUD 目录中的类可以继承它。此类在其构造函数中仅接受 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; } }
-
导入前面的抽象类并将其扩展到任何需要连接到 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 将返回一个包含用户列表的数组,如果没有用户则返回一个空数组。然后,此结果将在 fetch_users.php 文件中使用 zend-diactoros 的 JsonResponse 方法转换为 JSON。最后,它将由 /middleware/ 目录中的中间件进行修饰。这将产生以下输出:
{"status":200,"data":[{"uuid":"...","name":"Jane","slug":"jane",...},{...},{...}]}
这就是关于 PHP API 的全部内容。它相当简单,不是吗?在本练习中,我们将跳过在 API 端处理 CORS 的任务,因为我们将使用 Nuxt Axios 和 Proxy 模块在我们将要创建的 Nuxt 应用程序中无缝且轻松地处理 CORS。让我们开始吧!
您可以在本书的 GitHub 存储库中的 /chapter-16/nuxtphp/proxy/backend/ 中找到此 PHP API,而此 API 的其余 CRUD 类可以在 /chapter-16/nuxtphp/proxy/backend/module/User/Controller/ 中找到。 |