Урок 4. Второй шаг. Переход к работе с БД

Самое главное - не пугаться названия урока даже при отсутствии опыта работы с базами данных. Без них просто невозможно построить гибкий и легконаполняемый сайт. Отказ от использования БД не дает никаких преимуществ разработчику, а наоборот, здорово уменьшает возможности по созданию сайта и быстрому динамическому изменению содержимого. Построение серьезного ресурса без БД - это как рыбалка без удилища: вроде бы и можно кого-то поймать, однако делать это крайне неудобно. Иными словами, советуем обязательно научиться работать с БД, активно используя в проектах. На этом закончим агитацию, будем считать, что мы убедили всех в необходимости использования БД.

Работать с БД на Parser очень удобно. В Parser встроена мощная система взаимодействия с различными СУБД. В настоящее время Parser может работать с MySQL (MariaDB), Postgres, SQLite, Oracle, а также с любой СУБД через драйверы ODBC (в т. ч. MS SQL, MS Access). Поскольку исходные коды Parser 3 являются открытыми, возможно добавление поддержки любых других знакомых разработчику СУБД после создания соответствующего драйвера. При этом работа с ними не требует практически никаких дополнительных знаний собственно Parser. Все, что нужно, - это подключиться к выбранной СУБД и работать, используя SQL в объеме и формате, поддерживаемом СУБД. При передаче SQL-запросов Parser может только экранировать апострофы соответствующей конструкцией в зависимости от СУБД для защиты от уязвимостей, а все остальное передается как есть.

Со всеми возможностями Parser по работе с различными СУБД в рамках данного урока мы знакомиться, конечно же, не будем. Остановимся на MySQL. Почему именно на этой СУБД? Прежде всего потому, что она очень распространена и многие веб-проекты используют именно ее. Кроме того, практически все компании, занимающиеся сетевым хостингом, предоставляют клиентам возможность работы с этой СУБД. Ну и, несомненно, немаловажный фактор - она бесплатна, доступна и легка в освоении.

Теперь определимся с тем, что именно мы будем хранить в базе данных. Очевидный ответ: будем хранить новости. Причем таблица СУБД с новостями должна содержать такие поля: уникальный номер новости в базе, который будет формироваться автоматически СУБД, дата внесения новости в базу, по которой мы будем проводить выборку новостей за конкретное число, заголовок новости и собственно ее текст. Просто, без тонкостей и премудростей, однако это эффективно работает.

Есть еще один вопрос, с которым нужно определиться: «Каким образом новости будут попадать в базу?» Можно их заносить и из командной строки СУБД, но это неудобно. Мы же предлагаем решение, ориентированное на интернет, - создание на сайте раздела администрирования с формой для ввода новостей прямо из браузера.

Постановка задачи закончена, переходим к ее практическому решению. Далее нам потребуется установленная СУБД MySQL, без которой рассматриваемый здесь пример просто не будет работать.

Прежде всего, средствами MySQL создаем новую базу данных с именем p3test, содержащую одну-единственную таблицу news с полями id, date, header, body:

id
int not null auto_increment primary key
date
date
header
varchar(255)
body
text


Теперь создадим раздел администрирования, который даст возможность заполнить созданную базу данных новостями. Для этого в  корневом каталоге сайта создаем каталог
admin, а в нем - index.html, в который пишем следующее:

@greeting[]
Администрирование новостей

@content[]
<h1>Добавление новостей</h1>

$now[^
date::now[]]

<form method="POST">
<p>
   Date: <input name="date" value="${now.
year}-${now.month}-${now.day}"/>
   Header: <input name="header"/>
</p>
<p>

   
Body:<br/>
   <textarea cols="50" name="body" rows="5"></textarea>
</p>
<p>
   <input type="submit" value="Add New" name="posted"/>
   <input type="reset" value="Cancel"/>
</p>
</form>

#начало обработки
^if(
def $form:date && def $form:header && def $form:body){
   ^
connect[$connect_string]{
      ^
void:sql{insert into news
         (date, header, body)
      values 
         ('$form:date', '$form:header', '$form:body')
      }

      …сообщение добавлено
      }
}{
   …для добавления новости необходимо заполнить все поля формы
}

Также требуется в корневом файле
auto.p перед методом main добавить метод auto. Этот метод используется для инициализации глобальных переменных, т. е. переменных, которые будут доступны на всех страницах сайта. В нем мы зададим строку подключения к базе данных, о которой чуть позже.

@auto[]
$connect_string[
mysql://root@localhost/p3test]

Структура этой страницы полностью соответствует придуманной нами структуре страниц сайта. Все элементы, как то: приветствие, две части body, footer и header, присутствуют. Теперь нужно вспомнить, откуда на этой странице появятся header и footer. Конечно, из функции main корневого auto.p).

Незнакомые конструкции встречаются только в основной части. Разберемся с ней. В начале - обычная HTML-форма, с подстановкой текущей даты в поле
date как значения по умолчанию. Сделано это исключительно для удобства пользователей.

Легкое недоумение может вызвать запись:
${now.year}-${now.month}-${now.day}

Фигурные скобки здесь используются для того, чтобы получить строку вида «2001-11-06» (в таком формате мы собираемся хранить дату новости в БД). Если скобки не ставить, то Parser выдаст ошибку при обработке этого кода, поскольку не сможет понять, что нужно делать. Для него «-» будет частью имени. При необходимости четко отделить имя переменной от следующего за ним символа, скажем «-», как в нашем случае, нужно записать:

${имя_переменной}-

И в результате получится:
значение_переменной-

Настоятельно рекомендуем изучить Приложение 5, посвященное правилам составления имен.

Лучшим решением данной проблемы было бы использовать в этом месте конструкцию
^date.sql-string[]. Предлагаем самостоятельно доработать этот пример, пользуясь справочником. Если не получится, на следующем уроке мы покажем, как это сделать.

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

После закрывающего тега
</form> начинается блок обработки. Вначале с помощью if мы проверяем поля формы на пустоту. Этого можно, опять же, не делать, но мы хотим создать нечто большее, чем учебный экспонат без практического применения. Для того чтобы осуществить проверку, необходимо получить значения полей этой формы. В Parser это реализуется через статические переменные (поля). Мы просто обращаемся к полям формы как к статическим полям:

$form:поле_формы

Полученные таким образом значения полей мы и будем проверять на пустоту с помощью оператора
def и логического «И» (&&). Мы уже проверяли объект на существование в третьем уроке, но там был опущен оператор def, поскольку проверяли на пустоту таблицу. Как мы помним, таблица в выражении имеет числовое значение, равное числу строк в ней, поэтому любая непустая таблица считается определенной. Здесь же необходимо использовать def, как и в случае проверки на def других объектов. Если в поле ничего не было введено, то значение $form:поле_формы будет считаться неопределенным (undefined). После того как все значения полей заполнены, необходимо поместить их в базу данных. Для этого нужно сначала подключиться к базе данных, а затем выполнить запрос SQL для вставки данных в таблицу. Вот как мы это сделаем:

^connect[$connect_string]{
   ^void:sql{insert into news
      (date, header, body)
   values 
      ('$form:date', '$form:header', '$form:body')
   }

   …cообщение добавлено
}

Удобство Parser при работе с базами данных состоит в том, что он, за исключением редких случаев, не требует изучать какие-либо дополнительные операторы, кроме тех, которые предусмотрены в самой СУБД. Сессия работы с базой данных находится внутри оператора connect, общий синтаксис которого:

^connect[протокол://строка соединения]{методы, передающие запросы SQL}

Для MySQL это запишется так:

^connect[mysql://пользователь:пароль@хост/база_данных]{…}

В фигурных скобках помещаются методы, выполняющие SQL-запросы. При этом любой запрос может возвратить или не возвратить результат (например, в нашем случае нужно просто добавить запись в таблицу БД, не возвращая результат), поэтому Parser предусматривает различные конструкции для создания этих двух типов SQL-запросов. В нашем случае запрос записывается как:

^void:sql{insert into news 
      (date, header, body)
   values
      ('
$form:date
', '$form:header', '$form:body')
}

Кстати, это статический метод класса
void, поэтому нужно помнить про двоеточие.

То, что здесь не выделено цветом, является командами SQL. Ничего сложного здесь нет. Знакомым с SQL больше ничего и не потребуется объяснять, а тем, кто его почему-то пока не знает, еще раз рекомендуем изучить этот язык. В дальнейшем это многократно пригодится, а потраченное время точно не пропадет даром.

Продемонстрированный вариант взаимодействия с базой данных весьма изящен - Parser обеспечивает прозрачный доступ к СУБД и, за редким исключением, не требует каких-либо дополнительных знаний. При этом мы можем помещать в запросы SQL еще и данные из нашей формы, пользуясь конструкциями Parser. Возможности этого симбиоза просто безграничны. СУБД решает все задачи, связанные с обработкой данных (она ведь именно для этого и предназначена и очень неплохо с этим справляется), а нам остается только воспользоваться результатами ее работы. Аналогично происходит работа и с другими СУБД.

Теперь у нас есть форма, позволяющая помещать записи в нашу БД. Занесем в нее несколько записей. Теперь нам предстоит их оттуда извлечь, но перед этим неплохо бы немного доработать функцию
calendar, созданную на предыдущем уроке. Нужно, чтобы в календаре ставились ссылки на дни месяца, а выбранный день передавался как поле формы. Тогда по числам-ссылкам в календаре пользователь будет попадать в архив новостей за выбранный день. Модернизация эта неcложная, просто добавим немного HTML в auto.p раздела news: $days.$i в коде if обнесем ссылками таким образом:

<a href="/news/?day=$days.$i">$days.$i</a>

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

Теперь займемся
/news/index.html. В него заносим такой код:

@greeting[]
Страница новостей, заходите чаще!

@content[]

^calendar[]

$now[^date::now[]]

$day(^if(def $form:day){
   $
form:day
}{
   $now.
day
}
)

<h1>Новости за $day число</h1>

^
connect[$connect_string]{
   $news[^
table::sql{select
       date, header, body 
   from
      news 
   where
      date='${now.
year}-${now.month}-$day'
   }]
   ^if($news){
      ^news.
menu{
         
<article>
            $news.
date
            
<h2>$news.header</h2>
      
      ^taint[as-is][$news.body]
         </article>

      }
   }{
      За указанный период новостей не найдено.
   }
}

Структура обычная. Сверху размещаем меню-календарь вызовом
^calendar[] (напомним, что эта функция определена в auto.p раздела news). Основа информационной части страницы - выборка из базы данных новостей за тот день, по которому щелкнул пользователь (условие where в SQL-запросе). Это второй вариант выполнения SQL-запроса, при котором возвращается результат. Здесь результатом запроса будет таблица, с которой в дальнейшем мы будем работать. Поэтому необходимо создать объект класса table.

Познакомимся с еще одним конструктором класса
table, основанным на результате выборки из БД. Его логика абсолютно аналогична работе конструктора ^table::load[], только источником данных для таблицы является не текстовый файл, как в случае с пунктами меню, а результат работы SQL-запроса - выборка из базы данных:

$переменная[^table::sql{код SQL-запроса}]

Воспользоваться этим конструктором можно только внутри оператора
^connect[], то есть когда имеется установленное соединение с базой данных, поскольку обработкой SQL-запросов занимается сама СУБД. Результатом будет именованная таблица, имена столбцов которой совпадают с заголовками, возвращаемыми SQL-сервером в ответ на запрос.

Небольшое отступление. При создании SQL-запросов следует избегать использования конструкций вида select * from … поскольку постороннему человеку, не знающему структуру таблицы, к которой происходит обращение, невозможно понять, какие поля вернутся из БД. Подобные конструкции можно использовать только для отладочных целей, а в окончательном коде лучше всегда явно указывать названия полей таблиц, из которых делается выборка данных.

Остальная часть кода уже не должна вызывать вопросов:
if обрабатывает ситуацию, когда поле day (выбранный пользователем день на календаре, который передается из функции calendar) не определено, то есть человек пришел из другого раздела сайта через меню навигации. Если поле формы day определено (def), то используется день, переданный посетителем, в противном случае используется текущее число. Далее соединяемся с БД так же, как мы это делали, когда добавляли новости, создаем таблицу $news, в которую заносим новости за запрошенный день (результат SQL-запроса), после чего с помощью метода menu последовательно перебираем строки таблицы news и выводим новости, обращаясь к ее полям. Все понятно и знакомо, кроме одного вспомогательного оператора, который служит для специфического вывода текста новости:

^taint[as-is][$news.body]

В этом месте советуем отвлечься и внимательно прочесть
раздел справочника «Внешние и внутренние данные», где подробно описана логика работы операторов
taint и untaint. Это важные операторы, и каждый рано или поздно столкнется с необходимостью их использования. К тому же большую часть обработки данных Parser делает самостоятельно, она не видна на первый взгляд, но понимать логику действий необходимо.

Зачем здесь нужен оператор
taint? У нас есть страница для администрирования новостей, и мы хотим разрешить использование тегов HTML в записях. По умолчанию это запрещено, чтобы посторонний человек не мог внести JavaScript, например, перенаправляющий пользователя на другой сайт. Разрешить использование тегов HTML очень просто: достаточно пометить текст доверенным с помощью оператора taint:

^taint[as-is][текст новости]

В нашем случае используется значение
as-is, которое означает, что данные будут выведены так, как они хранятся в базе. Мы можем позволить себе поступить так, поскольку изначально не предполагается доступ обычных пользователей к разделу администрирования, через который добавляются новости.

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

Подведем итоги четвертого урока.

Что мы сделали: создали раздел администрирования для добавления новостей. Модернизировали функцию, формирующую календарь на текущий месяц. Наполнили раздел новостей данными из БД на основе запроса пользователей либо по умолчанию за текущую дату.

Что узнали:
·Как на Parser работать с СУБД MySQL.  
·Как делать различные SQL-запросы к БД (статический метод sql класса void и конструктор sql класса table).  
·Для чего используется оператор taint.  

Что надо запомнить: работа с базами данных в Parser осуществляется легко и понятно, нужно только изучить работу самой СУБД.

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


Copyright © 1997–2024 Art. Lebedev Studio | http://www.artlebedev.ru Дата обновления: 25.09.2024