跳转到内容

教程 - REST


概览

在本教程中,你将学习如何创建一个简单的应用程序,该程序提供一个RESTfulAPI 并使用不同的 HTTP 方法:

方法 描述
GET 检索和搜索数据
POST 添加数据
PUT 更新数据
DELETE 删除数据

注意

这只是一个示例应用程序。它缺少许多功能,例如身份验证、授权、输入清理和错误管理等。请将其用作你的应用程序的构建模块,或作为了解如何使用 Phalcon 构建 REST API 的教程。你也可以查看rest-api项目。

方法

该 API 包含以下方法:

方法 URL 动作
GET /api/robots 获取所有机器人
GET /api/robots/search/Astro 搜索名称中包含 'Astro' 的机器人
GET /api/robots/2 根据主键获取机器人
POST /api/robots 添加机器人
PUT /api/robots/2 根据主键更新机器人
DELETE /api/robots/2 根据主键删除机器人

应用程序

由于应用程序很简单,我们不会实现完整的 MVC 环境来开发它。在这种情况下,我们将为我们的需求使用一个微型应用。该应用程序的结构如下:

my-rest-api/
    models/
        Robots.php
    index.php
    .htaccess

首先,我们需要一个.htaccess文件,其中包含将请求 URI 重写到index.php文件(应用程序入口点)的所有规则:

<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^((?s).*)$ index.php?_url=/$1 [QSA,L]
</IfModule>

我们的大部分代码将放置在index.php.

<?php

use Phalcon\Mvc\Micro;

$app = new Micro();

$app->handle($_SERVER["REQUEST_URI"]);

现在我们需要创建路由,以便应用程序可以理解终端用户与应用程序交互时应该做什么。该index.php文件变更为:

<?php

use Phalcon\Mvc\Micro;

$app = new Micro();

$app->get(
    '/api/robots',
    function () {
    }
);

$app->get(
    '/api/robots/search/{name}',
    function ($name) {
    }
);

$app->get(
    '/api/robots/{id:[0-9]+}',
    function ($id) {
    }
);

$app->post(
    '/api/robots',
    function () {
    }
);

$app->put(
    '/api/robots/{id:[0-9]+}',
    function ($id) {
    }
);

$app->delete(
    '/api/robots/{id:[0-9]+}',
    function ($id) {
    }
);

$app->handle($_SERVER["REQUEST_URI"]);

当我们添加路由时,我们使用实际的 HTTP 方法名作为调用应用程序对象中的方法名。这使我们能够轻松地基于这些 HTTP 方法定义应用程序的监听点。

每个方法调用的第一个参数是路由,第二个参数是处理程序,即当用户调用该路由时我们应该做什么?在我们的示例中,我们为每个处理程序定义了匿名函数。对于以下路由:

/api/robots/{id:[0-9]+}

我们显式地将id参数设置为数字。当定义的路由匹配请求的 URI 时,相应的处理程序(匿名函数)将被执行。

模型

对于此应用程序,我们在数据库中存储和操作Robots数据。为了访问表,我们需要一个模型。下面的类允许我们以面向对象的方式访问表的每条记录。我们还使用内置验证器实现了业务规则。通过这样做,我们可以高度确保存储的数据符合应用程序的要求。这个模型文件需要在my-rest-api/models目录下。

<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;
use Phalcon\Messages\Message;
use Phalcon\Filter\Validation;
use Phalcon\Filter\Validation\Validator\Uniqueness;
use Phalcon\Filter\Validation\Validator\InclusionIn;

class Robots extends Model
{
    public function validation()
    {
        $validator = new Validation();

        $validator->add(
            "type",
            new InclusionIn(
                [
                    'message' => 'Type must be "droid", "mechanical", or "virtual"',
                    'domain'  => [
                        'droid',
                        'mechanical',
                        'virtual',
                    ],
                ]
            )
        );

        $validator->add(
            'name',
            new Uniqueness(
                [
                    'field'   => 'name',
                    'message' => 'The robot name must be unique',
                ]
            )
        );

        if ($this->year < 0) {
            $this->appendMessage(
                new Message('The year cannot be less than zero')
            );
        }

        // Validate the validator
        return $this->validate($validator);
    }
}

我们向模型附加了三个验证器。第一个检查机器人的类型。它必须是droid, mechanicalvirtual。任何其他值都会导致验证器返回false,并且操作(插入/更新)将失败。第二个验证器检查机器人的名称是否唯一。最后一个验证器检查year字段是否为正数。

数据库

我们需要将应用程序连接到数据库。对于此示例,我们将使用流行的 MariaDB 或类似的变体,如 MySQL、Aurora 等。除了数据库设置外,我们还将设置自动加载器,以便我们的应用程序知道在哪里查找所需的文件。

这些更改需要在index.php文件检索消息。

<?php

use Phalcon\Autoload\Loader;
use Phalcon\Mvc\Micro;
use Phalcon\Di\FactoryDefault;
use Phalcon\Db\Adapter\Pdo\Mysql as PdoMysql;

$loader = new Loader();
$loader->setNamespaces(
    [
        'MyApp\Models' => __DIR__ . '/models/',
    ]
);
$loader->register();

$container = new FactoryDefault();
$container->set(
    'db',
    function () {
        return new PdoMysql(
            [
                'host'     => 'localhost',
                'username' => 'asimov',
                'password' => 'zeroth',
                'dbname'   => 'robotics',
            ]
        );
    }
);

$app = new Micro($container);

操作

获取

第一个handler我们要实现的是当使用GETHTTP 方法发出请求时从数据库检索数据的方法。该端点将使用 PHQL 查询返回数据库中的所有记录,并以 JSON 格式返回结果。

对于get()/api/robots的处理程序变为:

<?php

$app->get(
    '/api/robots',
    function () use ($app) {
        $phql = 'SELECT id, name '
              . 'FROM MyApp\Models\Robots '
              . 'ORDER BY name'
        ;

        $robots = $app
            ->modelsManager
            ->executeQuery($phql)
        ;

        $data = [];

        foreach ($robots as $robot) {
            $data[] = [
                'id'   => $robot->id,
                'name' => $robot->name,
            ];
        }

        echo json_encode($data);
    }
);

PHQL允许我们使用高级、面向对象的 SQL 方言编写查询,内部会根据使用的数据库系统将你的查询转换为正确的 SQL 语句。匿名函数中的use语句提供了从本地作用域到匿名函数的对象注入。

获取 - 文本

我们可以通过机器人的名称或部分名称来获取它们。就 HTTP 方法而言,此搜索功能也将是一个get(),并将绑定到/api/robots/search/{name}端点。其实现方式类似于上面的示例。我们只需要稍微更改查询。

<?php

// Searches for robots with $name in their name
$app->get(
    '/api/robots/search/{name}',
    function ($name) use ($app) {
        $phql = 'SELECT * '
              . 'FROM MyApp\Models\Robots '
              . 'WHERE name '
              . 'LIKE :name: '
              . 'ORDER BY name'
        ;

        $robots = $app
            ->modelsManager
            ->executeQuery(
                $phql,
                [
                    'name' => '%' . $name . '%'
                ]
            )
        ;

        $data = [];

        foreach ($robots as $robot) {
            $data[] = [
                'id'   => $robot->id,
                'name' => $robot->name,
            ];
        }

        echo json_encode($data);
    }
);

获取 -id

通过使用机器人的id来获取它们与上述操作类似。我们只需要调整针对数据库运行的查询。所使用的 HTTP 方法也将是get(),而端点将是/api/robots/{id:[0-9]+}。针对此处理程序,如果未找到机器人,我们也会反馈报告。

The index.php再次发生改变:

<?php

use Phalcon\Http\Response;

$app->get(
    '/api/robots/{id:[0-9]+}',
    function ($id) use ($app) {
        $phql = 'SELECT * '
              . 'FROM MyApp\Models\Robots '
              . 'WHERE id = :id:'
        ;

        $robot = $app
            ->modelsManager
            ->executeQuery(
                $phql,
                [
                    'id' => $id,
                ]
            )
            ->getFirst()
        ;

        $response = new Response();
        if ($robot === false) {
            $response->setJsonContent(
                [
                    'status' => 'NOT-FOUND'
                ]
            );
        } else {
            $response->setJsonContent(
                [
                    'status' => 'FOUND',
                    'data'   => [
                        'id'   => $robot->id,
                        'name' => $robot->name
                    ]
                ]
            );
        }

        return $response;
    }
);

插入

我们的设计允许用户提交数据以便插入到数据库中。使用的 HTTP 方法是post()到达/api/robots端点。我们期望以 JSON 字符串的形式提交数据。

<?php

use Phalcon\Http\Response;

$app->post(
    '/api/robots',
    function () use ($app) {
        $robot = $app->request->getJsonRawBody();
        $phql  = 'INSERT INTO MyApp\ModelsRobots '
               . '(name, type, year) '
               . 'VALUES '
               . '(:name:, :type:, :year:)'
        ;

        $status = $app
            ->modelsManager
            ->executeQuery(
                $phql,
                [
                    'name' => $robot->name,
                    'type' => $robot->type,
                    'year' => $robot->year,
                ]
            )
        ;

        $response = new Response();

        if ($status->success() === true) {
            $response->setStatusCode(201, 'Created');

            $robot->id = $status->getModel()->id;

            $response->setJsonContent(
                [
                    'status' => 'OK',
                    'data'   => $robot,
                ]
            );
        } else {
            $response->setStatusCode(409, 'Conflict');

            $errors = [];
            foreach ($status->getMessages() as $message) {
                $errors[] = $message->getMessage();
            }

            $response->setJsonContent(
                [
                    'status'   => 'ERROR',
                    'messages' => $errors,
                ]
            );
        }

        return $response;
    }
);

使用 PHQL 对数据库执行查询后,我们会创建一个全新的Response对象。如果查询成功执行,我们就修改响应的状态码为201,文本为Created。最后我们更新最近创建记录的id并将机器人随响应一起返回。

如果出现错误,我们将响应状态码更改为409并附上文本Conflict并收集数据库操作所产生的所有错误。然后我们随响应一起发送这些错误信息。

更新

更新数据与插入数据类似。对于此操作,我们使用put()HTTP 方法以及端点/api/robots/{id:[0-9]+}URL中传递的id参数是要更新的机器人的 ID。提交的数据是以 JSON 格式发送的。

<?php

use Phalcon\Http\Response;

$app->put(
    '/api/robots/{id:[0-9]+}',
    function ($id) use ($app) {
        $robot = $app->request->getJsonRawBody();
        $phql  = 'UPDATE MyApp\Models\Robots '
               . 'SET name = :name:, type = :type:, year = :year: '
               . 'WHERE id = :id:';

        $status = $app
            ->modelsManager
            ->executeQuery(
                $phql,
                [
                    'id'   => $id,
                    'name' => $robot->name,
                    'type' => $robot->type,
                    'year' => $robot->year,
                ]
            )
        ;

        $response = new Response();

        if ($status->success() === true) {
            $response->setJsonContent(
                [
                    'status' => 'OK'
                ]
            );
        } else {
            $response->setStatusCode(409, 'Conflict');

            $errors = [];
            foreach ($status->getMessages() as $message) {
                $errors[] = $message->getMessage();
            }

            $response->setJsonContent(
                [
                    'status'   => 'ERROR',
                    'messages' => $errors,
                ]
            );
        }

        return $response;
    }
);

此操作与我们插入数据时的操作非常相似。如果更新操作成功,我们回传一个包含OK.

如果出现错误,我们将响应状态码更改为409并附上文本Conflict并收集数据库操作所产生的所有错误。然后我们随响应一起发送这些错误信息。

删除

删除操作与update操作几乎相同。对于该操作,我们使用的是delete()HTTP 方法以及端点/api/robots/{id:[0-9]+}URL中传递的id参数是待删除机器人的id。

The index.php再次发生改变:

<?php

use Phalcon\Http\Response;

$app->delete(
    '/api/robots/{id:[0-9]+}',
    function ($id) use ($app) {
        $phql = 'DELETE '
              . 'FROM MyApp\Models\Robots '
              . 'WHERE id = :id:';

        $status = $app
            ->modelsManager
            ->executeQuery(
                $phql,
                [
                    'id' => $id,
                ]
            )
        ;

        $response = new Response();

        if ($status->success() === true) {
            $response->setJsonContent(
                [
                    'status' => 'OK'
                ]
            );
        } else {
            $response->setStatusCode(409, 'Conflict');

            $errors = [];
            foreach ($status->getMessages() as $message) {
                $errors[] = $message->getMessage();
            }

            $response->setJsonContent(
                [
                    'status'   => 'ERROR',
                    'messages' => $errors,
                ]
            );
        }

        return $response;
    }
);

如果删除操作成功,我们将会返回一个包含以下内容的JSON数据:OK.

如果出现错误,我们将响应状态码更改为409并附上文本Conflict并收集数据库操作所生成的所有错误。然后我们随响应一起发送这些错误信息。

模式

为了在我们的数据库中创建表,我们需要使用以下SQL查询语句:

create database `robotics`;

create table `robotics`.`robots` (
 `id`    int(10)      unsigned         not null auto_increment,
 `name`  varchar(200) collate utf8_bin not null,
 `type`  varchar(20)  collate utf8_bin not null,
 `year`  smallint(4)  unsigned         not null,
 PRIMARY KEY (`id`)
)

运行

当然,你可以设置你的网络服务器来运行你的应用程序。有关设置说明,可以查阅Web 服务器设置文档并确保你的主机指向index.php文件。如果你想使用内置的PHP服务器,你需要创建一个名为.htrouter的组件中访问你的会话,如下所示:

<?php

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

if ($uri !== '/' && file_exists(__DIR__ . $uri)) {
    return false;
}

$_GET['_url'] = $_SERVER['REQUEST_URI'];

require_once __DIR__ . '/index.php';

的文件,然后运行以下命令:

$(which php) -S localhost:8000 -t / .htrouter.php

测试

有许多测试套件可用于测试此应用程序。我们将在每个路由上使用curl来验证是否正常运行。

获取所有机器人:

curl -i -X GET https://localhost/my-rest-api/api/robots

HTTP/1.1 200 OK
Date: Wed, 25 Dec 2019 01:02:03 GMT
Server: Apache/2.2.22 (Unix) DAV/2
Content-Length: 117
Content-Type: text/html; charset=UTF-8

[{"id":"1","name":"Robotina"},{"id":"2","name":"Astro Boy"},{"id":"3","name":"Terminator"}]

搜索按名称查找机器人:

curl -i -X GET https://localhost/my-rest-api/api/robots/search/Astro

HTTP/1.1 200 OK
Date: Wed, 25 Dec 2019 01:02:03 GMT
Server: Apache/2.2.22 (Unix) DAV/2
Content-Length: 31
Content-Type: text/html; charset=UTF-8

[{"id":"2","name":"Astro Boy"}]

获取按ID查找机器人:

curl -i -X GET https://localhost/my-rest-api/api/robots/3

HTTP/1.1 200 OK
Date: Wed, 25 Dec 2019 01:02:03 GMT
Server: Apache/2.2.22 (Unix) DAV/2
Content-Length: 56
Content-Type: text/html; charset=UTF-8

{"status":"FOUND","data":{"id":"3","name":"Terminator"}}

插入添加新机器人:

curl -i -X POST -d '{"name":"C-3PO","type":"droid","year":1977}' \
    https://localhost/my-rest-api/api/robots

HTTP/1.1 201 Created
Date: Wed, 25 Dec 2019 01:02:03 GMT
Server: Apache/2.2.22 (Unix) DAV/2
Content-Length: 75
Content-Type: text/html; charset=UTF-8

{"status":"OK","data":{"name":"C-3PO","type":"droid","year":1977,"id":"4"}}

尝试插入一个名称为已有机器人名称的新机器人:

curl -i -X POST -d '{"name":"C-3PO","type":"droid","year":1977}' \
    https://localhost/my-rest-api/api/robots

HTTP/1.1 409 Conflict
Date: Wed, 25 Dec 2019 01:02:03 GMT
Server: Apache/2.2.22 (Unix) DAV/2
Content-Length: 63
Content-Type: text/html; charset=UTF-8

{"status":"ERROR","messages":["The robot name must be unique"]}

更新使用未知类型查找机器人:

curl -i -X PUT -d '{"name":"ASIMO","type":"humanoid","year":2000}' \
    https://localhost/my-rest-api/api/robots/4

HTTP/1.1 409 Conflict
Date: Wed, 25 Dec 2019 01:02:03 GMT
Server: Apache/2.2.22 (Unix) DAV/2
Content-Length: 104
Content-Type: text/html; charset=UTF-8

{"status":"ERROR","messages":["Value of field 'type' must be part of
    list: droid, mechanical, virtual"]}

删除删除机器人:

curl -i -X DELETE https://localhost/my-rest-api/api/robots/4

HTTP/1.1 200 OK
Date: Wed, 25 Dec 2019 01:02:03 GMT
Server: Apache/2.2.22 (Unix) DAV/2
Content-Length: 15
Content-Type: text/html; charset=UTF-8

{"status":"OK"}
无噪 Logo
无噪文档
25 年 6 月翻译
版本号 5.9
文档源↗