Бортовой журнал Ктулху

Создание RESTful API на PHP

REST или в полной форме, Representational State Transfer стало стандартной архитектурой проектирования для разработки веб-API. 

REST является очень простым интерфейсом управления информацией без использования каких-то дополнительных внутренних прослоек. Каждая единица информации однозначно определяется глобальным идентификатором, таким как URL. Каждая URL в свою очередь имеет строго заданный формат.

REST использует методы HTTP-запроса, чтобы скомпоновать себя в существующую архитектуру HTTP. Эти операции состоят в следующем:

GET - используется для базовых запросов на чтение на сервер

PUT- Используется для изменения существующего объекта на сервере

POST- Используется для создания нового объекта на сервере

DELETE - используется для удаления объекта на сервере

А теперь к сути, т.е. написанию простого API, который вы можете использовать в своих проектах.

Что такое API

В широком смысле API - это интерфейс веб-приложения, позволяет публично использовать методы для доступа и управления извне. Обычное использование API - это когда необходимо получать данные из приложения (например, статья или какие-то другие данные) без фактического посещения ресурса (например сайта). Чтобы сделать это возможным, для приложения реализуется  API, который позволяет сторонним приложениям совершать запросы и возвращать указанные данные пользователю извне. В Интернете это часто делается с использованием RESTful.

В примере запроса статьи  API может содержать URI:

example.com/api/v1/recipe/article

Если отправить запрос GET в этот URL, ответ может быть списком самых последних новостей, запрос PUT может добавить новость в БД.

Если запросить /article /141, то это будет определенная новость. Это примеры показывают способ взаимодействия с приложением.

Создание своего API

Создадим файл .htaccess для того чтобы GET запросы преобразовывать в понятные контроллеру параметры.

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule api/v1/(.*)$ api/v1/api.php?request=$1 [QSA,NC,L]
</IfModule><IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule api/v1/(.*)$ api/v1/api.php?request=$1 [QSA,NC,L]
</IfModule>

Что же означают строки этого файла?

В первой строке проверяется существование модуля rewrite. Если он запущен, выполняются следующие строки.

Далее объявляется возможность переопределять URL. Это значит, что путь api/v1/ будет перенаправлен на api/v1/index.php. Символы (.*) обозначают переменные, которые необходимо обработать а $ - это ограничитель, после которого начинается следующая логика. Также, [NC] - означает нечувствительность к регистру, [QSA] - что переменные будут присоединены к новому URL, L - mod_rewrite больше не обрабатывает ничего кроме указанного.

 

Объявляем класс, свойства и конструктор.

abstract class API
{
 /**
 * Property: method
 * GET, POST, PUT, DELETE
 */
 protected $method = '';
 /**
 * Property: endpoint
 * The Model requested in the URI. eg: /files
 */
 protected $endpoint = '';
 /**
 * Property: verb
 * An optional additional descriptor about the endpoint, used for things that can
 * not be handled by the basic methods. eg: /files/process
 */
 protected $verb = '';
 /**
 * Property: args
 * Any additional URI components after the endpoint and verb have been removed, in our
 * case, an integer ID for the resource. eg: /<endpoint>/<verb>/<arg0>/<arg1>
 * or /<endpoint>/<arg0>
 */
 protected $args = Array();
 /**
 * Property: file
 * Stores the input of the PUT request
 */
 protected $file = Null;
/**
 * Constructor: __construct
 * Allow for CORS, assemble and pre-process the data
 */
 public function __construct($request) {
 header("Access-Control-Allow-Orgin: *");
 header("Access-Control-Allow-Methods: *");
 header("Content-Type: application/json");
$this->args = explode('/', rtrim($request, '/'));
 $this->endpoint = array_shift($this->args);
 if (array_key_exists(0, $this->args) && !is_numeric($this->args[0])) {
 $this->verb = array_shift($this->args);
 }
$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");
 }
 }
switch($this->method) {
 case 'DELETE':
 case 'POST':
 $this->request = $this->_cleanInputs($_POST);
 break;
 case 'GET':
 $this->request = $this->_cleanInputs($_GET);
 break;
 case 'PUT':
 $this->request = $this->_cleanInputs($_GET);
 $this->file = file_get_contents("php://input");
 break;
 default:
 $this->_response('Invalid Method', 405);
 break;
 }
 }
public function processAPI() {
 if (method_exists($this, $this->endpoint)) {
 return $this->_response($this->{$this->endpoint}($this->args));
 }
 return $this->_response("No Endpoint: $this->endpoint", 404);
 }
private function _response($data, $status = 200) {
 header("HTTP/1.1 " . $status . " " . $this->_requestStatus($status));
 return json_encode($data);
 }
private function _cleanInputs($data) {
 $clean_input = Array();
 if (is_array($data)) {
 foreach ($data as $k => $v) {
 $clean_input[$k] = $this->_cleanInputs($v);
 }
 } else {
 $clean_input = trim(strip_tags($data));
 }
 return $clean_input;
 }
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]; 
 }

}
 


Объявив этот абстрактный класс, мы запретили PHP создавать конкретный экземпляр этого класса. Оттуда мы можем только использовать методы, унаследовав в другом классе. Защищенный метод может быть доступен только в самом классе и его потомках.

Создание класса API

Создаем классMyAPI который наследует абстрактный класс API.

require_once 'API.class.php';
class MyAPI extends API
{
 protected $User;
public function __construct($request, $origin) {
 parent::__construct($request);
// Abstracted out for example
 $APIKey = new Models\APIKey();
 $User = new Models\User();
if (!array_key_exists('apiKey', $this->request)) {
 throw new Exception('No API Key provided');
 } else if (!$APIKey->verifyKey($this->request['apiKey'], $origin)) {
 throw new Exception('Invalid API Key');
 } else if (array_key_exists('token', $this->request) &&
 !$User->get('token', $this->request['token'])) {
throw new Exception('Invalid User Token');
 }
$this->User = $User;
 }
/**
 * Example of an Endpoint
 */
 protected function example() {
 if ($this->method == 'GET') {
 return "Your name is " . $this->User->name;
 } else {
 return "Only accepts GET requests";
 }
 }
 }

Использование API

// Requests from the same server don't have a HTTP_ORIGIN header
if (!array_key_exists('HTTP_ORIGIN', $_SERVER)) {
 $_SERVER['HTTP_ORIGIN'] = $_SERVER['SERVER_NAME'];
}
try {
 $API = new MyAPI($_REQUEST['request'], $_SERVER['HTTP_ORIGIN']);
 echo $API->processAPI();
} catch (Exception $e) {
 echo json_encode(Array('error' => $e->getMessage()));
}

Теперь, если перейти по ссылке /api/v1/example то можно увидеть ответ API.

Корявый перевод этого: http://coreymaynard.com/blog/creating-a-restful-api-with-php/