Организация универсального обработчика Ajax запросов

Этот сниппет обработчика ajax запросов я часто использую в своих проектах.

Представим себе такую задачу: необходимо реализовать CRUD (create, update, delete) операции элементов инфоблока Битрикс. При этом сами операции должны выполняться от имени такого пользователя, который:

  • авторизован на сайте
  • принадлежит определённой группе пользователей

Операции должны инициироваться POST обращением по Ajax к определённому скрипту (обработчику аякс запроса)

Решение

С одной стороны можно нагородить отдельные php скрипты-обработчики вида

  • create.php
  • update.php
  • delete.php

Но в этом случае мы столкнёмся с дублированием кода в каждом скрипте, т.к. помимо самой операции нужно:

  • проверять, авторизован ли пользователь
  • проверить, принадлежит ли он той группе пользователей, которая требуется
  • как-то сообщать юзеру, что операция выполнена с ошибкой, если таковая появится

Именно поэтому вместо такой реализации можно применить следующую идею:

Реализовать единый обработчик, который на входе будет принимать:

  • идентификатор метода (требуемой операции)
  • значения параметров, необходимых для выполнения операции

На выходе — массив данных, состоящий из двух элементов:

  • мета информация в случае успешности выполнения операции
  • информация о наличии ошибки и её текстовая расшифровка

Примерный вид такого обработчика будет следующий:

<?php
header('Content-Type: application/json'); 

require_once($_SERVER['DOCUMENT_ROOT'] . "/bitrix/modules/main/include/prolog_before.php");

class CRUD_IBlock {
    
    //ID инфоблока с которым работаем
    protected $iblock_id = 56; 
    //ID группы пользователя
    protected $user_group = 36;
    //текстовая информация по умолчанию на выходе - пустая
    protected $data = '';
    //ошибка выполнения операции по умолчания - есть. её текстовая расшифровка - пустая
    protected $error = ['status' => true, 'error_text'=>''];
            
    function __construct() {
        //если требуемый метод присутствует в нашем классе        
        if (method_exists($this, $_POST['method'])){
            global $USER;
            //проверяем, авторизован ли пользователь и принадлежит ли он требуемой группе пользователей
            if ($USER->IsAuthorized() && in_array($this->user_group, $USER->GetUserGroupArray())){
                //подключаем модуль работы с инфоблоками
                CModule::IncludeModule("iblock");
                //преобразовываем спец. символы в HTML сущности для защиты от SQL инъекций
                $operation_data = [];
                if (is_array($_POST['data'])){
                    foreach ($_POST['data'] as $key=>$value) {
                        $operation_data[htmlspecialcharsbx($key)] = htmlspecialcharsbx($value);
                    }
                }
                $method = (string)$_POST['method'];
                //вызываем метод c HTML безопасным представлением данных для выполнения операции
                $this->$method($operation_data);
            } else {
                //если пользователь не авторизован или не принадлежит нужной группе пользователей
                $this->error['error_text'] = 'Пожалуйста, авторизуйтесь под своей учетной записью.';
            }
        } else {
            //если вызвали несуществующий метод
            $this->error['error_text'] = 'Метод "'.$_POST['method'].'" отсутствует в классе CRUD_IBlock';
        }
        //отпрвляем JSON с данными
        echo json_encode(['data'=>$this->data, 'error'=>$this->error]);
    }

    protected function Create($operation_data){
        // создаём элемент. данные для создания берём из массива $operation_data
        // ...
        // успешность создания элемента записываем в $result
        if ($result){
            $this->error['status'] = false;
            $this->data = 'Элемент успешно создан';
        } else {
            $this->error['message'] = 'Описание причины невозможности создать элемент';
        }
    }

    protected function Update($operation_data){
        // обновляем элемент. данные для обновления берём из массива $operation_data
        // ...
        // успешность обновленния элемента записываем в $result
        if ($result){
            $this->error['status'] = false;
            $this->data = 'Элемент успешно обновлен';
        } else {
            $this->error['message'] = 'Описание причины невозможности обновить элемент';
        }
    }

    protected function Delete($operation_data){
        // удаляем элемент. данные для удаления берём из массива $operation_data
        // ...
        // успешность удаления элемента записываем в $result
        if ($result){
            $this->error['status'] = false;
            $this->data = 'Элемент успешно удален';
        } else {
            $this->error['message'] = 'Описание причины невозможности удалить элемент';
        }
    } 
}

$crud = new CRUD_IBlock();

Если оперировать вышеприведённым кодом, то на вход подаём:

  • method : название метода необходимой операции
  • data: данные, требуемые для выполнения операции

На выходе получаем:

  • data: мета данные после успешного выполнения операции
  • error: массив из двух элементов — первый с ключем status: есть ли ошибка, второй с ключем message: текстовая расшифровка ошибки

Как пользоваться

Если использовать JQuery ajax запрос, то его примерный общий вид будет следующий:

let data = {
    //какие-то данные для выполнения операции    
};

$.ajax({
        url:'url_обработчика',
        type:"POST",
        dataType: 'json',
        data: {
            method: 'название_метода',
            data: data  
        },
        success: function(result){
            //операция выполнена успешно
            if (!result.error.status){
                //мета данные в результате успешного выполнения операции
                console.log (result.data);
            } 
            //операция выполнена с ошибкой
            else {
                //расшифровка ошибки
                console.log (result.error.message);
            }
        }
})        

Собственно вот и вся магия.