[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
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^((?s).*)$ index.php?_url=/$1 [QSA,L]
</IfModule>
```
我們的大部分代碼都將放在`index.php`中。該文件創建如下:
```php
<?php
use Phalcon\Mvc\Micro;
$app = new Micro();
// Define the routes here
$app->handle();
```
現在我們將按照上面的定義創建路由:
```php
<?php
use Phalcon\Mvc\Micro;
$app = new Micro();
// Retrieves all robots
$app->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
<?php
namespace Store\Toys;
use Phalcon\Mvc\Model;
use Phalcon\Validation;
use Phalcon\Validation\Validator\Uniqueness;
use Phalcon\Validation\Validator\InclusionIn;
class Robots extends Model
{
public function validation()
{
$validator = new Validation();
$validator->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
<?php
use Phalcon\Loader;
use Phalcon\Mvc\Micro;
use Phalcon\Di\FactoryDefault;
use Phalcon\Db\Adapter\Pdo\Mysql as PdoMysql;
// Use Loader() to autoload our model
$loader = new Loader();
$loader->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
<?php
// Retrieves all robots
$app->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
<?php
// Searches for robots with $name in their name
$app->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
<?php
use Phalcon\Http\Response;
// Retrieves robots based on primary key
$app->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
<?php
use Phalcon\Http\Response;
// Adds a new robot
$app->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
<?php
use Phalcon\Http\Response;
// Updates robots based on primary key
$app->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
<?php
use Phalcon\Http\Response;
// Deletes robots based on primary key
$app->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很容易。
- 常規
- Welcome
- 貢獻
- 生成回溯
- 測試重現
- 單元測試
- 入門
- 安裝
- Web服務器設置
- WAMP
- XAMPP
- 教程
- 基礎教程
- 教程:創建一個簡單的REST API
- 教程:V?kuró
- 提升性能
- 教程:INVO
- 開發環境
- Phalcon Compose (Docker)
- Nanobox
- Phalcon Box (Vagrant)
- 開發工具
- Phalcon開發者工具的安裝
- Phalcon開發者工具的使用
- 調試應用程序
- 核心
- MVC應用
- 微應用
- 創建命令行(CLI)應用程序
- 依賴注入與服務定位
- MVC架構
- 服務
- 使用緩存提高性能
- 讀取配置
- 上下文轉義
- 類加載器
- 使用命名空間
- 日志
- 隊列
- 數據庫
- 數據庫抽象層
- Phalcon查詢語言(PHQL)
- ODM(對象文檔映射器)
- 使用模型
- 模型行為
- ORM緩存
- 模型事件
- 模型元數據
- 模型關系
- 模型事務
- 驗證模型
- 數據庫遷移
- 分頁
- 前端
- Assets管理
- 閃存消息
- 表單
- 圖像
- 視圖助手(標簽)
- 使用視圖
- Volt:模板引擎
- 業務邏輯
- 訪問控制列表(ACL)
- 注解解析器
- 控制器
- 調度控制器
- 事件管理器
- 過濾與清理
- 路由
- 在session中存儲數據
- 生成URL和路徑
- 驗證
- HTTP
- Cookies管理
- 請求環境
- 返回響應
- 安全
- 加密/解密
- 安全
- 國際化
- 國際化
- 多語言支持