教程 - 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 环境来开发它。在这种情况下,我们将为我们的需求使用一个微型应用。该应用程序的结构如下:
首先,我们需要一个.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.
现在我们需要创建路由,以便应用程序可以理解终端用户与应用程序交互时应该做什么。该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 方法定义应用程序的监听点。
每个方法调用的第一个参数是路由,第二个参数是处理程序,即当用户调用该路由时我们应该做什么?在我们的示例中,我们为每个处理程序定义了匿名函数。对于以下路由:
我们显式地将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, mechanical或virtual。任何其他值都会导致验证器返回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';
的文件,然后运行以下命令:
测试¶
有许多测试套件可用于测试此应用程序。我们将在每个路由上使用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"]}
删除删除机器人: