Spec-Zone.ru › Phalcon 3

Урок 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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API