Как сделать rest api на php
Перейти к содержимому

Как сделать rest api на php

  • автор:

Пример простого создания REST API на PHP v.2

Пример простого создания REST API на PHP v.2

Restful API является одним из наиболее популярных способов взаимодействия между клиентами и серверами. Он позволяет взаимодействовать с сервером и передавать данные в унифицированном формате, который поддерживается большинством платформ.

Давайте разберемся, как создать Restful API на PHP.

Шаг 1: Настройка окружения

Перед тем, как начать использовать Restful API, нам нужно установить все необходимые инструменты и настроить окружение. Для начала установите PHP, Apache и MySQL. Нам также понадобится Composer, чтобы управлять зависимостями. Вы можете скачать его с официального сайта: https://getcomposer.org/. Чтобы установить необходимые библиотеки, создайте файл composer.json в корневой директории проекта и добавьте следующий код:

Затем запустите команду: composer install Он установит Slim Framework, который мы будем использовать для создания нашего Restful API.

Шаг 2: Создание маршрутов

Теперь мы можем начать создавать маршруты для нашего API. Для этого создайте файл index.php и добавьте следующий код:

require 'vendor/autoload.php'; $app = new \Slim\App; $app->get('/api/', function ($request, $response, $args) < $name = $args['name']; return $response->write("Hello, $name"); >); $app->run(); 

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

Шаг 3: Создание контроллеров

Теперь, когда мы создали наш маршрут, давайте добавим контроллер, который выполняет определенные действия в зависимости от запроса. Создайте новый файл в папке controllers и назовите его UsersController.php. В этом файле мы будем определять наши методы контроллера. Добавьте следующий код:

namespace App\Controllers; use Psr\Http\Message\RequestInterface as Request; use Psr\Http\Message\ResponseInterface as Response; class UsersController < public function index(Request $request, Response $response) < $response->getBody()->write("Index page"); return $response; > public function show(Request $request, Response $response, $args) < $id = $args['id']; $response->getBody()->write("Show page: $id"); return $response; > public function store(Request $request, Response $response) < $data = $request->getParsedBody(); $response->getBody()->write(json_encode($data)); return $response; > public function update(Request $request, Response $response, $args) < $id = $args['id']; $data = $request->getParsedBody(); $response->getBody()->write("Update page: $id"); return $response; > public function delete(Request $request, Response $response, $args) < $id = $args['id']; $response->getBody()->write("Delete page: $id"); return $response; > > 

В этом коде мы определяем методы контроллера для каждого из действий: index, show, store, update, delete.

Шаг 4: Использование контроллеров в маршрутах

Теперь мы можем использовать контроллеры в маршрутах, чтобы обрабатывать запросы к нашему API. Для этого откройте файл index.php и замените имеющийся маршрут на следующий:

$app->get('/api/user', 'App\Controllers\UsersController:index'); $app->get('/api/user/', 'App\Controllers\UsersController:show'); $app->post('/api/user', 'App\Controllers\UsersController:store'); $app->put('/api/user/', 'App\Controllers\UsersController:update'); $app->delete('/api/user/', 'App\Controllers\UsersController:delete'); 

Теперь мы используем методы контроллера, чтобы обрабатывать каждый из запросов.

Шаг 5: Использование базы данных

Чаще всего при создании RESTful API используется база данных для сохранения и доступа к информации. Давайте добавим базу данных MySQL в наш проект. Создайте новую базу данных MySQL и добавьте таблицу пользователей.

CREATE TABLE `users` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL, `email` varchar(255) NOT NULL, `password` varchar(255) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 

Теперь добавим класс DB в наш проект, который будет использоваться для установки соединения с базой данных и выполнения запросов. Создайте новый файл в папке lib и назовите его DB.php. Добавьте следующий код:

namespace App\Lib; use PDO; class DB < private static $instance; private static $db; private function __construct() < $host = "localhost"; $dbname = "restful_api"; $username = "root"; $password = ""; self::$db = new PDO("mysql:host=$host;dbname=$dbname;charset=utf8mb4", $username, $password); >public static function getInstance() < if (!self::$instance) < self::$instance = new DB(); >return self::$instance; > public static function query($query) < return self::$db->query($query); > > 

Теперь мы можем использовать этот класс для выполнения запросов к базе данных. В UsersControllers.php замените index метод на следующий:

public function index(Request $request, Response $response) < $query = "SELECT * FROM users"; $result = \App\Lib\DB::query($query)->fetchAll(PDO::FETCH_ASSOC); $response->getBody()->write(json_encode($result)); return $response; > 

Здесь мы выполняем запрос SELECT на таблицу пользователей и возвращаем результат в качестве JSON. Аналогично обновите остальные методы контроллера, используя соответствующие запросы для CRUD операций.

Шаг 6: Защита API ключом

Последний шаг — добавление аутентификации API. Есть много способов, чтобы обеспечить безопасность вашего Restful API, но простейшим способом является добавление API ключа. Возможность использования ключа API означает, что только авторизованные клиенты могут получить доступ к вашему API. Самый простой способ сделать это — добавить ключ API в запрос. Создайте новый файл в папке lib и назовите его Auth.php. Добавьте следующий код:

namespace App\Lib; class Auth < public static function isValid($apiKey) < $validKeys = ["abc123", "123abc", "qwerty"]; return in_array($apiKey, $validKeys); >> 

Здесь мы предполагаем, что у нас есть несколько действительных ключей API. Поэтому мы проверяем, есть ли ключ api в массиве действительных ключей. Откройте файл index.php и добавьте следующий код непосредственно перед $app->run() :

$app->add(function ($request, $response, $next) < $apiKey = $request->getQueryParam('apiKey'); if (!\App\Lib\Auth::isValid($apiKey)) < $response->getBody()->write("Unauthorized"); return $response->withStatus(401); > $response = $next($request, $response); return $response; >); 

Здесь мы добавляем middleware, который проверяет, является ли переданный ключ API действительным. Если ключ не является действительным, мы возвращаем статус 401 «Unauthorized». Теперь мы можем протестировать наш Restful API с помощью Postman или другого инструмента для тестирования API. Вот и все! Теперь вы знаете, как создать Restful API на PHP.

Простое REST api для сайта на php хостинге

Иногда бывает необходимо развернуть не большое рест апи для своего сайта, сделанного по технологии СПА (Vue, React или др.) без использования каких-либо фреймворков, CMS или чего-то подобного, и при этом хочется воспользоваться обычным php хостингом с минимальными усилиями на внедрение и разработку. При этом там же желательно разместить и сам сайт СПА (в нашем случае на vue).

Использование php позволяет для построения ендпоинтов апи использовать даже статические php файлы, размещаемые просто в папках на хостинге, которые предоставляют результат при непосредственном обращении к ним. И хотя, видимо в своё время, такой подход послужил широкому распространению php мы рассмотрим далее более программистский подход к созданию апи, который очень похож на используемый в библиотеке Node.js Express и поэтому интуитивно понятен, и прост для освоения. Для это нам понадобиться библиотека «pecee/simple-router».

Далее мы предполагаем, что у вас уже есть среда для запуска кода локально (LAMP, XAMP, docker) или как-то иначе и у вас настроено перенаправление всех запросов на индексный файл (index.php). Кроме, того мы предполагаем, что вы можете устанавливать зависимости через composer.

Структура проекта

На Рис.1. представлена общая структура проекта. Точкой входа является файл

index.phpв папке web. Сама папка webявляется публично доступной папкой, и должна быть указана в настройках сервера как корневая. В папке configбудут находится настройки роутов наших ендпоинтов. В папке controllerбудут обработчики ендпоинтов маршрутов. В папке middlewaresмы разместим промежуточные обработчике роутов для выполнения авторизации перед началом основного кода ендпоинта. В папках exceptions, views и models будут соответственно исключения, html шаблон и объектные модели. Полный код проекта тут.

Инсталляция и запуск

Для работы необходимо инсталлировать следующее содержимое composer.json (composer install в корне проекта).

// composer.json < "require": < "pecee/simple-router": "*", "lcobucci/jwt": "^3.4", "ext-json": "*" >, "autoload": < "psr-4": < "app\\": "" >> > 

Обратите внимание, что ‘app\’ объявлено как префикс для namespace. Данный префикс будет использоваться при объявлении неймспейсов классов.

Запуск всего остального кода происходит вызовом статического метода Router::route() в файле index.php

Так же тут подключаются роуты определённые в файле config/routes.php.

Подключение SPA на Vue.js 2 к проекту на php

Если вы развёртываете сборку vue отдельно от апи, то этот раздел можно пропустить.

Рассмотрим теперь то, как подключить проект на vue в данной конфигурации с использованием соответствующих маршрутов. Для этого содержимое сборки необходимо поместить в папку web. В файле маршрутов (‘/config/routes.php’) прописываем два правила:

; Router::setDefaultNamespace('app\controllers'); Router::get('/', 'VueController@run'); // правило 1 Router::get('/controller', 'VueController@run') ->setMatch('/\/([\w]+)/'); // правило 2 

Для пустого (корневого) маршрута ‘/’ вызывается метод run класса VueController. Второе правило указывает что для любого явно незаданного пути будет тоже вызываться VueController, чтобы обработка маршрута происходила на стороне vue. Это правило всегда должно быть последним, чтобы оно срабатывало только тогда, когда другие уже не сработали. Метод run представляет собой просто рендеринг файла представления с помощью метода renderTemplate(), определённого в родительском классе контроллера. Здесь мы также устанавливаем префикс для классов методы которых используются в роутах с помощью setDefaultNamespace.

renderTemplate('../views/vue/vue_page.php'); > > 

В свою очередь представление vue_page.php тоже просто отрисовка индексного файла сборки vue.

Итого мы подключили проект на vue к проекту на php, который уже готов к развертыванию на хостинге. Данный подход можно использовать для любых проектов на php. Осталось только рассмотреть, что собой представляет родительский класс AbstractController.

request = Router::router()->getRequest(); $this->response = new Response($this->request); > public function renderTemplate($template) < ob_start(); include $template; return ob_get_clean(); >public function setCors() < $this->response->header('Access-Control-Allow-Origin: *'); $this->response->header('Access-Control-Request-Method: OPTIONS'); $this->response->header('Access-Control-Allow-Credentials: true'); $this->response->header('Access-Control-Max-Age: 3600'); > > 

В конструкторе класса AbstractController определяются поля $request и $response. В $request хранится распарсенный классом Pecee\Http\Router запрос. А $response будет использоваться для создания ответов на запросы к апи. Определённый здесь метод renderTemplate используется для рендеринга представлений (html страниц). Кроме того, здесь определён метод устанавливающий заголовки для работы с политикой CORS. Его следует использовать если запросы к апи происходят не с того же адреса, т.е. если сборка vue запускается на другом веб-сервере. Теперь перейдём непосредственно к созданию апи.

Создание REST API эндпоинтов

Для работы с апи нам нужно произвести дополнительную обработку входящего запроса, потому что используемая библиотека не производит парсинг сырых данных. Для этого создадим промежуточный слой ProccessRawBody и добавим его как middleware в роуты для запросов к апи.

 $value) < $request->$key = $value; > > catch (\Throwable $e) < >> > > 

Здесь мы считываем из входного потока и помещаем полученное в объект $request для дальнейшего доступа из кода в контроллерах. ProccessRawBody реализует интерфейс IMIddleware обязательный для всех middleware.

Теперь создадим группу роутов для работы с апи использующее данный промежуточный слой.

 'api/v1', 'middleware' => [ ProccessRawBody::class ] ], function () < Router::post('/auth/sign-in', 'AuthController@signin'); Router::get('/project', 'ProjectController@index'); >);

У этой группы определён префикс «api/v1» (т.е. полный путь запроса должен быть например ‘/api/v1/auth/sign-in’), и ранее определённое нами middleware ProccessRawBody::class, так что в контроллерах наследованных от AbstractController доступны входные переменные через $request. AuthController рассмотрим чуть позже сейчас же мы уже можем воспользоваться методами не требующими авторизации, как например ProjectController::index.

response->json([ [ 'name' => 'project 1' ], [ 'name' => 'project 2' ] ]); > >

Как видим, на входящий запрос, в ответе возвращаются данные о проектах.

Остальные роуты создаются аналогичным образом.

Авторизация по JWT токену

Теперь перейдём к роутам требующим авторизации. Но перед этим реализуем вход и получение jwt-токена. Для создания токена и его валидации мы будем использовать библиотеку “ lcobucci/jwt” Всё это будет у нас выполнятся по роуту определённому ранее ‘/auth/sign-in’. Соответственно в AuthController::singin у нас прописана логика выдачи jwt-токена после авторизации пользователя.

builder() // Configures the issuer (iss claim) ->issuedBy('http://example.com') // Configures the audience (aud claim) ->permittedFor('http://example.org') // Configures the id (jti claim) ->identifiedBy('4f1g23a12aa') // Configures the time that the token was issue (iat claim) ->issuedAt($now) // Configures the expiration time of the token (exp claim) ->expiresAt($now->modify('+2 minutes')) // Configures a new claim, called "uid" ->withClaim('uid', $user->id) // Configures a new header, called "foo" ->withHeader('foo', 'bar') // Builds a new token ->getToken($config->signer(), $config->signingKey()); return $this->response->json([ 'accessToken' => $token->toString() ]); > >

Здесь используется симметричная подпись для jwt с использованием секретного ключа ‘секретный_ключ’. По нему будет проверятся валидность токена при запросах к апи. Ещё можно использовать асимметричную подпись с использованием пары ключей.

Можно также отметить, что можно создавать сколько угодно клаймов ->withClaim(‘uid’, $user->id) и сохранять там данные которые можно будет потом извлекать из ключа. Например, id пользователя для дальнейшей идентификации запросов от этого пользователя. Токен выдан на 2 минуты (->expiresAt($now->modify(‘+2 minutes’))) после чего он становится не валидным. ->issuedBy и ->permittedFor используются для oath2.

Теперь создадим группу роутов защищённую авторизацией. Для этого определим для группы роутов промежуточный слой Authenticate::class.

 'api/v1', 'middleware' => [ ProccessRawBody::class ] ], function () < Router::post('/auth/sign-in', 'AuthController@signin'); Router::get('/project', 'ProjectController@index'); Router::group([ 'middleware' =>[ Authenticate::class ] ], function () < // authenticated routes Router::post('/project/create', 'ProjectController@create'); Router::post('/project/update/', 'ProjectController@update') ->where(['id' => '[\d]+']); >); >); 

Как видите, группа с авторизацией объявлена внутри группы с префиксом “api/v1 ”. Рассмотрим роут ‘/project/update/’. Здесь объявлен параметр id который определён как число. В метод update, контроллера Projectcontroller будет передана переменная $id содержащая значение этого параметра. Ниже приведён пример запроса и ответ.

 > */ public function update(int $id): string < // код обновляющий проект return $this->response->json([ [ 'response' => 'OK', 'request' => $this->request->project, 'id' => $id ] ]); > > 

Вернёмся теперь к промежуточному слою Authenticate::class с помощью которого происходит авторизация запросов к апи.

parser()->parse($tokenString); if ( !$config->validator()->validate( $token, new SignedWith( new Sha256(), InMemory::plainText('секретный_ключ') ), new ValidAt(new FrozenClock(new DateTimeImmutable())) ) ) < throw new NotAuthorizedHttpException('Токен доступа не валиден или просрочен'); >$userId = $token->claims()->get('uid'); $request['uid'] = $userId; > > 

Здесь, считывается заголовок ‘Authorization: Bearer [token]’ (так называемая bearer авторизация) и извлекается оттуда токен, которые клиенты получают после логина и должны посылать со всеми запросами, требующими авторизацию. Далее с помощью парсера jwt-токен-строчка парсится. И дальше с помощью валидатора распарсенный токен валидируется. Метод validate() возвращает true or false. В случае не валидного токена выбрасывается исключение NotAuthorizedException. Если токен валидный, то мы извлекаем из него id пользователя $token->claims()->get(‘uid’) и сохраняем в переменную запроса $request, чтобы его можно было использовать дальше в контроллере. NotAuthorizedException определяется следующим образом:

В завершении рассмотрим ещё обработку ошибок. В файле routes.php запишем следующие строчки:

httpCode(401); break; > case Exception::class: < $response->httpCode(500); break; > > if (PROD) < return $response->json([]); > else < return $response->json([ 'status' => 'error', 'message' => $exception->getMessage() ]); > >);

В итоге файл routes.php будет выглядеть следующим образом:

Рис. 2. Итоговая структура проекта

; use app\middlewares\< Authenticate, ProccessRawBody >; use Pecee\< Http\Request, SimpleRouter\SimpleRouter as Router >; const PROD = false; Router::setDefaultNamespace('app\controllers'); Router::get('/', 'VueController@run'); Router::group([ 'prefix' => 'api/v1', 'middleware' => [ ProccessRawBody::class ] ], function () < Router::post('/auth/sign-in', 'AuthController@signin'); Router::get('/project', 'ProjectController@index'); Router::group([ 'middleware' =>[ Authenticate::class ] ], function () < // authenticated routes Router::post('/project/create', 'ProjectController@create'); Router::post('/project/update/', 'ProjectController@update') ->where(['id' => '[\d]+']); >); >); Router::get('/controller', 'VueController@run') ->setMatch('/\/([\w]+)/'); Router::error(function(Request $request, Exception $exception) < $response = Router::response(); switch (get_class($exception)) < case NotAuthorizedHttpException::class: < $response->httpCode(401); break; > case Exception::class: < $response->httpCode(500); break; > > if (PROD) < return $response->json([]); > else < return $response->json([ 'status' => 'error', 'message' => $exception->getMessage() ]); > >); 

Заключение

В итоге у нас получилось небольшое, простое REST api для небольших проектов которое можно использовать на обычном php хостинге с минимальными трудозатратами на его (хостинга) настройку. Полный код проекта тут.

Больше настроек роутов можно найти здесь. Вместо рассмотренной библиотеки «pecee/simple-router» можно использовать любую другую аналогичную библиотеку или даже микрофреймворк Slim.

Пс. Если вы используете публичный репозиторий или придерживаетесь бестпрактис, то не следует хранит секретный ключ в коде. Для этого можно использовать переменные среды или локальные файлы, которые не добавляются в репозиторий. Код работы с jwt токенами можно выделить в отдельный класс в папке services.

Создание современного API на PHP в 2020 году

Итак, на примере этого API, я хочу показать современную PHP архитектуру для высоконагруженных проектов. Когда проект еще в самом начале, и не то, что бизнеслогика (взаимоотношения с базой данных) не прописана, но и сама бизнес модель не очень ясна, построение эффективной IT архитектуры может идти только одним путем: необходимо жестко разделить frontend и backend.

Что обычно делали в таких ситуациях два-три года назад? Брался монолитный фрейворк типа Laravel или Yii2, вся бизнес модель разбивалась, худо-бедно, на блоки, а эти блоки уже имплементировались как модули фреймворка. В итоге еще через 3 года получалась огромная не поворотная машина, которая сама по себе медленная, а становилась почти невыносимо медленной, в которой фронтенд рендится через бэкенд посредством классической MVC архитеркутуры (пользователь отправил запрос, контроллер его подхватил, вызвал модель, та в свою очередь чего-то там натворила с базой данных, вернула все контроллеру, а тот наконец-то вызвал вьювер, вставил туда данные из модели и отдал это все пользователю, который уже успел открыть очередную банку пива. ). А… ну еще особо продвинутые ребята, они не просто вьюверели Tweeter Bootstrap, а во вьювер вкручивали на самом деле очень хорошие библиотеки типа JQuery или вместо вьювера использовали какой-нибудь фронтенд фреймворк. В итоге поддерживать такой БеЛаЗ становилось все сложнее, а ввести нового программиста в команду было очень сложно, ибо не все рождаются Энштейнами. Добавим сюда тотальное отсутствие документации разработчика (камменты в 9000 файлах почитаешь — там все есть!) и в итоге, смотря на это все, становилось по-настоящему грустно…

Но тут произошел ряд событий, который в корне изменил ситуацию. Во-первых, наконец-то, вышли стандарты PSR и Symfony внезапно перестал быть единственным модульным фремворком, во-вторых, вышел ReactJS, который позволил полноценно разделить фронтенд от бэкенда и заставить их общаться через API. И добивая последний гвоздь в крышку гроба старой системы разработки (MVC — это наше все!) выходит OpenAPI 3.0, собственно, который и регулирует стандарты этого общения через API между фронтендом и бэкендом.

И в мире PHP стало возможно делать следующее:

  1. Разделить бизнес модель на сервисы и микросервисы, и не поднимать для этого весь БеЛаЗ, а обслуживать запросы микросервисов буквально в пару строк кода — самый банальный пример: похожие товары (отдельный запрос GET — отдельный, малюпасенький API, который его обработал и вернул, и мгновенный вывод этой информации ReactJS в браузере пользователя. Основной БеЛаЗ даже и не узнал о том, что произошло.
  2. Писать API стандартизировано по стандарту OpenAPI 3.0 ( swagger.io ) в виде YAML или JSON файла, когда каждый программист не лезет в грязных сапогах в ядро системы, а например, культурно дописывает свою часть в общем YAML файле, тем самым устраняя вероятность ошибки от человека к человеку и уменьшая количество седых волос у тестеровщиков. Просто потом из готового YAML сгенерировал полностью рабочий и даже с middleware сервер. На каком угодно языке и фреймфорке.
  3. Теперь не надо стало нанимать кого-то, чтобы он, этот кто-то писал для вашего API библиотеки, которыми ваши клиенты будут обращаться к вашему API: github.com/OpenAPITools/openapi-generator — я насчитал генерацию более 40 серверов для API и даже не стал считать библиотеки для доступа к ним, ибо единственный язык программирования который я там не нашел — Dlang.

Теперь становятся вопросы, точнее два вопроса, а что у нас перед API и соответственно, что у нас «под хвостом» после API.

1.«Там вдали за рекой», далеко перед API у нас цветет, расползается на новую функциональность и картинки — ФРОНТЕНД (Предпочтительнее ReactJS, но Vue тоже сойдет. Хотя там из коробки всего столько много, что утяжелять процесс он будет, а вот насколько в реальной жизни это понадобится — не совсем понятно и зависит напрямую от бизнес модели). И НЕТ! Я к этому зверю даже близко подходить не буду, ибо с детства у меня на него аллергия. Тут нужен отдельный специалист. Я не фулстак и не пишу фронтенд.

2. Прямо вот перед самим API у нас… НЕ УГАДАЛИ… не NGINX, а RoadRunner roadrunner.dev/features. Заходим, читаем, понимаем, что это быстрее и вокеры расписываются по количеству процессоров, а посему никогда не будет таблички «МЫ НА ПРОФИЛАКТИКЕ», ибо просто надо вокеры переключить.

И на этом моменте хочу остановиться подробнее. Ибо в моем понимании есть три пути «как мух ловить», запросы то бишь…

1. В случае если весь API написан уже, и будет написан в дальнейшем, на PHP — голову ломать не зачем, ставим RoadRunner с prometheus.io

2. В случае, если система собирается из разных кусков, разные сервисы написаны на разных языках и дальше тоже не понятно на чем их писать будут:

2.1. Ставим NGINX UNIT — пользуемся поддерживаемыми языками.

2.2. Поднимаем ВСЕ РАВНО КАКУЮ систему контейнеров, Docker, LXC, LXD. Выбор опять же зависит от размера проекта — поддерживать сборку PROXMOX-LXC на хостинге в 12 процессоров, c 32Гб памяти, за 40 евро в месяц будет в разы дешевле, чем Docker сборки на Google Cloud Platform. В каждый контейнер ставим подходящий к языку сервер, и связываем все это HAProxy www.haproxy.org. HAProxy — шикарный балансер и прокси сервер который в корпоративной среде, не менее популярен, чем NGINX. Что он делает, а чего нет, читаем тут cbonte.github.io/haproxy-dconv/2.3/intro.html пункт 3.1. При такой архитектуре сервисы или микросервисы могут писаться на чем угодно и никто не зависит от ограничений накладываемыми RoadRunner или NGINX UNIT.

3. «Под хвостом» — Cycle ORM. Не ленимся, смотрим видео, что будет стоять за ней конкретно MySQL или PostgreSQL- опять, я бы оставил на после того, как будет понятна бизнес схема проекта. MySQL проще масштабируется, в PostgreSQL — больше бизнес логики перенесенной внутрь самой базы.

4. Пример, который можно посмотреть и пощупать. Там за основу взято тестовое задание. Все самые полезные вещи находятся в папке EXTRAS. Там уже есть jar file генератора, YAML swagger файл API, сгенерированный API stub через OpenAPITools в SLIM4. Даже с аутентификацией и middleware. Документация на API сгенрированная, правда swagger, не OpenAPITools. Предполагается, что некоторые юзеры залогинены и им выдан токен. Там уже стоит RoadRunner впереди. Стек — PHP 7.4.10, PostgreSQL 12.4.

После Git clone, composer install в файле /bootstrap.php прописываем юзера и пароль к базе, которую вначале создаем, потому что это PostgreSQL, по умолчанию сервер слушает локальный порт 8888, если нужно — меняем в файле /.rr.yaml, и выполняем команду: composer run-script fill-database. Все — никаких миграций. Пользуемся.

P.S. Всем залогиненым пользователям присвоен одинаковый токен. Вообще в примере нет никаких валидаций ввода пользователя и почти нет защит — это пример в основном нацеленный на архитектуру.

С уважением,
Кирилл Лапчинский
api-studio.com
mail@api-studio.com

Пример простого создания REST API на PHP

Пример простого создания 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); > > 

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *