如何在PHP中构建一个简单的REST API

679 阅读5分钟

在本教程中,我将教你如何用PHP和MySQL构建一个简单的REST API。

当涉及到通过API暴露数据和构建Web服务时,REST已经成为事实上的标准。事实上,现在大多数Web应用程序都是通过REST API来访问和暴露数据的。随着能够毫不费力地消费REST API的前端框架的普及,如果你的网络应用能够暴露REST API,这对你来说永远是一个加分项。

在这篇文章中,我们将建立一个简单的演示应用程序,它允许你通过REST端点从MySQL数据库中获取一个用户列表。

设置骨架

在这一节中,我们将简单介绍一下项目结构。

让我们看一下下面的结构。

├── Controller
│   └── Api
│       ├── BaseController.php
│       └── UserController.php
├── inc
│   ├── bootstrap.php
│   └── config.php
├── index.php
└── Model
    ├── Database.php
    └── UserModel.php

让我们试着理解项目的结构。

  • index.php:是我们应用程序的入口。它将充当我们应用程序的前台控制器。
  • inc/config.php: 保存我们应用程序的配置信息。主要是,它将持有数据库的证书。
  • inc/bootstrap.php: 用来启动我们的应用程序,包括必要的文件。
  • Model/Database.php:数据库访问层,将用于与底层的MySQL数据库交互。
  • Model/UserModel.php:User 模型文件,实现必要的方法与MySQL数据库中的用户表交互。
  • Controller/Api/BaseController.php: 一个基本的控制器文件,它拥有常用的实用方法。
  • Controller/Api/UserController.php:User 控制器文件,它拥有必要的应用程序代码来处理REST API调用。

这就是我们在接下来的文章中要实现的基本设置。

创建一个数据库和模型类

在这一部分,我们将创建一个数据库和用户表。我们还将创建必要的模型类,用于从数据库中获取用户。

创建一个数据库和用户表

通过在你的MySQL终端执行以下命令来创建rest_api_demo 数据库。(在命令行中用命令mysql )。

$CREATE DATABASE rest_api_demo;

如果你喜欢用这种方式处理你的数据库,你也可以使用像phpMyAdmin这样的工具。

一旦创建了rest_api_demo 数据库,继续通过运行以下语句创建users 表。

$use rest_api_demo;
$CREATE TABLE `users` (
  `user_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `username` varchar(60) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `user_email` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `user_status` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

这将在rest_api_demo 数据库中创建users 表。你还想用一些假的记录来填充这个表,以达到测试的目的。插入几条记录,你就可以开始了

创建模型类

在这一部分,我们将创建必要的模型类。

创建Model/Database.php文件,内容如下。

<?php
class Database
{
    protected $connection = null;

    public function __construct()
    {
        try {
            $this->connection = new mysqli(DB_HOST, DB_USERNAME, DB_PASSWORD, DB_DATABASE_NAME);
    	
            if ( mysqli_connect_errno()) {
                throw new Exception("Could not connect to database.");   
            }
        } catch (Exception $e) {
            throw new Exception($e->getMessage());   
        }			
    }

    public function select($query = "" , $params = [])
    {
        try {
            $stmt = $this->executeStatement( $query , $params );
            $result = $stmt->get_result()->fetch_all(MYSQLI_ASSOC);				
            $stmt->close();

            return $result;
        } catch(Exception $e) {
            throw New Exception( $e->getMessage() );
        }
        return false;
    }

    private function executeStatement($query = "" , $params = [])
    {
        try {
            $stmt = $this->connection->prepare( $query );

            if($stmt === false) {
                throw New Exception("Unable to do prepared statement: " . $query);
            }

            if( $params ) {
                $stmt->bind_param($params[0], $params[1]);
            }

            $stmt->execute();

            return $stmt;
        } catch(Exception $e) {
            throw New Exception( $e->getMessage() );
        }	
    }
}

这是一个数据库访问层类,它允许我们建立一个与MySQL数据库的连接。除了建立连接外,它还包含通用方法,如selectexecuteStatement ,允许我们从数据库中选择记录。我们不会直接使用Database 类,我们将创建相应的模型类,扩展Database 类,以便访问底层的MySQL数据库。

接下来,让我们创建Model/UserModel.php类,内容如下。

<?php
require_once PROJECT_ROOT_PATH . "/Model/Database.php";

class UserModel extends Database
{
    public function getUsers($limit)
    {
        return $this->select("SELECT * FROM users ORDER BY user_id ASC LIMIT ?", ["i", $limit]);
    }
}

值得注意的是,UserModel 类扩展了Database 类。

除此以外,它还包含getUsers 方法,它允许我们从MySQL数据库中选择用户。必须传递$limit 参数,这确保它不会一次选择所有记录。

当然,你可以根据你的要求在UserModel 类中定义更多的方法。在本教程中,我们将保持事情的简单性。

所以现在我们有了我们的数据库和模型类的设置。在下一节,我们将看到如何在我们的演示程序中创建控制器和相关文件。

创建应用层组件

在这一节中,我们将创建其余的文件,这些文件是我们的演示程序实际工作所需要的。

inc目录

对于初学者,我们将创建必要的配置文件。

创建inc/config.php文件,内容如下。

<?php
define("DB_HOST", "localhost");
define("DB_USERNAME", "demo");
define("DB_PASSWORD", "demo");
define("DB_DATABASE_NAME", "rest_api_demo");

请确保将所有的值更新为你在安装中使用的实际值。

接下来,继续创建inc/bootstrap.php文件,内容如下。

<?php
define("PROJECT_ROOT_PATH", __DIR__ . "/../");

// include main configuration file
require_once PROJECT_ROOT_PATH . "/inc/config.php";

// include the base controller file
require_once PROJECT_ROOT_PATH . "/Controller/Api/BaseController.php";

// include the use model file
require_once PROJECT_ROOT_PATH . "/Model/UserModel.php";
?>

首先,我们用我们应用程序的根目录初始化了PROJECT_ROOT_PATH 常数。这样,我们就可以使用PROJECT_ROOT_PATH 常数来准备我们应用程序中的绝对路径。接下来,我们包含了config.php文件,它保存了数据库连接信息。最后,我们包含了控制器和模型文件。

这就是在我们的应用程序中设置常用文件的情况。

控制器目录

在这一节中,我们将实现控制器,保存大部分的应用逻辑。

BaseController.php文件

创建Controller/Api/BaseController.php文件,内容如下。BaseController 类包含了其他控制器所使用的实用方法。

<?php
class BaseController
{
    /**
     * __call magic method.
     */
    public function __call($name, $arguments)
    {
        $this->sendOutput('', array('HTTP/1.1 404 Not Found'));
    }

    /**
     * Get URI elements.
     * 
     * @return array
     */
    protected function getUriSegments()
    {
        $uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
        $uri = explode( '/', $uri );

        return $uri;
    }

    /**
     * Get querystring params.
     * 
     * @return array
     */
    protected function getQueryStringParams()
    {
        return parse_str($_SERVER['QUERY_STRING'], $query);
    }

    /**
     * Send API output.
     *
     * @param mixed  $data
     * @param string $httpHeader
     */
    protected function sendOutput($data, $httpHeaders=array())
    {
        header_remove('Set-Cookie');

        if (is_array($httpHeaders) && count($httpHeaders)) {
            foreach ($httpHeaders as $httpHeader) {
                header($httpHeader);
            }
        }

        echo $data;
        exit;
    }
}

让我们快速浏览一下BaseController 类的所有方法。

__call 方法是一个神奇的方法,当你试图调用一个不存在的方法时,它会被调用。当有人试图调用一个我们没有实现的方法时,我们要利用这个机会抛出HTTP/1.1 404 Not Found 错误。如果这听起来让你感到困惑,不要担心,当我们在下一节测试我们的应用程序时,它将变得更有意义。

接下来,是getUriSegments 方法,它返回一个URI段的数组。当我们试图验证用户调用的REST端点时,它很有用。接着,是getQueryStringParams 方法,它返回一个查询字符串变量数组,这些变量与传入的请求一起被传递。

最后,是sendOutput 方法,用于发送API响应。当我们想发送API响应给用户时,我们将调用这个方法。

UserController.php文件

接下来,创建Controller/Api/UserController.php文件,内容如下。

<?php
class UserController extends BaseController
{
    /**
     * "/user/list" Endpoint - Get list of users
     */
    public function listAction()
    {
        $strErrorDesc = '';
        $requestMethod = $_SERVER["REQUEST_METHOD"];
        $arrQueryStringParams = $this->getQueryStringParams();

        if (strtoupper($requestMethod) == 'GET') {
            try {
                $userModel = new UserModel();

                $intLimit = 10;
                if (isset($arrQueryStringParams['limit']) && $arrQueryStringParams['limit']) {
                    $intLimit = $arrQueryStringParams['limit'];
                }

                $arrUsers = $userModel->getUsers($intLimit);
                $responseData = json_encode($arrUsers);
            } catch (Error $e) {
                $strErrorDesc = $e->getMessage().'Something went wrong! Please contact support.';
                $strErrorHeader = 'HTTP/1.1 500 Internal Server Error';
            }
        } else {
            $strErrorDesc = 'Method not supported';
            $strErrorHeader = 'HTTP/1.1 422 Unprocessable Entity';
        }

        // send output
        if (!$strErrorDesc) {
            $this->sendOutput(
                $responseData,
                array('Content-Type: application/json', 'HTTP/1.1 200 OK')
            );
        } else {
            $this->sendOutput(json_encode(array('error' => $strErrorDesc)), 
                array('Content-Type: application/json', $strErrorHeader)
            );
        }
    }
}

值得注意的是,UserController 类扩展了BaseController 类。理想情况下,这个类将包含与为用户实体定义的REST端点相关的动作方法。例如,在我们的案例中,/user/list REST端点对应的是listAction 方法。通过这种方式,你也可以为其他REST端点定义其他方法。

listAction 方法是用来从MySQL数据库中获取用户列表的。它包含了/user/list REST端点的整个逻辑。

listAction 方法中,我们首先初始化了几个变量,如$requestMethod$arrQueryStringParams 。接下来,我们检查用户是否已经用GET 方法调用了user/list 端点,否则我们不会进一步处理。最后,我们创建UserModel 对象,并调用getUsers 方法,从数据库中获取用户列表。我们还使用了json_encode 函数,在发送给用户之前将数组转换为JSON对象。

最后,我们使用sendOutput 方法向用户发送JSON响应。值得注意的是,由于我们发送的是JSON响应,所以响应的内容类型头值被设置为application/json

同样地,你也可以为其他端点定义其他方法。

index.php文件

index.php文件是我们应用程序的入口。让我们看看它的外观。

<?php
require __DIR__ . "/inc/bootstrap.php";

$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$uri = explode( '/', $uri );

if ((isset($uri[2]) && $uri[2] != 'user') || !isset($uri[3])) {
    header("HTTP/1.1 404 Not Found");
    exit();
}

require PROJECT_ROOT_PATH . "/Controller/Api/UserController.php";

$objFeedController = new UserController();
$strMethodName = $uri[3] . 'Action';
$objFeedController->{$strMethodName}();
?>

首先,我们使用parse_urlexplode 函数来初始化URI段到$uri 数组变量中。接下来,我们对URI段进行验证。最后,我们已经初始化了UserController 控制器并调用了相应的动作方法。

就这样,我们已经在我们的演示REST应用程序中创建了所有必要的文件。在下一节,我们将看到如何从终端用户的角度来调用它。

如何调用我们的REST API

在本节中,我们将看到如何调用我们的演示应用程序。在我们的应用程序中,我们已经建立了一个REST端点来获取用户的列表。

让我们看看我们的端点的URL是怎样的。

// https://localhost/index.php/{MODULE_NAME}/{METHOD_NAME}?limit={LIMIT_VALUE}
http://localhost/index.php/user/list?limit=20

如果你能回忆一下index.php文件,我们检查了如果$uri[2] 变量被设置为user 。另外,$uri[3] 变量值将作为一个方法名称。在上述案例中,$uri[3] 变量被设置为list 。因此,它最终会调用UserController 类的listAction 方法。

输出结果应该是这样的。

[
   {
      "user_id":1,
      "username":"Bob",
      "user_email":"bob@gmail.com",
      "user_status":0
   },
   {
      "user_id":2,
      "username":"John",
      "user_email":"john@gmail.com",
      "user_status":1
   },
   {
      "user_id":3,
      "username":"Mark",
      "user_email":"mark@gmail.com",
      "user_status":1
   },
   {
      "user_id":4,
      "username":"Ville",
      "user_email":"ville@gmail.com",
      "user_status":0
   }
]

正如你所看到的,它以JSON对象的形式返回一个用户列表。除此之外,如果有任何应用程序的错误,它也会以JSON对象的形式返回,以便调试。

总结

今天,我们讨论了如何用PHP和MySQL建立一个REST应用程序。为了演示,我们创建了一个演示应用程序,它允许你通过REST API从MySQL数据库中获取一个用户列表。