Безмозглый user info php. Суперглобальный массив $_SERVER. Сохраняем Информацию о Пользователях в БД

Иногда хочется отвлечься от текущей рутины кодирования и от небольших проблем, которым посвящается место в статьях на "деталях". Окинуть взглядом то, что делаешь долгое время. Итак, моё видение подходов к основной задаче php-программирования - генерации веб-страниц. Частично тему эту я уже затрагивал по частям - статьи про класс шаблонов и про XML вы можете найти в архиве. Теперь хотелось бы свести всё это воедино и сделать что-то монументальное. Первая часть.

1. Истоки php

С чего, собственно начался php для большинства из нас? С вставок скрипта в HTML-страницы. К примеру, собственный каталог ссылок. Страница со ссылками из определённой рубрики:

". mysql_result($result, 0). ""; mysql_free_result($result); $result = mysql_query("SELECT id, title, ratio, description, site_date FROM site WHERE rubrika=". intval($id). " ORDER BY ratio DESC, site_date DESC, title"); if (mysql_error()) echo mysql_error(); else while ($row = mysql_fetch_assoc($result)) echo "{$row}
{$row}
Записано: {$row}, рейтинг: {$row}

Просто и незатейливо, но главное, что этот код работает. Сложности начинаются, если попробовать написать название рубрики в заголовке страницы. Для этого придётся создать вставку php внутри тега , перенести туда код, выполняющий первый запрос (выборка имени рубрики), сохранять результат запроса в переменную, которую затем выводить в теле страницы.</p> <p>Следующее осложнение - попытка работать с cookies и выдавать их в зависимости от того, какие рубрики посещает пользователь. Чтобы лучше представить это: пусть мы решили, что на наш хоумпейдж пойдут толпы посетителей, и для потенциальных рекламодателей мы делаем баннерный движок с таргетингом. Скажем, пользователь, посетивший рубрики рок-музыка и музыкальные инструменты, на следующей странице увидит баннер с рекламой магазина электрогитар.</p> <p>В таком случае нам придётся сделать вставку php-кода в самом начале файла и передвинуть туда проверку наличия рубрики, ведь куки можно выдавать только до выдачи первого байта документа.</p> <p><i>Конечно, вы скажете, что это можно легко сделать при помощи механизма пользовательских сессий, что проще с точки зрения выдачи контента и безопаснее, потому что пользователь может вручную менять содержимое кук. Да, это так, но во-первых здесь представляется видение новичка программирования, делающего свою домашнюю страничку, во-вторых речь идёт о далёком прошлом - виртуально мы с вами находимся в 1998 году. </i></p> <p>Не сильно облегчает жизнь и вывод одинаковых блоков HTML- и php-кода в подключаемые файлы. Описанная выше задача - построить простой каталог ссылок с рубриками - пожалуй, верхний предел возможностей данного подхода. При выполнении более сложных задач очень велик риск столкнуться с большими трудозатратами.</p> <p>2. Усложнение кода</p> <p>Логичным развитием предыдущего подхода является полный переход на php в том смысле, что все страницы представляют собой php-код - в начале файла стоит открывающий тег "<?php", и дальше php-код не прерывается. Весь вывод делается через echo (print). Такой код, вроде бы, выглядит логичнее. Пример: страница подписки на новые ссылки в каталоге. В форме предлагается выбрать, на какие рубрики подписывается пользователь.</p><p> <?php include "db-connect.inc"; include "login-check.inc"; if (!$logged_in) { header ("Location: /register.php"); exit(); } include "page-header.inc"; print("<h2> class="club_article_small_caption">Новости для клиентов</h2> <table cellpadding=\"4\" cellspacing=\"0\" border=\"0\">"); $result = mysql_query("SELECT id, title, announce, news_date FROM news ORDER BY news_date DESC LIMIT 10"); if (mysql_error()) print(mysql_error()); else while ($row = mysql_fetch_assoc($result)) print("<tr><td>{$row}"); if ($row["news_date"] > date("Y-m-d", time() - 3600*24*14)) print("<font color=\"#cc0000\">new!</font>"); print("</td><td> <a href=\"read.php?id={$row}\"><b>{$row}</b></a> <br>{$row}</td></tr>"); print("</table>"); include "page-footer.inc"; ?> </p><p> <?php ... else while ($row = mysql_fetch_assoc($result)) print_announce($row); ... ?> </p><p>Но это нисколько не упрощает работу, и час расплаты обязательно наступит: возникнет необходимость переделать дизайн сайта. Дизайнер править такое просто откажется. Только тот, кто написал такой код, сможет в нём разобраться и заменить старый HTML на новый. Если этот человек уже не работает над проектом, то проект придётся переписывать почти с нуля или же он умрёт, превратившись в памятник человеческой недальновидности.</p> <p>Другой вариант развития подхода смешанного HTML и php. У php есть такое свойство как advanced-escaping . Он позволяет внутри языковых конструкций выводить обычный HTML-код:</p><p> <?php include "db-connect.inc"; include "login-check.inc"; if (!$logged_in) { header ("Location: /register.php"); exit(); } include "page-header.inc"; ?><h2 class="club_article_small_caption">Новости для клиентов</h2> <table cellpadding="4" cellspacing="0" border="0"><?php $result = mysql_query("SELECT id, title, announce, news_date FROM news ORDER BY news_date DESC LIMIT 10"); if (mysql_error()) print(mysql_error()); else while ($row = mysql_fetch_assoc($result)) { ?><tr><td><?=$row["news_date"]?><?php if ($row["news_date"] > date("Y-m-d", time() - 3600*24*14)) { ?><font color="#cc0000">new!</font><?php } ?></td><td> "> <b><?=$row["title"]?></b> <br><?=$row["announce"]?></td></tr><?php } ?></table><p>Верстальщик или дизайнер, открыв этот файл в HomeSite, увидят HTML-разметку с подсветкой и вполне смогут редактировать оформление страниц. В простых сайтах такой метод вполне пригоден. В сложных - ни к чёрту не годится.</p> <p>3. Переворот</p> <p>С выходом PHP 3.0... Кстати, не мешало бы восстановить хронологию, раз уж речь идёт о всеобъемлющей истории. Итак, хронология, которую удалось восстановить при помощи архива листа рассылки php announce и страницы из веб-архива :</p><p>08.1997 PHP 3.0 Beta1 06.06.1998 PHP 3.0 Release 19.07.1999 PHP 4.0 Beta1 22.05.2000 PHP 4.0 Release 10.12.2001 PHP 4.1 Release 22.04.2002 PHP 4.2 Release 10.10.2002 PHP 4.3.0pre1 (pre1 - это не бета, но ещё не Release Candidate) </p><p>Так вот, с выходом PHP 3.0 появился инструментарий для применения нового подхода в php-программировании - буферизации страниц в скриптах. (Важно заметить, что это совсем не та буферизация, которая есть сейчас .)</p> <p>Со временем задачи построения сайтов усложнялись, php использовался всё шире, сайты становились всё "динамичнее". Чем больше сайт, тем проблематичнее сделать всё на подключаемых файлах в смешанном php и HTML. Вполне логично следующим шагом расширения php стала обработка HTML-страницы в памяти скрипта и выдача её пользователю целиком или по частям.</p> <p>Реализовать данных подход помогла худо-бедно реализованная в PHP3 поддержка (ну хорошо, эмуляция) объектно-ориентированного программирования. Были созданы <i>классы шаблонов </i>, которые позволяли легко оперировать шаблоном страницы и данными, которые в него должны быть внесены.</p> <p>Это расширяло возможности программ, но одновременно требовало больше системных ресурсов. Сегодня можно сказать, что выгоды от применения такого подхода перекрыли затраты системных ресурсов, однако ещё года два-три назад много людей считало, что держать весь документ в памяти - непозволительная роскошь для веб-программирования.</p> <p>Итак, образно говоря, произошёл переворот в технологии: если раньше PHP прятался в HTML-коде и робко добавлял свою информацию, то теперь он вышел на первое место и держит весь HTML в своих лапах.</p> <p>4. Чем плохи самопальные классы шаблонов</p> <p>Спустя полгода я решил кое-что изменить в планах. Морализаторства на тему плохости самопальных классов не будет. Они хороши. Пользуйтесь ими, и прибудет с вами то, что вам нужно! Только учитывайте, что вам будут мешать некоторые обстоятельства.</p> <p>Первое. Если класс писался специально, это значит, что он несколько оторван от жизни. Пусть за классом стоит большой опыт разработок, всё равно он делался в тепличных условиях. Получившаяся технология будет иметь свои специфические условия использования, под которые придётся подгонять будущие разработки, либо переделывать класс.</p> <p>Второе. Класс, который синтезируется из наработок в разных проектах, тоже требует изменений - чем глобальнее он, тем больше.</p> <p>Итак, свои собственные классы делать можно (классы шаблонов, насколько я знаю, очень многие используют свои собственные). Если вы идёте дедуктивным методом, строя класс из оценки того, что понадобится, есть небольшая вероятность, что получится что-то гениальное. И большая вероятность, что получится не совсем то, что нужно. Можно применить индукцию - тогда после большой и долгой работы вы гарантированно получите то, что работает, но не обязательно гениальное.</p> <p>Плохость самоделок, которую я упоминал в предыдущих частях, заключается в первом случае в очень большой вероятности получить что попало, работающее не так, как вам мечталось, требующее к себе особого подхода и много кодирования. Во втором случае вы можете долго упорно работать и получить то, что уже было кем-то сделано и опубликовано до вас.</p> <p>Что касается сравнения технологий, то главный вопрос - уместность. Очень простой сайт можно делать на подключаемых файлах и не думать о классах и движках. Сайт посложнее лучше делать на шаблонах. Если сложность возрастает и на одной странице многое зависит от разных условий - меню навигации в зависимости от статуса и т.д. - лучше подумать о связке XML&XSLT.</p> <p>Пример: форум phpBB. В нём нельзя поменять оформление полностью, как вам хочется, потому что очень много элементов оформления находятся не в шаблонах, а в php-коде. Всякие формы для входа, меню управления для вошедшего пользователя и т.п. - вставляются в страницу через php-код, а не как вложенный шаблон. Cамое популярное решение - не всегда самое лучшее.</p> <p>Дмитрий Котеров так комментирует опыт изучения phpBB: написав класс шаблона, многие, будучи полны энтузиазма, начинают пихать всё под этот класс. Идея такого класса состоит в том, чтобы разделить код и оформление, но авторы phpBB наоборот перемешали их насколько это было возможно.</p> <p>Но на мой взгляд, даже при нормальной реализации этого подхода (класса шаблона) код получился бы громоздкий, файлов с шаблонами было бы очень много. В последнее время прихожу к выводу, что на классах шаблонов в принципе нельзя толком отделить php-код от данных и оформления. Со сменой дизайна сайта на таком движке обязательно придётся копаться в php-скриптах, и делать это будет не дизайнер, а вы.</p><p> <table border> <tr> <td colspan=2> <b>__global__ </b> <p> (hidden and automatically added) </td> </tr> <tr> <td><b>block1 </b></td> <td> <table border> <tr> <td colspan=2><b>block2 </b></td> </tr> <tr> <td><b>inner1 </b></td> <td><b>inner2 </b></td> </tr> </table> </td> </tr> </table> </p><p>В документации написано, что нет необходимости указывать в шаблоне, какой блок является дочерним от какого. Класс это сам поймёт.</p> <p>В документации к этому классу написано, что он знает, что inner1 - дочерний для блока block2, и нет необходимости сообщать ему об этом. Чтобы вставить данные в блок, достаточно выполнить такой код столько раз, сколько нужно строк:</p><p> <?php $tpl->setVariable(...); $tpl->parseCurrentBlock(); ?> </p><p>Чтобы внести контент в block1, нужно выполнить такое:</p><p> <?php $tpl->setCurrentBlock("inner1"); $tpl->setVariable(...); $tpl->parseCurrentBlock(); $tpl->setVariable(...); $tpl->parseCurrentBlock(); $tpl->parse("block1"); ?> </p><p>В итоге код скрипта выглядит так:</p><p> <?php $tpl = new HTML_Template_IT(); // загрузка шаблона или указание его через класс $tpl->loadTemplatefile(string filename [, boolean removeUnknownVariables, boolean removeEmptyBlocks]) // установка переменных "global", т.е. переменных, не входящих в блок (дочерний блок) $tpl->setVariable(string variablename, mixed value); // ещё один способ указания переменных - через массив $tpl->setVariable(array (string varname => mixed value)); // Будем использовать какой-нибудь блок, пусть даже глубоко вложенный в другие $tpl->setCurrentBlock(string blockname); // Повторяем столько, сколько нужно $tpl->setVariable(array (string varname => mixed value)); $tpl->parseCurrentBlock(); // получаем результат или печатаем его вызывая $tpl->show() $tpl->get(); ?> </p><p>Код удобнее и понятнее, чем с FastTemplate.</p> <p>Но многие программисты всё равно пишут свои собственные классы шаблона. Более простые, но имеющие некоторые функции, которых нет в общедоступных классах. Я писал собственный класс, в котором сделал блоки, в которые автоматически вставлялись результаты SQL-запросов, в добавок имелись шапка и хвост блока (например, теги <table> и </table>), которые появлялись в результате только если в SQL-запросе были строки.</p> <p>В PHP3 появился модуль функций обработки XML. При помощи функций этого модуля можно создавать собственных обработчики XML-кода, но, однако, нельзя проверять правильность XML-документа.</p> <p><i>Небольшой экскурс в теорию о том, что такое XML и зачем он нужен. </i></p> <p><i>XML - это способ записи структурированных данных. Под "структурированными данными" обычно подразумевают такие вещи, как электронные таблицы, адресные книги, конфигурационные параметры, финансовые транзакции, технические чертежи и так далее. XML представляет собой набор правил (вы можете также считать их инструкциями или соглашениями) для разработки текстовых форматов, которые позволят вам структурировать ваши данные. </i></p> <p>Пояснение о том, что такое структура.</p><p> <eshop> <!-- категории товаров --> <category id="3"> <title>Мясные продукты Рыба килограмм 100 Мясо килограмм 200 Отходы производства Икра рыбья килограмм 10 Растительные продукты Подсолнух штука 50

Вот так в XML можно записать структурированные данные о продовольственном магазине, в котором товары разделены на категории, а категории могут быть подчинены друг другу. Конечно, эти данные можно записывать по-другому, скажем, текст с табуляторами в качестве разделителей и с указанием id родительской категории (кстати, при записи данных в формате XML, как видно выше, его указывать не нужно). XML - это уже готовый формат для записи такой структуры, в рамках которого вам остаётся лишь придумать названия узлов (тегов) и правила, в которые должна укладываться структура (например, что товар не может быть расположен вне категории или что уровней категорий может быть не больше трёх). Далее:

XML - это не язык программирования, и вам не нужно быть программистом, чтобы использовать или изучать его. XML облегчает компьютеру задачу создания и чтения данных, обеспечивая при этом однозначность их структуры. XML позволяет избежать распространенных ошибок проектирования языков: он расширяем, независим от платформы, включает поддержку интернационализации и локализации. XML полностью совместим с Unicode .

Разработчики php предлагают обрабатывать отдельные элементы XML-документа функциями php. Пример из руководства по php:

"B", "EMPHASIS" => "I", "LITERAL" => "TT"); // Функция для начала тега (может выводить сложный текст, но в данном примере выдаёт // просто соответствующий тег). function startElement($parser, $name, $attrs) { global $map_array; if ($htmltag = $map_array[$name]) { print "<$htmltag>"; } } // Функция для конца тега function endElement($parser, $name) { global $map_array; if ($htmltag = $map_array[$name]) { print ""; } } // Функция для текста (текстового узла) function characterData($parser, $data) { print $data; } $xml_parser = xml_parser_create(); // use case-folding so we are sure to find the tag in $map_array xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING, true); xml_set_element_handler($xml_parser, "startElement", "endElement"); xml_set_character_data_handler($xml_parser, "characterData"); // Чтение XML-файла if (!($fp = fopen($file, "r"))) { die("could not open XML input"); } while ($data = fread($fp, 4096)) { if (!xml_parse($xml_parser, $data, feof($fp))) { die(sprintf("XML error: %s at line %d", xml_error_string(xml_get_error_code($xml_parser)), xml_get_current_line_number($xml_parser))); } } xml_parser_free($xml_parser); ?>

Данный пример можно расширить, добавив массив имён тегов, которые будут обрабатываться иным образом, и соответствующие условия в функции startElement и endElement.

Код выглядит просто ужасно. Для использования в преобразовании XML в HTML-страницы он практически непригоден, хотя кое-где применялся, например на сайте How IT works . Конечно, можно писать собственные автоматизаторы, которые читали бы конфигурационные файлы и создавали массивы, но по суммарным трудозатратам выгоднее использовать мощные классы шаблонов, такие как Smarty.

Работа над этим проектом началась ещё в 1999 году, когда группа разработчиков начала писать спецификацию движка шаблонов. Определив, что он должен делать, разработчики пытались написать модуль на C, но в конце концов решили, что лучше сделать класс на php, доступный и понятный всем разработчикам сайтов.

Сейчас Smarty - один из проектов PHP, его сайт находится на сервере PHP по адресу smarty.php.net .

Формально Smarty - класс шаблона. На самом же деле, его функциональность на порядок выше, чем у множества *Template.

Во-первых, Smarty производит не только вставку переменных в шаблон, но и выполняет php-код в самом шаблоне. Во-вторых, шаблоны в Smarty преобразуются в php-скрипты, и всю грязную работу по вставке переменных в текст и выполнению логических конструкций отдаёт встроенному в php парсеру. Эти php-скрипты при включённом кешировании сохраняются в файлы, которые вызываются при следующих обращениях к шаблонам. Когда шаблоны меняются, скрипты генерируются снова. Такое кеширование существенно ускоряет работу скриптов.

Также Smarty умеет обрабатывать встроенные в шаблоны логические конструкции if-else, преобразуя их в php-код. Аналогично обрабатываются конструкции, названные variable modifiers. Они позволяют убрать некоторые функции из основного скрипта, перенеся их в шаблон и их кешируемые php-скрипты.

{* Написать заголовок заглавными буквами *}

{$title|upper},

{* Сократить топик до 40 символов, поставив... в конце *} Topic: {$topic|truncate:40:"..."}

Этот текст преобразуется в такой код:

_run_mod_handler("upper", true, $this->_tpl_vars["Name"]); ?> Topic: _run_mod_handler("truncate", true, $this->_tpl_vars["Name"], 40, "..."); ?>

Доступен и набор встроенных переменных таких, как $smarty.now (текущее время). Идилическую картину дополняет вывод окна отладки, содержащего значения переменных, вызов вложенных шаблонов, поддержка настраиваемых фильтров ввода/вывода и плагинов.

Всё было бы прекрасно, будь Smarty меньше в размерах. Сейчас же (версия 2.3.1) "весит" 144 килобайта и на AMD K6 266 MHz работает ощутимо медленно. Разработчики Smarty рекомендуют использовать Zend Accelerator или PHP Accelerator .

DOM - на данный момент последнее слово в шаблонах документов в php. Я описывал модуль в статье «XML: спецификация и функции DOM в PHP».

Несмотря на многословность и большой объём кода, выполняющего достаточно простые операции, DOM XML имеет большие перспективы.

Главный недостаток текстового подхода в редактировании XML-документа (редактирования в скрипте, естественно) - то, что нужно либо внимательно вчитываться в спецификицию XML, либо не быть до конца быть уверенным в том, что же получилось в итоге.

Например, вставить текст в узел - простая задача. Однако в XML-документе служебные символы и символы, не входящие в таблицу ISO, должны быть преобразованы в XML-сущности (<) или в номера символов UTF-8 (&x0442;). В модуле DOM XML достаточно сконвертировать текст в UTF-8 и вызвать метод create_text_node.

На моём сайте пока что работает старый движок, генерирующий текстовую строку, и иногда он выкидывает интересные фокусы с не-well-form-ными документами. Кроме того, объекты можно передавать как параметр функции, а имя вызываемого метода делать переменным.

Наконец, главное преимущество DOM XML - то, что бинарный модуль для текстовой обработки документа в php отсутствует. Это значит, что вам самим придётся писать парсер.

Что касается большого объёма кода, то это вполне естественно. Ведь если вы хотите редактировать документ через текст, то пишете регулярные выражения, используете набор строковых функций. Этого кода нужно тоже немало, если соблюдать условности XML.

Операции с документом через DOM нужно группировать в свои собственные функции, то есть строить DOM-овый класс шаблона.

В отдельный шаблон можно вынести общие для сайта или его разделов данные. Остальное - вставлять в документ приёмом, который я описывал:

document_element(); $child = $root1->child_nodes(); $root2 = $dom2->document_element(); for ($i = 0; $i < sizeof($child); $i++) $root2->append_child($child[$i]->clone_node()); ?>

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

Options FollowSymLinks RewriteEngine On # проверка наличия файла с тем же именем # и расширением phpxml для корня раздела RewriteCond %{DOCUMENT_ROOT}/$1/index.phpxml -f # если такой файл есть, правило срабатывает RewriteRule ^(+)/?$ /$1/index.php # это правило сработает для адреса, переписанного # по предыдущему правилу, и для всех остальных # файлов, отправляя запрос в скрипт composer.php. RewriteCond %{DOCUMENT_ROOT}/$1.phpxml -f RewriteRule ^(+)\.php$ /composer.php [L]

В скрипте composer.php будет открываться файл с тем же именем, что и запрашивался, но с расширением phpxml. Сервер Apache при обработке правил уже проверил факт существования этого файла.

6.1 Валидация документов

Это инструмент, который должен прийти на смену или значительно потеснить самодельные средства проверки данных. Сейчас стало модно отказываться от базы данных и хранить данные в файлах.

С базой было просто: получаешь массив и делаешь из него текстову строку (циклом или классом шаблона наподобие php-templates), либо получить строку при помощи sql2xml дерево объектов (или опять же строку). С файлом работать сложнее, потому что на всякий случай желательно проверять целостность данных.

Есть два подхода в создании разметки. Первый - разметка, ориентированная на данные (data-centric markup), второй - разметка, ориентированная на документ (document-centric markup).

Пример разметки ориентированной на данные:

ETCC FIA-organized racing series. Uses racing cars. BTCC British Touring Car Championship Analogue to ETCC, except that races take. http://btcc.co.uk DTM Deuche Tourenwagen Masters

Два уровня иерархии - строка + поле (естественно, если их станет 3 или 4, качественно ничего не изменится), в общем, то же самое, что и запрос из базы данных, отформатированный при помощи sql2xml. Пример документо-ориентированной разметки:

FIA organized about 20 racing servies, including 2 touring cars championships. European Touring Car Championship (ETCC) uses significantly tuned road cars, mostly BMW. Another FIA series is British Touring Car (BTCC) is analogue to ETCC, except that races take. Allthough, these are not the world"s most famous championships. DTM (Deuche Tourenwagen Masters), the most famous Touring racing series not only in Germany.

Этот вариант написан свободным человеческим языком, но данные в элементах acro и title остались те же, их можно получить из документа теми же запросами XPath или XQuery: /championships/championship/title Первый вариант разметки можно представить в виде таблицы базы данных. Можно усложнить его, для примера - фрагмент документа:

DTM Deuche Tourenwagen Masters Most famous German Touring racing series. Laurent Aiello Bernd Schneider Marcel Fassler Jean Alesi Cristian Albers

От этого документ не перестанет быть data-centric. Новый документ можно представить как 2 таблицы, соединяемые при запросе оператором LEFT JOIN.

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

Кстати, в этом заключается основное отличие XML-баз данных от не-XML. XML-database работает и с разметкой, ориентированной на документ (а не только в том, что результаты выдаются в XML). Разметку же типа data-centric вполне можно хранить в реляционной БД, а на выходе форматировать утилитой вроде sql2xml .

Так вот, возвращаясь к валидации документов, нужно заметить, что документо-ориентированную разметку очень сложно проверить на соответсвтие DTD или XML-схеме простым строковым парсером.

Ещё пример - разметка формы в моём классе TXF:

0"/>

Я проверяю это через DTD-схему. Проверка соответствия схеме документа - стандартная функция библиотеки DOM XML.

Для общеиспользуемых форматов обмена данными, например, RSS, есть спецификации и DTD-схемы (XML-схемы). Чтобы не забивать голову написанием собственного RSS-парсера, можно просто добавить в документ схему и проверить на допустимость. Затем доставать из документа нужные узлы через get_content или выражения XPath.

6.2 Лирическое отступление

Срочно завершать написание этой части серии меня заставила последняя заметка Spectator-a под названием «Шаблоны в PHP для чайников» .

Призывает забыть слова типа XML, XHTML «и прочий X...», попутно даёт заслуженного пинка Smarty, и, завершая свой ЭКСпромт, призывает умников бриться бритвой Оккама.

Бедная бритва! Подняли на знамёна её, машут ей везде, где можно, тыкают и пытаются нанести ею увечья оппонентам.

К чёрту бритву! К чёрту это дурацкое правило "не плодить сущности сверх необходимого". Это слишком избитая истина, чтобы её напоминать людям, а для того, чтобы оценивать других она вообще не годится. Количество "сверх необходимого" для каждого человека разное. Заказчик сайта может сказать: "А зачем мне ваши скрипты, сделайте в простом HTML, чтобы мы потом FrontPage-ом могли редактировать! Читали про бритву Оккама?"

Я долгое время считал, что основная причина, почему стоит использовать новые технологии в программировании - это эффект масштаба. Как мне написали в комментариях к старой статье, "с ростом объёмов сайта ты утонешь в своих шаблонах". И в своих статьях я упирал именно на это - мол, пожалуйста, балуйтесь со своим хитрым include и кодом, смешанным с HTML, оно поперёк горла встанет, когда надо будет сделать много программного кода.

На самом деле, есть ещё одна причина. Это навыки каждого конкретного человека. Кто хорошо знает XML, XSLT и XPath, либо умеет быстро находить решения задач в этой среде, тот в большем количестве случаев выполнит проект именно на XML-технологиях. Ему это сделать будет просто и легко, потому что он это хорошо умеет. Кто не знает или знает плохо - будет делать "простонародными" способами.

Остаётся лишь вопрос, будут ли его решения на XML-технологиях эффективнее, чем если бы он делал на классах шаблона или смешанном PHP&HTML? Да, будут. Приведу аллегорическое сравнение.

Вы видели, как играют в волейбол опытные игроки и "ламеры"? Команды "кульков" правдами и неправдами перекидывают мяч на другую сторону, чтобы соперник уронил мяч у себя. Нападающий удар они делать не умеют, и, руководствуясь пресловутой бритвой, играют просто. Опытный игрок намного лучше кулька умеет перекидывать мяч. Однако большего результата он добьётся, если выполнит хороший нападающий удар.




Есть еще вопросы или что-то непонятно - добро пожаловать на наш

Получает объект WP_User, который содержит все данные указанного пользователя.

Данные, возвращаемые функций полностью соответствуют полям таблиц БД: wp_users и wp_usermeta (описание таблиц ).

Это pluggable функция - т.е. её можно заменить из плагина. Это значит, что она будет работать (подключается) только после подключения всех плагинов, а до этого момента функция еще не определена... Поэтому нельзя вызывать эту и зависящие от неё функции прямо из кода плагина. Их нужно вызывать через хук plugins_loaded или позднее, например хук init .

Замена функции (переопределение) - в плагине можно создать функцию с таким же названием, тогда она заменит текущую функцию.

✈ 1 раз = 0.000296с = быстро | 50000 раз = 0.78с = очень быстро | PHP 7.1.2RC1, WP 4.7.2

Хуков нет.

Возвращает

WP_User/false. Объект данных или false, если не удалось найти указанного пользователя.

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

get_userdata($userid); $userid(число) (обязательный) ID пользователя, данные которого нужно получить.
По умолчанию: нет

Примеры

#1 Как выводить данные из полученного объекта данных

$user_info = get_userdata(1); echo "Имя пользователя: " . $user_info->user_login . "\n"; echo "Уровень доступа: " . $user_info->user_level . "\n"; echo "ID: " . $user_info->ID . "\n"; /* Выведет: Имя пользователя: admin Уровень доступа: 10 ID: 1 */

#1.2 Данные в переменную

Еще одни пример, только тут запишем данные сначала в переменные, а затем выведем из на экран:

$user = get_userdata(1); $username = $user->user_login; $first_name = $user->first_name; $last_name = $user->last_name; echo "$first_name $last_name зашел(а) на сайт под логином: $username."; /* Объект $user: WP_User Object( => stdClass Object( => 80 => kogian => $P$BJFHKJfUKyWv1TwЛОВАЕnYU0JGNsq. => kogian => [email protected] => http://example.com/ => 2016-09-01 00:34:42 => => => kogian) => 80 => Array( => 1) => wp_capabilities => Array( => subscriber) => Array( => 1 => 1 => 1) => => 1) */

#2 Методы класса

Получаемый объект с помощью get_userdata() - это экземпляр класса и у него есть методы, которые можно использовать. Иногда это может пригодится. Вот простой пример получения опции пользователя, с помощью метода $user->get() :

$user = get_userdata(1); echo $username = $user->get("user_login");

Список некоторых методов:

    get($key) - вернет значение опции;

    has_prop($key) - проверяет установлена ли указанная опция;

    has_cap($cap) - проверяет имеет ли пользователь указанную возможность или роль;

    get_role_caps() - получает все возможности роли пользователя и объединяет их с индивидуальными возможностями пользователя;

    add_role($role) - добавляет роль к пользователю;

    remove_role($role) - удаляет роль у пользователя;

  • set_role($role) - устанавливает роль пользователя;

Заметки

Вот некоторые полезные значения полей таблиц wp_users и wp_usermeta, которые вы можете использовать для получения данных:

  • display_name

user_meta

  • user_description

    wp_capabilities (массив)

    admin_color (тема админ панели. По умолчанию - свежая (fresh))

    closedpostboxes_page

  • source_domain

Также надо отметит, что с версии 3.2., возвращаемые данные немного изменились: возвращается объект WP_User . Данные в объекте разбиваются на группы: data , caps , roles (раньше данные возвращались в общем списке).

Однако, благодаря "волшебным" (служебным) методам PHP данные можно получить как и прежде, например, сейчас данные хранятся так: get_userdata(1)->data->rich_editing , но получить их можно так: get_userdata(1)->rich_editing , несмотря на то, что var_dump() не покажет эту взаимосвязь.

Код get userdata : wp-includes/pluggable.php WP 5.2.2

Во втором уроке мы напишем еще два класса и полностью закончим внутреннюю часть скрипта.

План

Цель серии уроков создать простое приложение, которое позволяет пользователям регистрироваться, входить, выходить и менять настройки. Класс, который будет содержать всю информации о пользователе будет называться User и он будет определен в файле User.class.php. Класс, который будет отвечать за вход\выход будет называться UserTools (UserTools.class.php).

Немного про именование классов

Правильным тоном является называть файлы с описанием класса таким же именем как и сам класс. Таким образом легко определить цель каждого файла в папке с классами.

Также обычно в конце названия файла класса добавляют.class или.inc. Таким образом мы четко определяем предназначение файла и можем с помощью.htaccess ограничить доступ к этим файлам.

Класс Пользователей (User.class.php)

Этот класс будет определять каждого пользователя. С ростом данного приложения определение "Пользователь" может существенно измениться. К счастью, ООП программирование позволяет легко добавлять дополнительные атрибуты пользователей.

Конструктор

В этом классе мы будем использовать конструктор - это функция, которая автоматически вызывается при создании очередной копии класса. Это позволяет нам автоматически публиковать некоторые атрибуты после создания проекта. В этом классе конструктор будет брать единственный аргумент: ассоциативный массив, который содержит один ряд из таблицы users нашей БД.

require_once "DB.class.php"; class User { public $id; public $username; public $hashedPassword; public $email;
public $joinDate;
//Конструктор вызывается при создании нового объекта //Takes an associative array with the DB row as an argument. function __construct($data) { $this->id = (isset($data["id"])) ? $data["id"] : ""; $this->username = (isset($data["username"])) ? $data["username"] : ""; $this->hashedPassword = (isset($data["password"])) ? $data["password"] : ""; $this->email = (isset($data["email"])) ? $data["email"] : ""; $this->joinDate = (isset($data["join_date"])) ? $data["join_date"] : ""; }
public function save($isNewUser = false) { //create a new database object. $db = new DB(); //if the user is already registered and we"re //just updating their info. if(!$isNewUser) { //set the data array $data = array("username" => ""$this->username"", "password" => ""$this->hashedPassword"",
"email" => ""$this->email"");
//update the row in the database $db->update($data, "users", "id = ".$this->id); }else { //if the user is being registered for the first time. $data = array("username" => ""$this->username"", "password" => ""$this->hashedPassword"", "email" => ""$this->email"", "join_date" => """.date("Y-m-d H:i:s",time())."""); $this->id = $db->insert($data, "users"); $this->joinDate = time(); } return true; } } ?>

Объяснение

Первая часть кода, вне зоны класса, обеспечивает подключение класса в БД (поскольку в классе User есть функция, которая требует этот класс).

Вместо переменных класса “protected” (использовались в 1-м уроке) мы определяем их как “public”. Это означает, что любой код вне класса имеет доступ к этим переменным при работе с объектом User.

Конструктор берет массив, в котором колонки в таблице являются ключами. Мы задаем переменную класса используя $this->variablename. В примере данного класса, мы прежде всего проверяем существует ли значение определенного ключа. Если да, тогда мы приравниваем переменную класса к этому значению. В противном случае - пустая строка. Код использует краткую форму записи оборота if:

$value = (3 == 4) ? "A" : "B";

В данном примере мы проверяем равняется ли 3 четырем! Если да - тогда $value = “A”, нет - $value = “B”. В нашем примере результат $value = “B”.

Сохраняем Информацию о Пользователях в БД

Функция сохранения используется для внесения изменений в таблицу БД с текущими значениями в объекте User. Эта функция использует класс БД, который мы создали в первом уроке. Используя переменные класса, устанавливается массив $data. Если данные о пользователе сохраняются впервые, тогда $isNewUser передается как $true (по умолчанию false). Если $isNewUser = $true, тогда вызывается функция insert() класса DB. В противном случае вызывается функция update(). В обоих случаях информация от объекта user будет сохранена в БД.

Класс UserTools.class.php

Этот класс будет содержать функции, которые имеют отношение к пользователям: login(), logout(), checkUsernameExists() и get(). Но с расширением данного приложения, Вы можете добавить еще множество других.

//UserTools.class.php require_once "User.class.php"; require_once "DB.class.php";
class UserTools {
//Log the user in. First checks to see if the //username and password match a row in the database. //If it is successful, set the session variables //and store the user object within.
public function login($username, $password)
{
$hashedPassword = md5($password); $result = mysql_query("SELECT * FROM users WHERE username = "$username" AND password = "$hashedPassword""); if(mysql_num_rows($result) == 1) { $_SESSION["user"] = serialize(new User(mysql_fetch_assoc($result))); $_SESSION["login_time"] = time(); $_SESSION["logged_in"] = 1; return true; }else{ return false; } }
//Log the user out. Destroy the session variables. public function logout() { unset($_SESSION["user"]); unset($_SESSION["login_time"]); unset($_SESSION["logged_in"]); session_destroy(); } //Check to see if a username exists. //This is called during registration to make sure all user names are unique. public function checkUsernameExists($username) { $result = mysql_query("select id from users where username="$username""); if(mysql_num_rows($result) == 0) { return false; }else{ return true; }
}
//get a user //returns a User object. Takes the users id as an input public function get($id) { $db = new DB(); $result = $db->select("users", "id = $id"); return new User($result); } }
?>

Функция login()

Функция login() понятна по названию. Она берет аргументы пользователя $username и $password и проверяет их соответствие. Если все совпадает, создает объект User со всей информацией и сохраняет его в сессии. Обратите внимание, что мы только используем функцию PHP serialize(). Она создает сохраненный вариант объекта, который можно легко отменить с помощью unserialize(). Также время логина будет сохранено. Это может использоваться в дальнейшем для предоставления пользователям информации о длительности пребывания на сайте.

Вы также можете заметить, что мы выставляем $_SESSION["logged_in"] на 1. Это позволяет нам легко проверить на каждой странице залогинен ли пользователь. Достаточно проверить только эту переменную.

Функция logout()

Также простая функция. Функция PHP unset() очищает переменные в памяти, в то время как session_destroy() удалит сессию.

Функция checkUsernameExists()

Кто знает английский легко поймет функцию. Она просто запрашивает БД, использован ли подобный логин или нет.

Функция get()

Эта функция берет уникальный id пользователя и делает запрос к БД с помощью класса DB, а именно функции select(). Она возьмет ассоциативный массив с рядом информации о пользователе и создаст новый объект User, передавая массив конструктору.

Где можно это использовать? К примеру, если Вы создадите страницу, которая должна отображать специфические профили пользователей, Вам необходимо будет динамически брать эту информацию. Вот так Вы можете это сделать: (допустим УРЛ http://www.website.com/profile.php?userID=3)

//note: you will have to open up a database connection first. //see Part 1 for further information on doing so. //You"ll also have to make sure that you"ve included the class files.
$tools = new UserTools(); $user = $tools->get($_REQUEST["userID"]); echo "Username: ".$user->username.""; echo "Joined On: ".$user->joinDate."";

Легко! Правда?

Последний штрих серверной части: global.inc.php

global.inc.php необходим для каждой страницы сайта. Почему? Таким образом мы разместим все обычные операции, которые нам понадобятся на странице. К примеру, мы начнем session_start(). Соединение с БД также откроется.

require_once "classes/UserTools.class.php";
require_once "classes/DB.class.php";
//connect to the database $db = new DB(); $db->connect();
//initialize UserTools object $userTools = new UserTools(); //start the session
session_start();
//refresh session variables if logged in if(isset($_SESSION["logged_in"])) { $user = unserialize($_SESSION["user"]); $_SESSION["user"] = serialize($userTools->get($user->id)); } ?>

Что он делает?

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

После соединения, мы начинаем функцию session_start(). Функция создает сессию или продолжает текущую, если пользователь уже залогинен. Поскольку наше приложение рассчитано на то, чтобы пользователи входили\выходили, эта функция обязательна на каждой странице.

Далее мы проверяем залогинен ли юзер. Если да - мы обновим $_SESSION["user"], чтобы отображать самую последнюю информацию о юзере. К примеру, если пользователь меняет свой емейл, в сессии будет храниться еще старый. Но с помощью авто обновления такого не случится.

На этом вторая часть подошла к концу! Завтра ожидайте заключительный урок по этой теме.

Всего наилучшего!

февраль 5 , 2017

Я не знаю ни одного php-фреймворка. Это печально и стыдно, но законом пока не запрещено. А при этом поиграться с REST API хочется. Проблема в том, что php по умолчанию поддерживает только $_GET и $_POST. А для RESTful-сервиса надобно уметь работать еще и с PUT, DELETE и PATCH. И не очень очевидно, как культурно обработать множество запросов вида GET http://site.ru/users, DELETE http://site.ru/goods/5 и прочего непотребства. Как завернуть все подобные запросы в единую точку, универсально разобрать их на части и запустить нужный код для обработки данных?

Почти любой php-фреймворк умеет делать это из коробки. Например, Laravel, где роутинг реализован понятно и просто. Но что если нам не нужно прямо сейчас заниматься изучением новой большой темы, а хочется просто быстро завести проект с поддержкой REST API? Об этом и пойдет речь в статье.

Что должен уметь наш RESTful-сервис?

1. Поддерживать все 5 основных типов запросов: GET, POST, PUT, PATCH, DELETE.
2. Разруливать разнообразные маршруты вида
POST /goods
PUT /goods/{goodId}
GET /users/{userId}/info
и прочие сколь угодно длинные цепочки.

Внимание: это статья не про основы REST API
Я предполагаю, что Вы уже знакомы с REST-подходом и понимаете, как это работает. Если нет, то в интернетах много замечательных статей по основам REST - я не хочу дублировать их, моя идея - показать, как с REST работать на практике.

Какой функционал мы будем поддерживать?

Рассмотрим 2 сущности - товары и пользователи.

Для товаров возможности следующие:

  • 1. GET /goods/{goodId} — Получение информации о товаре
  • 2. POST /goods — Добавление нового товара
  • 3. PUT /goods/{goodId} — Редактирование товара
  • 4. PATCH /goods/{goodId} — Редактирование некоторых параметров товара
  • 5. DELETE /goods/{goodId} — Удаление товара

По пользователям для разнообразия рассмотрим несколько вариантов с GET

  • 1. GET /users/{userId} — Полная информация о пользователе
  • 2. GET /users/{userId}/info — Только общая информация о пользователе
  • 3. GET /users/{userId}/orders — Список заказов пользователя

Как это заработает на нативном PHP?

Первое, что мы сделаем - это настроим.htaccess так, чтобы все запросы перенаправлялись на файл index.php. Именно он и будет заниматься извлечением данных.

Второе - определимся, какие данные нам нужны и напишем код для их получения - в index.php.
Нас интересуют 3 типа данных:

  • 1. Метод запроса (GET, POST, PUT, PATCH или DELETE)
  • 2. Данные из URL-a, например, users/{userId}/info - нужны все 3 параметра
  • 3. Данные из тела запроса
И третье, напишем код, который запускает нужные функции. Функции разбиты по файлам, все по феншую, добавить новые пути и методы для RESTful-сервиса будет очень просто.

.htaccess

Создадим в корне проекта файл.htaccess

RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^(.+)$ index.php?q=$1

Этими загадочными строками мы повелеваем делать так:
1 - направить все запросы любого вида на царь-файл index.php
2 - сделать строку в URL-е доступной в index.php в get-параметре q. То есть данные из URL-а вида /users/{userId}/info мы достанем из $_GET["q"].

index.php

Рассмотрим index.php строка за строкой. Для начала получим метод запроса.

// Определяем метод запроса $method = $_SERVER["REQUEST_METHOD"];

Затем данные из тела запроса

// Получаем данные из тела запроса $formData = getFormData($method);

Для GET и POST легко вытащить данные из соответствующих массивов $_GET и $_POST. А вот для остальных методов нужно чуть извратиться. Код для них вытаскивается из потока php://input , код легко гуглится, я всего лишь написал общую обертку - функцию getFormData($method)

// Получение данных из тела запроса function getFormData($method) { // GET или POST: данные возвращаем как есть if ($method === "GET") return $_GET; if ($method === "POST") return $_POST; // PUT, PATCH или DELETE $data = array(); $exploded = explode("&", file_get_contents("php://input")); foreach($exploded as $pair) { $item = explode("=", $pair); if (count($item) == 2) { $data = urldecode($item); } } return $data; }

То есть мы получили нужные данные, скрыв все детали в getFormData - ну и отлично. Переходим к самому интересному - роутингу.

// Разбираем url $url = (isset($_GET["q"])) ? $_GET["q"] : ""; $url = rtrim($url, "/"); $urls = explode("/", $url);

Выше мы узнали, что.htaccess подложит нам параметры из URL-a в q-параметр массива $_GET. То есть в $_GET["q"] попадет примерно такая строка: users/10 . Независимо от того, каким методом мы запрос дергаем.

А explode("/", $url) преобразует нам эту строку в массив, с которым уже можно работать. Таким образом, составляйте сколько угодно длинные цепочки запросов, например,
GET /goods/page/2/limit/10/sort/price_asc
И будьте уверены, получите массив

$urls = array("goods", "page", "2", "limit", "10", "sort", "price_asc");

Теперь у нас есть все данные, нужно сделать с ними что-нибудь полезное. А сделают это всего лишь 4 строки кода

// Определяем роутер и url data $router = $urls; $urlData = array_slice($urls, 1); // Подключаем файл-роутер и запускаем главную функцию include_once "routers/" . $router . ".php"; route($method, $urlData, $formData);

Улавливаете? Мы заводим папку routers, в которую складываем файлы, манипулирующие одной сущностью: товарами или пользователями. При этом договариваемся, что название файлов совпадают с первым параметром в urlData - он и будет роутером, $router. А из urlData этот роутер нужно убрать, он нам больше не нужен и используется только для подключения нужного файла. array_slice($urls, 1) и вытащит нам все элементы массива, кроме первого.

Теперь осталось подключить нужный файл-роутер и запустить функцию route с тремя параметрами. Что же это за function route? Условимся, что в каждом файле-роутере будет определена такая функция, которая по входным параметрам определит, какое действие инициировал пользователь, и выполнит нужный код. Сейчас это станет понятнее. Рассмотрим первый запрос - получение данных о товаре.

GET /goods/{goodId}

Файл routers/goods.php

// Роутер function route($method, $urlData, $formData) { // Получение информации о товаре // GET /goods/{goodId} if ($method === "GET" && count($urlData) === 1) { // Получаем id товара $goodId = $urlData; // Вытаскиваем товар из базы... // Выводим ответ клиенту echo json_encode(array("method" => "GET", "id" => $goodId, "good" => "phone", "price" => 10000)); return; } // Возвращаем ошибку header("HTTP/1.0 400 Bad Request"); echo json_encode(array("error" => "Bad Request")); }

Содержимое файла - это одна большая функция route, которая в зависимости от переданных параметров выполняет нужные действия. Если метод GET и в urlData передан 1 параметр (goodId), то это запрос о получении данных о товаре.

Внимание: пример очень упрощенный
В реале, конечно же, нужно дополнительно проверять входные параметры, например, что goodId - это число. Вместо того, чтобы писать код здесь, Вы, вероятно, подключите нужный класс. И для получения товара создадите объект оного класса и вызовете у него какой-то метод.
А может быть, передадите управление какому-то контроллеру, который уже озаботится инициализацией нужных моделей. Вариантов много, мы рассматриваем только общую структуру кода.

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

Давайте попробуем на примере: откройте консоль браузера и выполните код

$.ajax({url: "/examples/rest/goods/10", method: "GET", dataType: "json", success: function(response){console.log("response:", response)}})

Код отправит запрос на сервер, где я развернул подобное приложение и выведет ответ. Убедитесь, что интересующий наш маршрут /goods/10 действительно отработал. На вкладке Network Вы заметите такой же запрос.
И да, /examples/rest - это корневой путь нашего тестового приложения на сайт

Если Вам привычнее пользоваться curl-ом в консоли, то запустите в терминале это - ответ будет тот же самый, да еще и с заголовками от сервера.

Curl -X GET https://сайт/examples/rest/goods/10 -i

В конце функции мы написали такой код.

// Возвращаем ошибку header("HTTP/1.0 400 Bad Request"); echo json_encode(array("error" => "Bad Request"));

Он значит, что если мы ошиблись с параметрами или запрашиваемый маршрут не определен, то вернем клиенту 400-ю ошибку Bad Request. Добавьте, например, к URL-у что-то вроде goods/10/another_param и увидите ошибку в консоли и ответ 400 - кривой запрос не прошел.

По http-кодам ответов сервера
Мы не будем заморачиваться с выводом разных кодов, хотя по REST-у это и стоит делать. Клиентских ошибок много. Даже в нашем простом случае уместна 405 в случае неправильно переданного метода. Намеренно не хочу усложнять.
В случае успеха сервер у нас всегда вернет 200 ОК. По хорошему, при создании ресурса стоит отдавать 201 Created. Но опять-таки в плане упрощения эти тонкости мы отбросим, а в реальном проекте Вы их легко реализуете сами.

По совести говоря, статья закончена. Думаю, Вы уже поняли подход, каким образом разруливаются все маршруты, вынимаются данные, как это протестировать, как добавлять новые запросы и т.д. Но я для завершения образа приведу реализацию оставшихся 7 запросов, которые мы обозначили в начале статьи. Попутно приведу пару интересных замечаний, а в конце выложу архив с исходниками.

POST /goods

Добавление нового товара

// Добавление нового товара // POST /goods if ($method === "POST" && empty($urlData)) { // Добавляем товар в базу... // Выводим ответ клиенту echo json_encode(array("method" => "POST", "id" => rand(1, 100), "formData" => $formData)); return; }

urlData сейчас пустой, но зато используется formData - мы ее просто выведем клиенту.

Как сделать "правильно"?
Согласно канонам REST в post-запросе следует отдавать обратно только id созданной сущности или url, по которому эту сущность можно получить. То есть в ответе будет или просто число - {goodId} , или /goods/{goodId} .
Почему я написал "правильно" в кавычках? Да потому, что REST - это набор не жестких правил, а рекомендаций. И как будете реализовывать именно Вы, зависит от Ваших предпочтений или уже принятых соглашений на конкретном проекте.
Просто имейте в виду, что другой программист, читающий код и осведомленный о REST-подходе, будет ожидать в ответе на post-запрос id созданного объекта или url, по которому можно get-запросом вытащить данные об этом объекте.

Тестим из консоли

$.ajax({url: "/examples/rest/goods/", method: "POST", data: {good: "notebook", price: 20000}, dataType: "json", success: function(response){console.log("response:", response)}})

Curl -X POST https://сайт/examples/rest/goods/ --data "good=notebook&price=20000" -i

PUT /goods/{goodId}

Редактирование товара

// Обновление всех данных товара // PUT /goods/{goodId} if ($method === "PUT" && count($urlData) === 1) { // Получаем id товара $goodId = $urlData; // Обновляем все поля товара в базе... // Выводим ответ клиенту echo json_encode(array("method" => "PUT", "id" => $goodId, "formData" => $formData)); return; }

Здесь уже все данные используются по-полной. Из urlData вытаскивается id товара, а из formData - свойства.

Тестим из консоли

$.ajax({url: "/examples/rest/goods/15", method: "PUT", data: {good: "notebook", price: 20000}, dataType: "json", success: function(response){console.log("response:", response)}})

Curl -X PUT https://сайт/examples/rest/goods/15 --data "good=notebook&price=20000" -i

PATCH /goods/{goodId}

Частичное обновление товара

// Частичное обновление данных товара // PATCH /goods/{goodId} if ($method === "PATCH" && count($urlData) === 1) { // Получаем id товара $goodId = $urlData; // Обновляем только указанные поля товара в базе... // Выводим ответ клиенту echo json_encode(array("method" => "PATCH", "id" => $goodId, "formData" => $formData)); return; }

Тестим из консоли

$.ajax({url: "/examples/rest/goods/15", method: "PATCH", data: {price: 25000}, dataType: "json", success: function(response){console.log("response:", response)}})

Curl -X PATCH https://сайт/examples/rest/goods/15 --data "price=25000" -i

К чему эти понты с PUT и PATCH?
Разве одного PUT не достаточно? Разве не выполняют они одно и то же действие - обновляют данные объекта?
Именно так - внешне действие одно. Разница в передаваемых данных.
PUT предполагает, что на сервер передаются все поля объекта, а PATCH - только измененные . Те, которые переданы в теле запроса. Обратите внимание, что в предыдущем PUT мы передали и название товара, и цену. А в PATCH - только цену. То есть мы отправили на сервер только измененные данные.
Нужен ли Вам PATCH - решайте сами. Но помните о том читающем код программисте, о котором я упоминал выше.

DELETE /goods/{goodId}

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

// Удаление товара // DELETE /goods/{goodId} if ($method === "DELETE" && count($urlData) === 1) { // Получаем id товара $goodId = $urlData; // Удаляем товар из базы... // Выводим ответ клиенту echo json_encode(array("method" => "DELETE", "id" => $goodId)); return; }

Тестим из консоли

$.ajax({url: "/examples/rest/goods/20", method: "DELETE", dataType: "json", success: function(response){console.log("response:", response)}})

Curl -X DELETE https://сайт/examples/rest/goods/20 -i

С DELETE-запросом все понятно. Теперь давайте рассмотрим работу с пользователями - роутер users и соответственно, файл users.php

GET /users/{userId}

Получение всех данных о пользователе. Если GET-запрос вида /users/{userId} , то мы вернем всю информацию о пользователе, если дополнительно указывается /info или /orders , то соответственно, только общую информацию или список заказов.

// Роутер function route($method, $urlData, $formData) { // Получение всей информации о пользователе // GET /users/{userId} if ($method === "GET" && count($urlData) === 1) { // Получаем id товара $userId = $urlData; // Вытаскиваем все данные о пользователе из базы... // Выводим ответ клиенту echo json_encode(array("method" => "GET", "id" => $userId, "info" => array("email" => "[email protected]", "name" => "Webdevkin"), "orders" => array(array("orderId" => 5, "summa" => 2000, "orderDate" => "12.01.2017"), array("orderId" => 8, "summa" => 5000, "orderDate" => "03.02.2017")))); return; } // Возвращаем ошибку header("HTTP/1.0 400 Bad Request"); echo json_encode(array("error" => "Bad Request")); }

Тестим из консоли

$.ajax({url: "/examples/rest/users/5", method: "GET", dataType: "json", success: function(response){console.log("response:", response)}})

Curl -X GET https://сайт/examples/rest/users/5 -i

GET /users/{userId}/info

Общая информация о пользователе

// Получение общей информации о пользователе // GET /users/{userId}/info if ($method === "GET" && count($urlData) === 2 && $urlData === "info") { // Получаем id товара $userId = $urlData; // Вытаскиваем общие данные о пользователе из базы... // Выводим ответ клиенту echo json_encode(array("method" => "GET", "id" => $userId, "info" => array("email" => "[email protected]", "name" => "Webdevkin"))); return; }

Тестим из консоли

$.ajax({url: "/examples/rest/users/5/info", method: "GET", dataType: "json", success: function(response){console.log("response:", response)}})

Curl -X GET https://сайт/examples/rest/users/5/info -i

GET /users/{userId}/orders

Получение списка заказов пользователя

// Получение заказов пользователя // GET /users/{userId}/orders if ($method === "GET" && count($urlData) === 2 && $urlData === "orders") { // Получаем id товара $userId = $urlData; // Вытаскиваем данные о заказах пользователя из базы... // Выводим ответ клиенту echo json_encode(array("method" => "GET", "id" => $userId, "orders" => array(array("orderId" => 5, "summa" => 2000, "orderDate" => "12.01.2017"), array("orderId" => 8, "summa" => 5000, "orderDate" => "03.02.2017")))); return; }

Тестим из консоли

$.ajax({url: "/examples/rest/users/5/orders", method: "GET", dataType: "json", success: function(response){console.log("response:", response)}})

Curl -X GET https://сайт/examples/rest/users/5/orders -i

Итоги и исходники

Исходники из примеров статьи -

Как видим, организовать поддержку REST API на нативном php оказалось не так уж и сложно и вполне законными способами. Главное - это поддержка маршрутов и нестандартных для php методов PUT, PATCH и DELETE.

Основной код, реализовывающий эту поддержку, уместился в 3 десятка строк index.php. Остальное - это уже обвязка, которую можно реализовать как угодно. Я предложил это сделать в виде подключаемых файлов-роутеров, имена которых совпадают с сущностями Вашего проекта. Но можно подключить фантазию и найти более интересное решение.