PHP: разрабатываем свой xForum

Никита "Nikitos" Кислицин

Xakep, номер #050, стр. 086-089

nikitoz@real.xakep.ru; http://nikitos.inc.ru

Я много думал, прежде чем приступить к написанию этого материала. Уже вышли два номера. Можно и нужно подвести какой-то итог: ты уже знаком с базовыми функциями, освоил основные приемы программирования на PHP, умеешь писать приложения для взаимодействия с базами данных. Теперь мы напишем web-форум с упором на функциональность кода :).

О быстроте кода

Как ты наверняка знаешь, почти в любом современном языке программирования кодеру предоставляется возможность создавать из базовых выражений языка блоки кода, объединенные в логическое понятие "функция". Это целесообразно в случаях, когда некоторый код многократно используется в рамках одного приложения, либо для упрощения жизни ленивого соседа. Так, например, программист на Дельфи может, фактически не думая, писать сложнейшие приложения, используя функции из импортированной библиотеки. И нет ничего необычного в том, что весят такие программы под мегабайт, а процессорного времени кушают в пять раз больше, чем должны. На данном этапе это мало кого волнует. Тут важнее быстрота разработки приложений, нежели сотня килобайт сэкономленной памяти.

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

Мы же напишем форум, исходя из несколько иной концепции программирования - использования высокоуровневых узкопрофильных функций, определяемых нами же непосредственно перед обращением к ним. Следует заметить, что ряд функций в PHP представляют собой реализацию именно такого подхода. Так, например, SQL-функции уже описаны в спецификации транслятора, в то время как базируются на более низкоуровневых сетевых функциях и стандартных выражениях.

What is this shit?

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

Technical equipment

Все сообщения будут храниться в базе данных mySQL. До написания скриптов следует продумать структуру используемых таблиц. Первая носит имя "xforum". В ней хранится информация обо всех открытых дискуссиях. "xforum" имеет следующие поля: pid int not null auto_increment primary key, post text, author_name varchar(50), author_email varchar(50), date date, time varchar(20), ip varchar(30). Назначение каждого из полей, думается, очевидно, из их названий. Поясню лишь, что pid расшифровывается как: post id.

Также есть таблица "xanswer", в которой хранятся все ответы на каждую из дискуссий. Она имеет следующую структуру: aid int not null auto_increment primary key, pid int, answer text, author_name varchar (50), author_email varchar (50), date date, time varchar(20), ip varchar(30), где aid расшифровывается как answer id.

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

Третья таблица используется для хранения информации об администраторах и имеет следующие поля: uid int not null auto_increment primary key, admin_name varchar(30), admin_email varchar(30), access int. uid расшифровывается как user id.

Итак... Форум будет представлять собой компактную систему из двух скриптов - forum.php и admin.php. Первый – это ядро форума, второй позволяет этот форум администрировать.

Coding

Напомню, описание функций в общем виде осуществляется следующим образом:

function name([$using_var1, $using_var2, ...])
{
function_code;
retu $retu;
}

Служебное выражение "function name" объявляет функцию с именем name. В круглых скобках указываются принимаемые параметры. Они могут быть опущены, если функция таковых не использует. Процедура возвращает выражение, следующее за служебным словом retu, которое также может отсутствовать. В качестве примера приведу функцию, которая вычисляет площадь прямоугольника по его сторонам $a и $b.

Function square ($a, $b)
{
retu $a*$b;
}

Вызов процедуры в программе выглядит следующим образом:

<? Echo square(10, 43); ?>

Такой код, очевидно, выведет на экран 10*43=430.

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

Функция addmessage($messg,$name,$email) добавляет в БД новую тему $messg, написанную человеком по имени $name, имеющим электронный адрес $email. Дата и время создания сообщения определяются при помощи функции date(params), возвращающей строку, используя указанный шаблон, в который можно подставлять символы, указывающие на некоторый текущий временной параметр (год, месяц, день, час и т.д.). Параметры могут быть самыми разными. Подробнее об этом можно почитать на моем сайте http://nikitos.inc.ru.

Далее составляется строка запроса к SQL-серверу, которая вставит в таблицу "xforum" новую запись. $REMOTE_ADDR - стандартная переменная окружения веб-сервера, в ней лежит IP-адрес запросившего страницу компьютера.

showmessages($page) постранично отображает все темы форума. Постраничность реализуется следующим образом: составляется запрос на получение ВСЕХ дискуссий, в переменную $a помещается количество возвращенных запросом записей (mysql_num_rows()). $a делится на тридцать (именно такое количество сообщений мы выводим на одной странице), результат округляется в большую сторону (функция ceil()). Это и есть то количество страниц, на котором могут разместиться все существующие темы. Функция принимает единственный параметр - $page, обозначающий номер выводимой страницы. Создается sql-запрос, возвращающий не более 29 отсортированных по убыванию параметра $pid записей, начиная с параметра $from, который, естественно, зависит от номера страницы.

Функция reply($pid,$messg,$name,$email) добавляет в таблицу "xanswer" новую запись с ответом $messg чела $name на мессагу $pid.

showmessg($pid) - выводит на экран сообщение $pid, вставляя его в разноцветную табличку. replies($pid) показывает все ответы на дискуссию $pid, также размещая их в табличке, но выделяя другим цветом.

После описания всех этих функций (они простые и хорошо прокомментированы) начинается непосредственно программа, в которой различным образом комбинируются описанные функции. Там все понятно из комментариев. Занавес. Напомню, конечную версию форума и более подробную информацию по используемым функциям ты можешь найти на моем сайте nikitos.inc.ru в разделе "PHP".

<... html/заголовки ...>
<?
#Проверка переменных, если хотя бы что-то не так, то скрипт не выполняется.
#О security у нас будет отдельный материал, тогда и поговорим.
function addmessage_form()
{
# ... выводит html - теги формы для добавления нового сообщения. Поля: name, email, messg и hidden-поле act, имеющее параметр add.
}
function addanswer_form($pid)
{
# ... выводит html - теги формы для ответа на сообщение. Поля: name, email, messg и два hidden-поля pid=$pid и act="reply"
}
function head()
{
# теги, создающие табличку для вывода тем форума
}
function connect() #функция подключения к БД
{
$co=mysql_connect("localhost", "root","");
if (!($co)) {echo "Невозможно подключиться!";} else { retu $co; }
}
function addmessage($messg,$name,$email) # Функция, добавляющая новую тему в таблицу xforum
{
$date = date("Y-m-d"); #составляем строку с датой
$time = date("H-i"); #строку со временем
$sql="insert into xforum values(Null, '$messg', '$name', '$email', '$date', '$time', '$REMOTE_ADDR')"; #sql-запрос, добавляющий в таблицу новую запись
$a=mysql_query($sql); # отправляет запрос серверу
}
function showmessages($page) #функция, постранично выводящая темы форума
{
$from=30*($page-1); # считаем параметр $from
$sql2="select * from xforum";
$res2=mysql_query($sql2);
$a=mysql_num_rows($res2); #считаем количество тем в форуме
$b=ceil($a/30); # считаем количество страниц с результатами запроса
echo "Страницы: "; for ($i=1; $i<=$b; $i++) {if ($i!=$page) {echo "<a href=forum.php?page=$i>";} echo "$i"; if ($i!=$page) {echo "</a>";} echo " ";}
# выводим в цикле список доступных страниц с сообщениями
$sql="select * from xforum order by 'pid' desc limit $from,29"; #запрос на постраничный вывод дискуссий форума
$res=mysql_query($sql);
head(); # Создаем таблицу с темами
while($result=mysql_fetch_array($res)) # Выводим темы
{
$head=substr($result[post],0,60); // беру первые 60 символов сообщения
$sql="select * from xanswer where pid='$result[pid]'";
$a=mysql_query($sql);
$otvetov=mysql_num_rows($a); // Считаем количество ответов по данной теме
echo "<tr bgcolor=white><td><a href=forum.php?pid=$result[pid]&act=read>$head</a></td><td><a href=mailto:$result[author_email]>$result[author_name]</a></td><td>$result[date]</td><td>$result[time]</td><td>$otvetov</td></tr>"; // Выводим строку таблицы
}
echo "</table>";
}
function reply($pid,$messg,$name,$email) # функция, добавляющая ответ на сообщение номер $pid
{
$date = date("Y-m-d");
$time = date("H-i");
$sql="insert into xanswer values(null, '$pid', '$messg','$name','$email','$date', '$time', '$REMOTE_ADDR')";
$a=mysql_query($sql);
}
function showmessg($pid) # функция, выводящая вопрос дискуссии
{
$sql="select * from xforum where pid='$pid'";
$a=mysql_query($sql);
$b=mysql_num_rows($a);
if($b==0) { $ok=false; } else {
$result=mysql_fetch_array($a);
echo "<table width=65%><tr><td bgcolor=#FFCC00 width=15%>Автор: $result[author_name]<br>Дата: $result[date]<br>Время: $result[time]<br>Ip: $result[ip]</td><td bgcolor=#889DAA>$result[post]</tr></td></table> ";
}
}
function replies($pid) # Функция, выводящая все ответы на вопрос дискуссии
{
$sql="select * from xanswer where pid='$pid'";
$a=mysql_query($sql);
echo "<table width=65%>";
while($result=mysql_fetch_array($a))
{
echo "<tr><td bgcolor=#FFA54A width=15%>Автор: $result[author_name]<br>Дата: $result[date]<br>Время: $result[time]<br>Ip: $result[ip]</td><td bgcolor=#CFCFCF>$result[answer]</tr></td>";
}
echo "</table>";
}
#Началась сама программа
$db=connect(); // Подключаемся
mysql_select_db("test",$db);
if (!isset($act)) {if (!isset($page)) {$page=1;} showmessages($page); #Если скрипт без параметров, то просто выводим последние 30 тем и предлагаем пользователю добавить свою собственную
addmessage_form();}
if ($act=="read") {showmessg($pid); replies($pid); addanswer_form($pid);} #если юзер читает мессагу, то показываем ее, выводя также все ответы
if ($act=="add") {if (!isset($page)) {$page=1;} addmessage($messg,$name,$email); showmessages($page); addmessage_form();} //Если юзер добавляет новую тему...
if ($act=="reply") {reply($pid,$messg,$name,$email); showmessg($pid); replies($pid); addanswer_form($pid);} #Если отвечает в уже открытой дискуссии...
# Приведенный скрипт небезопасен! В нем нет проверок на некорректные значения аргументов! Полная версия доступна на сайте nikitos.inc.ru. В полной версии также добавлена возможность поиска по сообщениям.

Содержание
ttfb: 5.77712059021 ms