Простой RESTful-сервис на нативном PHP. Создание RESTful API на PHP Создание api на php

Не так давно один из моих посетителей мне задал вопрос по e-mail : "". Я решил, что это будет весьма полезно другим пользователям, тем более, что на кажущуюся сложность процесса, всё очень и очень просто. Необходимо лишь обладать самыми элементарными знаниями PHP .

Если Вы вдруг не понимаете, о чём идёт речь, то прочитайте сначала статью: . Идём дальше. Давайте разберём, а для каких сайтов нужен вообще API :

Первое, что необходимо усвоить - это то, что API нужен далеко не каждому сайту (даже если он принадлежит одной из вышеуказанных групп).

Если же Вы считаете, что API на Вашем сайте необходим, то давайте разберём пример того, как он создаётся. Пусть у нас будет такая задача: есть ЭПС (как, например, WebMoney ). И мы хотим, чтобы пользователь мог из своего кода, пользуясь нашим API , узнать свой баланс на счёте.

Создадим файл (например, api.php ), который у нас будет принимать GET-запросы от пользователей на получение различной информации. Напишем в этом обработчике такой код:

if ($_GET["action"] == "getbalance") {
$balance;
//Узнаём из базы данных баланс аккаунта и записываем в переменную balance
echo $balance;
}
?>

Теперь разработчики API должны дать информацию пользователям о том, как надо отправлять запрос, чтобы пользователь мог узнать свой баланс на аккаунте:

Http://mysite.ru/api.php?action=getbalance&key=fa9sgwlgjs9gdsjlgjdsjglsdlgs

Этот запрос пользователи формируют в своих скриптах (например, через cURL ). Параметр key - это уникальный ключ каждого пользователя. И ответом этого запроса будет число, отвечающее за баланс пользователя. Аналогично создаются и все другие возможности API . Можно добавлять другие различные параметры: например, получить список операций пополнения счёта с одной даты по другую. Желательно, сами списки возвращать в формате JSON .

Разрабатывая проект, я столкнулся с необходимостью организации клиент-серверного взаимодействия приложений на платформах iOS и Android с моим сайтом на котором хранилась вся информация - собственно БД на mysql, картинки, файлы и другой контент.
Задачи которые нужно было решать - достаточно простые:
регистрация/авторизация пользователя;
отправка/получение неких данных (например список товаров).

И тут-то мне захотелось написать свой API для взаимодействия с серверной стороной - большей своей частью для практического интереса.

Входные данные

В своем распоряжении я имел:
Сервер - Apache, PHP 5.0, MySQL 5.0
Клиент - Android, iOS устройства, любой браузер

Я решил, что для запросов к серверу и ответов от него буду использовать JSON формат данных - за его простоту и нативную поддержку в PHP и Android. Здесь меня огорчила iOS - у нее нет нативной поддержки JSON (тут пришлось использовать стороннюю разработку).

Так же было принято решение, что запросы можно будет отсылать как через GET так и через POST запросы (здесь помог $_REQUEST в PHP). Такое решение позволило проводить тестирование API через GET запросы в любом доступном браузере.

Внешний вид запросов решено было сделать таким:
http://[адрес сервера]/[путь к папке api]/?[название_api].[название_метода]=

Путь к папке api - каталог на который нужно делать запросы, в корне которого лежит файл index.php - он и отвечает за вызов функций и обработку ошибок
Название api - для удобства я решил разделить API группы - пользователь, база данных, конент и тд. В таком случае каждый api получил свое название
Название метода - имя метода который нужно вызвать в указанном api
JSON - строковое представление JSON объекта для параметров метода

Скелет API

Скелет API на серверной стороне состоит из нескольких базовых классов:
index.php - индексный файл каталога в Apache на него приходятся все вызовы API, он осуществляет парсинг параметров и вызов API методов
MySQLiWorker - класс-одиночка для работы с базой MySQL через MySQLi
apiBaseCalss.php - родительский класс для всех API в системе - каждый API должен быть наследован от этого класса для корректной работы
apiEngine.php - основной класс системы - осуществляет разбор переданных параметров (после их предварительного парсинга в index.php) подключение нужного класса api (через require_once метод), вызов в нем нужного метода и возврат результата в JSON формате
apiConstants.php - класс с константами для api вызовов и передачи ошибок
apitest.php - тестовый api для тестирования новых методов перед их включением в продакшн версию

Весь механизм выглядит следующим образом:
Мы делаем запрос на сервер - к примеру www.example.com/api/?apitest.helloWorld= {}
На серверной стороне файл index.php - производит парсинг переданных параметров. Index.php берет всегда только первый элемент из списка переданных параметров $_REQUEST - это значит что конструкция вида www.example.com/api/?apitest.helloWorld= {}&apitest.helloWorld2 - произведет вызов только метода helloWorld в apitest. Вызова же метода helloWorld2 непроизойдет

Теперь подробней о каждом

Я попробовал достаточно документировать файлы, чтобы не занимать много место под текст. Однако в тех файлах где нет комментариев, я все ж приведу описание.

Index.php

Как уже говорил раньше это входной индексный файл для Apache а значит все вызовы вида www.example.com/api будет принимать он.

0){ require_once "apiEngine.php"; foreach ($_REQUEST as $apiFunctionName => $apiFunctionParams) { $APIEngine=new APIEngine($apiFunctionName,$apiFunctionParams); echo $APIEngine->callApiFunction(); break; } }else{ $jsonError->error="No function called"; echo json_encode($jsonError); } ?>

Первым делом устанавливаем тип контента - text/html (потом можно сменить в самих методах) и кодировку - UTF-8.
Дальше проверяем, что у нас что-то запрашивают. Если нет то выводим JSON c ошибкой.
Если есть параметры запроса, то подключаем файл движка API - apiEngine.php и создаем класс движка с переданными параметрами и делаем вызов api метода.
Выходим из цикла так как мы решили что будем обрабатывать только один вызов.

apiEngine.php

Вторым по важности является класс apiEngine - он представляет собой движок для вызова api и их методов.
apiFunctionParams = stripcslashes($apiFunctionParams); //Парсим на массив из двух элементов - название API, - название метода в API $this->apiFunctionName = explode("_", $apiFunctionName); } //Создаем JSON ответа function createDefaultJson() { $retObject = json_decode("{}"); $response = APIConstants::$RESPONSE; $retObject->$response = json_decode("{}"); return $retObject; } //Вызов функции по переданным параметрам в конструкторе function callApiFunction() { $resultFunctionCall = $this->createDefaultJson();//Создаем JSON ответа $apiName = strtolower($this->apiFunctionName);//название API проиводим к нижнему регистру if (file_exists($apiName . ".php")) { $apiClass = APIEngine::getApiEngineByName($apiName);//Получаем объект API $apiReflection = new ReflectionClass($apiName);//Через рефлексию получем информацию о классе объекта try { $functionName = $this->apiFunctionName;//Название метода для вызова $apiReflection->getMethod($functionName);//Провераем наличие метода $response = APIConstants::$RESPONSE; $jsonParams = json_decode($this->apiFunctionParams);//Декодируем параметры запроса в JSON объект if ($jsonParams) { if (isset($jsonParams->responseBinary)){//Для возможности возврата не JSON, а бинарных данных таких как zip, png и др. контетнта return $apiClass->$functionName($jsonParams);//Вызываем метод в API }else{ $resultFunctionCall->$response = $apiClass->$functionName($jsonParams);//Вызыаем метод в API который вернет JSON обект } } else { //Если ошибка декодирования JSON параметров запроса $resultFunctionCall->errno = APIConstants::$ERROR_ENGINE_PARAMS; $resultFunctionCall->error = "Error given params"; } } catch (Exception $ex) { //Непредвиденное исключение $resultFunctionCall->error = $ex->getMessage(); } } else { //Если запрашиваемый API не найден $resultFunctionCall->errno = APIConstants::$ERROR_ENGINE_PARAMS; $resultFunctionCall->error = "File not found"; $resultFunctionCall->REQUEST = $_REQUEST; } return json_encode($resultFunctionCall); } } ?>

apiConstants.php

Данный класс используется только для хранения констант.

MySQLiWorker.php

Класс-одиночка для работы с базой. В прочем это обычный одиночка - таких примеров в сети очень много.

dbName = $dbName; self::$instance->dbHost = $dbHost; self::$instance->dbUser = $dbUser; self::$instance->dbPassword = $dbPassword; self::$instance->openConnection(); } return self::$instance; } //Определяем типы параметров запроса к базе и возвращаем строку для привязки через ->bind function prepareParams($params) { $retSTMTString = ""; foreach ($params as $value) { if (is_int($value) || is_double($value)) { $retSTMTString.="d"; } if (is_string($value)) { $retSTMTString.="s"; } } return $retSTMTString; } //Соединяемся с базой public function openConnection() { if (is_null($this->connectLink)) { $this->connectLink = new mysqli($this->dbHost, $this->dbUser, $this->dbPassword, $this->dbName); $this->connectLink->query("SET NAMES utf8"); if (mysqli_connect_errno()) { printf("Подключение невозможно: %s\n", mysqli_connect_error()); $this->connectLink = null; } else { mysqli_report(MYSQLI_REPORT_ERROR); } } return $this->connectLink; } //Закрываем соединение с базой public function closeConnection() { if (!is_null($this->connectLink)) { $this->connectLink->close(); } } //Преобразуем ответ в ассоциативный массив public function stmt_bind_assoc(&$stmt, &$out) { $data = mysqli_stmt_result_metadata($stmt); $fields = array(); $out = array(); $fields = $stmt; $count = 1; $currentTable = ""; while ($field = mysqli_fetch_field($data)) { if (strlen($currentTable) == 0) { $currentTable = $field->table; } $fields[$count] = &$out[$field->name]; $count++; } call_user_func_array("mysqli_stmt_bind_result", $fields); } } ?>

apiBaseClass.php

Ну вот мы подошли к одному из самых важных классов системы - базовый класс для всех API в системе.

mySQLWorker = MySQLiWorker::getInstance($dbName,$dbHost,$dbUser,$dbPassword); } } function __destruct() { if (isset($this->mySQLWorker)){ //Если было установленно соединение с базой, $this->mySQLWorker->closeConnection(); //то закрываем его когда наш класс больше не нужен } } //Создаем дефолтный JSON для ответов function createDefaultJson() { $retObject = json_decode("{}"); return $retObject; } //Заполняем JSON объект по ответу из MySQLiWorker function fillJSON(&$jsonObject, &$stmt, &$mySQLWorker) { $row = array(); $mySQLWorker->stmt_bind_assoc($stmt, $row); while ($stmt->fetch()) { foreach ($row as $key => $value) { $key = strtolower($key); $jsonObject->$key = $value; } break; } return $jsonObject; } } ?>

Как видно данный класс содержит в себе несколько «утилитных» методов, таких как:
конструктор в котором осуществляется соединение с базой, если текущее API собирается работать с базой;
деструктор - следит за освобождением ресурсов - разрыв установленного соединения с базой
createDefaultJson - создает дефолтный JSON для ответа метода
fillJSON - если подразумевается что запрос вернет только одну запись, то данный метод заполнит JSON для ответа данными из первой строки ответа от БД

Создадим свой API

Вот собственно и весь костяк этого API. Теперь рассмотрим как же это все использовать на примере создания первого API под названием apitest. И напишем в нем пару простых функций:
одну без параметров
одну с параметрами и их же она нам и вернет, чтобы было видно, что она их прочитала
одну которая вернет нам бинарные данные

И так создаем класс apitest.php следующего содержания

createDefaultJson(); $retJSON->withoutParams = "It\"s method called without parameters"; return $retJSON; } //http://www.example.com/api/?apitest.helloAPIWithParams={"TestParamOne":"Text of first parameter"} function helloAPIWithParams($apiMethodParams) { $retJSON = $this->createDefaultJson(); if (isset($apiMethodParams->TestParamOne)){ //Все ок параметры верные, их и вернем $retJSON->retParameter=$apiMethodParams->TestParamOne; }else{ $retJSON->errorno= APIConstants::$ERROR_PARAMS; } return $retJSON; } //http://www.example.com/api/?apitest..jpg"); } } ?>

Для удобства тестирования методов, я дописываю к ним адрес по которому я могу сделать быстрый запрос для тестирования.

И так у нас три метода

Function helloAPI() { $retJSON = $this->createDefaultJson(); $retJSON->withoutParams = "It\"s method called without parameters"; return $retJSON; }

Это простой метод без параметров. Его адрес для GET вызова www.example.com/api/?apitest.helloAPI= {}

Результатом выполнения будет вот такая страница (в браузере)

Этот метод принимает в параметры. Обязательным является TestParamOne, для него и сделаем проверку. Его его не передать, то будет выдан JSON с ошибкой

Function helloAPIWithParams($apiMethodParams) { $retJSON = $this->createDefaultJson(); if (isset($apiMethodParams->TestParamOne)){ //Все ок параметры верные, их и вернем $retJSON->retParameter=$apiMethodParams->TestParamOne; }else{ $retJSON->errorno= APIConstants::$ERROR_PARAMS; } return $retJSON; }
Результат выполнения

helloAPIResponseBinary

И последний метод helloAPIResponseBinary - вернет бинарные данные - картинку хабра о несуществующей странице (в качестве примера)
function helloAPIResponseBinary($apiMethodParams){ header("Content-type: image/jpeg"); echo file_get_contents("http://сайт/i/error-404-monster.jpg"); }
Как видно - здесь есть подмена заголовка для вывода графического контента.
Результат будет такой

Разрабатывая проект, я столкнулся с необходимостью организации клиент-серверного взаимодействия приложений на платформах iOS и Android с моим сайтом на котором хранилась вся информация - собственно БД на mysql, картинки, файлы и другой контент.
Задачи которые нужно было решать - достаточно простые:
регистрация/авторизация пользователя;
отправка/получение неких данных (например список товаров).

И тут-то мне захотелось написать свой API для взаимодействия с серверной стороной - большей своей частью для практического интереса.

Входные данные

В своем распоряжении я имел:
Сервер - Apache, PHP 5.0, MySQL 5.0
Клиент - Android, iOS устройства, любой браузер

Я решил, что для запросов к серверу и ответов от него буду использовать JSON формат данных - за его простоту и нативную поддержку в PHP и Android. Здесь меня огорчила iOS - у нее нет нативной поддержки JSON (тут пришлось использовать стороннюю разработку).

Так же было принято решение, что запросы можно будет отсылать как через GET так и через POST запросы (здесь помог $_REQUEST в PHP). Такое решение позволило проводить тестирование API через GET запросы в любом доступном браузере.

Внешний вид запросов решено было сделать таким:
http://[адрес сервера]/[путь к папке api]/?[название_api].[название_метода]=

Путь к папке api - каталог на который нужно делать запросы, в корне которого лежит файл index.php - он и отвечает за вызов функций и обработку ошибок
Название api - для удобства я решил разделить API группы - пользователь, база данных, конент и тд. В таком случае каждый api получил свое название
Название метода - имя метода который нужно вызвать в указанном api
JSON - строковое представление JSON объекта для параметров метода

Скелет API

Скелет API на серверной стороне состоит из нескольких базовых классов:
index.php - индексный файл каталога в Apache на него приходятся все вызовы API, он осуществляет парсинг параметров и вызов API методов
MySQLiWorker - класс-одиночка для работы с базой MySQL через MySQLi
apiBaseCalss.php - родительский класс для всех API в системе - каждый API должен быть наследован от этого класса для корректной работы
apiEngine.php - основной класс системы - осуществляет разбор переданных параметров (после их предварительного парсинга в index.php) подключение нужного класса api (через require_once метод), вызов в нем нужного метода и возврат результата в JSON формате
apiConstants.php - класс с константами для api вызовов и передачи ошибок
apitest.php - тестовый api для тестирования новых методов перед их включением в продакшн версию

Весь механизм выглядит следующим образом:
Мы делаем запрос на сервер - к примеру www.example.com/api/?apitest.helloWorld= {}
На серверной стороне файл index.php - производит парсинг переданных параметров. Index.php берет всегда только первый элемент из списка переданных параметров $_REQUEST - это значит что конструкция вида www.example.com/api/?apitest.helloWorld= {}&apitest.helloWorld2 - произведет вызов только метода helloWorld в apitest. Вызова же метода helloWorld2 непроизойдет

Теперь подробней о каждом

Я попробовал достаточно документировать файлы, чтобы не занимать много место под текст. Однако в тех файлах где нет комментариев, я все ж приведу описание.

Index.php

Как уже говорил раньше это входной индексный файл для Apache а значит все вызовы вида www.example.com/api будет принимать он.

0){ require_once "apiEngine.php"; foreach ($_REQUEST as $apiFunctionName => $apiFunctionParams) { $APIEngine=new APIEngine($apiFunctionName,$apiFunctionParams); echo $APIEngine->callApiFunction(); break; } }else{ $jsonError->error="No function called"; echo json_encode($jsonError); } ?>

Первым делом устанавливаем тип контента - text/html (потом можно сменить в самих методах) и кодировку - UTF-8.
Дальше проверяем, что у нас что-то запрашивают. Если нет то выводим JSON c ошибкой.
Если есть параметры запроса, то подключаем файл движка API - apiEngine.php и создаем класс движка с переданными параметрами и делаем вызов api метода.
Выходим из цикла так как мы решили что будем обрабатывать только один вызов.

apiEngine.php

Вторым по важности является класс apiEngine - он представляет собой движок для вызова api и их методов.
apiFunctionParams = stripcslashes($apiFunctionParams); //Парсим на массив из двух элементов - название API, - название метода в API $this->apiFunctionName = explode("_", $apiFunctionName); } //Создаем JSON ответа function createDefaultJson() { $retObject = json_decode("{}"); $response = APIConstants::$RESPONSE; $retObject->$response = json_decode("{}"); return $retObject; } //Вызов функции по переданным параметрам в конструкторе function callApiFunction() { $resultFunctionCall = $this->createDefaultJson();//Создаем JSON ответа $apiName = strtolower($this->apiFunctionName);//название API проиводим к нижнему регистру if (file_exists($apiName . ".php")) { $apiClass = APIEngine::getApiEngineByName($apiName);//Получаем объект API $apiReflection = new ReflectionClass($apiName);//Через рефлексию получем информацию о классе объекта try { $functionName = $this->apiFunctionName;//Название метода для вызова $apiReflection->getMethod($functionName);//Провераем наличие метода $response = APIConstants::$RESPONSE; $jsonParams = json_decode($this->apiFunctionParams);//Декодируем параметры запроса в JSON объект if ($jsonParams) { if (isset($jsonParams->responseBinary)){//Для возможности возврата не JSON, а бинарных данных таких как zip, png и др. контетнта return $apiClass->$functionName($jsonParams);//Вызываем метод в API }else{ $resultFunctionCall->$response = $apiClass->$functionName($jsonParams);//Вызыаем метод в API который вернет JSON обект } } else { //Если ошибка декодирования JSON параметров запроса $resultFunctionCall->errno = APIConstants::$ERROR_ENGINE_PARAMS; $resultFunctionCall->error = "Error given params"; } } catch (Exception $ex) { //Непредвиденное исключение $resultFunctionCall->error = $ex->getMessage(); } } else { //Если запрашиваемый API не найден $resultFunctionCall->errno = APIConstants::$ERROR_ENGINE_PARAMS; $resultFunctionCall->error = "File not found"; $resultFunctionCall->REQUEST = $_REQUEST; } return json_encode($resultFunctionCall); } } ?>

apiConstants.php

Данный класс используется только для хранения констант.

MySQLiWorker.php

Класс-одиночка для работы с базой. В прочем это обычный одиночка - таких примеров в сети очень много.

dbName = $dbName; self::$instance->dbHost = $dbHost; self::$instance->dbUser = $dbUser; self::$instance->dbPassword = $dbPassword; self::$instance->openConnection(); } return self::$instance; } //Определяем типы параметров запроса к базе и возвращаем строку для привязки через ->bind function prepareParams($params) { $retSTMTString = ""; foreach ($params as $value) { if (is_int($value) || is_double($value)) { $retSTMTString.="d"; } if (is_string($value)) { $retSTMTString.="s"; } } return $retSTMTString; } //Соединяемся с базой public function openConnection() { if (is_null($this->connectLink)) { $this->connectLink = new mysqli($this->dbHost, $this->dbUser, $this->dbPassword, $this->dbName); $this->connectLink->query("SET NAMES utf8"); if (mysqli_connect_errno()) { printf("Подключение невозможно: %s\n", mysqli_connect_error()); $this->connectLink = null; } else { mysqli_report(MYSQLI_REPORT_ERROR); } } return $this->connectLink; } //Закрываем соединение с базой public function closeConnection() { if (!is_null($this->connectLink)) { $this->connectLink->close(); } } //Преобразуем ответ в ассоциативный массив public function stmt_bind_assoc(&$stmt, &$out) { $data = mysqli_stmt_result_metadata($stmt); $fields = array(); $out = array(); $fields = $stmt; $count = 1; $currentTable = ""; while ($field = mysqli_fetch_field($data)) { if (strlen($currentTable) == 0) { $currentTable = $field->table; } $fields[$count] = &$out[$field->name]; $count++; } call_user_func_array("mysqli_stmt_bind_result", $fields); } } ?>

apiBaseClass.php

Ну вот мы подошли к одному из самых важных классов системы - базовый класс для всех API в системе.

mySQLWorker = MySQLiWorker::getInstance($dbName,$dbHost,$dbUser,$dbPassword); } } function __destruct() { if (isset($this->mySQLWorker)){ //Если было установленно соединение с базой, $this->mySQLWorker->closeConnection(); //то закрываем его когда наш класс больше не нужен } } //Создаем дефолтный JSON для ответов function createDefaultJson() { $retObject = json_decode("{}"); return $retObject; } //Заполняем JSON объект по ответу из MySQLiWorker function fillJSON(&$jsonObject, &$stmt, &$mySQLWorker) { $row = array(); $mySQLWorker->stmt_bind_assoc($stmt, $row); while ($stmt->fetch()) { foreach ($row as $key => $value) { $key = strtolower($key); $jsonObject->$key = $value; } break; } return $jsonObject; } } ?>

Как видно данный класс содержит в себе несколько «утилитных» методов, таких как:
конструктор в котором осуществляется соединение с базой, если текущее API собирается работать с базой;
деструктор - следит за освобождением ресурсов - разрыв установленного соединения с базой
createDefaultJson - создает дефолтный JSON для ответа метода
fillJSON - если подразумевается что запрос вернет только одну запись, то данный метод заполнит JSON для ответа данными из первой строки ответа от БД

Создадим свой API

Вот собственно и весь костяк этого API. Теперь рассмотрим как же это все использовать на примере создания первого API под названием apitest. И напишем в нем пару простых функций:
одну без параметров
одну с параметрами и их же она нам и вернет, чтобы было видно, что она их прочитала
одну которая вернет нам бинарные данные

И так создаем класс apitest.php следующего содержания

createDefaultJson(); $retJSON->withoutParams = "It\"s method called without parameters"; return $retJSON; } //http://www.example.com/api/?apitest.helloAPIWithParams={"TestParamOne":"Text of first parameter"} function helloAPIWithParams($apiMethodParams) { $retJSON = $this->createDefaultJson(); if (isset($apiMethodParams->TestParamOne)){ //Все ок параметры верные, их и вернем $retJSON->retParameter=$apiMethodParams->TestParamOne; }else{ $retJSON->errorno= APIConstants::$ERROR_PARAMS; } return $retJSON; } //http://www.example.com/api/?apitest.helloAPIResponseBinary={"responseBinary":1} function helloAPIResponseBinary($apiMethodParams){ header("Content-type: image/png"); echo file_get_contents("http://habrahabr.ru/i/error-404-monster.jpg"); } } ?>

Для удобства тестирования методов, я дописываю к ним адрес по которому я могу сделать быстрый запрос для тестирования.

И так у нас три метода

Function helloAPI() { $retJSON = $this->createDefaultJson(); $retJSON->withoutParams = "It\"s method called without parameters"; return $retJSON; }

Это простой метод без параметров. Его адрес для GET вызова www.example.com/api/?apitest.helloAPI= {}

Результатом выполнения будет вот такая страница (в браузере)

Этот метод принимает в параметры. Обязательным является TestParamOne, для него и сделаем проверку. Его его не передать, то будет выдан JSON с ошибкой

Function helloAPIWithParams($apiMethodParams) { $retJSON = $this->createDefaultJson(); if (isset($apiMethodParams->TestParamOne)){ //Все ок параметры верные, их и вернем $retJSON->retParameter=$apiMethodParams->TestParamOne; }else{ $retJSON->errorno= APIConstants::$ERROR_PARAMS; } return $retJSON; }
Результат выполнения

helloAPIResponseBinary

И последний метод helloAPIResponseBinary - вернет бинарные данные - картинку хабра о несуществующей странице (в качестве примера)
function helloAPIResponseBinary($apiMethodParams){ header("Content-type: image/jpeg"); echo file_get_contents("http://habrahabr.ru/i/error-404-monster.jpg"); }
Как видно - здесь есть подмена заголовка для вывода графического контента.
Результат будет такой

У семерых козлов и волк без глазу

Создание 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 запросы преобразовывать в понятные контроллеру параметры.

RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule api/v1/(.*)$ api/v1/api.php?request=$1

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

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

Далее объявляется возможность переопределять URL. Это значит, что путь api/v1/ будет перенаправлен на api/v1/index.php. Символы (.*) обозначают переменные, которые необходимо обработать а $ - это ограничитель, после которого начинается следующая логика. Также, - означает нечувствительность к регистру, - что переменные будут присоединены к новому 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: //// * or // */ 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)) { $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; } }

Объявив этот абстрактный класс, мы запретили 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())); }

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

У меня часто спрашивают, как работать с API . Наиболее, популярной задачей, связанной с API , является - работа с API ВКонтакте . В данной статье я покажу, как работать с API ВКонтакте , а, главное, покажу Вам, что все API работают по единому принципу .

Вот порядок действий , который Вам необходимо сделать, чтобы начать работать с любым API :

  1. Найти документацию по нужному API .
  2. Посмотреть примеры подключения к API . Тут могут быть разные варианты. Например, на одном сервисе потребуют секретный ключ, выдаваемый после регистрации. На втором сервисе, все запросы не требуют секретных ключей. На третьем сервисе, есть и с секретным ключом, и без варианты работы. Иногда, секретный ключ называют токеном .
  3. Скопировать простой готовый код с сайта документации и проверить его работу. Если не работает, то ищите ошибку у себя, так как едва ли она будет у сервиса.
  4. Найти в документации метод , который решает Вашу задачу.
  5. Используя примеры и описание метода, отправить правильный запрос к API .
  6. В зависимости от формата ответа, разобрать его на "составные части ", а дальше уже делать то, что требуется - вывести пользователю, сохранить в файл, отправить в базу данных и тому подобное.

А в качестве примера следования данной инструкции мы разберём API ВКонтакте . Итак:

  1. Ссылка на документацию .
  2. В данном случае есть методы, которые общедоступны, а потому не требуют токена, а есть методы, требующие его.
  3. В данном случае, примеров на конкретном языке я не нашёл. Возможно, они есть, но явно не на виду. Обычно же, у API для каждого метода есть примеры на разных языках.
  4. Мы хотим вывести 5 последних записей со стены конкретного пользователя, а затем вывести их у себя на сайте . Нужный нам метод .

Теперь мы должны отправить запрос к API , используя описание метода. Делать мы это будем через PHP :

$wall = file_get_contents("https://api.vk.com/method/wall.get?v=5.3&filter=others&domain=myrusakov&count=5");
print_r($wall);
?>

В данном случае, я использую API 5.3 (v=5.3 ), вывожу все записи независимо от автора (filter=others ) со своей страницы (domain=myrusakov ) в количестве 5 штук (count=5 ). Думаю, что здесь всё очень прозрачно.

Мы получили ответ в формате JSON , и теперь нам нужно перейти к последнему пункту - разобрать ответ на "составные части ". Дальше мы с Вами выведем записи со стены в более-менее читабельном виде на странице нашего сайта. Результирующий PHP-код :

$wall = file_get_contents("http://api.vk.com/method/wall.get?v=5.3&filter=others&domain=myrusakov&count=5"); // Отправляем запрос
$wall = json_decode($wall); // Преобразуем JSON-строку в массив
$wall = $wall->response->items; // Получаем массив комментариев
for ($i = 0; $i < count($wall); $i++) {
echo "

".($i + 1).". ".$wall[$i]->text."
".date("Y-m-d H:i:s", $wall[$i]->date)."

"; // Выводим записи
}
?>

Как видите, всё очень просто. Самая сложная часть - разобрать на составные элементы. Я предпочитаю выводить результат от API через print_r , а уже затем писать обработку этого. Хотя можно посмотреть пример ответа в описании метода, а дальше уже написать парсер.

2024 samgupsnn.ru. Samgupsnn - Немного о компьютере и современных гаджетах.