Урок 7: Создание простого REST API
В этом уроке мы объясним, как создать простое приложение, которое предоставляет RESTful API с использованием различных HTTP методов:
- GET для получения и поиска данных
- POST для добавления данных
- PUT для обновления данных
- DELETE для удаления данных
Определение 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 мы создаём следующее:
use Phalcon\Mvc\Micro; $app = new Micro(); // Define the routes here $app->handle();
Теперь мы создадим маршруты, как определено выше:
use Phalcon\Mvc\Micro;
$app = new Micro();
// Retrieves all robots
$app->get(
"/api/robots",
function () {
}
);
// Searches for robots with $name in their name
$app->get(
"/api/robots/search/{name}",
function ($name) {
}
);
// Retrieves robots based on primary key
$app->get(
"/api/robots/{id:[0-9]+}",
function ($id) {
}
);
// Adds a new robot
$app->post(
"/api/robots",
function () {
}
);
// Updates robots based on primary key
$app->put(
"/api/robots/{id:[0-9]+}",
function () {
}
);
// Deletes robots based on primary key
$app->delete(
"/api/robots/{id:[0-9]+}",
function () {
}
);
$app->handle();
Каждый маршрут определяется методом с тем же именем, что и HTTP-метод, в качестве первого параметра передаётся шаблон маршрута, а затем обработчик. В данном случае, обработчик является анонимной функцией. Следующий маршрут: '/api/robots/{id:[0-9]+}', например, явно устанавливает, что параметр «id» должен иметь числовой формат.
Когда определённый маршрут совпадает с запрошенным URI, приложение выполняет соответствующий обработчик.
Создание модели
Наш API предоставляет информацию о «роботах»; эти данные хранятся в базе данных. Следующая модель позволяет нам получить доступ к этой таблице объектно-ориентированным способом. Мы реализовали некоторые бизнес-правила с использованием встроенных валидаторов и простых проверок. Это даст нам уверенность в том, что сохранённые данные соответствуют требованиям нашего приложения:
namespace Store\Toys;
use Phalcon\Mvc\Model;
use Phalcon\Mvc\Model\Message;
use Phalcon\Mvc\Model\Validator\Uniqueness;
use Phalcon\Mvc\Model\Validator\InclusionIn;
class Robots extends Model
{
public function validation()
{
// Type must be: droid, mechanical or virtual
$this->validate(
new InclusionIn(
[
"field" => "type",
"domain" => [
"droid",
"mechanical",
"virtual",
]
)
)
);
// Robot name must be unique
$this->validate(
new Uniqueness(
[
"field" => "name",
"message" => "The robot name must be unique",
]
)
);
// Year cannot be less than zero
if ($this->year < 0) {
$this->appendMessage(
new Message("The year cannot be less than zero")
);
}
// Check if any messages have been produced
if ($this->validationHasFailed() === true) {
return false;
}
}
}
Теперь нам нужно установить соединение, которое будет использоваться этой моделью, и загрузить её в наше приложение:
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:
// 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» в анонимной функции позволяет нам легко передавать некоторые переменные из глобальной в локальную область видимости.
Обработчик поиска по имени будет выглядеть так:
// 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» довольно похож; в этом случае мы также уведомляем, найден ли робот или нет:
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 для вставки:
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» указывает, какой робот должен быть обновлён:
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» указывает, какой робот должен быть удалён:
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;
}
);
Тестирование приложения
С помощью curl мы протестируем каждый маршрут в нашем приложении, проверив его корректную работу.
Получение всех роботов:
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"}]
Поиск робота по имени:
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"}]
Получение робота по его идентификатору:
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"}}
Вставка нового робота:
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"}}
Попытка вставить нового робота с именем существующего робота:
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"]}
Или обновление робота с неизвестным типом:
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"]}
Наконец, удаление робота:
curl -i -X DELETE http://localhost/my-rest-api/api/robots/4
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"}
Заключение
Как мы видим, разработка RESTful API с Phalcon проста. Позже в документации мы подробно объясним, как использовать микро-приложения и язык PHQL.
© 2011–2017 Phalcon Framework Team
Licensed under the Creative Commons Attribution License 3.0.
https://docs.phalconphp.com/en/latest/reference/tutorial-rest.html