Создание простого REST API.

В этом уроке мы объясним, как создать простое приложение, которое предоставляет RESTful , используя различные методы HTTP:

  • GET извлечение и поиск данных
  • POST добавление данных
  • PUT обновление данных
  • DELETE удаление данных

Примечание: Это всего лишь пример приложения. Ему не хватает многих функций, таких как аутентификация, авторизация, дезинфекция ввода и управление ошибками, чтобы назвать несколько. Пожалуйста, используйте его как строительный блок для вашего приложения или как учебник, чтобы понять, как вы можете построить REST API с Phalcon. Вы также можете посмотреть на проект rest-api.

Определение API.

API состоит из следующих методов:

MethodURLAction
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. Файл создается следующим образом:

<?php

use Phalcon\Mvc\Micro;

$app = new Micro();

$app->handle(
    $_SERVER["REQUEST_URI"]
);

Теперь мы создадим маршруты, как мы определили выше:

<?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.

Первый параметр каждого вызова метода-это маршрут, а второй-обработчик, т. е. что мы делаем, когда пользователь вызывает этот маршрут. В нашем примере для каждого обработчика определены анонимные функции. Для следующего маршрута: /api/robots/{id:[0-9]+}, например, явно указывает, что параметр id должен иметь числовой формат.

Когда определенный маршрут соответствует запрошенному URI, приложение выполняет соответствующий обработчик.

Создание модели.

Наш API предоставляет информацию о robots, эти данные хранятся в базе данных. Следующая модель позволяет получить доступ к этой таблице объектно-ориентированным способом. Мы реализовали некоторые бизнес-правила, используя встроенные валидаторы и простые валидации. Это даст нам душевное спокойствие, что сохраненные данные отвечают требованиям нашего приложения. Этот файл модели  Robots.php должен быть помещен в папку models .

<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;
use Phalcon\Messages\Message;
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(
                [
                    'message' => 'Тип должен быть "droid", "mechanical", или "virtual"',
                    'domain' => [
                        'droid',
                        'mechanical',
                        'virtual',
                    ],
                ]
            )
        );

        $validator->add(
            'name',
            new Uniqueness(
                [
                    'field'   => 'name',
                    'message' => 'Имя робота должно быть уникальным',
                ]
            )
        );

        if ($this->year < 0) {
            $this->appendMessage(
                new Message('Год не может быть меньше нуля')
            );
        }

        if ($this->validationHasFailed() === true) {
            return false;
        }
    }
}

Мы прикрепляем к модели три валидатора. Первый проверяет тип робота. Это должен быть droid, mechanical или virtual. Любое другое значение вернет валидатору значение false , и операция  (insert/update) завершится ошибкой. Второй валидатор проверяет уникальность имени для нашего робота. Последний валидатор проверяет поле year на положительное число.

База данных

Нам нужно подключить наше приложение к базе данных. Для этого примера мы будем использовать популярный MariaDB или аналогичные варианты, такие как MySQL, Aurora и т.д. В дополнение к настройке базы данных, мы собираемся настроить автозапуск, так что наше приложение будет знать, где искать необходимые файлы.

Теперь мы должны установить соединение, которое будет использоваться этой моделью, и подключить его в наше приложение [файл: index.php]:

<?php

use Phalcon\Loader;
use Phalcon\Mvc\Micro;
use Phalcon\Di\FactoryDefault;
use Phalcon\Db\Adapter\Pdo\Mysql as PdoMysql;

$loader = new Loader();
$loader->registerNamespaces(
    [
        '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);

Извлечение данных.

Get

Первый обработчик, который мы реализуем, - это тот, который извлекает данные из базы данных, когда запрос выполняется с помощью метода 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 в анонимной функции предлагает инъекцию объекта из локальной области в анонимную функцию.

Get - Text

Мы можем заставить роботов использовать их имя или часть имени. Эта функция поиска также будет get() , что касается метода HTTP, и она будет привязана к конечной точке /api/robots/search/{name}. Реализация аналогична описанной выше. Нам просто нужно немного изменить запрос.

Обработчик поиска по имени будет выглядеть как [Файл: index.php]:

<?php

// Поиск роботов с именем $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);
    }
);

Get - id

Получить робота с помощью их id iаналогично вышеописанным операциям. Нам просто нужно будет настроить запрос, который мы запускаем к базе данных. Используемый метод HTTP также будет get() , а конечная точка будет /api/robots/{id:[0-9]+}. Для этого обработчика мы также сообщаем, если робот не был найден.

Поиск по полю id очень похож, в этом случае мы также уведомляем, найден робот или нет [Файл: index.php]:

<?php

use Phalcon\Http\Response;

$app->get(
    '/api/robots/{id:[0-9]+}',
    function ($id) use ($app) {
        $phql = 'SELECT * '
              . 'FROM MyApp\ModelsRobots '
              . '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;
    }
);

Вставка данных.

Принимая данные в виде строки JSON, вставленной в тело запроса, мы также используем PHQL для вставки [Файл: index.php]:

<?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]+}. Переданный параметр id в URL-адресе-это идентификатор обновляемого робота. Данные представлены в формате 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;
    }
);

Эта операция очень похожа на ту, которую мы используем при вставке данных. Если операция обновления прошла успешно, мы отправляем обратно полезную нагрузку JSON с OK.

Если что-то не так, мы меняем код состояния ответа на 409 с текстом Conflict и собираем все ошибки, которые были произведены при работе с базой данных. Затем мы отправляем эти сообщения об ошибках обратно с ответом.

Удаление данных.

Удаление почти идентично процессу обновления. Для этой операции мы используем метод delete() HTTP и конечную точку /api/robots/{id:[0-9]+}. Переданный параметр id pв URL-адресе-это идентификатор удаляемого робота.

Файл: index.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 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(200) COLLATE utf8_bin NOT NULL,
 `year` smallint(2) unsigned NOT NULL,
 PRIMARY KEY (`id`)
)

Тестирование нашего приложения

С помощью 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"}]

Получение робота по его id:

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":["Имя робота должно быть уникальным"]}

Или обновить робота неизвестного типа:

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":["Значение поля type должно быть частью списка: Droid, Mechanical, Virtual"]}

Наконец, удалите робота:

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"}

Вывод

Как мы видели, разработка RESTful API с в Phalcon проста с помощью микро-приложений и PHQL.