В данной статье рассказывается пример простого 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);
}
}
Методы связанные с базой данных и получением данных из нее просто для примера.