Бортовой журнал Ктулху
Самым дорогим футболистом Океании стал Мгумга Мгумга. Цена за трансферт этого форварда сборной Фиджи составила зеркальце площадью 14 квадратных метров (А.Козак)

Создание 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/