Использование JSON в web приложениях. Обработка данных PHP скрипта.

Владимир | | Ajax, JavaScript, PHP.

В предыдущей статье я обещал показать пример использования JSON в реальном web приложении. А обещания, как известно, нужно выполнять, тем более такие простые 🙂 . К тому же, уже написано очень подходящее для этого примера web приложение.

Приложение, о котором идет речь, используется для тестирования регулярных выражений. Подробно о его структуре и работе я писал в статье «Тестирование регулярных выражений». Точнее, это был цикл из трех статей (часть 1, часть 2, часть 3).

Сейчас я только кратко напомню его принцип работы. Приложение состоит из четырех файлов.
index.html – страница с формой.
scripts.js – скрипт с функцией, которая выполняет ajax запрос.
prototype.js – файл с библиотекой prototype (используется для упрощения работы с JavaScript).
analyzer.php – серверная часть приложения (php скрипт), выполняет поиск в тексте по заданному регулярному выражению и возвращает результат, который вставляется в index.html между тегами <div id=”results”>…</div>.

На данный момент результат работы скрипта (analyzer.php) представляет собой строку, содержащую кроме результатов поиска еще и html разметку, т.е. полностью подготовленную к вставке в страницу.

Такой подход имеет свои преимущества. Например, максимально сокращается количество JavaScript кода. Но, если мы захотим изменить дизайн приложения или порядок вывода результатов, то придется изменять серверную часть (analyzer.php). Теперь представьте, что мы имеем несколько страниц (например, на разных языках), которые должны использовать analyzer.php. Представили? Думаю, никому не понравится идея поддерживать несколько версий одного и того же скрипта.

Чтобы избавиться от всех этих недостатков, мы перенесем всю работу, касающуюся разметки страницы в клиентскую часть приложения, а серверная часть будет возвращать только результаты обработки текста.
При этом чтобы сохранить «чистоту эксперимента» обеспечим неизменный внешний вид приложения, т.е. пользователь не должен заметить отличий новой версии от предыдущей.

Передавать данные будем в формате JSON, т.к. существуют удобные функции для преобразования объектов PHP и JS в него и обратно.

Заранее результаты работы скрипта неизвестны, поэтому мы будем записывать результаты поиска в ассоциативный массив. Рассмотрим этот массив подробнее. Он содержит следующие поля:

$results['matches_num'] //количество найденных совпадений
$results['matches'] //массив с результатами поиска
$results['err_message'] //текст сообщения об ошибке
$results['counter_value'] //текущее значение счетчика

Теперь посмотрим, как изменится серверная часть приложения (analyzer.php).

$t = null;
$r = null;
$c = null;
//создаем массив с результатами поиска
$results = array();
if (isset($_POST['exp']) && isset($_POST['text'])
		&& isset($_POST['textcase']) && ($_POST['exp'] != '')
		&& ($_POST['text'] != '')) {
	$t = $_POST['text'];
	$r = $_POST['exp'];
	$c = $_POST['textcase'];
	//убираем двойные слеши если они были добавлены автоматически
	$r = stripslashes($r);
	$t = stripslashes($t);
	//если не нужно учитывать регистр, преобразуем все в нижний регистр
	if ($c == "false") {
		$t = mb_convert_case($t, MB_CASE_LOWER, "UTF-8");
		$r = mb_convert_case($r, MB_CASE_LOWER, "UTF-8");
	}
	//проверяем, есть ли слеши
	$forwardSlash = "/^/.*/i";
	$backwardSlash = "/.*/.?$/i";
	if (preg_match($forwardSlash, $r) == FALSE) {
		$r = "/".$r;
	}
	if (preg_match($backwardSlash, $r) == FALSE) {
		$r = $r."/";
	}
	//ищем все совпадения текста заданному выражению
	$res = null;
	$n = FALSE;
	$n = preg_match_all($r, $t, $res);
	//если совпадения найдены, выводим результат
	if ($n != FALSE) {
		$results['matches_num'] = $n;
		$results['matches'] = $res;
	}
	else {
		$results['matches_num'] = 0;
	}
}
else {
	$results['err_message'] = "Нужно задать регулярное выражение и текст для поиска";
}
//считаем количество вызовов скрипта
if (!file_exists('counter.txt')) {
	touch('counter.txt');
	$fh = fopen('counter.txt', 'r+');
	fwrite($fh, '1');
	fclose($fh);
	$results['counter_value'] = 1;
}
else {
	$fh = fopen('counter.txt', 'r+');
	$counter = fgets($fh);
	$counter += 1;
	rewind($fh);
	fwrite($fh, (string)$counter);
	fclose($fh);
	$results['counter_value'] = $counter;
}
echo json_encode($results);

Если вы сравните этот файл с его предыдущей версией, то заметите, что вместо отправки отформатированных результатов (строки, начинающиеся с echo …), мы заполняем данными наш массив. В последней строке мы преобразовываем массив в формат JSON и отправляем клиентской части (scripts.js).

В целом, количество кода в analyzer.php сократилось за счет отсутствия html разметки. Также сократилась длина передаваемой строки.

Теперь перейдем к клиентской части нашего приложения.
Сраница с формой (index.html) остается без изменений. Здесь я ее приводить не буду, смотрите статью «Тестирование регулярных выражений (клиентская часть)».

А вот в файле scripts.js произошли существенные изменения.

function analyseText() {
    //читаем значения введенные в поля
    var e = $('regexp').value;
    var t = $('searchText').value;
    var c = $('case').checked;
    //формируем строку с параметрами запроса
    var pars = $H({exp:e, text:t, textcase:c}).toQueryString();
    //отправляем ajax запрос
    new Ajax.Request("analyser.php",
            {method:"post", parameters:pars, onSuccess:parseResponse});
}

function parseResponse(transport) {
    var data = eval('(' + transport.responseText + ')');
    if (data.err_message == null) {
        if (data.matches_num > 0) {
            $('results').innerHTML = "<span class="bold">Найдено совпадений: "
                    + data.matches_num + "</span><br />";
            data.matches.each(
                function(value, index) {
                    if (index != 0) {
                        $('results').innerHTML += "Совпадения с подмаской "
                            + index + "<br />";
                    }
                    for (j = 0; j < value.length; j++) {
                        $('results').innerHTML += value[j] + "<br />";
                    }
                }
            );
        }
        else {
            $('results').innerHTML = "Совпадения не найдены";
        }
    }
    else {
        $('results').innerHTML = data.err_message;
    }
    $('results').innerHTML += "<p><small>Количество вызовов программы: "
        + data.counter_value + "</p></small>";
}

Прежде всего, обратите внимание на функцию analyseText(). В предыдущей версии отправка ajax запроса выполнялась так:

new Ajax.Updater("results", "analyser.php", {method:"post", parameters:pars});

Этот метод нам не подходит, т.к. он вставляет в страницу результат без обработки. Поэтому в этот раз мы используем:

new Ajax.Request("analyser.php", {method:"post", parameters:pars, onSuccess:parseResponse});

Запрос отправляется точно также, но результат передается функции, указанной в параметре onSuccess, т.е. parseResponse.
Примечание: здесь используются методы класса Ajax из библиотеки Prototype.

Эта функция (строки 13-40) преобразует строку JSON в объект JavaScript (строка 14), форматирует результат и вставляет его в блок <div id=”results”>.
После преобразования строки в объект JavaScript, мы можем получить доступ к данным, используя свойства этого объекта. Названия свойств совпадают с именами ключей в ассоциативном массиве, сформированном в analyzer.php.

Разберем подробнее работу этой функции. В строке 15 мы проверяем свойство err_message, если оно не установлено, то продолжаем обработку, в противном случае выводим сообщение об ошибке (строка 36).

После этого проверяем количество совпадений (поле matches_num). Если совпадения найдены, то выводим содержимое массива, который находится в свойстве matches (строки 19-29). Для вывода массива мы используем возможности, предоставляемые библиотекой prototype, в данном случае метод each(). В этом методе мы определили анонимную функцию (строки 20-28), которая вызывается для каждого элемента массива.
Т.к. каждый элемент массива matches содержит результаты поиска для одной из подмасок регулярного выражения, то для вывода результатов мы используем вложенный цикл (строки 25-27).

Вы, наверно, заметили, что при вставке содержимого массивов в страницу используется функция $('results'). Это функция входит в состав prototype и её работа аналогична document.getElementById('results').

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

Вы можете скачать архив с файлами приложения.

Постовой

Не хотите мерзнуть? Используйте современный теплоизоляционный материал

  • Amber

    хорошая статья. популярный метод — то что нужно! спасибо автору.

  • Amber

    хорошая статья. популярный метод — то что нужно! спасибо автору.

  • Всегда пожалуйста 🙂

  • Всегда пожалуйста 🙂

  • roman

    А в чем гибкость то?
    Сначала дизайн был на серверной стороне потом перенесли на клиентскую.

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

  • roman

    А в чем гибкость то?
    Сначала дизайн был на серверной стороне потом перенесли на клиентскую.

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

  • Pingback: Обзоры, статьи, версии спецификации языка ECMAScript » Упрощаем разработку сайтов с JavaScript()

  • Vii

    Не хочу придераться, но зачем на клиенте так часто обрабатывать $()? Не лучше ли в самом начале написать что-то вроде var id = $()?

    • Наверное вы правы. Ваш вариант будет работать быстрее.
      Но с $() читается легче, к тому же большинство повторов находятся внутри операторов if … else, поэтому реально будут выполнены 2-3 вызова $().

  • Vii

    Не хочу придераться, но зачем на клиенте так часто обрабатывать $()? Не лучше ли в самом начале написать что-то вроде var id = $()?

    • Наверное вы правы. Ваш вариант будет работать быстрее.
      Но с $() читается легче, к тому же большинство повторов находятся внутри операторов if … else, поэтому реально будут выполнены 2-3 вызова $().

  • Vii

    Это, наверное одно из самых «забавных» мест в prototype — у php-программистов $() ассоциируется с обычной переменной (вольно-пересказанная цитата ))

    А вообще я конкретно гаворил про использование $() внутри цикла.

    • Да, в цикле пожалуй ее нужно было бы убрать.
      А вот название функции может и «забавное», может и путаница возникает, но зато оно короткое и легко запоминается, а это очень важно. Особенно если функция так часто используется.

  • Vii

    Это, наверное одно из самых «забавных» мест в prototype — у php-программистов $() ассоциируется с обычной переменной (вольно-пересказанная цитата ))

    А вообще я конкретно гаворил про использование $() внутри цикла.

    • Да, в цикле пожалуй ее нужно было бы убрать.
      А вот название функции может и «забавное», может и путаница возникает, но зато оно короткое и легко запоминается, а это очень важно. Особенно если функция так часто используется.

  • $() классная штука

    но я делаю для сокращения телодвижений так:

    function dgebi(var1){
    return document.getElementById(var1);
    }

    dgebi('id');

    • а за счет чего телодвижения сокращаются?

      $(«#id») короче чем dgebi('id');

  • $() классная штука

    но я делаю для сокращения телодвижений так:

    function dgebi(var1){
    return document.getElementById(var1);
    }

    dgebi('id');

    • а за счет чего телодвижения сокращаются?

      $(«#id») короче чем dgebi('id');

  • а за счет чего телодвижения сокращаются?
    $(«#id») короче чем dgebi('id');

    > $() — не всегда же используется prototype.js 🙂
    именно там

    function $(element) {
    if (arguments.length > 1) {
    for (var i = 0, elements = [], length = arguments.length; i < length; i++)
    elements.push($(arguments[i]));
    return elements;
    }
    if (Object.isString(element))
    element = document.getElementById(element);
    return Element.extend(element);
    }

    • Если prototype кажется тяжелым, можно использовать jquery 🙂

  • а за счет чего телодвижения сокращаются?
    $(«#id») короче чем dgebi('id');

    > $() — не всегда же используется prototype.js 🙂
    именно там

    function $(element) {
    if (arguments.length > 1) {
    for (var i = 0, elements = [], length = arguments.length; i < length; i++)
    elements.push($(arguments[i]));
    return elements;
    }
    if (Object.isString(element))
    element = document.getElementById(element);
    return Element.extend(element);
    }

    • Если prototype кажется тяжелым, можно использовать jquery 🙂

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

    jquery попробую, спасибо за совет

    • сделать вообще без сторонних библиотек, чтобы код летал

      Это замечательно, но есть один нюанс. JS код нужно писать с учетом особенностей разных браузеров. А из-за них код разбухает очень быстро. Например, стандартный пример отправки простого AJAX запроса занимает строк 80 (если я не ошибаюсь), а все из-за того, что в IE и FF отправку выполняют разные объекты.
      Конечно, если нужно просто подсветить элемент или сделать выпадающее меню, то грузить библиотеку, наверное, не стоит. Но как если понадобятся более сложные эффекты и/или AJAX, то с библиотекой время разработки сокращается в разы. А сама библиотека не так уж много и весит, ведь практически все современные браузеры работают с сжатыми (zip) файлами. jQuery в таком виде весит 16кБ. Я даже писал пост на эту тему 😉

      • ццц

        бугагага)
        ajax можно отправить в 5 строк чистейшого javascript.

        • Хотелось бы взглянуть на эти 5 строк.

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

    jquery попробую, спасибо за совет

    • сделать вообще без сторонних библиотек, чтобы код летал

      Это замечательно, но есть один нюанс. JS код нужно писать с учетом особенностей разных браузеров. А из-за них код разбухает очень быстро. Например, стандартный пример отправки простого AJAX запроса занимает строк 80 (если я не ошибаюсь), а все из-за того, что в IE и FF отправку выполняют разные объекты.
      Конечно, если нужно просто подсветить элемент или сделать выпадающее меню, то грузить библиотеку, наверное, не стоит. Но как если понадобятся более сложные эффекты и/или AJAX, то с библиотекой время разработки сокращается в разы. А сама библиотека не так уж много и весит, ведь практически все современные браузеры работают с сжатыми (zip) файлами. jQuery в таком виде весит 16кБ. Я даже писал пост на эту тему 😉

      • ццц

        бугагага)
        ajax можно отправить в 5 строк чистейшого javascript.

        • Хотелось бы взглянуть на эти 5 строк.