". $row[1]. "");
pring("". $row[2]. "");
print("". $row[0]. " ");
};
}
/* в случае ошибки БД программа выводит сообщение сервера (конечно, можно обойтись без такой проверки, но тогда пользователю посыплются ругательства PHP). */
else {
print ("Ошибка БД в запросе "$request". MySQL пишет ". mysql_error());
};
/* если дальше предусмотрено выполнение каких-либо операций, лучше всего сразу очистить память */
mysql_free_result ($result);
Ещё одна полезная вещь: если это новостная лента, скажем, на главной странице сайта, нужно ограничить количество новостей (скаджм 10). И ещё надо, чтоб дата отображалась не как "2000-12-05 22-26-47", а "5.12.2000 22:46":
$request = "SELECT ntext, ntitle, date_format(ndate,'%e.%m.%Y %H:%i') as ndate1 FROM news ORDER BY ndate DESC LIMIT 10";
Ограничиваем количество новостей ровно десятью, и не надо никаких условий в цикле while. Функция date_format форматирует дату так, как нам надо (буквы - как в функции PHP date, а "e" - это число месяца без нуля в начале). Почему пишу "as ndate1", а не "as ndate"? Проверьте сами :) (подсказка: см. директиву ORDER BY в том же запросе).
Но, господа, это всё было только цветочки. Самое главное, чего пока нет - это возможности администратору работать с новостями, кликая мышкой, а не набирая SQL-запросы. Итак,
Запись новостей
Создаётся новость путём отправки запроса точно так же, как и запроса на создание таблицы или выборки строк. Перед вставкой данных в запрос, их лучше обработать функцией addslashes, чтобы одинарные кавычки MySQL воспринял, как часть текста:
mysql_query ("INSERT INTO news (ntitle, ntext, ndate) VALUES ('". addslashes($ntitle). "', '". addslashes($ntext). "', NOW())");
В поле типа DATE вставляем текущее время функцией now. Если нужно вставить не текущее время, можно сделать так:
..., '". date("Y-m-d H-i-s", $date). "', ...
Этот формат - для типа DATETIME. Если тип данных DATE, то надо использовать "Y-m-d", и если TIME, соответственно "H-i-s". Переменная $date здесь содержит дату/время, определенные функцией mktime.
Запросы на вставку строки (INSERT)
Поле идентификатора вставлять не нужно. На это есть свойство поля AUTO_INCREMENT.
Забавно читать, как в форуме пишут:
- Как мне быть с генератором случайных чисел?! неправильно работает!
- А зачем тебе?
- Да в базе id использовать...
В общем, не надо самодеятельности.
Если в поле формата DATE, TIME, DATETIME или TIMESTAMP надо вставить текущее время, используйте встроенную в mysql функцию NOW: "INSERT INTO vote (ip, date) VALUES ($REMOTE_ADDR, NOW())"
Хранимые в базе пароли лучше прикрыть функцией php md5: "INSERT INTO user (login, pass) VALUES ('$login', ". md5($pass). ")" "SELECT * FROM user WHERE login='$login' AND pass=". md5($pass)
Советы, кажется, уже исчерпаны. Напоследок. С недавних пор я стал думать, что при написании скриптов, работающих с БД, надо ориентироваться не только на глупого и шаловливого посетителя, но и на криворукого администратора. Даже если мы внимательно будем следить за текстом, который вставляем в текстовое поле (одинарные кавычки не писать, делать их автозамену в Word-е, белое не носить), вероятность попадания служебных символов в запрос ненулевая.
Запросы на выборку данных (SELECT)
Во избежание путаницы полей (если встречаются поля с одинаковыми названиями) используйте в запросах оператор AS: "SELECT table1.id as id1, table2.id as id2". Это поможет избежать ошибок в запросе (например, если не указана таблица, а поле с таким названием есть в нескольких запрашиваемых таблицах, mysql выдаёт ошибку), а так же вы избежите недоразумений при работе с полученными данными (echo $row["id1"] писать гораздо проще, чем $row[$x]).
Данные типа DATE, TIME, DATETIME и TIMESTAMP можно форматировать с помощью функции date_format (см. руководство по mysql). Используйте его, и не форматируйте данные через php - это не просто "самодеятельность", а ещё и растрата системных ресурсов.
По возможности минимально используйте LEFT JOIN для объединения таблиц. Это весьма трудоёмкая операция для базы данных.
Там, где можно, используйте идентификаторы - выборка данных при указании ключевого поля происходит быстрее, чем при указании обычного.
Вместо "WHERE id=1 OR id=3 OR id=232" можно использовать встроенную функцию IN: "WHERE id IN (1,3,232)".
Если нужен текстовый поиск, осторожней со знаком "%". Во всяком случае, запросы типа somefield LIKE '%a%' лучше не делать - опять же слишком трудоёмкая операция. По крайней мере, надо фильтровать слова и отрезать те, которые короче 3 символов.
Используйте минимум необходимых полей в запросе. "SELECT * FROM sometable" выполняется медленнее, чем "SELECT id FROM sometable", тем более если в таблице много данных. Для подсчёта количества строк в таблице вообще (или подпадающих под некоторое условие) достаточно одного поля.
Разбивайте данные на страницы, используя оператор LIMIT. Это экономит время выполнения запроса и уменьшает объем страницы, которую получает пользователь.
Даже если вам не грозит "падение" от наплыва посетителей, лучше взять себе в привычку, чтобы потом не было проблем с адаптацией к новым задачам. Теперь о безопасности работы.
Старайтесь не допускать внесения в базу данных символа одинарной кавычки ("'"), поскольку это служебный символ запросов БД. Перед внесением в базу поле можно обработать функцией str_replace: $somefield = str_replace("'", "'", $somefield);
К тому же это лишний барьер на пути взломщиков вашего сайта. Пример "взлома" простой:
mysql_query("UPDATE users SET password=PASSWORD('$passwd') WHERE login='$login'");
Если кавычку не обработать на входе, злоумышленник может в качестве логина сунуть строку "vasya_pupkin' OR login LIKE '%". В базу данных залетит запрос: mysql_query("UPDATE users SET password=PASSWORD('$passwd') WHERE login='vasya_pupkin' OR login LIKE '%'"); То есть все пароли будут одинаковые. Это только один пример. Итак,
Обрабатывайте данные, получаемые из адресной строки или из формы, и приводите их к нужному типу во избежание ошибок и "взломов" сайта. (ещё пример: если требуется идентификатор, то есть целое число, надо обработать его с помощью intval: $id = intval($id)).
Имитация файлов и директорий
DL 16.1.2001
Адрес вашего сайта появляется на пользовательском экране одновременно с дизайном и контентом. Поэтому адрес является полноправной частью сайта. Адрес типа www.фирма.ру (www.фирма.город.ру), естественно, гораздо лучше, чем www.geocities.com/Gonduras/San-Pedrillio/~наша_фирма, кто спорит. А вот по вопросу понятных человеку адресов внутри сайта общественность четкого консенсуса пока не нашла.
Однако пользователю приятнее было бы видеть адрес типа /services/special/ чем /content.phtml?q=e23908a234cc239b3445127.
Лирическое отступление. Помню, на Интернити-99 мне показали флэш-ролик [] Laser Jet 3100. Через пару недель я вспомнил про него и решил скачать его из дома. Я бы долго бродил в бесполезных поисках по сайту [] (чего вы смеетесь, это так и было!), если бы не их адреса. На HP адреса были понятные? что-то вроде "/products/printers/laserjet/3100", а на сайте Лексмарка было вот именно это непонятное "q=492898748273". Я был в сомнениях, но через день вспомнил-таки, что это был HP :).
Кстати, на этом сайте адреса выпусков, версий для печати и всех информационных страниц (ссылки, файлы и т.д.) виртуальные, файлов с такими названиями не существует.
Делается это достаточно просто. В файле .htaccess пишутся строчки, например
ErrorDocument 404 all.php
ErrorDocument 403 all.php
ErrorDocument 401 all.php
Файл all.php обрабатывает переменную $REQUEST_URI и, если нужная информация найдена, выдет команду
header ("HTTP/1.0 200 Ok");
Это необходимо для того, чтобы броузер IE 4 считал, что страница найдена, а не подставлял вместо нее свою служебную вывеску "адрес не найден". В остальных случаях, даже если запрошен адрес "all.php", пользователю будет выдаваться сообщение о том, что файл не найден.
Если при вызове функции header сервер ругается матом "Error 500" - смотрите [] и [].
Конечно же, выдать заголовок и нарисовать страницу ? нехитрое дело. Отслеживание результатов запросов, проверка на ошибки ? это скорее рутина. Самое ответственное дело ? разбор запрашиваемого адреса.
Что такое title и rub_text ? объяснять не надо. Поле address ? это адрес, по которому будет запрашиваться рубрика (новости нужно сделать рубрикой первого уровня, и в поле address будет "news"). Поле parent_id ? идентификатор рубрики уровнем выше.
Теперь, если рубики заведомо не могут быть ниже второго уровня, анализ адреса не составит особого труда.
$url = $REQUEST_URI;
// убираем слэши из начала и конца адреса
$url = ereg_replace("^/", "", $url);
$url = ereg_replace("/$", "", $url);
$dir = explode("/", $url)
// случай, когда запрошена рубрика второго уровня.
if (sizeof($dir)==2) {
// составляется запрос, объединяющий таблицу rubrika саму с собой. Здесь надо заметить только, что таблица first подразумевает рубрику второго уровня, а second, наоборот, первого.
$request = "SELECT first.id, first.title, first.rub_text FROM rubrika first, rubrika second WHERE first.parent_id=second.id AND first.address='". $dir[1]. "' AND second.address='". $dir[0]. "'";
// Отправляем запрос в базу, а потом обрабатываем результат.
$result = mysql_query($request);
if (!mysql_error() && @mysql_num_rows($result)==1) {
}
// Это на случай, если запрос прошел успешно, но ничего не найдено
elseif (!mysql_error()) {
}
// ...и на случай, если произошла ошибка.
else
die ("Ошибка БД. MySQL пишет: ". mysql_error());
}
// Запрошена рубрика первого уровня. тут и делать-то нечего :)
elseif (!ereg("/", $url)) {
$request = "SELECT id, title, rub_text FROM rubrika WHERE address='$url'";
...
};
Хватит примеров? Дальше ? дело фантазии. Подведем итоги, распишем положительные и отрицательные моменты.
+ Красивые адреса, возможность зайти в рубрику, набрав ее адрес на клавиатуре. Благодарность от фанатов клавиатуры.
+ Уменьшение количества файлов, уменьшение количества повторяющихся операций в разных файлах.
+ Централизация вывода. Сбор большинства операций в единой точке входа.
+ Скрытие некоторой технологической части сайта.
- Увеличение ресурсоемкости за счет проверки адреса и компиляции большого файла вместо нескольких маленьких.
- Сложность с введением новых параметров (я, можно сказать, удачно вывернулся с версией для печати, но было бы более логично видеть адреса типа /13/print). Кое-что придется сбрасывать, например в куки.
- Кое-что, например, поиск, так и останется вне "точки входа" (хотя... "" [] делает поиск в адресе, но для более-менее сложного сайта это будет неудобно или невозможно).
- Дополнительные сложности с адресами картинок и навигации по сайту (броузер-то мерит все адреса относительно открытого документа, пусть даже из несуществующего адреса).
Итак, что мы получаем в итоге? Красота, даже адресов, требует жертв.
Напоследок: в этом выпуске я использовал кучу регулярных выражений, поэтому (и по просьбам читателей) обещаю в скором времени затронуть эту тему.
Сервер ищет файл с тем же именем
Оказывается, достаточно прописать в установках директории (httpd.conf или .htaccess) строку
Options Multiviews
или, если директива Options уже есть, добавить MultiViews к ней. Тогда если пользователь набирает "/foo/bar", сервер будет искать файл с именем "foo" и с любым расширением. Найденный с наибольшим совпадением (вот это для меня загадка) файл он обработает с его типом mime, то есть если есть news.php, а набран адрес news/, то сервер отдаст адрес на обработку php. Если это картинка, то сервер отдаст ее броузеру именно как картинку (послав соотвествующий заголовок content-type). А в news.php разбираем $REQUEST_URI. Например, если новости выводятся целой лентой, либо за определенную дату, разбор можно сделать таким:
/* Первый вариант ? когда набран адрес типа "/news/010120", возможно с дробью на конце. Символы ^ и $ здесь обозначают привязку к началу и концу строки. Подстрока [0-9]{6} означает 6 цифр (если у вас новости могут быть датированы 1999-м годом и раньше, используйте адреса с полным форматом года и 8 цифр вместо 6). */
if (ereg("^/news/([0-9]{6})$", $REQUEST_URI, $match) ereg("^/news/([0-9]{6})/$", $REQUEST_URI, $match)) {
}
/* второй вариант ? набран адрес просто "/news" или "/news/" */
elseif (ereg("^/news/$", $REQUEST_URI) ereg("^/news$", $REQUEST_URI)) {
}
/* запросы ко всем остальным адресам (в этом файле) считаются попытками взлома сайта =) */
else
die ("Error 404 Not found");
То же самое можно сделать, например, с каталогом продукции фирмы ? вынести все это дело в отдельный файл catalogue.php, а адреса сделать вида "/catalogue/rubrik1/rubrik2/rubrik3". При этом в файле catalogue.php начало строки будет откусываться, а дальше можно обработать по принципу, описанному в предыдущем выпуске. Остальное же можно отправить, опять же, в ErrorDocument.
Сервер переписывает запросы
Очень полезная вещь mod_rewrite. Ею можно сделать все вышеописанное, и много другого.
К сожалению, моя битва с mod_rewrite не увенчалась успехом (Костя пишет, что нижеописанного достаточно для работы Rewrite Engine под Unix. У меня под win98? ни в какую...). Поэтому описываю очевидные вещи и то, что описал .
Для начала надо раскомментировать строку
LoadModule mod_rewrite
в httpd.conf. В конфигурации директории пишем строку "RewriteEngine On". Затем ? команду RewriteRule:
RewriteRule <шаблон> <замена>
Например RewriteRule ^(.*).html$ /otherdir/$1.html (все без кавычек). Вот, собственно, и все. Все, что я так и не смог проверить : Я спросил у ясеня, я спросил у тополя, я спросил у форума... форум не ответил мне. (мелодично) Я спрошу у публики... На всякий случай, спрашиваю у уважаемой публики: как запустить Rewrite Engine под win98 se + apache/1.3.14 + php/4.0.4-Antonio (установлен как модуль) ?
А пока еще один пример (опять же от Шевченко):
RewriteEngine On
RewriteRule ^(.*).htm$ /portal/$1
ForceType application/x-httpd-php
Там лежит один файл с именем "portal", в который перенаправляются все запросы к html-файлам в данной директории. И получается, как будто там лежат файлы.
Напоследок вспомним [].
Вот [], в са-а-амом конце Носик говорит про ресурсоемкость их технологии. "Издательская машинка, написанная Максимом Евгеньевичем Мошковым, (который ) [] интересна тем, что она весит около 60Kb"
Не знаю, что из себя представляет эта система (скорее всего, скомнилированный ), гадать не буду. Мне хотелось бы прикинуть, как динамическую адресацию можно реаизовать через php. Впрочем, тут не в нем дело ? был бы Апач. Итак, схема адресов <рубрика>/<год>/<месяц>/<день>/<новость>. Если отрезать имя новости, получим материалы рубрики за день. Если отрезать день, месяц и год, получим последние материалы рубрики.
RewriteRule ^([a-z]+)/$ rubika_last/$1
RewriteRule ^([a-z]+)/([0-9]{4})/([0-9]{2})/([0-9]{2})/$ rubrika_date/$1/$2-$3-$4
RewriteRule ^([a-z]+)/([0-9]{4})/([0-9]{2})/([0-9]{2})/([a-z]+)/$ rubrika_news/$1/$2-$3-$4/$5
ForceType application/x-httpd-php
Преимущества этого метода по сравнению с единой точкой входа EerrorDocument очевидны: движок php интерпретирует только то, что будет выполняться. Никаких switch/case или if/elseif/else, никаких лишних строк.
Все остальное ? отдаем ErrorDocument "как в предыдущей задаче".
Сервер разбирает запрос
Метод похожий, но на вскидку менее ресурсоемкий, потому что не приходится искать файлы по директории.
В установках директории (опять же httpd.conf или .htaccess) пишем:
ForceType application/x-httpd-php
В директории лежит файл с именем "news" (именно "news", без расширения). Когда запрашивается адрес "/news", либо "/news/bla-bla-bla", сервер выполняет файл news как php-скрипт. А внутри него производится обработка переменной $REQUEST_URI.
Чтобы не писать для каждого подобного файла свой блок FilesMatch, нужно немного изменить строку шаблона. Пусть сервер ищет файлы без расширения, то есть те, у которых в имени нет точки:
Очень удобно! Когда-нибудь поставлю такое же и себе.
|