教程:创建一个简单的REST API
最后更新于:2022-04-02 05:13:15
[TOC]
# 教程:创建一个简单的REST API
在本教程中,我们将解释如何使用不同的HTTP方法创建一个提供[RESTful](http://en.wikipedia.org/wiki/Representational_state_transfer) API的简单应用程序:
* `GET` 检索和搜索数据
* `POST` 增加数据
* `PUT` 修改数据
* `DELETE` 删除数据
## 定义API
API包含以下方法:
| 方法 | URL | Action |
| -------- | ------------------------ | ---------------------------------------------- |
| `GET` | /api/robots | 检索所有机器人 |
| `GET` | /api/robots/search/Astro | 搜索名称中带有“Astro”的机器人|
| `GET` | /api/robots/2 | 根据主键2检索机器人 |
| `POST` | /api/robots | 添加一个新机器人 |
| `PUT` | /api/robots/2 | 根据主键2更新机器人 |
| `DELETE` | /api/robots/2 | 删除基于主键2的机器人 |
## 创建应用程序
由于应用程序非常简单,我们不会实现任何完整的MVC环境来开发它。在这种情况下,我们将使用微应用程序来实现我们的目标。
以下文件结构绰绰有余:
```php
my-rest-api/
models/
Robots.php
index.php
.htaccess
```
首先,我们需要一个`.htaccess`文件,其中包含将请求URI重写到`index.php`文件(应用程序入口点)的所有规则:
```apacheconfig
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^((?s).*)$ index.php?_url=/$1 [QSA,L]
```
我们的大部分代码都将放在`index.php`中。该文件创建如下:
```php
handle();
```
现在我们将按照上面的定义创建路由:
```php
get(
'/api/robots',
function () {
// Operation to fetch all the robots
}
);
// Searches for robots with $name in their name
$app->get(
'/api/robots/search/{name}',
function ($name) {
// Operation to fetch robot with name $name
}
);
// Retrieves robots based on primary key
$app->get(
'/api/robots/{id:[0-9]+}',
function ($id) {
// Operation to fetch robot with id $id
}
);
// Adds a new robot
$app->post(
'/api/robots',
function () {
// Operation to create a fresh robot
}
);
// Updates robots based on primary key
$app->put(
'/api/robots/{id:[0-9]+}',
function ($id) {
// Operation to update a robot with id $id
}
);
// Deletes robots based on primary key
$app->delete(
'/api/robots/{id:[0-9]+}',
function ($id) {
// Operation to delete the robot with id $id
}
);
$app->handle();
```
每个路由都使用与HTTP方法同名的方法定义,作为第一个参数,我们传递路由模式,然后是处理程序。在这种情况下,处理程序是一个匿名函数。以下路径:`/api/robots/{id:[0-9]+}`,例如,显式设置 `id` 参数必须具有数字格式。
当定义的路由与请求的URI匹配时,应用程序将执行相应的处理程序。
## 创建模型
我们的API提供有关`robots`的信息,这些数据存储在数据库中。以下模型允许我们以面向对象的方式访问该表。我们使用内置验证器和简单验证实现了一些业务规则。这样做将使我们高枕无忧,保存的数据符合我们的应用程序的要求。此模型文件`Robots.php`应放在`models`文件夹中。
```php
add(
'type',
new InclusionIn(
[
'domain' => [
'Mechanical',
'Virtual',
'Droid',
]
]
)
);
$validator->add(
'name',
new Uniqueness(
[
'message' => 'The robot name must be unique',
]
)
);
return $this->validate($validator);
}
}
```
现在,我们必须建立一个这个模型使用的连接,并在我们的app [File:`index.php`]中加载它:
```php
registerNamespaces(
[
'Store\Toys' => __DIR__ . '/models/',
]
);
$loader->register();
$di = new FactoryDefault();
// Set up the database service
$di->set(
'db',
function () {
return new PdoMysql(
[
'host' => 'localhost',
'username' => 'asimov',
'password' => 'zeroth',
'dbname' => 'robotics',
]
);
}
);
// Create and bind the DI to the application
$app = new Micro($di);
```
## 检索数据
我们将实现的第一个处理程序是通过方法GET返回所有可用的机器人。让我们使用PHQL来执行这个简单的查询,将结果作为JSON返回。[文件:`index.php`]
```php
get(
'/api/robots',
function () use ($app) {
$phql = 'SELECT * FROM Store\Toys\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`允许我们容易地将一些变量从全局传递到本地范围。
按名称处理程序搜索看起来像[File:`index.php`]:
```php
get(
'/api/robots/search/{name}',
function ($name) use ($app) {
$phql = 'SELECT * FROM Store\Toys\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`搜索它非常相似,在这种情况下,我们还会通知机器人是否被找到[文件:`index.php`]:
```php
get(
'/api/robots/{id:[0-9]+}',
function ($id) use ($app) {
$phql = 'SELECT * FROM Store\Toys\Robots WHERE id = :id:';
$robot = $app->modelsManager->executeQuery(
$phql,
[
'id' => $id,
]
)->getFirst();
// Create a response
$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;
}
);
```
## 插入数据
将数据作为插入请求正文的JSON字符串,我们也使用PHQL进行插入[File:`index.php`]:
```php
post(
'/api/robots',
function () use ($app) {
$robot = $app->request->getJsonRawBody();
$phql = 'INSERT INTO Store\Toys\Robots (name, type, year) VALUES (:name:, :type:, :year:)';
$status = $app->modelsManager->executeQuery(
$phql,
[
'name' => $robot->name,
'type' => $robot->type,
'year' => $robot->year,
]
);
// Create a response
$response = new Response();
// Check if the insertion was successful
if ($status->success() === true) {
// Change the HTTP status
$response->setStatusCode(201, 'Created');
$robot->id = $status->getModel()->id;
$response->setJsonContent(
[
'status' => 'OK',
'data' => $robot,
]
);
} else {
// Change the HTTP status
$response->setStatusCode(409, 'Conflict');
// Send errors to the client
$errors = [];
foreach ($status->getMessages() as $message) {
$errors[] = $message->getMessage();
}
$response->setJsonContent(
[
'status' => 'ERROR',
'messages' => $errors,
]
);
}
return $response;
}
);
```
## 更新数据
数据更新与插入类似。作为参数传递的`id`表示必须更新哪个机器人[File:`index.php`]:
```php
put(
'/api/robots/{id:[0-9]+}',
function ($id) use ($app) {
$robot = $app->request->getJsonRawBody();
$phql = 'UPDATE Store\Toys\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,
]
);
// Create a response
$response = new Response();
// Check if the insertion was successful
if ($status->success() === true) {
$response->setJsonContent(
[
'status' => 'OK'
]
);
} else {
// Change the HTTP status
$response->setStatusCode(409, 'Conflict');
$errors = [];
foreach ($status->getMessages() as $message) {
$errors[] = $message->getMessage();
}
$response->setJsonContent(
[
'status' => 'ERROR',
'messages' => $errors,
]
);
}
return $response;
}
);
```
## 删除数据
数据删除与更新类似。作为参数传递的`id`表示必须删除哪个机器人[File:`index.php`]:
```php
delete(
'/api/robots/{id:[0-9]+}',
function ($id) use ($app) {
$phql = 'DELETE FROM Store\Toys\Robots WHERE id = :id:';
$status = $app->modelsManager->executeQuery(
$phql,
[
'id' => $id,
]
);
// Create a response
$response = new Response();
if ($status->success() === true) {
$response->setJsonContent(
[
'status' => 'OK'
]
);
} else {
// Change the HTTP status
$response->setStatusCode(409, 'Conflict');
$errors = [];
foreach ($status->getMessages() as $message) {
$errors[] = $message->getMessage();
}
$response->setJsonContent(
[
'status' => 'ERROR',
'messages' => $errors,
]
);
}
return $response;
}
);
```
## 创建数据库
现在我们将为我们的应用程序创建数据库运行SQL查询,如下所示:
```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(200) COLLATE utf8_bin NOT NULL,
`year` smallint(2) unsigned NOT NULL,
PRIMARY KEY (`id`)
)
```
## 测试我们的应用
使用[curl](http://en.wikipedia.org/wiki/CURL),我们将测试应用程序中的每条路径,以验证其是否正常运行。
获取所有机器人:
```bash
curl -i -X GET http://localhost/my-rest-api/api/robots
HTTP/1.1 200 OK
Date: Tue, 21 Jul 2015 07:05:13 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"}]
```
按名称搜索机器人:
```bash
curl -i -X GET http://localhost/my-rest-api/api/robots/search/Astro
HTTP/1.1 200 OK
Date: Tue, 21 Jul 2015 07:09:23 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获取机器人:
```bash
curl -i -X GET http://localhost/my-rest-api/api/robots/3
HTTP/1.1 200 OK
Date: Tue, 21 Jul 2015 07:12:18 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"}}
```
插入一个新机器人:
```bash
curl -i -X POST -d '{"name":"C-3PO","type":"droid","year":1977}'
http://localhost/my-rest-api/api/robots
HTTP/1.1 201 Created
Date: Tue, 21 Jul 2015 07:15:09 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"}}
```
尝试插入一个具有现有机器人名称的新机器人:
```bash
curl -i -X POST -d '{"name":"C-3PO","type":"droid","year":1977}'
http://localhost/my-rest-api/api/robots
HTTP/1.1 409 Conflict
Date: Tue, 21 Jul 2015 07:18:28 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"]}
```
或者更新未知类型的机器人:
```bash
curl -i -X PUT -d '{"name":"ASIMO","type":"humanoid","year":2000}'
http://localhost/my-rest-api/api/robots/4
HTTP/1.1 409 Conflict
Date: Tue, 21 Jul 2015 08:48:01 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"]}
```
最后,删除一个机器人:
```bash
curl -i -X DELETE http://localhost/my-rest-api/api/robots/1
HTTP/1.1 200 OK
Date: Tue, 21 Jul 2015 08:49:29 GMT
Server: Apache/2.2.22 (Unix) DAV/2
Content-Length: 15
Content-Type: text/html; charset=UTF-8
{"status":"OK"}
```
## 结论
正如我们所看到的,使用微应用程序和PHQL开发使用Phalcon的[RESTful](http://en.wikipedia.org/wiki/Representational_state_transfer) API很容易。
';