教程 - 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
我们要实现的是当使用GET
HTTP 方法发出请求时从数据库检索数据的方法。该端点将使用 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"]}
删除删除机器人: