Пример простого создания REST API на PHP

Шаг за шагом руководство по созданию простого REST API на PHP. Какие ключевые этапы необходимо пройти для создания REST API на PHP? Какие практические примеры использования REST API в PHP можно привести?

Пример простого создания REST API на PHPПрактический материал CMS.BY
CMS.BY

В данной статье рассказывается пример простого REST API реализация которого на PHP без использования каких-либо фреймворков. Из-за того, что никак фреймворки применяться не будут, необходимо начать с того, что перенаправления всех запросов на точку входа, это index.php. Сервер где используется Apache, это можно реализовать в файле htaccess который должен находится в корне проекта:

Options +FollowSymLinks
IndexIgnore */*
RewriteEngine on

# Перенаправление с domain.by на domain.by/api
RewriteCond %{REQUEST_URI} ^/$
RewriteRule ^(.*)$ /api/$1 [R=301]

#Если адрес начинается с api/ , то перенаправлять все запросы на index.php
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^api/(.*)$ /index.php

Исходя из правил, ссылка должна начинаться с /api , допустим, для API работающего с таблицей people должна иметь такой вид: domain.by/api/people

Пример файла index.php:

require_once 'PeopleApi.php';

try {
    $api = new usersApi();
    echo $api->run();
} catch (Exception $e) {
    echo json_encode(Array('error' => $e->getMessage()));
}

Как видно из примера, будет идти работа с объектом peopleApi, в примере показано подключение файла c классом непосредственно через require_once.

require_once 'PeopleApi.php';

Кроме людей еще возможно нужно будет сделать API и по другим сущностям, и поэтому все классы различных API должны иметь один общий костяк, который будет определять метод запроса, действие для выполнения и так далее. Создаем файл API с абстрактным классом Api:

abstract class Api
{
    public $apiName = ''; //people

    protected $method = ''; //GET|POST|PUT|DELETE

    public $requestUri = [];
    public $requestParams = [];

    protected $action = ''; //Название метода для выполнения


    public function __construct() {
        header("Access-Control-Allow-Orgin: *");
        header("Access-Control-Allow-Methods: *");
        header("Content-Type: application/json");

        //Массив GET параметров разделенных /
        $this->requestUri = explode('/', trim($_SERVER['REQUEST_URI'],'/'));
        $this->requestParams = $_REQUEST;

        //Определение метода запроса
        $this->method = $_SERVER['REQUEST_METHOD'];
        if ($this->method == 'POST' && array_key_exists('HTTP_X_HTTP_METHOD', $_SERVER)) {
            if ($_SERVER['HTTP_X_HTTP_METHOD'] == 'DELETE') {
                $this->method = 'DELETE';
            } else if ($_SERVER['HTTP_X_HTTP_METHOD'] == 'PUT') {
                $this->method = 'PUT';
            } else {
                throw new Exception("Unexpected Header");
            }
        }
    }

    public function run() {
        //Первые 2 элемента массива URI должны быть "api" и название таблицы
        if(array_shift($this->requestUri) !== 'api' || array_shift($this->requestUri) !== $this->apiName){
            throw new RuntimeException('API Not Found', 404);
        }
        //Определение действия для обработки
        $this->action = $this->getAction();

        //Если метод(действие) определен в дочернем классе API
        if (method_exists($this, $this->action)) {
            return $this->{$this->action}();
        } else {
            throw new RuntimeException('Invalid Method', 405);
        }
    }

    protected function response($data, $status = 500) {
        header("HTTP/1.1 " . $status . " " . $this->requestStatus($status));
        return json_encode($data);
    }

    private function requestStatus($code) {
        $status = array(
            200 => 'OK',
            404 => 'Not Found',
            405 => 'Method Not Allowed',
            500 => 'Internal Server Error',
        );
        return ($status[$code])?$status[$code]:$status[500];
    }

    protected function getAction()
    {
        $method = $this->method;
        switch ($method) {
            case 'GET':
                if($this->requestUri){
                    return 'viewAction';
                } else {
                    return 'indexAction';
                }
                break;
            case 'POST':
                return 'createAction';
                break;
            case 'PUT':
                return 'updateAction';
                break;
            case 'DELETE':
                return 'deleteAction';
                break;
            default:
                return null;
        }
    }

    abstract protected function indexAction();
    abstract protected function viewAction();
    abstract protected function createAction();
    abstract protected function updateAction();
    abstract protected function deleteAction();
}

Напоследок осталось реализовать абстрактные методы и свойство $apiName, которые будут уникальны для каждого отдельного API. Для примера создаем файл PeopleApi.php:

require_once 'Api.php';
require_once 'Db.php';
require_once 'People.php';

class PeopleApi extends Api
{
    public $apiName = 'people';

    /**
     * Метод GET
     * Вывод списка всех записей
     * https://domain.by/people
     * @return string
     */
    public function indexAction()
    {
        $db = (new Db())->getConnect();
        $people = People::getAll($db);
        if($people){
            return $this->response($people, 200);
        }
        return $this->response('Data not found', 404);
    }

    /**
     * Метод GET
     * Просмотр отдельной записи (по id)
     * https://domain.by/people/1
     * @return string
     */
    public function viewAction()
    {
        //id должен быть первым параметром после /people/x
        $id = array_shift($this->requestUri);

        if($id){
            $db = (new Db())->getConnect();
            $user = People::getById($db, $id);
            if($user){
                return $this->response($user, 200);
            }
        }
        return $this->response('Data not found', 404);
    }

    /**
     * Метод POST
     * Создание новой записи
     * https://domain.by/people + параметры запроса name, email
     * @return string
     */
    public function createAction()
    {
        $name = $this->requestParams['name'] ?? '';
        $email = $this->requestParams['email'] ?? '';
        if($name && $email){
            $db = (new Db())->getConnect();
            $user = new People($db, [
                'name' => $name,
                'email' => $email
            ]);
            if($user = $user->saveNew()){
                return $this->response('Data saved.', 200);
            }
        }
        return $this->response("Saving error", 500);
    }

    /**
     * Метод PUT
     * Обновление отдельной записи (по ее id)
     * https://domain.by/people/1 + параметры запроса name, email
     * @return string
     */
    public function updateAction()
    {
        $parse_url = parse_url($this->requestUri[0]);
        $userId = $parse_url['path'] ?? null;

        $db = (new Db())->getConnect();

        if(!$userId || !People::getById($db, $userId)){
            return $this->response("User with id=$userId not found", 404);
        }

        $name = $this->requestParams['name'] ?? '';
        $email = $this->requestParams['email'] ?? '';

        if($name && $email){
            if($user = People::update($db, $userId, $name, $email)){
                return $this->response('Data updated.', 200);
            }
        }
        return $this->response("Update error", 400);
    }

    /**
     * Метод DELETE
     * Удаление отдельной записи (по ее id)
     * https://domain.by/people/1
     * @return string
     */
    public function deleteAction()
    {
        $parse_url = parse_url($this->requestUri[0]);
        $userId = $parse_url['path'] ?? null;

        $db = (new Db())->getConnect();

        if(!$userId || !People::getById($db, $userId)){
            return $this->response("User with id=$userId not found", 404);
        }
        if(People::deleteById($db, $userId)){
            return $this->response('Data deleted.', 200);
        }
        return $this->response("Delete error", 500);
    }

}

Методы связанные с базой данных и получением данных из нее просто для примера.

Практическая помощь

Практическая помощь по теме статьи

Если инструмент или статья подсветили проблему, можно перейти от диагностики к аккуратному внедрению решения.

Материал помог решить задачу?

Оценка помогает нам делать базу знаний точнее.

Редакция CMS.BY

Редакция CMS.BY

С нами Мир познавать проще и надёжнее

Обсудим по существу

Есть задача по сайту или Битрикс24?

От разработки нового проекта до исправления ошибок, обновления, интеграции или технической поддержки.

15 лет опыта Работа с существующим кодом Понятный план действий