Записи в рубрике 'PHP Статьи'
Очередной раз заглянув в почту и обнаружив очередную пачку писем от серверов решил положить этому конец. Упрощало задачу то что, большую часть писем генерируют мои собственные программы и скрипты, которые установлены на различных серверах.
Большая часть этих сообщений не являются критичными. Например, <Невозможно соединится с сервером>, <Обработано файлов столько-то>, <Затрачено времени столько-то> и т.п. Я рассмотрел два основных варианта этой проблемы:
Первый вариант, в лоб - использовать перенаправление вывода или собственные лог-файлы. Этот вариант не удобен для мониторинга, так как придётся просматривать больше количество файлов отчётов.
Второй вариант - не изобретать велосипед и использовать syslog.
syslog - стандарт отправки сообщений о происходящих в системе событиях (логов), использующийся в компьютерных сетях, работающих по протоколу IP.
Протокол syslog прост: отправитель посылает короткое текстовое сообщение, размером меньше 1024 байт получателю сообщения. Получатель при этом носит имя <syslogd>, <syslog daemon>, либо же, <syslog server>. Сообщения могут отправляться как по UDP, так и по TCP. Как правило, такое сообщение отсылается в открытом виде.
Syslog используется для удобства администрирования и обеспечения информационной безопасности. Он реализован под множество платформ и используется в множестве устройств. Поэтому, использование syslog позволяет обеспечить сбор информации с разных мест и хранение её в едином репозитории.
Как правило syslog и syslogd работают внутри одного сервера (то есть все соединения локальны) и никакой внешней сетевой деятельности не производится.
Теперь, как все это выглядит на практике.
Perl:
use Sys::Syslog;
openlog(”имя вашей программы”, “ndelay,pid”, “local0″);
syslog(LOG_WARNING, “Программа запущена”);
…
# тут программа
…
syslog(LOG_WARNING, “Программа завершила выполнение”);
closelog();
PHP:
openlog(”имя вашей программы”, LOG_PID | LOG_PERROR, LOG_LOCAL0);
syslog(LOG_WARNING,”Программа запущена”);
…
// тут программа
…
syslog(LOG_WARNING, “Программа завершила выполнение”);
closelog();
C/C++:
#include <syslog.h>
openlog(”имя вашей программы”, 0, LOG_USER);
syslog(LOG_NOTICE, “Can not open file “%s” for writing.”,filename);
closelog();
Далее можете поискать ваши сообщения в журналах syslog. Тут все зависит от настроек syslogd. Обычно это /var/log/messages.
В документации PHP написано, что в windows тоже работает, но я не пробовал.
Подробнее о флагах и значениях можно прочитать в руководствах:
C
PHP
Perl
Автор: mike
Tags:
log,
php,
syslog
Март 24, 2008
95% бесплатных php-скриптов (и не только php) - полный «отстой». Оно и понятно: хороший программист бесплатно ничего писать не будет, а если и будет, то только в свободное время в качестве развлечения, и уж, конечно не всякие банальности, вроде гостевых книг. Или, как говорил Рома Воронежский: «Вот проблема с этими творческими людьми: они всегда желают быть композиторами, художниками и писателями. В результате производством труб большого диаметра занимаются бездарности».
Именно так это и происходит.
Сегодня опять ковырялся в каталогах бесплатных скриптов, главным образом из любопытства, но еще и в тайной надежде найти что-нибудь забавное. В прошлый раз из «забавных» скриптов я нашел, например, «скрипт вывода текстового файла в php». Думал - парсер. Оказалось - да: почти что парсер. Привожу скрипт целиком: «?php include (”text.txt”); ?». Или вот вижу скрипт, написано «This script will reverse the text you give it. reversed: .ti evig uoy txet eht esrever lliw tpircs siht It isn’t very useful, it’s just funny. Try it out :)», то есть скрипт переворачивает строку задом-наперед. Самые худшие ожидания оправдались: они делали это циклом. Наверное, не знали, что в php есть специальная, уже готовая функция для этого:
Нашел скрипт поиска по сайту: он обшаривает директории, которые вы указали, открывает все html-файлы и тупо сравнивает: У меня тоже такой же скрипт был давным-давно написан, но потом, когда я понял, что народ поиском все-таки пользуется (сюрприз!), решил сделать его по-человечески. С индексом и прочими благами цивилизации: Сделал. В результате 1.5 мегабайта заметок превращаются в 900-килобайтный индекс за 17 секунд (индексацию надо проводить раз в несколько дней, или даже реже - в зависимости от скорости обновления сайта), после чего поиск по индексу происходит меньше одной секунды.
В общем, решил я все-таки поделиться этим скриптом. Краткая информация: скрипт на php, для работы никаких mysql не надо, предполагается, что html-ные (или txt-овые, как у меня) файлы где-то лежат, а не хранятся в mysql. В общем, поисковая машина для небольшого (ну, или «среднего») сайта. Как, например, spectator.ru.
Итак, начали:
Самое первое - скрипт индексации. Для чего он нужен?.. Вот у меня 278 заметок. Если мы будем открывать каждый файл и искать совпадения, то нам надо будет открыть 278 файлов. А это ой как долго: Более того, нам надо будет 278 раз провести хитрые манипуляции с этими файлами (про манипуляции - ниже). Если же у нас есть индекс, то во-первых, поиск происходит в одном файле (индексе), во-вторых, все эти «хитрые манипуляции» уже выполнены.
Алгоритм индексирующего скрипта такой:
Открываем очередной файл
Убираем из него «мусор» ( зачем убирается мусор - понятно, чем мусора меньше, тем ищется быстрее: ):
переводы строк
html-тэги
знаки препинания
слова, короче трех букв (а зачем они там?)
Делаем заглавные буквы строчными.
Убираем повторяющиеся слова. (Действительно, зачем нам вся это тавтология?)
Записываем все в индекс.
Если еще есть файлы, переходим к пункту 1.
Реализуется это все на php - легко!
<?php
// Spectator’s Indexing Script
// (C) Spectator.ru
// Для работы требуется PHP 4 или выше.
// Если вы будете использовать этот скрипт, ссылка на Spectator.ru
// крайне желательна. Спасибо.
// ставим скрипт “на счетчик” (чтобы знать, как долго он выполнялся
$ttt=microtime();
$ttt=((double)strstr($ttt, ‘ ‘)+(double)substr($ttt,0,strpos($ttt,’ ‘)));
$indexdir=”text”; #индексируемая директория
$indexfile=”indexfile.txt”; #файл, в котором будет лежать индекс
// если вы хотите индексировать файлы в нескольких директориях, надо
// внести несколько махоньких добавлений…
// делаем так, чтобы не было таймаута из-за того, что скрипт будет долго
// выполняться (на всякий случай) и из-за того, что пользователь нажмет
// кнопку “стоп” в браузере
$abort = ignore_user_abort(1);
set_time_limit(600);
// Функция, удалающая слова, короче 3х букв. Пригодится дальше.
function sw (&$item1, $key) { if (strlen($item1)<3) $item1=”"; }
// по очереди открываем все файлы в директории и проверяем, можно ли их
// индексировать у меня можно индексировать только файлы, которые имеют
// вид “число.txt” то есть && (is_numeric(str_replace (”.txt”,”", $file)))
// это вам наверняка не понадобится.
$handle=opendir(’./’.$indexdir);
while (false!==($file = readdir($handle))):
if ($file!=”.” && $file!=”..” && (is_numeric(str_replace (”.txt”,”", $file)))):
// открываем очередной файл
$fd = fopen ($indexdir.”/”.$file, “r”);
$contents = fread ($fd, filesize ($indexdir.”/”.$file));
Fclose ($fd);
// убираем переводы строк
$contents=str_replace (”n”,” “, $contents);
$contents=str_replace (”r”,”", $contents);
// убираем хтмл-тэги
$contents=str_replace (’<br>’, ‘ ‘, $contents);
$contents=str_replace (’<p>’, ‘ ‘, $contents);
$contents=strip_tags ($contents);
// убираем знаки препинания и цифры
// все эти строки работают быстрей, чем один eregi_replace!
$contents=str_replace (’ -’, ‘ ‘, $contents);
$contents=str_replace (’.', ‘ ‘, $contents);
$contents=str_replace (’,', ‘ ‘, $contents);
$contents=str_replace (’!', ‘ ‘, $contents);
$contents=str_replace (’?', ‘ ‘, $contents);
$contents=str_replace (’:', ‘ ‘, $contents);
$contents=str_replace (’;', ‘ ‘, $contents);
$contents=str_replace (’)', ‘ ‘, $contents);
$contents=str_replace (’(', ‘ ‘, $contents);
$contents=str_replace (’”‘, ‘ ‘, $contents);
// убираем заглавные буквы
$contents=strtolower ($contents);
// разбиваем на слова, убираем слова, короче 3х букв
$contents=explode (” “, $contents);
// вот и функция пригодилась…
array_walk ($contents, ’sw’);
// убираем повторяющиеся слова
$contents=array_unique ($contents);
// соединяем слова
$contents=implode (” “, $contents);
// формируем соответствующую строку в индексе.
$fullfile.=$file.”| “.$contents.” n”;
// индекс-файл будет иметь вид:
// имя_файла|индекс_для_данного_файла n
// имя_файла|индекс_для_данного_файла n
// имя_файла|индекс_для_данного_файла n
echo ($file.” проиндексирован<br>”);
// переходим к следующему файлу
endif;
endwhile;
closedir($handle);
// убираем двойные пробелы
while (stristr($fullfile, ” “)) $fullfile=str_replace (” “,” “,$fullfile);
// индекс готов, сохраняем его
$fp = fopen($indexfile, “w+”);
fwrite($fp, $fullfile);
fclose($fp);
// считаем, как долго работал скрипт
$ddd=microtime();
$ddd=((double)strstr($ddd, ‘ ‘)+(double)substr($ddd,0,strpos($ddd,’ ‘)));
echo (”<br>Время индексации: “.(number_format(($ddd-$ttt),3)).
” секунд<br>”);
echo (”Размер индекса: “.(number_format((round ((filesize($indexfile))/1024)) ,
0, “.”,”.”))).” Kb”;
?>
Итак, у нас есть индекс. Дальше - просто. Так ведь?.. Надо просто произвести поиск в нем. Берем функцию eregi, например:
Хотя я делал совсем по-другому:
Лирическое отступление: часто, когда надо проверить, если в строке какая-нибудь комбинация символов, пишут что-то вроде этого:
if (eregi(’this must be found’,$string))
echo ‘found!!’;
else
echo ‘нифига не found!’;
Способ хороший, но тормозной - из-за eregi. (Функция это работает с регулярными выражениями, поэтому и тормозит). По той же причине рекомендуется использовать там, где это можно, str_replace вместо ereg_replace. Быстрее раз в 10: Поэтому крутые программеры ;), когда им надо проверить, найдено ли что-то в строке, используют функцию strstr. На самом деле, она для этого не предназначена, (верней, «предназначена не для этого»), ибо она «Find first occurrence of a string», то есть «ищет первое местонахождение строки» и выводит строку, начиная с этого самого местоположения. Запутал, верно? (смайлик).
Ок, вот пример с php.net:
$email = ’sterling@designmultimedia.com’;
$domain = strstr ($email, ‘@’);
print $domain;
// выводит: @designmultimedia.com
Теперь понятно? Функция ищет, где в строке встречается подстрока «@» и выводит все после нее (включительно). Что самое главное - если ничего на найдено, то функция возвращает false. Именно поэтому ее можно использовать вот так:
if (stristr($string, ‘this must be found’))
echo ‘found!!’;
else
echo ‘нифига не found!’;
У меня в скрипте для поиска в индексе используется stristr. Кроме того, поиск понимает простейший синтаксис: «+» (слово должно быть найдено, aka AND), «-» (слово не должно быть найдено, aka NOT) и «*» (звездочка). Но, анализируя то, что искали у меня на сайте, могу сказать только одно: Где-то в дискуссии про поисковые машины и их AI (искусственный интеллект), я нашел такую фразу, что «проще выучить последнего дебила пользоваться языком запросов, чем научить поисковую машину угадывать, что же именно этому дебилу надо/». Действительно, запрограммировать поисковую систему так, чтобы она сразу же выдавала то, что надо по идиотским запросам - сложно. Но, похоже, обучить ИХ составлять запросы правильно еще сложней:
В моем поиске всеми этими значками никто не пользуется, как бы я ни распинался. Хотя, когда мне надо найти у себя что-то конкретное, я нахожу с первого запроса (да, конечно, я ведь примерно знаю, что искать, и сам писал скрипт поиска, но все-таки:)
Скрипт простой, но работает надежно. Если правильно составить запрос - то находит все с первого раза. В принципе, можно еще сделать сортировку по релевантности, сделать так, чтобы из найденного файла показывался кусок с текстом, где искомое слово было бы выделено, и прочее: Но это вы делайте сами:
<?php
// Spectator’s Site Search Script
// (C) Spectator.ru
// Для работы требуется PHP 4 или выше.
// Если вы будете использовать этот скрипт, ссылка на Spectator.ru
// крайне желательна. Спасибо.
// файл с индексом
$indexfile=”indexfile.txt”;
// обрабатываем запрос
$total=0;
$qu2=str_replace (”+”,”&”,$words);
// убираем заглавные буквы
$qu2=strtolower ($qu2);
// обрубаем в конце лишние пробелы
$qu2=chop($qu2);
// убираем двойные пробелы
while (stristr($qu2,” “)) $qu2=str_replace (” “,” “,$qu2);
echo (’Запрос: ‘.$qu2);
echo (’<p><p><p>’);
// разбиваем запрос на слова
$words = explode (’ ‘, $qu2);
// удаляем в запросе все лишнее (знаки препинания, и прочее)
$qu2=eregi_replace (’[.?,!()#”:;|]’, ”, $qu2);
// проверяем длинну запроса
if (strlen($qu2)>2):
// открываем индекс
$index=file ($indexfile);
$num= (count($index)-1);
// для каждой сточки из индекса (одна строчка=один файл) выполняем:
for ($i=1; $i<$num+2; $i++):
$contents=$index[$i-1];
$wordcount =0;
$mustfound=1;
// выполняем для каждого их запрошенных слов:
$mustntfound=1;
for ($q=0; $q<count($words); $q++):
// обработка знаков *, + и -
// знак *
if (stristr($words[$q], “*”)) {$search=str_replace (”*”,”",$words[$q]); }
else { $search=” “.$words[$q].” “;}
// если в слове есть звездочка, то убираем звездочку и добавляем в начало
// и конец слова по пробелу если нет проблелов, то слово будет искаться не
// целиком, а “вообще”, то есть на запрос “чай” будет выводиться и слово
// “случайный”.
// bug: скрипт не учитывает, где в слове стоит звездочка и считает, что в
// любом случае она стоит в конце (!!)
// знак & (или +)
if (stristr($search, “&”)) {$search=str_replace (”&”,”",$search); $mustfound++; }
// если стоит знак +, то количество слов, которые _должны_ быть найдены,
// увеличиваются на 1
// знак -
if (stristr($search, “-”)) {
$search=str_replace (”-”,”",$search);
$mustntfound=0;
}
// если стоит знак +, то если слово найдено, весь результат умножается на 0
// (смотри дальше).
// если слово найдено, считаем его и умножаем на $mustntfound, то есть на 1,
// если найдено “правильное” слово и на 0, если найдено слово, помеченное
// знаком -
if (stristr($contents, $search)) {
$wordcount++;
$wordcount=$wordcount*$mustntfound;
}
endfor;
// проверяем, все ли слова, помеченные знаком + найдены,
// либо (если таких слов нет), найдено ли вообще хоть одно слово
if ($wordcount >= $mustfound):
// находим имя файла, в котором это найдено
$file=explode (”|”,$contents);
$file=$file[0];
// выводим имя файла, в котором это найдено с ссылкой
// (этот кусочек вам надо будет переделать под собственные нужды).
$file=str_replace (”.txt”, “”, $file);
$file=str_replace (”_”, “.”, $file);
echo (”<a href=”.$file.”>”.$file.”</a><br>”);
// считаем, колько всего файлов надено
$total++;
endif;
// переходим к следующемуу файлу
endfor;
// выводим результаты
if ($total!=0) echo (’<br><br>Всего найдено страниц: ‘.$total);
else echo (”<b>Ничего не найдено!</b><p>Возможно, вы”.
.” просто не правильно составили запрос. Как это сделать правильно”.
.” - смотрите <a href=search>вот здесь</a>.”);
else:
echo (”<br>Слишком короткий запрос!”);
endif;
?>
<!– Форма для поиска: –>
<form method=get action=search.php>
<input type=text size=19 name=words value=”" maxlength=150>
<input type=submit class=frm value=Go>
</form>
PS. PHP.net - очень хороший сайт. 99% того, что мне надо, я нахожу там. Все просто: если вам не понятно, как работает какая-то функция, достаточно просто ввести ее в поле поиска на том сайте и вам выдадут описание функции, но кроме того, на php.net к описанию каждой функции пользователи могут оставлять комментарии, поэтому вам выдадут и гору толковых комментариев. То, что надо.
© http://spectator.ru/
Добавить комментарий >>>
Tags:
php,
search,
поиск
Март 23, 2008
В каждой области существуют свои мифы, каждая сфера деятельности овеяна некоторой тайной, в следствие которой появляются мифы. Я попытался описать 5 наиболее распространенных на мой взгляд мифов о Web программировании.
Миф №1. C++/Pascal рулят миром.
Многие думают что, только такие гиганты программирования как Страуструп могут создать идеальный язык. Нет, на самом деле действительно на C можно написать все, что только можно реализовать в виде логической цепочки действий, но(!): обращу ваше внимание на слово “написать”. Т.е. написать то можно, но вот сколько это потребует усилий, какова будет цена выбора в пользу “идеального” языка. Возьмем, например PHP. Все знают (ну теперь точно все), что интерпретатор PHP написать на C. Что из этого следует? А то, что для того что бы писать скрипты для web на C так же легко и быстро, нужно будет написать аналог PHP. Все дело в задаче и “себестоимости” ее выполнения.
Миф №2. Web программист никогда не напишет “ничего серьезного”.
Под “серьезным” обычно понимается масштабный проект, который отнимает кучу времени нервов, но приносит таки “достойное” вознаграждение. ОК, вот аналогия из жизни. Капуста на рынке стоит N, в магазине эта же капуста стоит 1,25N, в супермаркете premium класса эта же капуста стоит 5N, а на базаре на рублевке эта же капуста стоит 1000N. И что, кто тут рубит капусту (пишет что-то серьезное)? Так вот, имхо, вопрос не в том, как вырастить капусту (написать программу), а как ее продать, а это уже вопрос из области маркетинга и программирование (хоть на PHP, хоть на C/Pascal) тут вообще не причем.
Миф №3. Лучше начинать учиться со “сложных” языков.
Отчасти, да. Вот только, как всегда завеса тайны, внесла коррективы трактовку этой фразы. Действительно что бы понять суть азы программирования (типы данных, ссылки, основы ООП) лучше начинать изучать тот язык где эти самые азы реализованы лучше всего, но(!): Обычно фраза “Лучше начинать учиться со “сложных” языков” употребляется в том контексте, что допустим человек выучивший PHP не сможет перейти допустим на C. Повторюсь PHP написан на C и унаследовал очень много от прорадителя. Значит PHP похож на C, ровно столько же сколько C похож на PHP. Почему кто-то не сможет перейти от простого к сложному, а от сложного к простому этот же самый человек сможет. Т.е. не изучая PHP вы способны изучить C, а вот освоив PHP вы сразу же, по мановению волшебной палочки, теряете свойство “могу изучить C”. Бред!
Миф №4. Пиши с нуля.
Этот миф часто возникает сам по себе в головах начинах кодеров. “Ах, сколько кода, как долго с ним разбираться”: “лучше я напишу свой аналог, в котором будет присутствовать только “то, что мне нужно”". Почему это миф? Во-первых, если сложно понять, чужой код, то это еще не значит что он плохой. Во-вторых, составляем список “того что нужно”, сравниваем с тем, что есть и забиваем на “пиши с нуля”. Лучше писать надстройки для, например, управлениями обмена ссылками между 10 (100,1000 нужное подчеркнуть) форумов, модули ко всяким CMS наконец. Другое дело, если кодить что-то уникальное по своей природе, но в 99,99% случаях фраза “пиши с нуля” употребляется из-за нежелания разбираться в чужом коде и учиться в целом. Психологический вопрос, имхо. Конечно, можно кодить с нуля для “закрепления пройденного материала”, но опять же это укладывается в те самые 0,001%.
Миф №5. Все уже написано.
Вот только не надо опускать руки! На самом деле написано настолько мало, что аж [censored]! Другое дело, если в процессе обучения (практики) кодинга, возникает ситуация когда не находится задачи к которой стоит “приложить руки”. И вот тут в игру вступают “срули”. У них все написано, все сделано, все места заняты: пора пойти и застрелится. Что тут можно сказать. Люди, это не программирование придумает задачи для жизни, а жизнь дает пишу (кусочек хлебушка и чашку и икорочкой) программисту. Не там ищите идеи. Действительно, накодить что-нить очень тяжело, а вот например накодить парсер, новостного сайта сложнее, еще сложнее усовершенствовать его и сделать настраиваемые уведомленения по e-mail, icq и sms. Еще сложнее написать систему отслеживания копий текста с сайта (мало ли кто-то “чисто случайно” забыл поставить ссылку на вас). Да много чего можно сделать.
Вместо заключения скажу, что многие мифы это на самом деле простая подмена понятий, или какие-то психологические барьеры. Никто не может решить за вас, что лучше, что правда, и что делать.
Автор: Kotov
Блог PHP разработчика
Tags:
php
Март 22, 2008
Для начала немного теории. Всё, что написано ниже, справедливо для сервера Apache (их в интернете подавляющее большинство). Когда вы набираете в строке несуществующий адрес или переходите по “битой ссылке”, на страничке высвечивается жирными буквами сообщение “Not found” - “документ не найден”, хотя вкупе с устрашающим видом надписи может быть переведено неопытным пользователем как “пошёл вон” :). Кстати, пользователи IE возможно и не видели эту страничку ни разу, поскольку он – IE – формирует в этом случае своё сообщение с “дружественным” содержанием, типа “обновите страничку, позвоните другу, который вам эту ссылочку дал…”, но это делу не помогает. Иногда IE может и показать оригинальное сообщение сервера, но только в том случае, если оно больше определённого размера (по умолчанию 512 байт). В общем, итог всегда один – страницы нет и посетитель недоволен. А нам, администраторам сайтов, надо заботиться, чтобы эта потеря была менее болезненной.
Давайте разберёмся, что происходит на сервере при запросе правильных/ошибочных URL.
При GET-запросе URL (тот, что в адресной строке браузера) передаётся серверу, а на выходе клиенту выдаётся набор заголовков с последующим полем данных. Заголовки никогда не выводятся пользователю напрямую и содержат много служебной информации, которая может быть обработан CGI-скриптами (а это нам скоро и понадобится). Среди этих заголовков всегда есть так называемые коды ответов. Если не вдаваться в подробности, то они передаются примерно так:
GET /index.htm
HTTP/1.1 404 Not Found
Первая строка говорит о том, что пользователем (точнее, браузером пользователя) был передан запрос на страницу index.htm, а вторая строка сообщает ему, что такой документ не найден, после чего следует блок данных - HTML-страница с сообщением об ошибке. Если URL правильный, всё происходит так, как и должно быть – передаётся код ответа 200 и запрошенный файл. Код ответа 200 передаётся всегда при удачном запросе, но мы его никогда не видим. Как уже говорилось, при неудачном запросе сервер сгенерирует код ответа в диапазоне 400…499 и мы увидим стандартное сообщение об ошибке, которое, огорчает пользователя и портит репутацию сайта. Вообще говоря, у хорошего веб-мастера таких ошибок на сайте быть не должно, но не его вина, что например пользователь вдруг сказал “принеси то, не знаю что”.
Перейдём от рассуждений к делу. У сервера Apache имеется стандартная директива обработки ошибок ErrorDocument, которая сопоставляет коду ошибки адрес документа, который будет показан пользователю. Обычно перенаправление устанавливают на документ, содержащий логотип сайта и краткую информацию “что делать”. Увидев такую страницу вместо стандартного сообщения на fatal.ru (два года назад, когда начал заниматься веб-программированием), я долго был под впечатлением! Откуда они знают, что у меня такой страницы нет?
Формат директивы такой:
ErrorDocument 400 400.html
В случае ошибки 400 пользователю выдаётся файл 400.html – всё очень просто и удобно. Сразу же отметим, что можно использовать четыре варианта передачи сообщения об ошибке:
ErrorDocument 500 http://foo.example.com/cgi-bin/tester
ErrorDocument 404 /cgi-bin/bad_urls.pl
ErrorDocument 401 /subscription_info.html
ErrorDocument 403 “Доступ запрещён
В последнем случае вместо файла пользователь увидит на экране сообщение, следующее за одиночной кавычкой (закрывать кавычку не следует!).
Прописать директиву можно в двух местах – конфигурационном файле Apache httpd.conf или в файле управления доступом к директориям .htaccess. В первом случае вы должны иметь доступ к httpd.conf, а для этого вы должны являться администратором сервера (и, скорее всего, вам эта статья не понадобилась бы
или пользователем хостинга - во втором случае. Причём на хостинге должно быть включено редактирование .htaccess самим пользователем (это позволяют все платные и почти все бесплатные службы хостинга). Кроме того, на платных серверах часто установлена панель управления сайтом cPanel. Так вот там можно самому создавать и редактировать страницы ошибок на основе SSI, даже если вы не специалист.
Короче, создаём в корне своего сайта файлик .htaccess и записываем в него строчку “ErrorDocument 400 400.html”, при условии, что файл 400.html уже существует.
Дальше начинается самое интересное, а именно – как будет выглядеть эта страничка. Создать страничку можно как минимум тремя способами.
Самый простой вариант – набросать в HTML-редакторе или “Блокноте” обычную страничку без графического оформления и сохранить её под нужным именем.
Вариант посложнее – можно добавить в этот HTML-файл директивы SSI, которые, к примеру, будут показывать URL, адрес, с которого пришёл пользователь, время, подключить внешние файлы и т.д.
Но согласитесь, первые два варианта – это примитив, не достойный настоящего веб-мастера. Опытные программисты пишут страницы ошибок на PHP. В этом есть одно достоинство – мы получаем доступ к переменным окружения, что даёт возможность лучше анализировать ошибочную ситуацию, выводить больше информации пользователю, вести лог ошибок в файле или базе данных, а не тупо выводить Not found и посылать бедного юзера на главную страницу.
Как вы уже догадались, мы будем рассматривать именно третий вариант. Начнём с простого – как PHP-скрипт получит код ошибки? Естественно, через параметр. Например, вот так:
error.php?400
В самом скрипте код ошибки после знака вопроса будет доступен в переменной $argv[0]. Теперь проверим скрипт (он должен вывести код ошибки, указанный после “?”):
<?php
echo $argv[0];
?>
Можно было бы передать параметр в классическом виде error.php?id=400, но так проще и безопаснее - этот скрипт принимает только одну переменную, зачем лишние лазейки? Сразу же обезопасим скрипт от ввода неверных данных: приведём код к целому типу и сделаем присвоение кода 404 по умолчанию, если скрипт был вызван без параметров. Приучайте себя к написанию безопасного кода! Теперь даже если ввести error.php?404abc или даже error.php?abc – значение переменной $id (она введена для читабельности) всё равно будет равно 404.
<?php
// проверяем переменную
$id = $argv[0];
$id = abs(intval($id));
if (!$id) $id = 404;
echo $id;
?>
Следующее, что мы сделаем – сопоставим коду русскоязычное описание. Это можно сделать при помощи операторов switch…case, но опытный программист сделает это более аккуратно и красиво – через ассоциативный массив, в котором ключ – это код ошибки, а значение – это её описание. Сделать такой массив очень просто, да и дополнять его легче, чем списки switch…case. Рекомендую начинающим программистам всегда использовать ассоциативный массив вместо switch…case, если в списке больше трёх позиций – выигрыш в скорости, размере кода и понятности. Список кодов ошибок можно найти на официальном сайте W3C (организация по веб-стандартам) http://www.w3.org/Protocols/HTTP/HTRESP.html или в любой книге по веб-программированию. Я сделал так:
<?php
// проверяем переменную
$id = $argv[0];
$id = abs(intval($id));
if (!$id) $id = 404;
// ассоциативный массив кодов и описаний
$a[401] = “Требуется авторизация”;
$a[403] = “Пользователь не прошел аутентификацию, доступ запрещен”;
$a[404] = “Документ не найден”;
$a[500] = “Внутренняя ошибка сервера”;
$a[400] = “Неправильный запрос”;
// выводим код и описание
echo “$id $a[$id]”;
?>
В результате выполнения этого кода пользователь увидит сообщение “404 Документ не найден” (по-крайней мере, так вывелось у меня, вы можете смоделировать другую ошибку). В принципе, тот же результат можно было получить при использовании первого способа, но разве я сказал, что мы на этом остановимся?
Кстати, пока не забыл. Ошибки с кодами 500…599 встречаются обычно, когда ошибку совершает сценарий, расположенный на вашем сервере. Некоторые интерпретаторы сценариев, например PHP, никогда не допускают такой ошибки. Если в вашем PHP-скрипте есть ошибка, интерпретатор сам выведет ошибочное сообщение с указанием её происхождения, а не просто “Ошибка”. Со стороны сервера это будет выглядеть как нормальный ответ клиенту, поэтому ошибка 500 при использовании PHP у вас практически никогда не возникнет. В отличие от него, Perl генерирует ошибку 500 и записывает сообщение о ней в лог сервера. Иди потом, разбери его – сложность отладки Perl-скриптов очень высокая. Это была одна из причин, по которой мне в своё время посоветовали перейти с Perl на PHP, что я и сделал, чего и вам желаю.
Вернёмся к написанию скрипта. Основная часть – обработка кода ошибки – уже готова, займёмся “довесками”. Для начала выведем поясняющее сообщение с ошибочным URL:
echo “Запрошенный Вами URL: <b>http://$SERVER_NAME$REQUEST_URI</b><br /> “;
Здесь включены две глобальные переменные $SERVER_NAME и $REQUEST_URI. Первая содержит имя сервера, вторая – URI (не путать с URL), который был запрошен. Выведем ещё на всякий случай IP-адрес, название браузера и текущее время на сервере.
$time = date(”d.m.Y H:i:s”);
Ваш IP: <b>$REMOTE_ADDR</b><br />
Ваш браузер: <b>$HTTP_USER_AGENT</b><br />
Текущее время сервера: <b>$time</b><br />
Согласитесь, это уже намного интересней. Если пользователь пришёл на ошибку по ссылке с другой страницы, покажем ему и этот URL этой страницы (переменная $HTTP_REFERER). Дополнительно можно показать реальный IP-адрес клиента, если он работает через прокси (переменная $HTTP_X_FORWARDER_FOR).
if ($HTTP_REFERER) $body .= “Вы пришли со страницы: <b>$HTTP_REFERER</b><br />\n”;
if ($HTTP_X_FORWARDER_FOR) $body .= “Ваш IP через прокси: <b>$HTTP_X_FORWARDER_FOR</b><br />\n”;
Ну и в завершение в самом низу “подпись” сервера (не на всех хостингах она работает корректно, потому что это чисто “серверная” переменная):
$_SERVER[’SERVER_SIGNATURE’]
Думаю, этой информации будет достаточно, для того чтобы пользователь не почувствовал, что пришёл на “последнюю страницу интернета”. Но есть ещё одна деталь, которую не упустит внимательный программист. Вспомните, откуда вы чаще всего попадаете на “ошибочные страницы”? Ну конечно из поисковых систем, в которых информация быстро устаревает. В подавляющем большинстве случаев мелкие проекты, типа FoxWeb переезжают с сервера на сервер, а потом на новом сервере сайт модернизируется, а старый остаётся на старом месте. Ну, в общем вы меня поняли :). У меня было так: первый сайт был открыт на kiiut.fatal.ru, потом всё его содержимое было скопировано на foxweb.net.ru. В поисковых системах хранятся ссылки на оба сайта. Через какое-то время старое содержимое на foxweb было удалено, а ссылки на него остались в поисковиках. Если заменить адрес сервера в ошибочной ссылке, то она вполне будет работать. Поясню на примере.
Предположим, человек нашёл в поисковой системе что-то вроде http://foxweb.net.ru/catalog/sbornik_fox1.html. У нашего сервера такой ссылки нет, но она должна была остаться на старом сервере со старым содержимым. Тогда предложим пользователю перейти по адресу http://kiiut.fatal.ru/catalog/sbornik_fox1.html. Опишем эту особенность в программе:
Возможно интересующую Вас информацию можно найти по старому адресу:<br />
<a href=”http://kiiut.fatal.ru$REQUEST_URI” mce_href=”http://kiiut.fatal.ru$REQUEST_URI” target=”_blank”><b>http://kiiut.fatal.ru$REQUEST_URI</b></a><br />
Даже если у вас не было такой ситуации, как у меня, возможно вы переписывали скрипты и адреса поменялись. Например, http://someserver.ru/catalog/ заменим на http://someserver.ru/?catalog или http://someserver.ru/cgi-bin/catalog.cgi. Надеюсь, общий смысл вам понятен, главное знать, что чем заменять.
Есть ещё одна особенность применения ошибочных страниц. Если несуществующая страница вызвана через вложенные директории, а заменяющая страница (наш скрипт) лежит в корне, то картинки на ней не будут отображены, а ссылки не будут работать. Почему? Это произойдёт в том случае, если вы используете на своём сайте относительные пути. Приведу пример:
http://someserver.ru/dir1/dir2/dir3/ - ошибочный адрес.
http://someserver.ru/error.php?404 – заменяющая страница будет вызвана по ОШИБОЧНОМУ ПУТИ, так как будто она там и хранится! Тогда ссылки на ней типа “page1.html” будут реально приводить вас по адресу http://someserver.ru/dir1/dir2/dir3/page1.html что будет вызывать ещё большее количество ошибок. Излечиться от этого можно, используя абсолютные пути ссылок и картинок (с именем сервера) или ставить перед относительным путём знак “/”, что на большинстве серверов указывает на корневую директорию сайта. Заметьте, что знак “./” будет указывать на текущую директорию.
В заключении приведу полный текст скрипта. Он может использоваться как сам по себе (отдельная страничка), так и в качестве модуля основного движка (вызывается движком и вставляется в серединку шаблона, как это сделано на моём сайте). Для серьёзных профессиональных проектов может оказаться полезным записывать информацию об ошибках в базу данных.
Фрагмент файла http.conf или .htaccess для правильной обработки ошибок:
ErrorDocument 400 /error.php?400
ErrorDocument 401 /error.php?401
ErrorDocument 403 /error.php?403
ErrorDocument 404 /error.php?404
ErrorDocument 500 /error.php?500
Текст скрипта error.php:
<?php
$id = $argv[0];
$id = abs(intval($id));
if (!$id) $id = 404;
// ассоциативный массив кодов и описаний
$a[401] = “Требуется авторизация”;
$a[403] = “Пользователь не прошел аутентификацию, доступ запрещен”;
$a[404] = “Документ не найден”;
$a[500] = “Внутренняя ошибка сервера”;
$a[400] = “Неправильный запрос”;
// определяем дату и время в стандартном формате
$time = date(”d.m.Y H:i:s”);
// эта переменная содержит тело сообщения
$body =<<<END
Запрошенный Вами URL: <b>http://$SERVER_NAME$REQUEST_URI</b><br />
Возможно интересующую Вас информацию можно найти по старому адресу:<br />
<a href=”http://kiiut.fatal.ru$REQUEST_URI” mce_href=”http://kiiut.fatal.ru$REQUEST_URI” target=”_blank”><b>http://kiiut.fatal.ru$REQUEST_URI</b></a><br />
<br />
Ваш IP: <b>$REMOTE_ADDR</b><br />
Ваш браузер: <b>$HTTP_USER_AGENT</b><br />
Текущее время сервера: <b>$time</b><br />
END;
if ($HTTP_REFERER) $body .= “Вы пришли со страницы: <b>$HTTP_REFERER</b><br />\n”;
if ($HTTP_X_FORWARDER_FOR) $body .= “Ваш IP через прокси: <b>$HTTP_X_FORWARDER_FOR</b><br />\n”;
?>
<h1><i><?=$id?></i> <?=$a[$id]?></h1>
<p><?=$body?></p>
<?=$GLOBALS[’SERVER_SIGNATURE’]?>
Желаю всем удачного программирования и 200-го кода!
Автор: fox++
http://foxweb.net.ru/
Tags:
404,
Apache,
error,
php
Март 21, 2008
Сегодня расскажу вам о том, как написать гостевую книгу на PHP и MySQL. Ничего сложного в этом нет, да и возможности данной гостевой не самые большие: постраничный вывод записей, проверка вводимых данных, возможность удалять записи.
Допустим, что у вас уже есть PHP, MySQL и веб-сервер. Вы всё установили и настроили.
Начнём с создания таблицы, в которой будут храниться данные нашей гостевой книги. Будем спрашивать у пользователя имя и комментарий. При желании пользователь сможет сообщить адреса электронной почты и домашней странички. Для администрирования книги нам понадобится ещё одно поле, уникальное для каждой записи, - идентификатор. Ну и дата, конечно. В итоге получается такая таблица:
CREATE TABLE gb (
id int(10) unsigned NOT NULL auto_increment,
datetime datetime DEFAULT ‘0000-00-00 00:00:00′ NOT NULL,
name varchar(100) NOT NULL,
email varchar(100),
www varchar(100),
message text NOT NULL,
PRIMARY KEY (id)
);
Таблица у нас есть. Теперь можно приступать к программированию.
Для создадим файл с настройками гостевой книги:
<?php
// общие константы
define(’PATH’, ‘/gb/’); // путь к гостевой книге
define(’RECSPERPAGE’, 10); // количество записей на одной странице
define(’ADMIN_EMAIL’, ‘artem@sapegin.ru’); // email администратора
define(’ERROR_LOG_FILE’, ‘logs/error.log’); // файл лога ошибок
// Параметры БД
define(’DBHOST’, ‘localhost’); // имя хоста
define(’DBUSER’, ‘root’); // имя пользователя
define(’DBPASSWD’, ”); // пароль
define(’DBNAME’, ‘test’); // имя базы данных
?>
Теперь подумаем, какие вспомогательные функции нам понадобятся. На нужно будет взаимодействовать с СУБД, проверят и обрабатывать вводимые пользователем данные. Так же для функций администрирования на понадобится отличать администратора от простых пользователей.
Начнём с работы с СУБД.
<?php
/** recource db_connect ( string host, string user, string passwd, string dbname )
* Подключение к СУБД и открытие базы данных
*/
function db_connect($host, $user, $passwd, $dbname)
{
$link = mysql_pconnect($host, $user, $passwd) or die(’Could not connect to database’);
mysql_select_db($dbname) or die(’Could not select database’);
return $link;
}
/** Выполняет запрос к БД
*
* @param текст запроса
* @return resource id
*/
function db_query($query)
{
$result = mysql_query($query)
or die(’Bad database query’);
return $result;
}
/** Выполняет запрос к БД (placeholder)
*
* @param текст запроса
* @param*
* @return resource id
*/
function db_query_ex($query)
{
$values = func_get_args();
array_shift($values);
$i = 0;
return db_query(preg_replace(’%\?%e’, ‘”\’”.addslashes($values[$i++]).”\’”‘,
$query));
}
?>
Обработка строк (проверка и фильтрация вводимых пользователем данных).
<?php
/**
* Проверяет является ли строка адресом e-mail
*/
function strings_isemail($string)
{
return preg_match(’%[-\.\w]+@[-\w]+(?:\.[-\w]+)+%’, $string);
}
/**
* Добавление ссылок на http и e-mail
*/
function strings_addlinks($string)
{
return preg_replace(
‘%((?:http|ftp)://[-\w]+(?:\.[-\w]+)+\b[-\w:@&?=+,!/~*$\.\’\%]*)(?<![\.,?!)])%i’,
‘<a href=”\\1″>\\1<a>’,
$string
);
}
/**
* Чистка строки
*/
function strings_clear($string)
{
$string = trim($string);
$string = stripslashes($string);
return htmlspecialchars($string, ENT_QUOTES);
}
/**
* Обрезание строки
*/
function strings_stripstring($text, $wrap, $length)
{
$text = preg_replace(’%(\S{’.$wrap.’})%’, ‘\\1 ‘, $text);
return substr($text, 0, $length);
}
?>
Написание аутентификации администратора я оставляю вам в качестве домашнего задания. Есть достаточно много способов и их обсуждение - тема отдельной статьи. Я приведу лишь функцию-заглушку:
<?php
/**
* Проверка: администратор или обычный пользователь
*/
function auth_is_admin()
{
return @$_GET[’admin’];
}
?>
Далее идёт достаточно большой модуль, в котором содержится почти весь HTML-код гостевой книги, - шаблон. В нём нет ничего сложного и его написание можно вполне под силу верстальщику сайта, если у вас таковой имеется.
<?php
/**
* заголовок страницы
*/
function template_header($page)
{
?><html>
<head>
<title>page <?=$page?> < fjGuestbook Demo</title>
<style>
body{
padding: 15px;
margin: 0;
color: #333;
background-color: #eee;
border-left: 30px solid #adba8e;
font: 500 .9em verdana, arial, helvetica;
}
a:link{color: #250;}
a:visited{color: #639;}
a:active,a:hover{
color: #c00;
text-decoration: underline;
}
h1 { font-size: 150%; }
h2 { font-size: 110%; }
.c{margin-bottom: 10px;}
.cn{
background-color: #d2d6bc;
padding: 2px 4px;
margin-bottom: 4px;
}
</style>
</head>
<body>
<h1>fjGuestbook Demo</h1><?php
}
/**
* окончание страницы
*/
function template_footer()
{
?>
<p>fjGuestbook 1.2. Copyright © 2002—2004
<a href=”http://sapegin.ru”>Artem Sapegin</a></p>
</body></html>
<?php
}
/**
* форма добавления новой записи
*/
function template_form($name, $email, $www, $message, $error)
{
// вывод сообщения об ошибке
function error($error)
{
if($error) echo ‘<br><font color=#880000>’.$error.
‘</font>’;
}
echo ‘<h2>Добавить новое сообщение</h2>
<p><table cellspacing=”2″ cellpadding=”2″ border=”0″>
<form action=’.PATH.’?add=1 method=post><tr>
<td>Имя<font color=#880000>*</font>:</td>
<td><input type=text name=”name” size=30
maxlength=100 value=”‘.$name.’”>’;
@error($error[’name’]);
echo ‘</td>
</tr><tr>
<td>Email:</td>
<td><input type=text name=”email” size=30
maxlength=100 value=”‘.$email.’”>’;
@error($error[’email’]);
echo ‘</td>
</tr><tr>
<td>URL:</td>
<td><input type=text name=”www” size=30
maxlength=100 value=”‘.$www.’”>’;
echo ‘</td>
</tr><tr>
<td>Сообщение<font color=#880000>*</font>:</td>
<td><textarea cols=40 rows=5
name=”message”>’.$message.’</textarea>’;
@error($error[’message’]);
echo ‘</td>
</tr><tr>
<td> </td>
<td><small><font color=#880000>*</font>
— Обязательные поля</small></td>
</tr><tr>
<td> </td>
<td><input name=”sb” type=submit
value=”Добавить сообщение”></td>
</form></tr>
</table>’;
}
/**
* печать одной записи гостевой книги
*/
function template_show_body($id, $name, $email, $www, $message, $datetime)
{
$out = ‘<div class=c><div class=cn><b>’.$name.’</b>’;
// если есть email или homepage - печатаем их
if($email || $www)
{
$out .= ‘( ‘;
if($email)
$out .= ‘ <a href=mailto:’.$email.’>email</a>’;
if($email && $www)
$out .= ‘ | ‘;
if($www)
$out .= ‘ <a href=’.$www.’>www</a>’;
$out .= ‘ )’;
}
$out .= ‘ пишет ‘.$datetime.’:</div>’.$message.’</div>’;
// если гостевую книгу просматривает администратор - печатаем кнопку
// удаления записи
if(auth_is_admin())
{
$out .= ‘<div class=c>[ <a href=’.PATH.’?admin=1&del=’.$id.
‘>удалить</a> ]</div>’;
}
return $out;
}
?>
И вот, мы наконец-то дошли до главного. До модуля гостевой книги. Постараюсь написать побольше комментариев, чтобы вам было понятно.
<?php
/**
* Создание таблицы, если её ещё нет
*/
function gb_install()
{
db_query(
‘CREATE TABLE IF NOT EXISTS gb (
id int(10) unsigned NOT NULL auto_increment,
datetime datetime NOT NULL default \’0000-00-00 00:00:00\’,
name varchar(100) NOT NULL default \’\',
email varchar(100) default NULL,
www varchar(100) default NULL,
message text NOT NULL,
PRIMARY KEY (id),
INDEX (datetime)
) TYPE=MyISAM;’
);
}
/**
* Добавление записи в гостевую книгу
*/
function gb_add($name, $email, $www, $message, &$error)
{
// проверяем правильность заполнения полей
$error = ”;
if(empty($name))
$error[’name’] = ‘Это обязательное поле’;
if(empty($message))
$error[’message’] = ‘Это обязательное поле’;
if(!empty($email) && !strings_isemail($email))
$error[’email’] = ‘Это не email’;
// если не было ошибок - добавляем
if(!$error)
{
// чистим данные
$name = strings_clear($name);
$message = strings_clear($message);
$name = strings_stripstring($name, 15, 100);
$email = strings_stripstring($email, 100, 100);
$www = strings_stripstring($www, 100, 100);
$message = strings_stripstring($message, 100, 2000);
$message = nl2br($message);
// если пользователь поленился написать http:// перед адресом - сделаем
// это за него
if(!empty($www) && ‘http://’ != substr($www, 0, 7))
$www = ‘http://’.$www;
// запрос на добавление записи в базу данных
db_query_ex(’INSERT INTO gb (name, email, www, message, datetime)
VALUES(?, ?, ?, ?, NOW())’, $name, $email, $www, $message);
// перекидываем браузер на первую страницу
// это нужно, чтобы, если пользователь нажмет кнопку Refresh,
// запись не добавилась еще раз
header(’Location: ‘.PATH.”?page=1″);
}
}
// удаление записи из гостевой книги
function gb_delete($id)
{
// запрос на удаление записи из базы данных
// WHERE id = ‘.$id указывает на запись, которую следует удалить
db_query_ex(’DELETE FROM gb WHERE id = ?’, $id);
header(’Location: ‘.PATH.”?page=1″); // ???
}
// вывод страницы с записями
function gb_show($page)
{
// положение первой записи страницы
$begin = ($page - 1) * 10;
// выборка записей из базы данных
// SELECT * FROM gb - все поля из бд gb
// ORDER BY datetime DESC - сортировка по дате, новые сверху
// LIMIT ‘.$begin.’,’.RECSPERPAGE - ограничение:
// RECSPERPAGE (см. defines.php) записей начиная с $begin
$result = db_query(’SELECT * FROM gb ORDER BY datetime DESC LIMIT ‘.
$begin.’, ‘.RECSPERPAGE);
$out = ”;
// цикл по всем выбранным записям
while($row = mysql_fetch_array($result))
$out .= template_show_body($row[’id’], $row[’name’], $row[’email’],
$row[’www’], $row[’message’], $row[’datetime’]);
// уничтожаем результат
mysql_free_result($result);
echo $out;
}
// вывод списка страниц
function gb_showpages($current)
{
// узнаем число записей в гостевой книге
$result = db_query(’SELECT * FROM gb’);
$rows = mysql_num_rows($result);
if($rows)
{
$pages = ceil($rows / RECSPERPAGE);
// печатаем ссылки на страницы (номер текущей страницы не является ссылкой)
echo ‘<div class=c>’;
for($i = 1; $i <= $pages; $i++)
{
if($i != $current)
echo ‘ | <a href=’.PATH.’?page=’.$i.’>’.$i.’</a>’;
else
echo ‘ | ‘.$i;
}
echo ‘ |’;
// если это не полследняя страница печатаем ссылку “Дальше”
if($current < $pages)
echo ‘ >a href=’.PATH.’?page=’.($current + 1).
‘>Дальше >></a>’;
echo ‘</div>’;
}
}
?>
И последнее - объединяем всё вместе.
<?php
/**
* fjGuestbook 1.2
*
* Ядро гостевой книги
*
* Copyright 2002-2004 Artem Sapegin
* http://sapegin.ru
*/
// подключаем модули
require_once ‘my/defines.php’;
require_once ‘my/template.php’;
require_once ‘engine/lib/strings.php’;
require_once ‘engine/lib/auth.php’;
require_once ‘engine/lib/bd.php’;
require_once ‘engine/gb.php’;
// подключаемся к БД
db_connect(DBHOST, DBUSER, DBPASSWD, DBNAME);
// создаём таблицу, если её нет
gb_install();
// получаем данные формы, если форма была отправлена
if (!empty($_POST[’sb’]))
{
$name = @$_POST[’name’];
$email = @$_POST[’email’];
$www = @$_POST[’www’];
$message = @$_POST[’message’];
$formerr = ”;
}
else
{
$name = $email = $www = $message = $formerr = ”;
}
// если в GET-запросе не указан номер страницы, выводим первую
if(is_numeric(@$_GET[’page’]))
$page = $_GET[’page’];
else
$page = 1;
// если нужно добавить запись, добавляем
if(@$_GET[’add’])
gb_add($name, $email, $www, $message, $formerr);
// если нужно удалить запись, удаляем
if(isset($_GET[’del’]) && auth_is_admin())
gb_delete(intval($_GET[’del’]));
// печатаем гостевую книгу
template_header($page);
gb_showpages($page);
gb_show($page);
gb_showpages($page);
template_form($name, $email, $www, $message, $formerr);
template_footer();
?>
Как видите, ничего сложного не было. Но если вопросы всё же возникнут - спрашивайте - постараюсь помочь.
Вы так же можете скачать гостевую книгу.
Автор: Artem Sapegin
http://sapegin.ru
Источник: http://www.codenet.ru
Tags:
MySQL,
php
Март 18, 2008
Виват, дорогие читатели ! Сегодня, в этот ничем не знаменательный день, а может быть совершенно наоборот, я написал “это”, а сейчас вам предстоит всё “это” прочитать, а самое главное понять. Сегодня мы с вами посвятим время такой теме, как “Системы голосований на РНР”.
Да, на сегоднешний день этой довольно интересной теме посвященны целые горы статей, но как-то маловато статей рассказывают всё чётко и во всех мелочах. Именно про эти мелочи и чётко мы сейчас и поговорим. Наверное больше половины из вас участвовали в разных интерактивных голосования, форумных pool’ах, и в прочей дребедени. Но как это всё построено ? На каком алгоритме это всё “пашет” ?
А алгоритмов существует великое множество. Сейчас я перечислю самые популярные среди разработчиков:
XML- вопрос и ответы, храняться в одном XML файле, с которого через парсер и “достают” все данные для обеспечения работы голосований.
Хранение данных в файле - этот тип хранения более популярен чем его XML-ий товарищ. Принцип такой: вопрос помещаеться в первую строку файла, после построчно выводятся варианты ответа, а через некий разделитель количество голосов. Однако в отличие от предыдущего данный метод требует больше времени для написания, и немного ограничивает возможности.
Хранение данных в БД (способ №1) - этот способ являеться самым рациональным, ведь он не требует особых ментальных усилий, при этом обеспечивая высокую продуктивнось, но для него, во-первых, требуеться наличие БД, во-вторых, этот метод занимает много физической памяти базы данных. Мы рассмотрим его в самом начале. Принцип таков: вопрос и прочие статические данных располагаються в некой абстрактной таблице “а”, при этом ответы в таблице “б”. На каждый ответ приходится ряд “р”, таблице “а”, который имеет ссылку на номер голосования находящегося в таблице “а”. То есть между таблицей “а” и “б” устанавливаеться прямая связь. Почему я сказал, что этот способ требует немалого пространства в БД ? Так потому, что под каждый ответ выделяеться отдельный столбец, а это крайне не рационально в случае при работе с БД.
Хранение данных в БД (способ №2) - этот способ самый сложный среди всех вышеперечисленных, однако он не занимает много место, при этом он совмещает ответы, вопрос, и количество ответов в отной таблице, и в этой таблице ряд отводиться под всё голосование в целом, хотя эта компактность компенсируеться повышеной сложностью обновления данных, и засчёт каждого нового голоса. Принцип в том, что все ответы помещаються в одну строку, при чём в особом порядке, так, что позиции каждого ответа соответствует позиция значения количества голосов, людей которые проголосовали именно за этот вариант. Так же поле вариантов ответов, и соответственно количества голосов будут кодироваться в формате base64, для уменьшения размера конечной строки.
Всё, это были все варианты выполнения алгоритма процесса голосования, которые мы сегодня рассмотрим.
Лично мой выбор второй вариант хранения в БД, так как я автор данного способа, и он мне более по душе, но тем кто только начинает осваивать технологии Веб-программирования, я советую почитать про первый вариант при работе с БД. Что ж, теперь можно перейти к практической части, и она начнёться именно с первого варианта при работе с БД. Но я забыл сказать про довольно важную на мой взгляд деталь. Я забыл сказать про защиту от повторного голосования, это мы будем делать во всех голосования при помощи БД, поскольку именно этот способ обеспечивает полную целостность данных, ибо для доступа к БД третьим лицам им нужно будет иметь данные для доступа или взломать БД, что крайне проблематично, поэтому хакерам будет довольно сложно нанести скрипту урон в этой части. А файлы всегда можно удалить, даже если на них будет блокировка “666″, то всё равно они защищены не в полном объёме.
Поэтому я выбрал БД. Посему сейчас вам нужно создать табличку для храния данных о пользователях которые уже проголосовали.
Структура таблички такова:
ТАБЛИЦА `alredy_vote`:
id- BIGINT- AUTO_INCREMENT- PRIMARY KEY
vote_id- BIGINT- NOT NULL- UNIQUED
ip- TEXT- NOT NULL
Название оставляю вам, но скажу что я буду использовать “alredy_voted”. Что ж, а теперь на практику 
Первый вариант при работе с БД.
Что ж, по моему мнению для тех кто называет себя программистами на “достаточном” уровне хватило бы одного только описания в начале, но во всяком случае я опишу данный способ со всеми частицами. Итак, с начала мы создадим две таблички в вашей базе данных, называйте их как хотите, но в статье я буду использовать названия “pools_answs” (для вариантов ответа) и “pools” (для самих голосований). Их структура сдедующая:
ТАБЛИЦА `pools`:
id- BIGINT- AUTO_INCREMENT- PRIMARY KEY
question- TEXT- NOT NULL
status- ENUM(’on’,'off’)- DEFAULT ‘on’- NOT NULL
ТАБЛИЦА `pools_answs`:
id- BIGINT- AUTO_INCREMENT- PRIMARY KEY
vote_id- BIGINT- NOT NULL- UNIQUED
value- TEXT- NOT NULL
Вот, это структура таблиц для работы нашей системы, я привожу лишь их сруктуру, поскольку если вы даже языка SQL не знаете, то вам делать здесь явно нечего. Итак, первой нашей с вами функцией будет функция для добавления голосований.
Так же хочу упомянуть, что пользователь сам может устанавливать количество вариантов ответа. Конечно есть соблазн использовать DOM модель и любимый нами всеми JS (ведь так ?), но я воздержусь, и воспользуюсь простым и банальным параметром QUERY_STRING и старым, родимым $_GET интерфейсом. Что ж, давайте попытаемся воплотить всё это безобразие на “холсте” 
<?
$count=isset($_GET[’count’])? $_GET[’count’]:5;
if(!isset($_POST[’add’])){
print”<form action=” method=’post’ name=’addPool’>”;
print”<table width=’300′ height=’50′ align=’center’>”;
print”<tr><td colspan=’2′ style=’text-align:center;’><input
size=’40′ type=’text’ name=’question’
value=’Введите вопрос голосования’ onFocus=’this.select();’></td>
</tr>”;
print”<tr><td style=’text-align:center;’ colspan=’2′><button
onClick=\”top.location=’”.$_SERVER[’PHP_SELF’].”?count=”.($count+1).”‘\”>
Добавить вариант</button></td></tr>”;
for($i=0;$i<$count;$i++){
print”<tr><td>Вариант ответа №”.$i.”:</td><td><input
type=’text’ name=’answs[]’></td></tr>”;
}
print”<tr><Td colspan=’2′ style=’text-align:center;’><input
type=’submit’ name=’add’ value=’Добавить’></td></tr>”;
print”</table>”;
print”</form>”;
}else{
$question=$_POST[’question’];
$answs=$_POST[’answs’];
if(trim($question)==”){
die(”Вы не ввели вопрос !”);
}
$count=0;
for($i=(count($answs)-1);$i>=0;$i–){
if(trim($answs[$i])==”){
$count++;
}
if($count==count($answs) || (count($answs)-$count)<2){
die(’Должно быть как минимум 2 варианта ответа!’);
}
}
$conn_id=@mysql_connect(”localhost”,”root”,”") or die(”Ошибка соединения с
сервером БД !”);
@mysql_select_db(”shockstudio”);
$check=@mysql_query(”SELECT id FROM `pools` WHERE question=’”.$question.”‘”,
$conn_id) or die(”Ошибка запроса к БД !”);
if(@mysql_num_rows($q)!=0){
die(”Голосование с таким вопросом уже существует !”);
}
unset($check);
$q=@mysql_query(”INSERT into `pools` VALUES(”,’”.$question.”‘,’on’)”,$conn_id)
or die(”Ошибка запроса к БД !”);
unset($q);
$q=@mysql_query(”SELECT id FROM `pools` WHERE question=’”.$question.”‘”,$conn_id)
or die(”Ошибка во время запроса к серверу !”);
$row=@mysql_fetch_array($q);
$id=$row[’id’];
unset($q,$row);
for($i=(count($answs)-1);$i>=0;$i–){
if(trim($answs[$i])!=”){
$q=@mysql_query(”INSERT into `pools_answs` VALUES(”,’”.$id.”‘,’”.$answs[$i].”‘,
”)”,$conn_id) or die(”Ошибка запроса к БД !”);
}
}
}
?>
Что ж, это и есть функциональная база для добавления голосования в базу данных. Давайте остановимся на этом и рассмотрим всё детальней.
С самого начала мы получаем количество вариантов ответа, которые нужно вывести для редактирования. Мы объявляем переменную `count`, которая в соответствии от существования переменной `count` в QUERY_STRING будет принимать значение или переменной QUERY_STRING (если такая сущестует) и значение по умолчанию, а именно 5. После этого мы работаем над пользовательским интерфейсом, и при этом создаём интересную кнопочку, которая и служит для добавления варианта ответа. При её нажатии значение переменной `count` помещаеться в строку запроса в броузере, и при этом она увеличиваеться на единицу. Вот как всё просто, прям до ужаса. Далее всё просто, и вот мы перешли к обработке полученных из формы данных.
В начале обработки идёт процесс валидации данных. Мы проверям заполнены ли поля формы, а после этого количество вариантов ответа. Для проверки заполненности полей “ответов”, мы объявляем переменную $count, которая будет каждый раз увеличиваться, если $i-й элемент массива будет не заполнен.
После, если количество незаполненных полей ($count), будет равно общей сумме вариантов ответа, или их разница будет менее 2-х, то будет возбужденно исключение, которое сообщит пользователю о том, что нужно ввести минимум 2 варианта ответов. На этом проверка данных завершаеться, и мы переходим к валидации данных в БД. Сначала нам нужно проверить, не существует ли уже голосования с таким вопросом, и если существует то возбуждаем ошибку. Если всё же нет, то добавляем данные в таблицу.
Следующим шагом будет получение индификатора (id) текущей записи, для добавления вариантов ответа в таблицу. После того как мы получили ID, мы снова делаем перебор, только теперь уже с иной целью. Сейчас мы будем добавлять каждый i-й элемент массива, при условии что он не пустой, в таблицу для ответов, при этом мы так же добавляем и индификатор голосования к которому будет привязан данных ответ. Ну и в случае успеха, выводим сообщение о том, что всё прошло успешно. Как видите, без учёта некоторых моментов, всё довольно легко. Но это только первый кусочек той большой мозаики общей функциональностию. Сейчас мы переходим к следующей части, которая и будет неким тестом предыдущей, а именно воплотим сам механизм голосования через интерфейс сайта. В контексте данного варианта исполнения, это займёт почти несколько строк, так как нам всего-то навсего нужно обновить поле в одной таблице, и занести данные в другую.
Что ж, вот мой вариант исполнения данной задачи:
<?
if(!isset($_POST[’vote’])){
print”<form action=” method=’post’ name=’vote’>”;
print”<table width=’400′ height=’50′ align=’center’>”;
$conn_id=@mysql_connect(”localhost”,”root”,”") or
die(”Ошибка соединения с сервером БД !”);
@mysql_select_db(”db”);
$q=@mysql_query(”SELECT * FROM `pools` WHERE status=’on’”,$conn_id) or
die(”Ошибка запроса к БД !”);
if(@mysql_num_rows($q)==0){
echo”Голосования не найдены !”;
}else{
$id=mt_rand(1,@mysql_num_rows($q));
unset($q);
$q=@mysql_query(”SELECT * FROM `pools` WHERE id=’”.$id.”‘”,$conn_id) or
die(”Ошибка запроса к БД !”);
$row=@mysql_fetch_array($q);
print”<tr><Td colspan=’2′>Q: “.$row[’question’].”</td>
</tr>”;
unset($q);
$vote_check=@mysql_query(”SELECT id FROM `alredy_vote` WHERE ip=’”.
$_SERVER[’REMOTE_ADDR’].”‘”,$conn_id) or die(”Ошибка запроса к БД !”);
$q=@mysql_query(”SELECT id,value FROM `pools_answs` WHERE vote_id=’”.
$id.”‘”,$conn_id) or die(”Ошибка запроса к БД !”);
if(@mysql_num_rows($q)==0){
die(”Вопросы не найдены !”);
}else{
while($row=@mysql_fetch_array($q)){
$row2=@mysql_fetch_array($q2);
if(@mysql_num_rows($vote_check)!=0){
$q2=@mysql_query(”SELECT count FROM `pools_answs` WHERE id=’”.$row[’id’].”‘”,
$conn_id) or die(”Ошибка запроса к БД !”);
print”<tr><td>”.$row[’value’].”</td><td>”.
$row2[’count’].”</td></tr>”;
}else{
print”<tr><td>”.$row[’value’].”</td><td><input
type=’radio’ name=’answer’ value=’”.$row[’id’].”‘></td></tr>”;
print”<input type=’hidden’ name=’id’ value=’”.$id.”‘>”;
print”<tr><td colspan=’2′><input type=’submit’ name=’vote’
value=’Проголосовать’></td></tr>”;
}
}
}
}
print”</table>”;
print”</form>”;
@mysql_close($conn_id);
}else{
$id=$_POST[’id’];
$answer=$_POST[’answer’];
$conn_id=@mysql_connect(”localhost”,”root”,”")
or die(”Ошибка во время запроса к серверу !”);
@mysql_select_db(”db”);
$q=@mysql_query(”SELECT id FROM `aredy_vote` WHERE ip=’”.
$_SERVER[’REMOTE_ADDR’].”‘”,$conn_id)
or die(”Ошибка во время запроса к серверу !”);
if(@mysql_num_rows($q)!=0){
print”Вы уже участвовали в данном голосовании !”;
}else{
$q=@mysql_query(”INSERT into `alredy_vote` VALUES(”,’”.$id.”‘,’”.
$_SERVER[’REMOTE_ADDR’].”‘)”,$conn_id) or die(”Ошибка запроса к БД !”);
unset($q);
$q=@mysql_query(”UPDATE `pools_answs` SER count=count+1 WHERE id=’”.$id.
“‘ AND vote_id=’”.$_POST[’answer’].”‘”,$conn_id) or die(”Ошибка запроса к БД !”);
print”Ваш голос учтён. Спасибо за участие !!”;
}
@mysql_close($conn_id);
}
?>
Я конечно перегнул на счёт нескольких строк, хотя оно в действительсности так и есть, ведь для обновления данных в БД нужно всего-то навсего 2 запроса к БД. Теперь давайте посмотрим на сомнительные места более пристально. Ну, сначала мы получаем все записи из БД, после делаем рандомизацию
(случайную выборку), ну и после получаем данные по “выпавшему” элементу. Но главным в пользовательском интерфейсе являеться именно то, чтобы обеспечить недоступность для пользователя повторной попытки проголосовать. Для этого мы делаем запрос к таблице `aredy_vote`, где мы проверяем наличие записи у которой ip-адрес равен текущему значению $_SERVER[’REMOTE_ADDR’], и если такая действительно найдена, то мы вместо радио-боксов выводим количество голосов, по каждому элементу, и так же убираем кнопочку для отправки данных формы. После вывода пользовательского интерфейса нам необходимо обработать данные поступившие из него. Но что именно нам оттуда понадобиться ?
Нам нужно будет всего-то две переменные, а именно индификатор текущего голосования, и, безусловно, вариант ответа. После этого мы производим запрос к БД, с целью проверки принимал ли данный пользователь участие в голосовании, с той же целью безопастности.
После этой проверки, мы добавляем данные о пользователе в таблицу “проголосовавших”, ну и только после этого мы обновляем данные по голосованию.
И после всех этих манипуляций мы закрываем соединение с БД и благодарим пользователя за участие в нашем голосовании. Вот мы и проделали уже около 40% всей работы над данным проектом, нам ещё осталось три механизма, а какие поговорим дальше. Сейчас я предлагаю вам перейти к следующей ступени разработки проекта, а именно к редактированию уже существующего голосования. Это уже не будет новым для вас. Однако скажу, что при редактировании голосования, мы будем обнулять его результаты.
То есть не обнулять, а удалять все элементы “отв