Как написать api для сайта

75322-1

Возникла ситуация, когда необходимо использовать 2 БД MySQL на разных серверах для снижения нагрузки.

1 БД для основных задач и 2 БД для хранения различной статистики по всем модулям. А Статистика, как всем известно, — это куча цифр, которыми не хочется засорять основную БД.

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

Oauth использовать решил не стоит, да и было интересно как выйдет API своими руками.

API, ну или Парсер

Да, действительно, способ проще чем кажется. Можно данный способ организации назвать как Парсер, ведь будем использовать @file_get_contents()

Но мне важен результат!

Поехали…

Качаем исходники тут и разбираем что к чему

Собираем функционал API

У нас в итоге получится 2 части.

1 — Клиентская

2 — Серверная (API)

Файлы клиентской части

-- api.php // Соединение клиента с API

-- class.a.php // Класс запросов

-- config.php // Конфигурация MySQL сервера

И Серверная

-- index.php // Тестирование

-- function.api.php // Обработка и отправка запроса к API

Совсем не много…

А теперь по порядку каждый файл

Клиентская часть

-- function.api.php // Обработка и отправка запроса к API

<? 
function ApiQuery($data){ if($data){ 
$uid = '118'; // ID пользователя 
$key = '0d50GthZoEpoqGlskDkfWsNRqsHuTO4F'; // Ключ 
if(!$data)exit("Неправильный запрос"); 
$url = 'http://api.site.ru/api.php'; 
foreach($data as $k=>$item):
 $gets[$k] = $item;
 endforeach;
 
 $get = $url . '?' . urldecode(http_build_query($gets)) . '&uid=' . $uid . '&key=' . $key; // URL для парсера
 $r = json_decode(@file_get_contents($get));
 return $r;
 }
 
 }
 
?>

Так как задача у меня, создать API для хранения статистики по каждому пользователю, добавил параметр $uid. По нему и будет работать SELECT, INSERT, UPDATE и DELETE.

Чтобы пользователь не имел прав управлять строками других пользователей, создан параметр $key, который можно генерировать при регистрации пользователя и сделать проверку на совпадения $key и $uid у определенного пользователя. и $key и $uid соответственно должны быть уникальными!

$url — это адрес к нашему серверу, а точнее к файлу api.php (о нем читать ниже).

Дальше мы отправляем запрос @file_get_contents($get); к серверу и получаем ответ с результатом в формате json_decode()

Начал с клиентской части потому что, мне кажется интереснее посмотреть что получится, а не то как это сделано. И поэтому, вот как использовать MySQL удаленно

<? 
include('function.api.php'); // Функция для отправки запроса в API 

$data = ApiQuery([ // Вывод строк из БД "query" => "select",
 "table" => "scripts"
 ]);

 $datai = ApiQuery([ // Добавление строки в БД
 "query" => "insert",
 "table" => "scripts",
 "values" => [
 "script" => "222",
 "step" => "333"
 ]
 ]);
 
 $datau = ApiQuery([ // Редактирование строки в БД
 "query" => "update",
 "table" => "scripts",
 "where" => "id=5",
 "values" => [
 "script" => "987654321",
 "step" => "555111"
 ]
 ]);

 $datad = ApiQuery([ // Удаление строки в БД
 "query" => "delete",
 "table" => "scripts",
 "where" => "id=5"
 ]);
 
 if($data){ // Выводим все что мы получили через API
 foreach($data as $item){
 echo $item -> id . ') ' . $item -> script . ' => ' . $item -> step . '
';
 }
 }
 
?>

Подключаем function.api.php на нужных страницах для связи с API сервера со статистикой к примеру.

А остальное все итак понятно. Там написал все 4 функции для примера, используя таблицу в бд следующего вида

CREATE TABLE IF NOT EXISTS `scripts` (
 `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
 `script` int(11) NOT NULL,
 `step` int(11) NOT NULL,
 `uid` int(11) NOT NULL,
 PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=cp1251 AUTO_INCREMENT=1 ;

И тут логично понятные параметры

query — запрос (SELECT, UPDATE, INSERT или DELETE)

table — таблица в бд (в моем случае ‘scripts’)

where — условия обычного формата (тут не стал что то изобретать. Типа, [id=5 OR id=6])

а так же…

order — ORDER by

limit — LIMIT

values — и тут передаем параметры для добавления и редактирования в следующем формате

[ "script" => "222", "step" => "333" ]

Мне кажется, просто и понятно…

Серверная часть API

Конечно же config.php

<? $dbl = "localhost"; // Имя сервера 
$dbu = ""; // Имя пользователя 
$dbp = ""; // Пароль 
$dbn = ""; // Имя базы данных 
$db = mysql_connect ("$dbl","$dbu","$dbp"); 
mysql_select_db("$dbn",$db); 
?>

Проверка и отправка запроса в БД через класс API

 <? if($_GET['key'] == '0d50GthZoEpoqGlskDkfWsNRqsHuTO4F'){ 
include('config.php'); 
include('class.a.php'); 
$Api = new Api; 

$VALUES = $_GET['values']; 
if($_GET['uid']){ $uid = $_GET['uid'];
 
if($_GET['where']){ 
$WHERE = "(" . $_GET['where'] . ") AND uid='".$_GET['uid']."'"; 
}else{ 
$WHERE = "uid='".$_GET['uid']."'"; 
} 
$VALUES['uid'] = $_GET['uid']; 
}else{ 
$WHERE = $_GET['where']; 
} 
$data = array( 'key' => $_GET['key'],
 'table' => $_GET['table'],
 'where' => $WHERE,
 'values' => $VALUES,
 'order' => $_GET['order'],
 'limit' => $_GET['limit']
 );
 
 if($_GET['query']){
 switch($_GET['query']){
 case 'select':
 $D = $Api -> select($data);
 break;
 case 'insert':
 $D = $Api -> insert($data);
 break;
 case 'update':
 $D = $Api -> update($data);
 break;
 case 'delete':
 $D = $Api -> delete($data);
 break;
 }
 echo json_encode( $D );
 }
 
 
 
 }
?>

Конечно как вы понимаете, проверка такого рода

if($_GET['key'] == '0d50GthZoEpoqGlskDkfWsNRqsHuTO4F'){}


лишь как пример. Тут надо делать проверку ключа и ID пользователя на соответствие. Тут проблем думаю не будет.

И файл class.a.php для разных запросов и возвращения результата

<?

class Api {

 public function select($data){
 
 $t = $data['table'];
 $w = $data['where'] != '' ? ' WHERE ('.$data['where'].')' : '';
 $o = $data['order'] != '' ? ' ORDER by '.$data['order'] : '';
 $l = $data['limit'] != '' ? ' LIMIT '.$data['limit'] : '';
 
 if($t){
 $q = mysql_query("SELECT * FROM `$t` $w $o $l"); 
 if(mysql_num_rows($q)>0){
 while ($r = mysql_fetch_assoc ($q)){
 $b[] = $r;
 }
 }
 return $b;
 }
 }
 
 public function insert($data){
 
 $t = $data['table'];
 $p = $data['values'];
 if($t == true AND $p == true){
 foreach($p as $k=>$i){
 $col[] = $k;
 $val[] = $i;
 }
 if($col) $cols = implode(',', $col);
 if($val) $vals = implode(',', $val);
 
 $b = mysql_query("INSERT INTO `$t` ($cols) VALUES ($vals)"); 
 return $b;
 }
 }
 
 public function update($data){
 
 $t = $data['table'];
 $w = $data['where'] != '' ? ' WHERE '.$data['where'] : '';
 $p = $data['values'];
 if($t == true AND $p == true){
 foreach($p as $k=>$i){
 $value[] = $k . "='" . $i . "'";
 }
 if($value)$values = implode(',', $value);
 
 $b = mysql_query("UPDATE $t SET $values $w"); 
 return $b;
 }
 
 }
 
 public function delete($data){
 
 $t = $data['table'];
 $w = $data['where'] != '' ? ' WHERE '.$data['where'] : '';

 if($t){ 
 $b = mysql_query("DELETE FROM $t $w"); 
 return $b;
 }
 }

}
 
?>

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

Расписывать не буду, итак ясно что делает данный файл…

Всем удачи в сайтостроении!


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

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

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

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

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

На Рис.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

<?php
//index.php

use PeceeSimpleRouterSimpleRouter as Router;

require_once __DIR__ . '/../vendor/autoload.php';
require_once (__DIR__ . '/../config/routes.php');
    
Router::start();

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

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

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

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

<?php

use Pecee{
    SimpleRouterSimpleRouter as Router
};

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

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

<?php

namespace appcontrollers;

class VueController extends AbstractController
{
    public function run()
    {
        return $this->renderTemplate('../views/vue/vue_page.php');
    }
}

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

<?php
// vue_page.php
include (__DIR__ . '/../../web/index.html');

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

<?php

namespace appcontrollers;

use PeceeHttpRequest;
use PeceeHttpResponse;
use PeceeSimpleRouterSimpleRouter as Router;

abstract class AbstractController
{
    /**
     * @var Response
     */
    protected $response;
    /**
     * @var Request
     */
    protected $request;

    public function __construct()
    {
        $this->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 хранится распарсенный классом PeceeHttpRouter запрос. А $response будет использоваться для создания ответов на запросы к апи. Определённый здесь метод renderTemplate используется для рендеринга представлений (html страниц). Кроме того, здесь определён метод устанавливающий заголовки для работы с политикой CORS. Его следует использовать если запросы к апи происходят не с того же адреса, т.е. если сборка vue запускается на другом веб-сервере. Теперь перейдём непосредственно к созданию апи.

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

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

<?php

namespace appmiddlewares;

use PeceeHttpMiddlewareIMiddleware;
use PeceeHttpRequest;

class ProccessRawBody implements IMiddleware
{

    /**
     * @inheritDoc
     */
    public function handle(Request $request): void
    {
        $rawBody = file_get_contents('php://input');

        if ($rawBody) {
            try {
             $body = json_decode($rawBody, true);
             foreach ($body as $key => $value) {
                 $request->$key = $value;
             }
            } catch (Throwable $e) {

            }
        }
    }
}

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

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

<?php
// routes.php
Router::group([
    'prefix' => '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.

<?php

namespace appcontrollers;

class ProjectController extends AbstractController
{
    public function index():string
    {
	// Какая-то логика для получения данных тут

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

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

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

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

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

<?php

namespace appcontrollers;

use appmodelsRequest;
use ArgumentCountError;
use DateTimeImmutable;
use LcobucciJWTConfiguration;
use LcobucciJWTSignerHmacSha256;
use LcobucciJWTSignerKeyInMemory;

class AuthController extends AbstractController
{
    public function signin()
    {
	      // Тут код авторизующий пользователя

        $config = Configuration::forSymmetricSigner(
            new Sha256(),
            InMemory::plainText('секретный_ключ')
        );
        $now   = new DateTimeImmutable();
        $token = $config->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.

<?php
//routes.php
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/{id}', 'ProjectController@update')
            ->where(['id' => '[d]+']);
    });
});

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

<?php

namespace appcontrollers;

class ProjectController extends AbstractController
{
    /**
     * post /api/v1/project/update/3
     * body:
        {
            "project": {
                "prop": "value"
            }
        }
     */
    public function update(int $id): string
    {
				// код обновляющий проект
        return $this->response->json([
            [
                'response' => 'OK',
                'request' => $this->request->project,
                'id' => $id
            ]
        ]);
    }
}

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

<?php

namespace appmiddlewares;

use appexceptionsNotAuthorizedHttpException;
use DateTimeImmutable;
use LcobucciClockFrozenClock;
use LcobucciJWTConfiguration;
use LcobucciJWTSignerHmacSha256;
use LcobucciJWTSignerKeyInMemory;
use LcobucciJWTValidationConstraintSignedWith;
use LcobucciJWTValidationConstraintValidAt;
use PeceeHttpMiddlewareIMiddleware;
use PeceeHttpRequest;

class Authenticate implements IMiddleware
{
    public function handle(Request $request): void
    {
        $headers = getallheaders();
        $tokenString = substr($headers['Authorization'] ?? '', 7);

        $config = Configuration::forSymmetricSigner(
            new Sha256(),
            InMemory::plainText('секретный_ключ')
        );

        $token = $config->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 определяется следующим образом:

<?php

namespace appexceptions;

class NotAuthorizedHttpException extends Exception
{

}

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

<?php
//routes.php
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()
        ]);
    }
});

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

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

Рис. 2. Итоговая структура проекта
<?php
//routes.php
use appexceptions{
    NotAuthorizedHttpException
};
use appmiddlewares{
    Authenticate,
    ProccessRawBody
};
use Pecee{
    HttpRequest,
    SimpleRouterSimpleRouter as Router
};

const PROD = false;

Router::setDefaultNamespace('appcontrollers');

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/{id}', '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

В системе реализованы 2 механизма для работы API 

  • Входящий запрос к методу API платформы  — внешняя система обращается по определенному endpoint платформы по HTTPS GET/POST и получает некий отклик в JSON/XML/Plain text формате.
  • Исходящий запрос к внешним системам — система подготавливает запрос к внешней системе (URL, параметры формы, HTTP заголовки и др.) в процедуре Request, отправляет его по HTTPS GET/POST и обрабатывает ответ в процедуре Response. 

Подготовка исходящих запросов, обработка отклика от исходящих запросов, обработка входящих запросов — вся обработка происходит через хранимые процедуры. 

Некоторые готовые интеграции на базе платформы:

  • Различные интеграции в документации
  • Примеры интеграции на Демостенде

Варианты интеграции с внешним API

Есть несколько вариантов/уровней интеграции: 

1. Вызов неких действий через кнопку Формы.

Используем процедуру SaveItem формы для вызова Внешнего действия apirequest, которое непосредственно выполняет запрос к внешней среде. 

Полученный ответ может быть обработан через spCallback (хранимая процедура, которая выполняется после указанного внешнего действия), либо через JS коллбек формы SaveItem (в JS обрабатываем JSON-поле additionalData). 

2. Кастом JS компонент.

Реализовать свой JS компонент, который обращается через Request JS к базе данных. В хранимой процедуре вызываем внешнее действие apirequest, затем обрабатываем отклик через JS. 

3. Подтягивание данных по API из внешней системы при загрузке формы или таблицы. 

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

4. Вебхук. 

Допустим, произошло некое событие в системе. Вызываем в процедурах sync, форме или другом месте внешнее действие apirequest, которое отправляет уведомление (информацию о событии в виде Get/Post запроса) во внешнюю систему. 

5. Прямой доступ к БД 

В некоторых случаях можно интегрировать 2 системы минуя слой API. 

Платформа может обращаться напрямую к другим СУБД (через SQL Server Linked Server). Запросы также будут писаться на SQL как и в обычном случае, но обработка данных будет уже происходить во внешней системе.

Если есть другая БД MS SQL Server, которая лежит на том же сервере, то можно дать права на некие объекты этой БД пользователю платформы. В этом случае работа практически ничем не будет отличаться от случая, когда мы используем родную базу Falcon Space (только префикс будет у таблиц добавляться с именем базы данных). 

Интерфейс управления API в личном кабинете администратора 

Управление API происходит на странице /asapi.

Управление входящими запросами: 

Управление исходящими запросами:

Тестирование запросов по аналогии с Postman (кнопка API Request):

Заполняем URL и параметры запроса (HTTP заголовки, файлы, параметры формы) и получаем отклик внешней системы. 

Что может и не может API платформы

Что может механизм API 

  • Отправлять исходящие запросы GET/POST
  • Отправлять свои HTTP заголовки 
  • Отправлять данные формы
  • Отправлять тело POST запроса в виде JSON
  • Отправлять файлы в POST запросе
  • Проводить тестовые запросы (по аналогии с postman)
  • Делать свой слой API для обращения к ним извне
  • Вводить авторизацию для входящих API запросов по токену(api пользователи и сессионные ключи для доступа, предварительные запросы для получения токена).
  • Логирование API запросов (по каждому запросу сохраняется input и output данные в trace). 

Что не может API платформы

  • Работа в формате SOAP (технически возможно, но очень трудоемко поддерживать сложный XML формат вручную). 
  • Система не использует разделение GET/POST/DELETE/PUT запросов для обработки сущностей по соглашениям REST. Каждый метод выполняет свое бизнес-действие, заложенное в соответствующей хранимой процедуре.

Входящие запросы API

Это слой API платформы, к которому обращается внешняя система через запросы GET/POST.

Как проходит основной процесс входящего запроса: 

  1. Обращение извне по HTTPS GET к методу auth для получения token доступа (система проверяет логин, пароль и выдает токен, который будет проверяться в дальнейших обращениях). 
  2. Отправка запроса action для выполнения некоего действия (Получить заказы, создать новый заказ). В рамках запроса происходит следующее:
    1. Проверяются права на выполнение операции
    2. Запускается хранимая процедура обработки действия, подготавливается ответ для вызывающей стороны в виде JSON, XML, Plain text.
    3. Выдается некий отклик на вызывающую сторону

Всегда лучше использовать коды для API только в нижнем регистре латиницей и без пробелов. Таким образом уменьшаются риски проблем с url rewrite неверных адресов

Для API используются обычные HTTPS запросы с ответом в формате JSON. Основные методы

  • auth — создание сессии пользования API (параметры username, password)

  • action — выполнение некоего метода API (параметры могут быть любые)

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

1. Вызываем по Get или Post метод авторизации /api/auth?username=&password={password}&output=json

  • Имя и пароль пользователя API задается в таблице as_api_users (это не логин/пароль обычного пользователя системы). 

  • output — необязательный параметр, задает формат вывода (json, xml,text).

Если данные корректные, то получим токен в отклике сервера. {«errorCode»:0,»token»:»7285440B-BD32-405F-813D-C26DFED23DF5″,»result»:true,»msg»:»»}
Если есть ошибки, то result = false и errorCode содержит код ошибки. 

2. Вызываем метод API 

/api/action/getOrders?token=7285440B-BD32-405F-813D-C26DFED23DF5&catID=1

Передаем token, action (код метода) и произвольные параметры. 

Если все хорошо, то мы получаем result: true и в data содержатся выходные данные от результата выполнения метода. 
Коды ошибок и описания к ним: 

Код

Описание ошибки

1

100

Неверный токен

2

101

Истекло время сессии

3

102

Не найдена реализация метода АПИ (т.е. нет хранимой процедуры метода)

4

103

Выполнение метода завершилось с ошибкой

5

104

Имя/пароль неверные

Примечание: 

  1. в ExtendedDictionaryParameter @parameters используем Key, Value2, а не Key, Value!
  2. в @parameters также передается содержимое самого запроса Request.InputStream (в Key=InputStream)
  3. в @parameters также передается ключ remoteIP — IP вызывающей стороне (например, по нему можно проверить легитимность запроса к API)
  4. Если используете для отправки метод POST, то обязательно указывайте   ‘content-type’: ‘application/x-www-form-urlencoded’. Проверять подобные запросы можно через программу postman.

Создание нового метода API

API создается следующим образом: 

  1. Создается действие в таблице as_api_actions (на странице /asapi)

  • entityCode указывает код сущности, с которой мы работаем, например order.
  • code — задает код действия. 

    2. Создается хранимая процедура api_{entityCode}_{code}, которая реализует основную логику метода API. 

CREATE procedure [dbo].[api_order_getOrders]
@parameters ExtendedDictionaryParameter READONLY, -- параметры которые переданы в метод
@username nvarchar(256) --пользователь API (это не логин пользователя в системе)
as
begin
	declare @catID int
	select @catID = cast(Value2 as int) from @parameters where [Key] = 'catID'

	/* select 1 - это информация об операции. В errorCode можно указать
          специфичные коды ошибок по операциям */
	select '' Msg, 1 Result, 0 errorCode, 0 onlyData

	/* select 2 - это данные, которые необходимо передать источнику запроса к API
        (в выходном JSON передаются в параметре data) */
	select * from ord_orders


        /* SELECT 3 Вызов внешних действий (напр Запрос API)*/

end

На входе: 

  • @parameters — входные параметры в API метод (что приходит из URL, из полей формы и коллекции Httpheaders). Также здесь хранится тело запроса с кодом Key = «InputStream»
  • @username  — логин API (важно его не путать с логином пользователя в системе). 

На выходе: 

  • SELECT 1:
    • результат операции (Result),
    • код результата (errorCode),
    • HTTP код ответа  (httpCode, по умолчанию идет 200),
    • редирект на какой-то адрес (redirectUrl),
    • onlyData — если 1, то для JSON и XML вывода данные будут формироваться чисто из данных из SELECT 2
  • SELECT 2 — произвольные данные, которые передаются вовне. 
  • SELECT 3 — Вызов внешних действий (уведомление на почту, телеграм и др.)

3.Вызываем метод как /api/action/actioncode1?token=token1&…{доп. параметры}….

Вызов API без авторизации

В этом случае нет необходимости использовать токены.

  • У action укажите параметр withoutToken=true.

  • Вызывайте метод без токена: /api/action/getOrders?catID=1

Установка формата вывода для действия. 
Для этого укажите json,text или xml в параметре outputType для действия (as_api_actions). 

Cвойство идемпотентности для создаваемых методов API

Для включения идемпотентности необходимо в запросе к API передавать определенный параметр или заголовок(например X-Request-ID), содержащий уникальный идентификатор: guid, комбинация из номера заказа, даты и суммы.
Каждый новый запрос, который необходимо обработать, должен включать новое значение X-Request-ID.
Таким образом можно избежать проблем с повторными запросами (когда операция дважды выполнится на сервере для 1 запроса).

Исходящие запросы к внешним API

Вы можете обратиться к внешним API через использование Внешних действий (код apirequest, использование описано в документации по Формам). 
Чтобы создать запрос, необходимо выполнить следующее:

  • создать запись о новом запросе (таблицы Исходящие запросы API на /asapi)

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

    • На входе: @parameters ExtendedDictionaryParameter (коллекция входных параметров в Key nvarchar(32), Value2 nvarchar(max))  и @username (текущий пользователь)

    • Возвращает SELECT 1 (Msg, Result и URL, ContentType)

      • URL — адрес, который будет вызван по HTTPS
      • RequestParameterForResponse — некая строка, которую потом можно извлечь в процедуре respose по одноименному ключу из @parameters.  В исходящий запрос эта информация не идет. 
      • ContentType — можно задать для POST запросов свой ContentType (для формы).
        • По умолчанию он подставляется для форм multipart/form-data; boundary=——
        • и для json тела application/json
    • и SELECT 2 (параметры которые будут передаваться вовне — name, value, type). 

      • type — вариант form, header, json.

        • Если отправить надо post запрос, то параметры ставьте в form.

        • Если передан json (используется для POST, имя можно также ставить в JSON) — то его содержимое будет телом всего запроса POST. Все остальные параметры типа Form в этом случае игнорируются (при этом параметры типа headers не игнорируются).

        • Если нужны обычные get параметры — то передавайте их через URL

  • реализовать процедуру обработки ответа — response

    • На входе ответ от внешнего источника в виде строки @response, @parameters ExtendedDictionaryParameter (коллекция входных параметров в Key, Value2, которые приходили в Request процедуру. Сюда также приходит от Request параметр RequestParameterForResponse)

    • Ответ

      • SELECT 1 Msg, Result и Response (может быть дополнительная обработка и выдача ответа вовне). 

      • SELECT 2 Вызов внешних действий (Внешние запросы API и т.д.)
  • вызвать запрос через внешние действия (при сохранении формы, отправке уведомления или других местах).

Ответ при вызове API, например, из формы, передается в поле Response из SELECT 1. 

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

Для тестирования API можно использовать метод /Api/Req/{code} — он вернет ответ в JSON формате. 

Пример хранимой процедуры request: 

CREATE PROCEDURE [dbo].[api_metrikaMonth_request]
	@parameters ExtendedDictionaryParameter READONLY,  -- входящие параметры для внутренней обработки (используйте Key, Value2)
	@username nvarchar(32)  -- текущий пользователь.
AS
BEGIN


declare @metrikaID nvarchar(256)
	set @metrikaID = '53312170' -- Falcon

--set @metrikaID = '60713749' -- DEMO

declare @date1 nvarchar(256) = convert(nvarchar(10), dateadd(day, -30, getdate()), 120)  --2020-04-26
declare @date2 nvarchar(256) = convert(nvarchar(10), getdate(), 120)


declare @token nvarchar(256) = 'token...'
declare @url nvarchar(512) = 'https://api-metrika.yandex.net/stat/v1/data/bytime?'
    	+ 'id='+@metrikaID
    	+ '&preset=sources_summary'
        + '&group=day'
    	+ '&metrics=ym:s:users,ym:s:visits,ym:s:manPercentage,ym:s:womanPercentage' +
        + '&date1=' + @date1   --2020-04-26
    	+ '&date2=' + @date2    -- 2020-04-26
    	-- SELECT 1  Msg, Result, Url (адрес, куда будет идти запрос)
	select '' Msg, 1 Result, @url Url

	-- SELECT 2 PARAMETERS - параметры, которые будут передаваться во внешний источник
--	select 'Content-type' name, 'application/json' value, 'header' [type] -- form (в форме передается), header (в http headers), get запросы передавайте прямо в URL
  --  union
  --  select 'Accept' name, 'application/json' value, 'header' [type]
    --union
    select 'Authorization' name, 'OAuth '+ @token value, 'header' [type]
END

Пример response:

CREATE PROCEDURE [dbo].[api_metrikaMonth_response]
	@response nvarchar(max),
	@parameters ExtendedDictionaryParameter READONLY,  -- входящие параметры для внутренней обработки (используйте Key, Value2 - те же что и на request)

	@username nvarchar(32)
AS
BEGIN
	-- SELECT 1
	select '' Msg, 1 Result, @response Response

	-- SELECT 2 Внешние действия

END


Как отправить исходящий запрос POST с JSON телом?

Для этого указываем тип запроса POST и передаем в процедуре Request в SELECT 2 только 1 параметр с type=json и value =тело запроса в формате строки с JSON.

В этом случае система возмет значение JSON как тело запроса POST. 

Как создать цепочку последовательных запросов? 

Отправляем исходящий запрос. В процедуре response обрабатываем ответ внешней системы и вызываем в ней через SELECT 2 внешнее действие с другим API методом. 

Примечание
Учитывайте длину кодов и ключей (не должны быть больше 32 символов).

Работа с JSON в SQL Server

  • https://docs.microsoft.com/ru-ru/sql/relational-databases/json/json-data-sql-server?view=sql-server-ver15
  • https://stackoverflow.com/questions/2867501/parse-json-in-tsql
  • https://habr.com/ru/post/343062/
  • https://habr.com/ru/post/317166/

Работа с XML в SQL Server 

  • https://www.sql.ru/forum/841296/razbor-xml-v-tablicu
  • https://stackoverflow.com/questions/15680259/parse-xml-in-sql-server/15681388
  • https://stackoverflow.com/questions/3989395/convert-xml-to-table-sql-server

Онлайн редакторы для XML и JSON

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

  • https://xmlgrid.net/ — просмотр XML
  • https://jsoneditoronline.org/ — редактор JSON

Функции, полезные для API 

В SQL есть набор полезных функций, которые выполняют стандартные преобразования 

as_md5 — генерирует хеш строку MD5. Пример: dbo.as_md5(‘123’)

as_hmac — генерирует хеш код в SHA1 и других форматах (https://ru.wikipedia.org/wiki/HMAC).  Пример: 

 select dbo.as_hmac('SHA1', convert(varbinary(max), @secret), 
	 convert(varbinary(max), @data ))

Важно, чтобы @secret и @data были varchar(max), а не nvarchar(max)

as_strToBase64 — генерирует значение в кодировке base64. Если на входе varbinary(max), то преобразуем специальным образом: 

select dbo.as_strToBase64(lower(convert(nvarchar(max), @sh1, 2))) 

as_NCharToUTF8Binary — преобразуем строку nvarchar в binary(max).  

fn_PBKDF2 — стандарт формирования ключа на основе пароля. https://ru.wikipedia.org/wiki/PBKDF2

На практике возникает множество нюансов и возвращаемые функции могут давать не совсем тот результат — это зависит от формата входных данных (varbinary, nvarchar или varchar, lower or not). Проверяйте промежуточные тестовые данные через онлайн сервисы: 

HMAC Service https://freeformatter.com/hmac-generator.html#ad-output

MD5 Online https://decodeit.ru/md5/

Base64 Online https://decodeit.ru/base64/

Falcon Space — функциональная веб-платформа разработки на узком стеке MS SQL/Bootstrap. Вводная по Falcon Space

Насколько полезной была статья?

Google поиск по нашей документации

В данной статье вы узнаете, как создать простой REST API в PHP.

  • 1. Обзор проекта
  • 1.1 Что такое REST API?
  • 1.2 Зачем нужен REST API?
  • 1.3 Где используется REST API?
  • 2. Файловая структура
  • 3. Настройка базы данных
  • 3.1 Создание таблицы категорий
  • 3.2 Дамп данных для таблицы категорий
  • 3.3 Создание таблицы товаров
  • 3.4 Дамп данных для таблицы товаров
  • 3.5 Подключение к базе данных
  • 4. Получение товаров
  • 4.1 Создание объекта Product
  • 4.2 Создание файла для чтения товаров
  • 4.3 Подключение к базе данных и таблице товаров
  • 4.4 Чтение товаров из базы данных
  • 4.5 Создание метода read()
  • 4.6 Уведомление пользователя о том, что товары не найдены
  • 5. Создание товаров
  • 5.1 Создание файла create.php
  • 5.2 Создание метода create()
  • 6. Получение одного товара
  • 6.1 Создание файла read_one.php
  • 6.2 Создание метода readOne()
  • 7. Обновление товара
  • 7.1 Создание файла update.php
  • 7.2 Создание метода update()
  • 8. Удаление товара
  • 8.1 Создание файла delete.php
  • 8.2 Создание метода delete()
  • 9. Поиск товаров
  • 9.1 Создание файла search.php
  • 9.2 Создание метода search()
  • 10. Пагинация товаров
  • 10.1 Создание файла read_paging.php
  • 10.2 Создание файла core.php
  • 10.3 Создание метода readPaging()
  • 10.4 Создание метода count()
  • 10.5 Получение массива пагинации
  • 11. Получение категорий
  • 11.1 Создание класса Category
  • 11.2 Создание метода readAll()
  • 11.3 Создание файла read.php

1. Обзор проекта

1.1 Что такое REST API?

REST — это концепция (архитектура) для организации взаимодействия между независимыми объектами (приложениями) посредством протокола HTTP. Включает в себя набор принципов (рекомендаций) взаимодействия клиент-серверных приложений. Обычно он представлен в формате JSON.

API — интерфейс взаимодействия с каким-либо объектом (программой, приложением), включающий в себя набор правил, которые позволяют одному приложению общаться с другим. Эти «правила» могут включать в себя операции создания, чтения, обновления и удаления. Примером API может служить всем известная библиотека jQuery.

REST API позволяет вашему приложению взаимодействовать с одним или несколькими различными приложениями, используя концепции REST.

RESP API PHP

1.2 Зачем нужен REST API?

Во многих приложениях REST API необходим, потому что это самый легкий способ создания, чтения, обновления или удаления информации между различными приложениями через Интернет или протокол HTTP. Эта информация представляется пользователю в одно мгновение, особенно если вы используете JavaScript для отображения данных на веб-странице.

1.3 Где используется REST API?

REST API может использоваться любым приложением, которое может подключаться к Интернету. Если данные из приложения могут быть созданы, прочитаны, обновлены или удалены с помощью другого приложения, это обычно означает, что используется REST API.

2. Файловая структура

  • api/
    • config/
      • core.php
      • database.php
    • objects/
      • product.php
      • category.php
    • product/
      • create.php/
      • delete.php/
      • read.php/
      • read_paging.php/
      • read_one.php/
      • update.php/
      • search.php/
    • category/
      • read.php
    • shared/
      • utilities.php

3. Настройка базы данных

Используя PhpMyAdmin, создайте новую базу данных api_db. После этого выполните следующие SQL-запросы, чтобы создать новые таблицы с образцами данных.

3.1 Создание таблицы категорий

CREATE TABLE IF NOT EXISTS `categories` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `name` varchar(256) NOT NULL,
    `description` text NOT NULL,
    `created` datetime NOT NULL,
    `modified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=19 ;

3.2 Дамп данных для таблицы категорий

INSERT INTO `categories` (`id`, `name`, `description`, `created`, `modified`) VALUES
(1, "Fashion", "Category for anything related to fashion.", "2014-06-01 00:35:07", "2014-05-30 17:34:33"),
(2, "Electronics", "Gadgets, drones and more.", "2014-06-01 00:35:07", "2014-05-30 17:34:33"),
(3, "Motors", "Motor sports and more", "2014-06-01 00:35:07", "2014-05-30 17:34:54"),
(5, "Movies", "Movie products.", "2019-05-20 10:22:05", "2019-08-20 10:30:15"),
(6, "Books", "Kindle books, audio books and more.", "2018-03-14 08:05:25", "2019-05-20 11:29:11"),
(13, "Sports", "Drop into new winter gear.", "2016-01-09 02:24:24", "2016-01-09 01:24:24");

3.3 Создание таблицы товаров

CREATE TABLE IF NOT EXISTS `products` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(32) NOT NULL,
  `description` text NOT NULL,
  `price` decimal(10,0) NOT NULL,
  `category_id` int(11) NOT NULL,
  `created` datetime NOT NULL,
  `modified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=65 ;

3.4 Дамп данных для таблицы товаров

INSERT INTO `products` (`id`, `name`, `description`, `price`, `category_id`, `created`, `modified`) VALUES
(1, "LG P880 4X HD", "My first awesome phone!", "336", 3, "2014-06-01 01:12:26", "2014-05-31 17:12:26"),
(2, "Google Nexus 4", "The most awesome phone of 2013!", "299", 2, "2014-06-01 01:12:26", "2014-05-31 17:12:26"),
(3, "Samsung Galaxy S4", "How about no?", "600", 3, "2014-06-01 01:12:26", "2014-05-31 17:12:26"),
(6, "Bench Shirt", "The best shirt!", "29", 1, "2014-06-01 01:12:26", "2014-05-31 02:12:21"),
(7, "Lenovo Laptop", "My business partner.", "399", 2, "2014-06-01 01:13:45", "2014-05-31 02:13:39"),
(8, "Samsung Galaxy Tab 10.1", "Good tablet.", "259", 2, "2014-06-01 01:14:13", "2014-05-31 02:14:08"),
(9, "Spalding Watch", "My sports watch.", "199", 1, "2014-06-01 01:18:36", "2014-05-31 02:18:31"),
(10, "Sony Smart Watch", "The coolest smart watch!", "300", 2, "2014-06-06 17:10:01", "2014-06-05 18:09:51"),
(11, "Huawei Y300", "For testing purposes.", "100", 2, "2014-06-06 17:11:04", "2014-06-05 18:10:54"),
(12, "Abercrombie Lake Arnold Shirt", "Perfect as gift!", "60", 1, "2014-06-06 17:12:21", "2014-06-05 18:12:11"),
(13, "Abercrombie Allen Brook Shirt", "Cool red shirt!", "70", 1, "2014-06-06 17:12:59", "2014-06-05 18:12:49"),
(26, "Another product", "Awesome product!", "555", 2, "2014-11-22 19:07:34", "2014-11-21 20:07:34"),
(28, "Wallet", "You can absolutely use this one!", "799", 6, "2014-12-04 21:12:03", "2014-12-03 22:12:03"),
(31, "Amanda Waller Shirt", "New awesome shirt!", "333", 1, "2014-12-13 00:52:54", "2014-12-12 01:52:54"),
(42, "Nike Shoes for Men", "Nike Shoes", "12999", 3, "2015-12-12 06:47:08", "2015-12-12 05:47:08"),
(48, "Bristol Shoes", "Awesome shoes.", "999", 5, "2016-01-08 06:36:37", "2016-01-08 05:36:37"),
(60, "Rolex Watch", "Luxury watch.", "25000", 1, "2016-01-11 15:46:02", "2016-01-11 14:46:02");

3.5 Подключение к базе данных

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

Создайте папку api и откройте её. Создайте папку config и в ней создайте файл database.php со следующим кодом.

<?php

class Database
{
    // укажите свои учетные данные базы данных
    private $host = "localhost";
    private $db_name = "api_db";
    private $username = "root";
    private $password = "";
    public $conn;

    // получаем соединение с БД
    public function getConnection()
    {
        $this->conn = null;

        try {
            $this->conn = new PDO("mysql:host=" . $this->host . ";dbname=" . $this->db_name, $this->username, $this->password);
            $this->conn->exec("set names utf8");
        } catch (PDOException $exception) {
            echo "Ошибка подключения: " . $exception->getMessage();
        }

        return $this->conn;
    }
}

4. Получение товаров

4.1 Создание объекта Product

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

Мы будем использовать этот класс для чтения данных из базы. Откройте папку api. Создайте папку objects. Откройте папку её и создайте файл product.php. Поместите в него следующий код.

<?php

class Product
{
    // подключение к базе данных и таблице "products"
    private $conn;
    private $table_name = "products";

    // свойства объекта
    public $id;
    public $name;
    public $description;
    public $price;
    public $category_id;
    public $category_name;
    public $created;

    // конструктор для соединения с базой данных
    public function __construct($db)
    {
        $this->conn = $db;
    }

    // здесь будет метод read()
}

4.2 Создание файла для чтения товаров

Код ниже содержит заголовки о том, кто может читать этот файл и какой тип содержимого он будет возвращать.

В данном случае наш файл read.php может быть прочитан кем угодно (звездочка * означает все) и вернет данные в формате JSON.

Откройте папку api. Создайте в ней папку product. Откройте её и создайте файл read.php со следующим кодом.

<?php

// необходимые HTTP-заголовки
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");

// подключение к базе данных будет здесь

4.3 Подключение к базе данных и таблице товаров

В приведенном ниже коде мы подключаем файлы database.php и product.php. Это файлы, которые мы создали ранее.

Нам нужно использовать метод getConnection() класса Database для получения соединения с базой данных. Мы передаем это подключение классу Product.

Замените комментарий // подключение к базе данных будет здесь в файле read.php следующим кодом.

// подключение базы данных и файл, содержащий объекты
include_once "../config/database.php";
include_once "../objects/product.php";

// получаем соединение с базой данных
$database = new Database();
$db = $database->getConnection();

// инициализируем объект
$product = new Product($db);
 
// чтение товаров будет здесь

4.4 Чтение товаров из базы данных

В приведенном ниже коде мы используем метод read() класса Product для получения данных из базы. Через переменную $num мы проверяем, найдены ли записи.

Если найдены записи, мы перебираем их с помощью цикла while, и добавляем каждую запись в массив $products_arr, устанавливаем код ответа 200 OK и показываем его пользователю в формате JSON.

Замените комментарий // чтение товаров будет здесь в файле read.php следующим кодом.

// запрашиваем товары
$stmt = $product->read();
$num = $stmt->rowCount();

// проверка, найдено ли больше 0 записей
if ($num > 0) {
    // массив товаров
    $products_arr = array();
    $products_arr["records"] = array();

    // получаем содержимое нашей таблицы
    // fetch() быстрее, чем fetchAll()
    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
        // извлекаем строку
        extract($row);
        $product_item = array(
            "id" => $id,
            "name" => $name,
            "description" => html_entity_decode($description),
            "price" => $price,
            "category_id" => $category_id,
            "category_name" => $category_name
        );
        array_push($products_arr["records"], $product_item);
    }

    // устанавливаем код ответа - 200 OK
    http_response_code(200);

    // выводим данные о товаре в формате JSON
    echo json_encode($products_arr);
}

// "товары не найдены" будет здесь

4.5 Создание метода read()

Мы использовали метод read() в предыдущем разделе, но он пока ещё не существует в классе Product. Нам нужно добавить этот метод. С помощью кода ниже, мы делаем запрос для получения записей из базы данных.

Откройте папку objects. Откройте файл product.php. Поместите следующий код в класс Product перед последней закрывающей фигурной скобкой вместо комментария // здесь будет метод read().

// метод для получения товаров
function read()
{
    // выбираем все записи
    $query = "SELECT
        c.name as category_name, p.id, p.name, p.description, p.price, p.category_id, p.created
    FROM
        " . $this->table_name . " p
        LEFT JOIN
            categories c
                ON p.category_id = c.id
    ORDER BY
        p.created DESC";

    // подготовка запроса
    $stmt = $this->conn->prepare($query);

    // выполняем запрос
    $stmt->execute();
    return $stmt;
}

4.6 Уведомление пользователя о том, что товары не найдены

Если переменная $num имеет нулевое или отрицательное значение, это означает, что из базы данных не возвращено никаких записей. Мы должны сообщить пользователю об этом.

В приведенном ниже коде мы устанавливаем код ответа 404 — Не найдено и сообщение, что Товары не найдены.

Замените комментарий // «товары не найдены» будет здесь в файле read.php следующим кодом.

else {
    // установим код ответа - 404 Не найдено
    http_response_code(404);

    // сообщаем пользователю, что товары не найдены
    echo json_encode(array("message" => "Товары не найдены."), JSON_UNESCAPED_UNICODE);
}

5. Создание товаров

5.1 Создание файла create.php

Откройте папку product и создайте в ней файл create.php со следующим содержимым.

<?php

// необходимые HTTP-заголовки
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Methods: POST");
header("Access-Control-Max-Age: 3600");
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");

// получаем соединение с базой данных
include_once "../config/database.php";

// создание объекта товара
include_once "../objects/product.php";
$database = new Database();
$db = $database->getConnection();
$product = new Product($db);

// получаем отправленные данные
$data = json_decode(file_get_contents("php://input"));

// убеждаемся, что данные не пусты
if (
    !empty($data->name) &&
    !empty($data->price) &&
    !empty($data->description) &&
    !empty($data->category_id)
) {
    // устанавливаем значения свойств товара
    $product->name = $data->name;
    $product->price = $data->price;
    $product->description = $data->description;
    $product->category_id = $data->category_id;
    $product->created = date("Y-m-d H:i:s");

    // создание товара
    if ($product->create()) {
        // установим код ответа - 201 создано
        http_response_code(201);

        // сообщим пользователю
        echo json_encode(array("message" => "Товар был создан."), JSON_UNESCAPED_UNICODE);
    }
    // если не удается создать товар, сообщим пользователю
    else {
        // установим код ответа - 503 сервис недоступен
        http_response_code(503);

        // сообщим пользователю
        echo json_encode(array("message" => "Невозможно создать товар."), JSON_UNESCAPED_UNICODE);
    }
}
// сообщим пользователю что данные неполные
else {
    // установим код ответа - 400 неверный запрос
    http_response_code(400);

    // сообщим пользователю
    echo json_encode(array("message" => "Невозможно создать товар. Данные неполные."), JSON_UNESCAPED_UNICODE);
}

5.2 Создание метода create()

Откройте папку objects. Откройте файл product.php и добавьте следующий код внутри класса Product (objects / product.php).

// метод для создания товаров
function create()
{
    // запрос для вставки (создания) записей
    $query = "INSERT INTO
            " . $this->table_name . "
        SET
            name=:name, price=:price, description=:description, category_id=:category_id, created=:created";

    // подготовка запроса
    $stmt = $this->conn->prepare($query);

    // очистка
    $this->name = htmlspecialchars(strip_tags($this->name));
    $this->price = htmlspecialchars(strip_tags($this->price));
    $this->description = htmlspecialchars(strip_tags($this->description));
    $this->category_id = htmlspecialchars(strip_tags($this->category_id));
    $this->created = htmlspecialchars(strip_tags($this->created));

    // привязка значений
    $stmt->bindParam(":name", $this->name);
    $stmt->bindParam(":price", $this->price);
    $stmt->bindParam(":description", $this->description);
    $stmt->bindParam(":category_id", $this->category_id);
    $stmt->bindParam(":created", $this->created);

    // выполняем запрос
    if ($stmt->execute()) {
        return true;
    }
    return false;
}

6. Получение одного товара

6.1 Создание файла read_one.php

Откройте папку product, создайте в ней файл read_one.php со следующим содержимым.

<?php

// необходимые HTTP-заголовки
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Headers: access");
header("Access-Control-Allow-Methods: GET");
header("Access-Control-Allow-Credentials: true");
header("Content-Type: application/json");

// подключение файла для соединения с базой и файл с объектом
include_once "../config/database.php";
include_once "../objects/product.php";

// получаем соединение с базой данных
$database = new Database();
$db = $database->getConnection();

// подготовка объекта
$product = new Product($db);

// установим свойство ID записи для чтения
$product->id = isset($_GET["id"]) ? $_GET["id"] : die();

// получим детали товара
$product->readOne();

if ($product->name != null) {

    // создание массива
    $product_arr = array(
        "id" =>  $product->id,
        "name" => $product->name,
        "description" => $product->description,
        "price" => $product->price,
        "category_id" => $product->category_id,
        "category_name" => $product->category_name
    );

    // код ответа - 200 OK
    http_response_code(200);

    // вывод в формате json
    echo json_encode($product_arr);
} else {
    // код ответа - 404 Не найдено
    http_response_code(404);

    // сообщим пользователю, что такой товар не существует
    echo json_encode(array("message" => "Товар не существует"), JSON_UNESCAPED_UNICODE);
}

6.2 Создание метода readOne()

Откройте папку objects. Откройте файл product.php и добавьте следующий код внутри класса Product.

// метод для получения конкретного товара по ID
function readOne()
{
    // запрос для чтения одной записи (товара)
    $query = "SELECT
            c.name as category_name, p.id, p.name, p.description, p.price, p.category_id, p.created
        FROM
            " . $this->table_name . " p
            LEFT JOIN
                categories c
                    ON p.category_id = c.id
        WHERE
            p.id = ?
        LIMIT
            0,1";
            
    // подготовка запроса
    $stmt = $this->conn->prepare($query);

    // привязываем id товара, который будет получен
    $stmt->bindParam(1, $this->id);

    // выполняем запрос
    $stmt->execute();

    // получаем извлеченную строку
    $row = $stmt->fetch(PDO::FETCH_ASSOC);

    // установим значения свойств объекта
    $this->name = $row["name"];
    $this->price = $row["price"];
    $this->description = $row["description"];
    $this->category_id = $row["category_id"];
    $this->category_name = $row["category_name"];
}

7. Обновление товара

7.1 Создание файла update.php

Откройте папку product, создайте в ней файл update.php и поместите в него следующий код.

<?php

// HTTP-заголовки
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Methods: POST");
header("Access-Control-Max-Age: 3600");
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");

// подключаем файл для работы с БД и объектом Product
include_once "../config/database.php";
include_once "../objects/product.php";

// получаем соединение с базой данных
$database = new Database();
$db = $database->getConnection();

// подготовка объекта
$product = new Product($db);

// получаем id товара для редактирования
$data = json_decode(file_get_contents("php://input"));

// установим id свойства товара для редактирования
$product->id = $data->id;

// установим значения свойств товара
$product->name = $data->name;
$product->price = $data->price;
$product->description = $data->description;
$product->category_id = $data->category_id;

// обновление товара
if ($product->update()) {
    // установим код ответа - 200 ok
    http_response_code(200);

    // сообщим пользователю
    echo json_encode(array("message" => "Товар был обновлён"), JSON_UNESCAPED_UNICODE);
}
// если не удается обновить товар, сообщим пользователю
else {
    // код ответа - 503 Сервис не доступен
    http_response_code(503);

    // сообщение пользователю
    echo json_encode(array("message" => "Невозможно обновить товар"), JSON_UNESCAPED_UNICODE);
}

7.2 Создание метода update()

В папке objects откройте файл product.php и добавьте новый метод update() внутри класса Product.

// метод для обновления товара
function update()
{
    // запрос для обновления записи (товара)
    $query = "UPDATE
            " . $this->table_name . "
        SET
            name = :name,
            price = :price,
            description = :description,
            category_id = :category_id
        WHERE
            id = :id";

    // подготовка запроса
    $stmt = $this->conn->prepare($query);

    // очистка
    $this->name = htmlspecialchars(strip_tags($this->name));
    $this->price = htmlspecialchars(strip_tags($this->price));
    $this->description = htmlspecialchars(strip_tags($this->description));
    $this->category_id = htmlspecialchars(strip_tags($this->category_id));
    $this->id = htmlspecialchars(strip_tags($this->id));

    // привязываем значения
    $stmt->bindParam(":name", $this->name);
    $stmt->bindParam(":price", $this->price);
    $stmt->bindParam(":description", $this->description);
    $stmt->bindParam(":category_id", $this->category_id);
    $stmt->bindParam(":id", $this->id);

    // выполняем запрос
    if ($stmt->execute()) {
        return true;
    }
    return false;
}

8. Удаление товара

8.1 Создание файла delete.php

Откройте папку product и создайте файл delete.php со следующим содержимым.

<?php

// HTTP-заголовки
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Methods: POST");
header("Access-Control-Max-Age: 3600");
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");

// подключим файл для соединения с базой и объектом Product
include_once "../config/database.php";
include_once "../objects/product.php";

// получаем соединение с БД
$database = new Database();
$db = $database->getConnection();

// подготовка объекта
$product = new Product($db);

// получаем id товара
$data = json_decode(file_get_contents("php://input"));

// установим id товара для удаления
$product->id = $data->id;

// удаление товара
if ($product->delete()) {
    // код ответа - 200 ok
    http_response_code(200);

    // сообщение пользователю
    echo json_encode(array("message" => "Товар был удалён"), JSON_UNESCAPED_UNICODE);
}
// если не удается удалить товар
else {
    // код ответа - 503 Сервис не доступен
    http_response_code(503);

    // сообщим об этом пользователю
    echo json_encode(array("message" => "Не удалось удалить товар"));
}

8.2 Создание метода delete()

В папке objects откройте файл product.php и добавьте новый метод в класс Product.

// метод для удаления товара
function delete()
{
    // запрос для удаления записи (товара)
    $query = "DELETE FROM " . $this->table_name . " WHERE id = ?";

    // подготовка запроса
    $stmt = $this->conn->prepare($query);

    // очистка
    $this->id = htmlspecialchars(strip_tags($this->id));

    // привязываем id записи для удаления
    $stmt->bindParam(1, $this->id);

    // выполняем запрос
    if ($stmt->execute()) {
        return true;
    }
    return false;
}

9. Поиск товаров

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

9.1 Создание файла search.php

В папке product создайте файл search.php со следующим кодом.

<?php

// HTTP-заголовки
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");

// подключение необходимых файлов
include_once "../config/core.php";
include_once "../config/database.php";
include_once "../objects/product.php";

// создание подключения к БД
$database = new Database();
$db = $database->getConnection();

// инициализируем объект
$product = new Product($db);

// получаем ключевые слова
$keywords = isset($_GET["s"]) ? $_GET["s"] : "";

// запрос товаров
$stmt = $product->search($keywords);
$num = $stmt->rowCount();

// проверяем, найдено ли больше 0 записей
if ($num > 0) {
    // массив товаров
    $products_arr = array();
    $products_arr["records"] = array();

    // получаем содержимое нашей таблицы
    // fetch() быстрее чем fetchAll()
    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {

        // извлечём строку
        extract($row);
        $product_item = array(
            "id" => $id,
            "name" => $name,
            "description" => html_entity_decode($description),
            "price" => $price,
            "category_id" => $category_id,
            "category_name" => $category_name
        );
        array_push($products_arr["records"], $product_item);
    }
    // код ответа - 200 OK
    http_response_code(200);

    // покажем товары
    echo json_encode($products_arr);
} else {
    // код ответа - 404 Ничего не найдено
    http_response_code(404);

    // скажем пользователю, что товары не найдены
    echo json_encode(array("message" => "Товары не найдены."), JSON_UNESCAPED_UNICODE);
}

9.2 Создание метода search()

В папке objects откройте product.php и добавьте метод search().

// метод для поиска товаров
function search($keywords)
{
    // поиск записей (товаров) по "названию товара", "описанию товара", "названию категории"
    $query = "SELECT
            c.name as category_name, p.id, p.name, p.description, p.price, p.category_id, p.created
        FROM
            " . $this->table_name . " p
            LEFT JOIN
                categories c
                    ON p.category_id = c.id
        WHERE
            p.name LIKE ? OR p.description LIKE ? OR c.name LIKE ?
        ORDER BY
            p.created DESC";

    // подготовка запроса
    $stmt = $this->conn->prepare($query);

    // очистка
    $keywords = htmlspecialchars(strip_tags($keywords));
    $keywords = "%{$keywords}%";

    // привязка
    $stmt->bindParam(1, $keywords);
    $stmt->bindParam(2, $keywords);
    $stmt->bindParam(3, $keywords);

    // выполняем запрос
    $stmt->execute();

    return $stmt;
}

10. Пагинация товаров

10.1 Создание файла read_paging.php

В папке product создайте файл read_paging.php со следующим кодом.

<?php

// установим HTTP-заголовки
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");

// подключение файлов
include_once "../config/core.php";
include_once "../shared/utilities.php";
include_once "../config/database.php";
include_once "../objects/product.php";

// utilities
$utilities = new Utilities();

// создание подключения
$database = new Database();
$db = $database->getConnection();

// инициализация объекта
$product = new Product($db);

// запрос товаров
$stmt = $product->readPaging($from_record_num, $records_per_page);
$num = $stmt->rowCount();

// если больше 0 записей
if ($num > 0) {

    // массив товаров
    $products_arr = array();
    $products_arr["records"] = array();
    $products_arr["paging"] = array();

    // получаем содержимое нашей таблицы
    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {

        // извлечение строки
        extract($row);
        $product_item = array(
            "id" => $id,
            "name" => $name,
            "description" => html_entity_decode($description),
            "price" => $price,
            "category_id" => $category_id,
            "category_name" => $category_name
        );
        array_push($products_arr["records"], $product_item);
    }

    // подключим пагинацию
    $total_rows = $product->count();
    $page_url = "{$home_url}product/read_paging.php?";
    $paging = $utilities->getPaging($page, $total_rows, $records_per_page, $page_url);
    $products_arr["paging"] = $paging;

    // установим код ответа - 200 OK
    http_response_code(200);

    // вывод в json-формате
    echo json_encode($products_arr);
} else {

    // код ответа - 404 Ничего не найдено
    http_response_code(404);

    // сообщим пользователю, что товаров не существует
    echo json_encode(array("message" => "Товары не найдены"), JSON_UNESCAPED_UNICODE);
}

10.2 Создание файла core.php

Этот файл содержит нашу базовую конфигурацию, такую как базовый URL и переменные пагинации.

Откройте папку config и создайте в ней файл core.php со следующим содержимым.

<?php

// показывать сообщения об ошибках
ini_set("display_errors", 1);
error_reporting(E_ALL);

// URL домашней страницы
$home_url = "http://localhost/api/";

// страница указана в параметре URL, страница по умолчанию одна
$page = isset($_GET["page"]) ? $_GET["page"] : 1;

// установка количества записей на странице
$records_per_page = 5;

// расчёт для запроса предела записей
$from_record_num = ($records_per_page * $page) - $records_per_page;

10.3 Создание метода readPaging()

В папке objects откройте файл product.php и добавьте метод readPaging(). Этот метод вернет список записей, ограниченный тем, что мы установили в $records_per_page файла core.php.

// получение товаров с пагинацией
public function readPaging($from_record_num, $records_per_page)
{
    // выборка
    $query = "SELECT
            c.name as category_name, p.id, p.name, p.description, p.price, p.category_id, p.created
        FROM
            " . $this->table_name . " p
            LEFT JOIN
                categories c
                    ON p.category_id = c.id
        ORDER BY p.created DESC
        LIMIT ?, ?";

    // подготовка запроса
    $stmt = $this->conn->prepare($query);

    // свяжем значения переменных
    $stmt->bindParam(1, $from_record_num, PDO::PARAM_INT);
    $stmt->bindParam(2, $records_per_page, PDO::PARAM_INT);

    // выполняем запрос
    $stmt->execute();

    // вернём значения из базы данных
    return $stmt;
}

10.4 Создание метода count()

Так же в классе Product (файл product.php) добавьте метод count() для подсчёта количества товаров.

// данный метод возвращает кол-во товаров
public function count()
{
    $query = "SELECT COUNT(*) as total_rows FROM " . $this->table_name . "";

    $stmt = $this->conn->prepare($query);
    $stmt->execute();

    $row = $stmt->fetch(PDO::FETCH_ASSOC);

    return $row["total_rows"];
}

10.5 Получение массива пагинации

В корне создайте папку shared, в ней файл utilities.php со следующим кодом.

<?php

class Utilities
{
    public function getPaging($page, $total_rows, $records_per_page, $page_url)
    {
        // массив пагинации
        $paging_arr = array();

        // кнопка для первой страницы
        $paging_arr["first"] = $page > 1 ? "{$page_url}page=1" : "";

        // подсчёт всех товаров в базе данных для подсчета общего количества страниц
        $total_pages = ceil($total_rows / $records_per_page);

        // диапазон ссылок для показа
        $range = 2;

        // отображать диапазон ссылок вокруг текущей страницы
        $initial_num = $page - $range;
        $condition_limit_num = ($page + $range) + 1;
        $paging_arr["pages"] = array();
        $page_count = 0;

        for ($x = $initial_num; $x < $condition_limit_num; $x++) {
            // убедимся, что $x > 0 И $x <= $total_pages
            if (($x > 0) && ($x <= $total_pages)) {
                $paging_arr["pages"][$page_count]["page"] = $x;
                $paging_arr["pages"][$page_count]["url"] = "{$page_url}page={$x}";
                $paging_arr["pages"][$page_count]["current_page"] = $x == $page ? "yes" : "no";
                $page_count++;
            }
        }

        // кнопка для последней страницы
        $paging_arr["last"] = $page < $total_pages ? "{$page_url}page={$total_pages}" : "";

        // формат json
        return json_encode($paging_arr);
    }
}

11. Получение категорий

11.1 Создание класса Category

Откройте папку objects и создайте новый файл category.php со следующим кодом.

<?php

class Category
{
    // соединение с БД и таблицей "categories"
    private $conn;
    private $table_name = "categories";

    // свойства объекта
    public $id;
    public $name;
    public $description;
    public $created;

    public function __construct($db)
    {
        $this->conn = $db;
    }

    // здесь будет метод для получение всех категорий товаров
}

11.2 Создание метода readAll()

С помощью данного метода мы получим все категории товаров из базы данных. замените комментарий // здесь будет метод для получение всех категорий товаров следующим кодом:

// метод для получения всех категорий товаров
public function readAll()
{
    $query = "SELECT
                id, name, description
            FROM
                " . $this->table_name . "
            ORDER BY
                name";

    $stmt = $this->conn->prepare($query);
    $stmt->execute();

    return $stmt;
}

11.3 Создание файла read.php

Создайте новую папку category в корне, и в ней файл read.php со следующим кодом.

<?php

// установим HTTP-заголовки
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");

// подключение файлов для соединения с БД и файл с объектом Category
include_once "../config/database.php";
include_once "../objects/category.php";

// создание подключения к базе данных
$database = new Database();
$db = $database->getConnection();

// инициализация объекта
$category = new Category($db);

// получаем категории
$stmt = $category->readAll();
$num = $stmt->rowCount();

// проверяем, найдено ли больше 0 записей
if ($num > 0) {

    // массив для записей
    $categories_arr = array();
    $categories_arr["records"] = array();

    // получим содержимое нашей таблицы
    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {

        // извлекаем строку
        extract($row);
        $category_item = array(
            "id" => $id,
            "name" => $name,
            "description" => html_entity_decode($description)
        );
        array_push($categories_arr["records"], $category_item);
    }
    // код ответа - 200 OK
    http_response_code(200);

    // покажем данные категорий в формате json
    echo json_encode($categories_arr);
} else {

    // код ответа - 404 Ничего не найдено
    http_response_code(404);

    // сообщим пользователю, что категории не найдены
    echo json_encode(array("message" => "Категории не найдены"), JSON_UNESCAPED_UNICODE);
}

API готов к использованию!

Frontend часть данного приложения (продолжение) — jQuery + AJAX + JSON.

Если вам понравилась данная статья, рекомендую к прочтению создание регистрации и авторизации в php с использованием JWT.

Понравилась статья? Поделить с друзьями:
  • Как написать and значком
  • Как написать ambient
  • Как написать ahk скрипт для samp
  • Как написать ahk на нажатие клавиш
  • Как написать abstract научной статьи на английском