Pull to refresh

Интерфейс Javascript < == > PHP

Reading time 7 min
Views 11K
Удивишись, что мой хабраюмор хабралюди понимают с трудом, перешел к написанию ещё одного интересного хабратопика. :)

Хочу предложить один удобный метод для взаимодействия Javascript с PHP.

Можно в PHP добавить класс, с возможность 'удаленного' запуска оттуда функций с параметрами.

Передача параметров

Каким образом лучше всего передать функции на вход PHP?
Наиболее подходящий способ — с помощью массива.
Как передать массив? Самый простой и наиболее оптимальный метод (учитывая, что мы имеем дело с Javascript) — это JSON. Передача параметров на запуск будет выглядеть, например:
{
«init_timer»:[],
«savedb»:[200],
«check_timer»:[]
}
что соответствует:
init_timer();
savedb(200);
check_timer();
Преобразовать это в массив на стороне PHP можно очень просто — с помощью json_decode. Осталось только разобрать полученный массив по полочкам, а полученный результат вывести обратно в виде JSON клиенту.

User Interface

Реализовал я это с помощью PHP-класса UserInterface.
class UserInterface{
  private $if;
  public $result=array();
  
  function add($groupname,$if){
    $this->if[$groupname][]=$if;
  }
  
  function __call($name, $args){
    if (isset($this->if[$name]))
      foreach ($this->if[$name] as $func)
        !is_array($res=&call_user_func_array($func,$args)) ||
            $this->result=array_merge_recursive($this->result,$res);
  }
  
  function run($info){
    if (is_array($info))
      foreach ($info as $func=>$parameters)
        !is_array($res=&call_user_func_array(array(&$this, $func),$parameters)) ||
            $this->result=array_merge_recursive($this->result,$res);
  }
}
* This source code was highlighted with Source Code Highlighter.
В этот класс мы будем добавлять новые методы (функции), а затем с помощью небольшой доработки, научим Javascript «видеть» и запускать их, получая результаты их работы.

Класс имеет следующие функции:
$this->add({название метода},{функциия}) позволяет добавляет новый метод. Если метод существует, добавляет в него ещё одну функцию.
$this->{название метода}({параметры}) идёт вызов всех добавленных функций в методе.
$this->run({массив}) запускает последовательно на выполнение несколько методов, используя параметры из массива.

Сразу сделаю ремарочку для непрофессионалов:
  !is_array($res=&call_user_func_array($func,$args)) ||   $result=array_merge_recursive($result,$res);
равносильно
  $res=&call_user_func_array($func,$args);
  if(is_array($res))
      $result=array_merge_recursive($result,$res);


Пример:
Необходимо создать интерфейс работы с БД с получением информации.
Для начала инициализируем интерфейс, которым будем пользоваться:
$UImethod = new UserInterface();
Добавляем группу, которая должна добавлять информацию в БД (savedb):
class db{
  static function save($num){
    //Функция для записи в БД
    //$num->...
  }

  static function load($num){
    //Функция для загрузки из БД
    //$db<-...
  return array(«DBresult»=>$db);
  }
}

$UImethod->add('savedb','db::save');
$UImethod->add('loaddb','db::load');
Запуск будет осуществляться с помощью функции:
$UImethod->savedb($xxx);
При этом, если вдруг нам потребуется добавить модуль создания логов, то обработку можно легко добавить в эту же группу:
class log{
  static function add_savelog($num){
    //Добавить новый лог
    //...
  }
}

$UImethod->add('savedb','log::add_savelog');
Теперь будет происходить последовательный запуск обеих функций, при запуске группы 'savedb'.

Работа Javascript c классом UserInterface

Сначала хочу немного остановиться на методе run. Это нам поможет в заимодействии.
Метод run позволяет запускать несколько групп с разными переметрами последовательно. Принимает названия групп он в виде массива:
$a=array(
  «savedb»=>array(200)
  «loaddb»=>array(200)
)
Тогда, запустив $UImethod->run($a); можно сохранить в базу данных — 200, затем её считать с помощью 'loaddb'.

Вы наверное заметили, что db::load возвращает результаты в виде массива. Эти результаты инкапсулируются(добавляются) или простыми словами попадают в общий массив $this->result. В результате, мы будем использовать этот массив, как ответ JavaScript'у на запрос.

Тоесть, например, запустив loaddb(200) и loaddb(300) — мы получим на выходе массив, имеющий заполненные поля: 'DBresult'=>('200'=>… и '300'=>...).

Ну а теперь самое главное — это реализвация декодирования (на вход) и кодирования (на выход) JSON массивов в PHP, с запуском вызываеммых с помощью Javascript методов:
if (!isset($_REQUEST['method']))
  die();
$functions=json_decode($_REQUEST['method'],TRUE);
$UImethod->run($functions);
echo json_encode($UImethod->result);

Запуск методов из JavaScript

Добрались до сладкого.
<html><head></head><body>
<script type=«text/javascript»>
  //Функция, реализующая AJAX запрос на сервер
  //Параметры URL, JS-функция, дополнительные параметры, [следующий URL, JS-func...]
  function ajax_load(url,parcer,par,next){
    var req, ab, done=0;
    this.request=function(){
     if (window.XMLHttpRequest)
       ajaxRequest = new XMLHttpRequest();
     else if (window.ActiveXObject){
       ajaxRequest = new ActiveXObject(«Msxml2.XMLHTTP»);
       if (!ajaxRequest)
        ajaxRequest = new ActiveXObject(«Microsoft.XMLHTTP»);
     }
     return ajaxRequest;
    }
      
    this.ReqStatus=function() {
     if (req.readyState == 4) {
       clearTimeout(ab);
       if (req.status == 200){
        parcer(req,par);
        if (next)
          ajax_load(next[0],next[1],next[2],next[3]);
       } else
        alert(«AJAX Error: \n» + req.statusText + "\n" + url);
     }
    }
  
    if (!url)
       return parcer(par);
   
    req=this.request();
    req.onreadystatechange = this.ReqStatus;
    req.open(«GET», url, true);
    req.send(null);
    ab = window.setTimeout(function(){req.abort();}, 10000);
    return req;
  }
  
  //Функция, которая кодирует запрос в JSON формат (func(param) -> «func»:[param]) и отсылает его на сервер (делая запрос «JSON.php?method=...»)
  function server_eval(mod,ret){
    mod=mod.replace(/\s*([\w\d_]+)\s*\(([\w\d_. \\,\[\]"]*)\)\s*;?/gi,'"$1":[$2],').replace("'",'"');
    mod='{'+mod.substr(0,mod.length-1)+'}';
    ajax_load("
JSON.php?method="+mod,ret,null);
  }
  
  //Обработчик запроса
  function time_result(res){
    db=eval('(' + res.responseText + ')');
    document.write(db['DBresult']);
  }
  
  //Запрос на сервер с обработчиком ответа time_result
  server_eval('savedb(200);loaddb(200);loaddb(300);',time_result);
</script>
</body></html>
* This source code was highlighted with Source Code Highlighter.
Собственно и всё.

server_eval посылает запрос в UserInterface файла JSON.php и при получении ответа, запускает функцию time_result, передавая ответ в параметрах. При этом можно осуществлять совершенно различные запросы в интерфесе и обрабатывать результаты, как душе будет угодно.

Для чего это можно использовать


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

Примеры:
1) Опять вернусь к разработке космической игры.
Игрок выбрал кораблю другую цель. Идёт передача выбора цели. «ship_new_target(180,320);return_space_objects(1220,1334);». 180- корабль, а 320- новая цель.
Новая цель может оказаться чем угодно. В случае, если это корабль, нам необходимо будет выделить её как врага. Если это станция, мы выделим её как станцию и автоматически пошлём корабль лететь на неё. Если это планета, тогда мы её выделять вообще не будем.
При этом мы можем добавить любой новый тип объекта не меняя кода, а просто добавив необходимые вызовы и как на них необходимо реагировать, когда в них будет необходимость.
Кстати говоря, функция ship_new_target ничего не возвращает. А вот функция return_space_objects занимается тем, что создаёт в общем массиве массив объектов, лежащих рядом с координатами (1220,1334). При этом — опять-же, обрабатываются, например порталы и добавляются в массив (используя некоторые правила). Затем, корабли, станции, вызывая последовательно функции метода return_space_objects и добавляя результаты DB-выборок в общий массив.
Таким образом мне проще общаться со всеми объектами и код у меня не разбросан по php-файлам, а лежит каждый участок в своём месте.

2) Форум.
Простой пример — У каждого поста есть свой ID. У каждой темы тоже есть свой ID. Они не пересекаются.
При любом ответе, вызывается функции 'add_reply(12345,«comment»);return_topic(12345);'.
В add_reply имеется несколько функций — функция добавление к топику, функция добавления к комментарию.
Если ID (12345), к которому прикрепляется сообщение, является новым топиком- тогда мы используем одну функцию, добавляя это сообщение, как топик. Если ID является комментарием пользователя, тогда мы используем совсем другую функцию, которая прикрепляет комментарий к нему.
Затем, простым добавлением библиотеки, можно сделать добавление комментариев в чём угодно, например к картинкам, к личным сообщениям (если друг имеет ID, который не пересекается с другими топиками, комментариями) итд.
Метод return_topic тоже работает так-же. Если ID топика, тогда мы возвращаем топик с комментариями. Если это ID комментария — то только комментарий с его вложениями. Тоже самое можно сделать и для использования картинок, музыки и всего, чего душе угодно.

Возврат ошибок, кстати, тоже возможен — используя, например 'ERROR', в который просто добавлять ошибки. При нескольких ошибках он будет выглядеть примерно так:
{«ERROR»:[«Не достаточно знаков»,«Не присутствует заголовок», итд]}.

P.S.

Можно, конечно, сделать и eval($_REQUEST['method']), но рассмотренный мною способ является более надежным в плане безопасности. :)
Tags:
Hubs:
+4
Comments 21
Comments Comments 21

Articles