Пример простого REST API на PHP.
В данной заметке пример самого простого REST API на PHP без использования какого-либо фреймворка и других средств. Целью есть предоставить общую картину — как это все работает.
Недавно я уже опубликовал статью, в которой описан процесс создания REST API для проекта на Yii2.
Т.к. никакие фреймворки с маршрутизаторами в примере использоваться не будут, нужно начать с перенаправления всех запросов на «точку входа» — файл index.php. Для сервера на Apache это можно сделать в файле .htaccess который должен располагаться в корне проекта:
Options +FollowSymLinks IndexIgnore */* RewriteEngine on # Перенаправление с ДОМЕН на ДОМЕН/api RewriteCond % ^/$ RewriteRule ^(.*)$ /api/$1 [R=301] #Если URI начинается с api/ то перенаправлять все запросы на index.php RewriteEngine On RewriteCond % !-f RewriteCond % !-d RewriteRule ^api/(.*)$ /index.php
Согласно правил, ссылка должна начинаться на /api и ,например, для API работающего с таблицей users должна иметь такой вид:
ДОМЕН/api/users
Пример файла index.php
run(); > catch (Exception $e) < echo json_encode(Array('error' =>$e->getMessage())); >
Как видно из кода — будем работать с объектом usersApi, т.е. с пользователями (таблица users). Т.к. для простоты примера я не использую тут Composer или другой механизм для автозагрузки классов, просто подключим файл класса с помощью
Кроме пользователей, может потребоваться сделать api и для других сущностей, поэтому все классы различных API должны иметь один общий костяк, который будет определять метод запроса, действие для выполнения и тд. Создаем файл Api.php c абстрактным классом Api:
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->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. Для этого создаем файл UsersApi.php:
getConnect(); $users = Users::getAll($db); if($users)< return $this->response($users, 200); > return $this->response('Data not found', 404); > /** * Метод GET * Просмотр отдельной записи (по id) * http://ДОМЕН/users/1 * @return string */ public function viewAction() < //id должен быть первым параметром после /users/x $id = array_shift($this->requestUri); if($id)< $db = (new Db())->getConnect(); $user = Users::getById($db, $id); if($user)< return $this->response($user, 200); > > return $this->response('Data not found', 404); > /** * Метод POST * Создание новой записи * http://ДОМЕН/users + параметры запроса name, email * @return string */ public function createAction() < $name = $this->requestParams['name'] ?? ''; $email = $this->requestParams['email'] ?? ''; if($name && $email)< $db = (new Db())->getConnect(); $user = new Users($db, [ 'name' => $name, 'email' => $email ]); if($user = $user->saveNew())< return $this->response('Data saved.', 200); > > return $this->response("Saving error", 500); > /** * Метод PUT * Обновление отдельной записи (по ее id) * http://ДОМЕН/users/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 || !Users::getById($db, $userId))< return $this->response("User with not found", 404); > $name = $this->requestParams['name'] ?? ''; $email = $this->requestParams['email'] ?? ''; if($name && $email)< if($user = Users::update($db, $userId, $name, $email))< return $this->response('Data updated.', 200); > > return $this->response("Update error", 400); > /** * Метод DELETE * Удаление отдельной записи (по ее id) * http://ДОМЕН/users/1 * @return string */ public function deleteAction() < $parse_url = parse_url($this->requestUri[0]); $userId = $parse_url['path'] ?? null; $db = (new Db())->getConnect(); if(!$userId || !Users::getById($db, $userId))< return $this->response("User with not found", 404); > if(Users::deleteById($db, $userId))< return $this->response('Data deleted.', 200); > return $this->response("Delete error", 500); > >
Методы связанные с базой данных и получением данных из нее просто для примера.
Пример простого создания REST API на PHP
В данной статье рассказывается пример простого REST API реализация которого на PHP без использования каких-либо фреймворков. Из-за того, что никак фреймворки применяться не будут, необходимо начать с того, что перенаправления всех запросов на точку входа, это index.php . Сервер где используется Apache , это можно реализовать в файле htaccess который должен находится в корне проекта:
Options +FollowSymLinks IndexIgnore */* RewriteEngine on # Перенаправление с domain.by на domain.by/api RewriteCond % ^/$ RewriteRule ^(.*)$ /api/$1 [R=301] #Если адрес начинается с api/ , то перенаправлять все запросы на index.php RewriteEngine On RewriteCond % !-f RewriteCond % !-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->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 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 not found", 404); > if(People::deleteById($db, $userId))< return $this->response('Data deleted.', 200); > return $this->response("Delete error", 500); > >