Главная  /  Разработки   /  PHP   /  Простой протокол JSON-API для мобильного приложения

Простой протокол JSON-API для мобильного приложения

Подкинули задачу, реализовать простой протокол JSON-API для мобильного приложения. Решить задачу предлагается на чистом PHP, без использования фреймворков, но с использованием ООП. Код должен быть аккуратен, без излишних усложнений. Тестовое задание с радио рекорд http://www.radiorecord.ru/test_task/

Само задание

Реализовать простой протокол JSON-API для мобильного приложения.

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

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

Предполагается, что сервер будет работать под управлением CentOS, PHP 5.5, MySQL, Nginx. Дополнительные библиотеки, при необходимости, будут доступны.

Описание протокола

Запрос

Вызов методов происходит путем отправки POST запроса на URL вида: http://server/api/MethodName

Ответ

Ответ присылается всегда в JSON формате (даже в случае ошибки) и всегда имеет следующий вид:

{
    status: "ok",
    payload: {},
    message: ""
}

Описание полей ответа

  • status — Cтатус операции («ok» | «error» — ошибка или успех)
  • payload — Полезная нагрузка (данные) — массив объектов или сам объект
  • message — Опциональный параметр — сообщение, которое нужно отобразить пользователю. Например, «Спасибо, ваша новость сохранена» или «Вы уже опубликовали такую новость».

Методы

Метод «Table»

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

Параметры запроса
  • table — название таблицы (обязательный параметр). На текущий момент разрешены следующие значения:
    • News
    • Session
  • id — ID объекта (опциональный параметр). Если не передан — возвращаются все объекты таблицы, если передан — возвращается единственная строка с указанным ID.
Пример ответа (таблица News)
{
    "status": "ok",
    "payload": [
    {
        "ID": 1,
        "ParticipantId": 1,
        "NewsTitle": "Доступна новая программа",
        "NewsMessage": "Новая программа выслана на почту всем участникам"
        "LikesCounter": 0
    },
    {
        "ID": 2,
        "ParticipantId": 3,
        "NewsTitle": "Еще одна новость",
        "NewsMessage": "Проверка новости"
        "LikesCounter": 0
    }
    ]
}
Пример ответа (таблица Session)
{
    "status": "ok",
    "payload": [
    {
        "ID": 123,
        "Name": "Annual report",
        "TimeOfEvent": "2016-12-15 16:00:00",
        "Description": "Anuual report by CEO",
        "Speakers": [
        {
            "ID": 1,
            "Name": "Speaker name
        }
        ]
    }
    ]
}

Метод SessionSubscribe

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

Параметры запроса
  • sessionId — ID сессии, на которую нужно записаться
  • userEmail — email пользователя
Пример ответа
{
    "status": "ok",
    "message": "Спасибо, вы успешно записаны!"
}
Пример ответа (ошибка)
{
    "status": "ok",
    "message": "Извините, все места заняты"
}

Метод PostNews

Вставить новость. Новость сразу (без премодерации) попадает в общий список новостей. Важно: новость одного и того же участника не должна дублироваться.

Параметры запроса
  • userEmail — email пользователя-автора
  • newsTitle — заголовок новости (string)
  • newsMessage — новость (string)
Пример ответа
{
    "status": "ok",
    "message": "Спасибо, ваша новость сохранена!"
}

Выполнение задания

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

├─ api/
├─── config/
├────── database.php - подключение к бд
├────── table.php - настройка доступности таблиц
├─── objects/
├────── Table.php - работа с бд
├─── SessionSubscribe/
├────── create.php - запись на сессию *sessionId=?, *userEmail=?
├─── PostNews/
├────── create.php - вставить новость *userEmail=?, *newsTitle=?, *newsMessage=?
├─── shared/
├────── Utilities.php - вспомогательная функция показа ошибок
├─── table/
├────── read.php - получение данных из таблиц id=?, *table=?

База данных

В бд была создана таблица Users, в таблицу Session добавлено поле max_users для определения максимального количества человек. Создана таблица Session_record для записи на сессию.

Подключение к бд config/database.php

Я выбрал PDO не просто так, я считаю его наиболее правильным, тем более для API, чем mysqli. Про PDO в двух словах не описать, но основное: 1. защита от sql инъекций, так как мы отправляем параметры. 2. возможность работать с любой поддерживаемой базой.

<?php
/*
=====================================================
 https://webarmen.com/
-----------------------------------------------------
 Copyright (c) 2018
=====================================================
 Данный код защищен авторскими правами
=====================================================
 Файл: database.php
-----------------------------------------------------
 Назначение: подключение к бд
=====================================================
*/
class Database {
    private $host = "localhost";
    private $db_name = "radio";
    private $username = "radio";
    private $password = "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 ) {
            die(json_encode(array(
                "status" => "error",
                "message" => $exception->getMessage()
            )));
        }

        return $this->conn;
    }
}
?>

Работа с таблицами objects/Table.php

Добавлю комментарии. Данный класс обрабатывает запросы в бд, грамотнее разбить их на несколько файлов и классов, так как API может быть намного больше.

<?php
/*
=====================================================
 https://webarmen.com/
-----------------------------------------------------
 Copyright (c) 2018
=====================================================
 Данный код защищен авторскими правами
=====================================================
 Файл: table.php
-----------------------------------------------------
 Назначение: работа с бд
=====================================================
*/
class Table {
    private $conn;

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

    function read_table( $id = 0, $table, $push = array(), $table_left, $table_left_param ) {
        if( $table_left AND $table_left_param ) {
            if( $id ) $where = " WHERE {$table}.ID = :id";
            $query = "SELECT {$table}.*, $table_left_param
                        FROM {$table}
                        LEFT JOIN {$table_left} ON {$table}.id = {$table_left}.id{$where}";
        } else {
            if( $id ) $where = " WHERE ID = :id";
            $query = "SELECT * FROM " . $table . $where;
        }

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

        if( $id ) {
            $id = intval($id);
            $stmt->bindParam(":id", $id);
        }

        $stmt->execute();

        while ( $row = $stmt->fetch(PDO::FETCH_ASSOC) ) {
            $product_item = array();

            extract($row);

            foreach ( $row as $name => $value ) {
                if( ($table_left AND $table_left_param) AND strpos($name, $table_left) !== false ) {
                    $name = str_replace($table_left, "", $name);
                    $item = array( $name => $value );
                    if( !is_array($product_item[$table_left]) ) $product_item[$table_left] = array();
                    $product_item[$table_left] = array_merge($product_item[$table_left], $item);
                } else {
                    $item = array( $name => $value );
                    $product_item = array_merge($product_item, $item);
                }
            }

            array_push($push, $product_item);
        }

        return $push;
    }

    function select_Session( $userEmail, $sessionId ) {
        $query = "SELECT a.id, a.max_users, b.id as user_id, b.email, c.session_id, COALESCE(count_session, 0) AS count_session
                    FROM Session a
                    left join Users b on b.email = :userEmail
                    left join Session_record c on c.user_id = b.id AND c.session_id = :sessionId
                    left join
                         (
                          SELECT id, session_id, COUNT(*) AS count_session
                          FROM Session_record
                         ) cou ON cou.session_id = :sessionId
                    WHERE a.id = :sessionId";

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

        $sessionId = intval($sessionId);
        $userEmail = htmlspecialchars(strip_tags(trim($userEmail)));

        $stmt->bindParam(':sessionId', $sessionId);
        $stmt->bindParam(':userEmail', $userEmail);

        $stmt->execute();

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

        return $stmt;
    }

    function create_session_record( $user_id, $sessionId ) {
        $query = "INSERT INTO Session_record SET user_id = :user_id, session_id = :sessionId";

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

        $user_id   = intval($user_id);
        $sessionId = intval($sessionId);

        $stmt->bindParam(":user_id", $user_id);
        $stmt->bindParam(":sessionId", $sessionId);

        if($stmt->execute()){
            return true;
        }
        return false;
    }

    function select_user( $userEmail ) {
        $query = "SELECT
                id
                    FROM
                Participant
                   WHERE
                Email = :userEmail LIMIT 0,1";

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

        $userEmail = htmlspecialchars(strip_tags(trim($userEmail)));

        $stmt->bindParam(":userEmail", $userEmail);

        $stmt->execute();

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

        return $stmt;
    }

    function duplicate_check( $user_id, $newsTitle, $newsMessage ) {
        $query = "SELECT ID FROM News WHERE ParticipantId = :user_id AND newsTitle = :newsTitle AND newsMessage = :newsMessage LIMIT 0,1";

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

        $user_id     = intval($user_id);
        $newsTitle   = htmlspecialchars(strip_tags(trim($newsTitle)));
        $newsMessage = htmlspecialchars(trim($newsMessage));

        $stmt->bindParam(":user_id", $user_id);
        $stmt->bindParam(":newsTitle", $newsTitle);
        $stmt->bindParam(":newsMessage", $newsMessage);

        $stmt->execute();

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

        return $stmt;
    }

    function create_news( $user_id, $newsTitle, $newsMessage ) {
        $query = "INSERT INTO News SET ParticipantId = :user_id, newsTitle = :newsTitle, newsMessage = :newsMessage";

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

        $user_id     = intval($user_id);
        $newsTitle   = htmlspecialchars(strip_tags(trim($newsTitle)));
        $newsMessage = htmlspecialchars(trim($newsMessage));

        $stmt->bindParam(":user_id", $user_id);
        $stmt->bindParam(":newsTitle", $newsTitle);
        $stmt->bindParam(":newsMessage", $newsMessage);

        if($stmt->execute()){
            return true;
        }
        return false;
    }
}
?>

Конфиг для метода Table

<?php
/*
=====================================================
 https://webarmen.com/
-----------------------------------------------------
 Copyright (c) 2018
=====================================================
 Данный код защищен авторскими правами
=====================================================
 Файл: config.php
-----------------------------------------------------
 Назначение: Настройка вывода доступных таблиц, для метода table => read
=====================================================
*/

//Таблицы с открытым доступом
$table_config = array("News", "Session");
//Поиск дополнительных таблиц left join
$table_left_config = array("Session" => "Speaker");
//Поля дополнительных таблиц left join
$table_left_param_config = array("Speaker" => "Speaker.ID AS SpeakerID, Speaker.Name AS SpeakerName");
?>

Метод Table/read.php

Нужно реализовать доступность к любой таблице базы данных и в случае чего, быстро открыть доступ к другой. Ко всему прочему, две таблицы могут быть объединены по id.

<?php
/*
=====================================================
 https://webarmen.com/
-----------------------------------------------------
 Copyright (c) 2018
=====================================================
 Данный код защищен авторскими правами
=====================================================
 Файл: read.php
-----------------------------------------------------
 Назначение: вывод данных из таблиц
=====================================================
*/
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 '../config/config.php';
include_once '../shared/utilities.php';
include_once '../objects/table.php';

$database =  new Database();
$db = $database->getConnection();
$utilities = new Utilities();
$table =     new Table($db);

$data         = json_decode(file_get_contents("php://input"));
$id           = intval($data->id);
$table_mysql  = htmlspecialchars(strip_tags(trim($data->table)));

if( !in_array($table_mysql, $table_config) ) $utilities->error("Данная таблица не доступна, пожалуйста посмотрите доступные таблицы ".json_encode($table_config));

$table_left = $table_left_config[$table_mysql];
$table_left_param = $table_left_param_config[$table_left];

$products_arr            = array("status" => "ok");
$products_arr["payload"] = array();
$products_arr["payload"] = $table->read_table($id, $table_mysql, $products_arr["payload"], $table_left, $table_left_param);
if( !$products_arr["payload"] ) $utilities->error("no data in table");

echo json_encode($products_arr);
?>

Метод SessionSubscribe/create.php

Запись на сессию.

<?php
/*
=====================================================
 https://webarmen.com/
-----------------------------------------------------
 Copyright (c) 2018
=====================================================
 Данный код защищен авторскими правами
=====================================================
 Файл: create.php
-----------------------------------------------------
 Назначение: запись на сессию
=====================================================
*/
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 '../config/config.php';
include_once '../shared/utilities.php';
include_once '../objects/table.php';

$database =  new Database();
$db = $database->getConnection();
$utilities = new Utilities();
$table =     new Table($db);

$data         = json_decode(file_get_contents("php://input"));
$sessionId    = intval($data->sessionId);
$userEmail    = htmlspecialchars(strip_tags(trim($data->userEmail)));

if( !$sessionId OR !$userEmail ) $utilities->error("no POST data");

$row = $table->select_Session($userEmail, $sessionId);

if( !$row ) $utilities->error("Не найдена сессия");
if( !$row['email'] ) $utilities->error("Пользователь не найден");
if( $row['session_id'] ) $utilities->error("Вы уже записаны");
if( $row['max_users'] <= $row['count_session'] ) {
    die(json_encode(
        array(
                "status" => "ok",
                "message" => "Извините, все места заняты"
            )
    ));
}

if( $table->create_session_record($row['user_id'], $sessionId) ) {
    echo json_encode(
        array(
            "status" => "ok",
            "messege" => "Спасибо, вы успешно записаны!"
        )
    );
} else {
    $utilities->error("error sql add");
}
?>

Метод PostNews/create.php

Добавляем новость в базу.

<?php
/*
=====================================================
 https://webarmen.com/
-----------------------------------------------------
 Copyright (c) 2018
=====================================================
 Данный код защищен авторскими правами
=====================================================
 Файл: create.php
-----------------------------------------------------
 Назначение: публикация новости
=====================================================
*/
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 '../config/config.php';
include_once '../shared/utilities.php';
include_once '../objects/table.php';

$database =  new Database();
$db = $database->getConnection();
$utilities = new Utilities();
$table =     new Table($db);

$data         = json_decode(file_get_contents("php://input"));
$userEmail    = htmlspecialchars(strip_tags(trim($data->userEmail)));
$newsTitle    = htmlspecialchars(strip_tags(trim($data->newsTitle)));
$newsMessage  = htmlspecialchars(trim($data->newsMessage));

if( !$userEmail OR !$newsTitle OR !$newsMessage ) $utilities->error("no POST data");

$us = $table->select_user($userEmail);
if( !$us['id'] ) $utilities->error("Пользователь не найден");

$dc = $table->duplicate_check($us['id'], $newsTitle, $newsMessage);
if( $dc['ID'] ) $utilities->error("Повторная новость");

if( $table->create_news($us['id'], $newsTitle, $newsMessage) ) {
    echo json_encode(
        array(
            "status" => "ok",
            "messege" => "Спасибо, ваша новость сохранена!"
        )
    );
} else {
    $utilities->error("error sql add");
}
?>

Вспомогательный класс, у меня он отображал ошибки

<?php
/*
=====================================================
 https://webarmen.com/
-----------------------------------------------------
 Copyright (c) 2018
=====================================================
 Данный код защищен авторскими правами
=====================================================
 Файл: utilities.php
-----------------------------------------------------
 Назначение: вспомогательные функции
=====================================================
*/
class Utilities {

    public function error( $massage ) {
        $text = array(
            "status"   =>  "error",
            "message"  =>  $massage
        );

        die(json_encode($text));
    }

}
?>

Весь код в рабочем вариант выложен на Github и Bitbucket

Скачать “api.zip” ver 1 Скачано 16 раз – 12 KB – md5 57d979bc5ec2435afdcea0e41588d37c

Занимаюсь сайтами с 2005 года, начинал еще с narod.ru и ucoz :-)

Оставить комментарий
Нажимая кнопку «Отправить», я принимаю пользовательское соглашение и подтверждаю, что ознакомлен и согласен с политикой конфиденциальности этого сайта.