PHP в деталях
Якоря
| ^ | привязка к началу строки |
| $ | привязка к концу строки |
Эти символы должны стоять соответственно в самом начале и в самом конце строки. Чтобы интерпретатор корректно понял символ $ в конце, желательно добавить к нему обратный слэш:
ereg("foo\$", $bar)
Квантификатор
Это, как я уже писал, указатель количества заданных символов. Квантификатором можно указать как конкретное значение, так и пределы. Если число заданных подпадает под пределы квантификатора, фрагмент выражения считается совпавшим с разбираемой строкой. Синтаксис:{<количество>}
либо
{<минимум>, <максимум>}
Если нужно указать только необходимый минимум, а максимума нет, просто ставим запятую и не пишем второе число: "{5,}" ("минимум 5"). Для наиболее часто употребляемых квантификаторов есть специальные обозначения:
| * | "звёздочка" или знак умножения | {0,} |
| + | плюс | {1,} |
| ? | вопросительный знак | {0,1} |
На практике такие символы используются чаще, чем фигурные скобки.
Набор символов
| . | точка | любой символ |
| [<символы>] | квадратные скобки | класс символов ("любое из") |
| [^<символы>] | негативный класс символов ("любое кроме") | |
| - | тире | обозначение последовательности в классе символов ("[0-9]" ? цифры) |
Особо объяснять ничего не нужно. Разве что следующее: не пользуйтесь классом символов для обозначения всего лишь одного (вместо "[ ]+" вполне сойдет " +"). Не пишите в классе символов точку ? это ведь любой символ, тогда другие символы в классе будут просто лишними (а в негативном классе получится отрицание всех символов).
Регулярные выражения. Часть 2. POSIX.
DL7.2.2001
Продолжаем наш разговор. Предыдущий выпуск был вводным, теорией. Сегодня как бы основная часть рассказа? стандарт POSIX. В следующем выпуске я опишу различия, вернее сказать надстройки стандарта совместимого с perl.
Итак, обо всем по порядку.
Регулярные выражения. Часть 3. PCRE.
DL16.2.2001
И вот, наконец, серия выпусков про регулярные выражения подходит к концу. Поговорим о регулярных выражениях совместимых с Perl (Perl compatible regular expressions? PCRE).
Самое главное их преимущество перед POSIX, как мне уже подсказывают ? возможность "жадного" поиска. Вопросительный знак в PCRE выступает еще и как минимизатор квантификатора:
.*?
Найдет минимальную подходящую строку. Вроде бы ничего особенного? Нет, это очень особенная вещь. Например, какой пример я приводил в прошлом выпуске про печатную версию текста?
$text = ereg_replace("]+)>[^<]+", "\\0 [\\1]", $text);
То есть, внури ссылки не должно быть тегов (например "..."). Если же сделать так:
$text = ereg_replace("]+)>.*", "\\0 [\\1]", $text);
Тогда мы получим... Правильно, весь текст между началом первой и концом последней ссылки.
Все проблемы снимает жадный поиск.
$text = preg_replace("/
Программа подберет для всех ссылок минимальную подходящую строку, т.е. только до тега "". Описывать значение такой особенности PCRE нет смыла ? оно огромное. :) Идем дальше.
Цифры теперь можно обозначить не как "[0-9]", а просто "\d". Не-цифры ("[^0-9]") как "\D". Очень удобно. Вот остальные обозначения:
| \w | [a-z0-9] |
| \W | [^a-z0-9] |
| \s | [ ] |
| \S | [^ ] |
Рекомендую заглянуть в выпуски про поиск ? там эти символы используются.
Строка шаблона, как вы уже заметили, начинается и заканчивается слэшами. Для чего нужен первый слэш, не знаю. Последний нужен для отделения шаблона от параметров. Параметры, которые я понял, таковы:
| i | регистронезависимый поиск |
| m | многостроковый режим. По умолчанию PCRE ищет сопвадения с шаблоном только внутри одной строки, а символы "^" и "$" совпадают только с началом и концом всего текста. Когда этот параметр установлен, "^" и "$" совпадают с началом и концом отдельных строк. |
| s | символ "." (точка) совпадает и с переносом строки (по умолчанию ? нет) |
| A | привязка к началу текста |
| E | заставляет символ "$" совпадать только с концом текста. Игнорируется, если установлен парамерт m. |
| U | Инвертирует "жадность" для каждого квантификатора (если же после квантификатора стоит "?", этот квантификатор перестает быть "жадным"). |
Естественно, регистр в параметрах имеет значение. Остальное о них можно прочесть в [].
Теперь о функциях PCRE.
Функция [] в отличие от ereg ищет только первое совпадение. Если нужно найти все совпадения и как-то обработать их результаты (но не напрямую через ) [], нужно пользоваться []. Параметры этой функции те же.
Из полезного отмечу функцию [], которая вставляет слэши перед всеми служебными символами (например, скобками, квадратными скобками и т.п.), чтобы те воспринимались буквально. Если у вас есть какой-либо ввод информации пользователем, и вы проверяете его через PCRE, лучше перед этим закомментировать служебные символы в пришедшей переменной (мало ли что он там напишет, это ведь по определению злобный хакер).
Это все, что я могу сказать про регулярные выражения. Дальше ? только искусство комбинирования строк и написания алгоритмов.
Помнится, в одном из пришлых выпусков я описал рассыльщик почты на классах. Теперь я добавил туда хранение адресов в файлах и подтверждение подписки. Разумеется, различные проверки адресов, получение списка активных и тому подобное ? все работает на PCRE. К сожалению, времени на тестирование и доводку не было, рассыльщик "сырой".
Регулярные выражения (regexp). Часть первая
DL6.2.2001
Обещал написать про регулярные выражения. Писал, писал, но что-то не то выходит. Слишком мелкая конкретика, пересказывание . Поэтому я решил, что необходимо (приняв степенную осанку) подготовить аудиторию, так сказать, начать с малого... (" - Дорогой, а где моя мама? - Теща уже на крыше.")
Начну с того, что php поддерживает два стандарта регулярных выражений: и, начиная с четвертой версии, . Первый стандарт используется и сервером Apache в mod_rewrite (см. ) а так же... MySQL в своих запросах (поищите слово "REGEXP" в руководстве по mysql, может сразу поймете, а я об этом позже расскажу). Второй, как ясно из названия, используется в системе perl. Два этих стандарта различаются несильно - во втором есть специальные символы, заменяющие наиболее часто используемые классы символов (например, цифры - \d, а буквы и цифры - \w) и специальные параметры шаблонов, позволяющие определять регистрозависимость поиска, привязку к концам строк и т.д (в функциях стандарта POSIX регистрозависимость реализована просто: есть функции ereg и ereg_eeplace, есть eregi (insensitive) и eregi_replace). В остальном же оба стандарта совместимы, а приемы написания шаблонов одинаковые.
Если вы работали с Norton/Volkov/Windows Commander или Far, то знаете такую вещь как wildcards. Например: delete c:\windows\*.* удаляет все файлы из указанной директории. :) В именах файлов особых изощрений делать не приходится, поэтому система простая: символ * означает любой набор символов, в том числе пустой (*.txt), символ ? - любой символ или никакого символа (document?.txt) и еще какие-то обозначения для букв и цифр (я, честно говоря, ими давно не пользовался, поэтому так не вспомню).
В регулярных выражениях подход иной. Система в первую очередь универсальна и должна уметь находить соответствия строк любым самым сложным запросам (странно, что я говорю "должна уметь", система ведь уже "умеет". Надеюсь читатель простит мне такие фразы, ведь они все относятся к уже работающей системе регулярных выражений.) Сейчас я назову термины, которые буду употреблять в дальнейшем, чтобы избежать растяжимых (в прямом и переносном смысле) определений.
Пользуйтесь функциями регулярных выражений только если вы не знаете точно, какая "там" строка. Из примеров: , в котором из строки поиска вырезаются служебные символы и короткие слова а так же вырезаются лишние пробелы (вернее, все пробелы сжимаются: " +" заменяется на один пробел). При помощи этих функций я проверяю email пользователя, оставляющего свой отзыв. Много полезного можно сделать, но важно иметь в виду: регулярные выражения не всесильны. Например, сложную замену в большом тексте ими лучше не делать. Ведь, к примеру, комбинация "(.*)" в программном плане означает перебор всех символов текста. А если шаблон не привязан к началу или концу строки, то и сам шаблон "двигается" программой через весь текст, и получается двойной перебор, вернее перебор в квадрате. Нетрудно догадаться, что еще одна комбинация "(.*)" означает перебор в кубе, и так далее. Возведите в третью степень, скажем, 5 килобайт текста. Получается 125 000 000 000 (прописью: сто двадцать пять миллиардов операций). Конечно же, если подходить строго, там стольких операций не будет, а будет раза в четыре-восемь меньше, но важен сам порядок цифр.
Итак, принципы, достоинства и недостатки описаны, теперь надо переходить к конкретике. Два (возможно, сразу следующих) выпуска будут посвящены двум стандартам регулярных выражений - POSIX и PCRE.
Статья относится к рубрикам:
. | (7)| архив | ссылки | форумы | что такое php |
| © , 2000-2002 © , 1999-2002 |
Структура
Сейчас будет сложное описание, мне оно и самому не нравится.Эта вещь необходима для сложных запросов. Например, вам надо, чтобы в тексте были либо только маленькие буквы, либо только большие, либо только цифры. Класс символов "[a-zA-Z0-9]" не подходит. Тогда пишем такое:
if (ereg("[a-z]+|[A-Z]+|[0-9]+", $text)) ...
Вертикальная черта? знак "или" регулярных выражений (знака "и", естественно, не существует ? это и есть само регулярное выражение). Разделенные вертикальной чертой шаблоны в официальной документации называются альтернативными ветвями (это подразумевает ветвление, т.е. наличие вложенных альтернативных ветвей). Программа сравнивает со строкой все ветви (проходясь по их ряду слева направо), до первого совпадения (это важно учесть, если у вас сложное выражение со вложенными ветвями). Для разделения уровней и отделения этого дерева альтернатив от остального шаблона используются обычные скобки. Если те же большие/маленькие буквы/цифры надо искать внутри контейнера тегов:
if (ereg("
Из сложного это, кажется, все. Теперь о более простом. Скобки по-научному называются subpattern (вложенный шаблон). И используются не только для сложных вариантов шаблонов, но и для гибкой замены фрагментов текста или получения их в переменную. К примеру, для печатной версии текста дублируем адреса ссылок текстом в скобках:
ereg_replace("]+)>[^<]+", "\\0 [\\1]", $text);
Первые скобки ? первый вложенный шаблон ? можно получить "на выходе" через обозначение "\n" (поскольку обратный слэш в php и многих других языках используется для спецсимволов, надо поставить перед ним еще один такой же, чтобы прогамма понимала его буквально). Под нулевым номером ? вся совпавшая строка. У себя в печатной версии статьи я не пишу ссылки сразу в тексте, а делаю их список в конце примерно так:
if (ereg("]+)>([^<]+)", $text, $match)) {
for ($a=0;$a
$b = $a+1;
$text = str_replace($match[0][$a], $match[0][$a]." [$b]", $text);
$match[1][$a] = "$b) ". $match[1][$a];
};
$text .= "
Ссылки, использованные в выпуске:
". implode("", $match[1]);
};
Функция ereg (и eregi), если ей указать в третьем параметре переменную, то туда будут записаны все подстроки в виде .
Это, собственно, все. Дальше нужно только уметь составлять шаблоны. Приведу несколько примеров.
1. (как я уже отметил, Apache работает со стандартом POSIX).
2. : из пользовательского поискового запроса делается sql-запрос. Если отбросить создание статистики поиска (сколько найдено всего, сколько по каждому слову), то получится, что необходимо всего 6-7 строк кода. Там же описана и подсветка слов в результатах поиска. Кстати, важное замечание: перед тем, как вырезать короткие слова из строки я заменяю пробелы между словами на двойные. Почему? Потому что совпадающие с шаблоном строки не должны наезжать друг на друга.
Объясню подробнее. Если в шаблоне нет никаких якорей, система проходится по тексту слева направо, и если найдено совпадение, кидает его в какие-то переменные, а затем перескакивает на следующий символ после совпавшего фрагмента. Мы ищем по шаблону "пробел, два непробела, пробел", а пробелы одиночные. Программа находит "пробел-короткое слово-пробел", заменяет это на один пробел, а затем перескакивает на... первую букву следующего слова. Это не пробел, поэтому даже если следующее слово тоже короткое, оно под шаблон не подойдет. Поэтому-то и надо предварительно заменить пробелы на двойные.
3. Как хранить новости в файлах и не бегать циклом по дате:
$handle=opendir($newsdir);
while ($file = readdir($handle)) {
if (is_file($file) && ereg("^[0-9]{6}\.txt\$", $file))
print ("
". ereg_replace("^([0-9]{2})([0-9]{2})([0-9]{2})\.txt\$", "\\1.\\2.20\\3", $file). " ". implode("", file($file)). "
");
closedir($handle);
4. Проверка правильного написания email-а:
if (!eregi("^[a-z0-9\._-]+@[a-z0-9\._-]+\.[a-z]{2,4}\$", $email))
print("Bad email: \"$email\"");
На этом все. В следующем выпуске ? стандарт PCRE, точнее дополнительные возможности, которые он предоставляет.
Автоматизация авторизации
Это нужно не только для упрощения работы с большим количеством пользователей и их большой "текучкой". Если нужно держать дополнительную информацию о пользователях, либо необходимо гибкое разграничение прав, лучше перенести авторизацию в базу.Каждая страница закрытой территории подключает файл с вот таким кодом:
$result = mysql_query("SELECT * FROM person WHERE login='". preg_replace("/[^w_-]/","",$PHP_AUTH_USER). "' AND pass='". md5($PHP_AUTH_PW). "'");
if (@mysql_num_rows($result)!=1) {
header("WWW-Authenticate: Basic realm=\"User area\"");
header("HTTP/1.0 401 Unauthorized");
print("Чтобы войти в пользовательскую часть сайта, надо ввести правильные имя и пароль. Если вы забыли пароль, вернитесь обратно и нажмите на ссылку \"забыл пароль\"n");
exit();
};
$user_row = mysql_fetch_array($result);
В первой строке из логина удаляются все символы кроме букв, цифр, тире и символа подчеркивания. Затем проверяется количество полученных строк, и только если это одна строка, дается доступ. В остальных случаях пользователь увидит в броузере окно, предлагающее ввести логин и пароль. Если же пользователь вошел успешно, в массиве $user_row мы имеем всю информацию о нем.
Конечно же, пример, который я привёл, имеет ряд существенных недостатков. Не переписывайте его один-в-один, чтобы потом не пасть жертвой попыток подбора пароля, потому что
1. защиты от подбора здесь нет
2. если таблица пользователей большая, при подборе пароля злоумышленник, скорее всего, "завалит" базу
Надо будет привести пример. В следующем выпуске по этой теме выложу файлик с кодом.
И последний на сегодня способ ? хранение зашифрованных данных в куках.
Есть скрипт для входа, остальные подключают код, позволяющий только продолжить действия в закрытой области ? если куки истекут, или он выйдет оттуда, придётся возвращаться на страницу для входа.
Входной скрипт проверяет логин и пароль и выдает две куки. В первой ? логин, чтобы сразу опознать пользователя ( в базе поле логина, естественно, уникальное или даже ключевое). Во второй куке ? хэш от времени входа и пароля (для полноты конспирации я добавляю к этим строкам букву "Ы" ? тогда хэш подобрать почти невозможно :).
Все остальные программы подключают код, который делает следующее. Делает запрос в базу ? выбирает строку с полученным логином. Из этой строки берет поле "log_time" и пароль и делает из них, как и описано выше, хэш. Сравнивает его с тем, что получил, и если они совпадают, выдает новую куку хэша, опять же, от пароля, времени и буквы "Ы" и делает запрос в базу данных "UPDATE user SET log_time='...' WHERE login='$cookie_login'".
if (isset($HTTP_COOKIE_VARS[$cookie_login]) && isset($HTTP_COOKIE_VARS[$cookie_code])) {
$login = $HTTP_COOKIE_VARS[$cookie_login];
$code = $HTTP_COOKIE_VARS[$cookie_code];
$result = mysql_query("SELECT date_format(log_date,'%Y%m%d%H%i%s') as log_date1, pass, uid FROM user WHERE email='$login' AND log_date>'DATE_SUB(NOW(),INTERVAL 15 MINUTE)'");
if (!mysql_error() && @mysql_num_rows($result)==1) {
$log_time0 = time();
$log_time1 = date("YmdHis", $log_time0);
$log_time2 = date("Y-m-d H:i:s", $log_time0);
$current_user = mysql_fetch_array($result);
if (md5($current_user["pass"].$current_user["log_date1"].$md5letter) == $code) {
mysql_query("UPDATE user SET log_date='$log_time2' WHERE uid=". $current_user["uid"]);
setcookie($cookie_code, md5($current_user["pass"].$log_time1.$md5letter), time()+900, $site_path);
$auth = true;
}
else
unset($current_user);
};
};
Опять же, здесь нет никакой защиты от подбора и атаки на сервер (кстати, здесь можно вместо буквы "Ы" писать IP-адрес пользователя ? чтобы, например, соседу по офису нельзя было взять файл с кукой и зайти со своего компьютера).
Это всё, что я хотел сказать сегодня. Защита от подбора и сессии ? на следующей неделе.
Дверца в защите: почтовый веб-интерфейс
DL6.3.2001
Сегодня я хочу рассказать уважаемой публике, как можно воровать адреса с некоторых веб-мейлов. Я сказал "как можно", а не "как нужно"! Успокойтесь, ничего особо ценного, как "инструментарий юного хацкера" в этой заметке не будет. Просто демонстрация возможностей PHP и недостатков (вопиющих недостатков!) некоторых почтовых веб-интерфейсов.
A. V. Komlin, [] Очень полезная статья.
Описывается сервис [].
Первое же тестирование выявило, что сервер подвержен простейшим ошибкам в фильтрации скриптов и других опасных тегов, но это оказалось не самым худшим.
Как уже было отмечено? во время работы пользователь идентифицируется по случайному многозначному идентификатору (id) , и разумеется (думал я) IP и/или cookies. Тест показал, что это не так! Узнав значение id на ящик оказалось возможным зайти с другого адреса, например после обрыва соединения! Более того не требовалось и поддержки "пирожков" (хотя узнать их при возможности выполнить скрипт проблемой не являлось).
Фактически, для несанкционированного просмотра ящика, достаточно себе поставить простейшую программу, регистрирующую обращения к 80-му (или другому указанному в адресе) порту, послать письмо, вставив в него нефильтруемый тег провоцирующий браузер на автоматическое обращение к машине атакующего (например с помощью ссылки на картинку, якобы расположенную на IP адресе взломщика
----------
GET /anyname.gif HTTP/1.0
Referer: http://www.hotbox.ru/message.php?id=b[skip]14cf&index=6&array_index=5
Connection: Keep-Alive
...
--------
Пример java-утилиты для прослушивания порта.
[skip]
Атакующему осталось только отключить поддержку cookies в своём браузере, полностью набрать указанный в Referer адрес и параллельно "работать с почтой " (почитать письма ,установить пересылку...) пока хозяин не выйдет из неё.
Если злоумышленник не имеет постоянного соединения с сетью, он может воспользоваться дырками в фильтрации тегов, чтобы установить с помощью языков сценариев пересылку (для этого придётся отправить дополнительное письмо с кодом подтверждения).
На самом деле, можно поставить взлом ящиков на поток и не караулить жертв, сидя на линии.
Итак, мы имеем почтовый сервис, который
а) авторизует пользователей по технологии аналогичной [].
б) не делает проверки IP-адресов
в) не проверяет содержимое писем формата html
г) не требует подтверждений изменений системных настроек
А проверка содержимого должна заключаться помимо всего прочего и в безжалостной резке всех картинок, которые не идут в аттачменте, а вызываются с другого адреса. ВСЕХ КАРТИНОК, ВСЕХ!
Мы же, крутые хацкеры, используем Apache+PHP.
Итак, что мы делаем.
1. Отправляем по разным адресам почтового сервиса письма в формате html с тегом

например, с поздравительной открыткой. :)
2. В директории с открыткой кладем файл .htaccess следующего содержания:
ForceType application/x-httpd-php
3. Вместо открытки пишем файл picture.jpg с таким кодом:
header("Content-type: image/gif");
include("otkrytka.jpg");
4. А поздравительную открытку кладем именно в файл otkrytka.jpg.
Директива ForceType в .htaccess заставляет сервер обрабатывать файл .jpg как скрипт php, который по завершении работы выдает пользователю картинку, и узнать, что скрипт что-то там делал, невозможно. А скрипт делает простую вещь ? разбирает переменную , выкусывает оттуда идентификатор сессии и... в два приема лишает пользователя ящика. На машине жертвы не происходит ничего, все делает скрипт на сервере злоумышленника:
5. [] с веб-мэйлом, имитируя отправку формы системных настроек (имена переменных надо написать ручками), а именно изменение пароля на нужный злоумышленнику (и, например, уведомляет автора о том, что такой-то ящик захвачен). Дабы не вызвать подозрений, можно формировать нужные заголовки ? рефереры, user agent и т.п.
6. Открывает второй сокет, имитируя нажатие кнопки "выход".
* Сокет - сетевое соединение. В данном случае точно такое же, как и соединение между веб-сервером и браузером.
ВСЕ! ПОЛЬЗОВАТЕЛЬ ОСТАЛСЯ БЕЗ СВОЕГО ЯЩИКА, не успев как следует рассмотреть картинку. Следующее, что он увидит ? сообщение типа "неправильный пароль, войдите еще раз".
Получается, что дырка в защите такого почтового сервиса (не Hotbox.ru, замечу, там что-то все-таки закрыто) на самом деле ? целая дверь. Сочетание передачи идентификатора сессии через ссылку (т.е. доступного кому угодно через поле Referer), а не через cookie и отсутствия проверки IP-адреса дает злоумышленнику возможность быстро и легко перехватить управление ящиком. Проверка же картинок здесь не спасает ? ничто не мешает вставить в текст письма ссылку, нажав на которую, пользователь передаст скрипту идентификатор сессии. Стоят ли удобства таких жертв? Думаю, нет.
Генерация HTTP запросов
Антон Калмыков (Antonio)26.3.2001
В последнее время я все более часто наблюдаю в основном форуме РНРClub вопросы на тему создания POST и GET запросов, а так же вопросы на тему: "Как мне посредством функции header сформировать POST запрос". Я считаю, что уже давно назрела необходимость расставить точки над "и" в использовании данной технологии, поскольку начинающие программисты просто не понимают принципов работы веба, как такового. Итак, начнем наше путешествие по миру протокола HTTP.
Клиент и сервер
Чудес в мире, а тем более в мире программизма и интернета не бывает! Усвойте это как незыблемую истину. И, если программа не работает или работает не так как хочется, то, значит, скорее всего, она либо написана не правильно, либо содержит ошибки.Итак, как же все-таки браузер просит сервер прислать ему хоть что-нибудь? Да очень просто! Надо только немного расслабиться и начать получать удовольствие от процесса :-)
Объектно-ориентированное программирование. Часть 3. Абстрактные классы БД
DL13.3.2001
Тема эта регулярно всплывает в форуме. Собственно, зачем они нужны, эти абстрактные классы баз данных? Большинство сайтов по-хорошему обойдутся и без таких средств унификации. Они работают либо без базы, либо на MySQL, чего для большей части проектов вполне достаточно. Да, да! Достаточно! Когда нагрузки возрастут, вот тогда купят и поставят Оракл, а чтобы скрипты не переписывать, пусть пользуются абстрактными классами. :) Про бесплатные и фирменные базы данных разговор отдельный, грозит превратиться в пустой флейм (а конференциях именно этим все и заканчивается), поэтому не буду его продолжать.
Вернусь к главной теме.
Абстрактные классы позволяют разработчикам писать код без оглядок на то, какая база данных стоит? только на ее возможности и особенности работы. Очень полезно, если работа идет с несколькими базами одновременно (не надо вспоминать, например, что писать: mysql_fetch_array или OCIFetchInto). Смена базы данных тоже многократно облегчится ? надо будет всего лишь изменить тип базы в инициализации объекта. Разумеется, все эти красивые вещи оборачиваются большей ресурсоемкостью программ.
Сразу отмечу, что, первое, эти классы я нашел не в каких-либо дебрях сети, а прямо "на поверхности" ? []. И второе ? написать свой собственный класс ? не проблема. И, конечно же, всех классов не опишешь, поэтому если вы считаете, что я пропустил здесь что-то полезное и хорошее, .
Немного о схеме работы с абстрактными классами.
Нормальная схема ? это два класса: соединение с базой и результат запроса. В принципе, идентификатор результата запроса можно было держать и внутри объекта БД (например в массиве результатов), но порядка и удобства ради результаты вынесены в отдельный класс.
Итак, найдено пять классов. Описываю в порядке убывания удобства (удобства на мой взгляд).
[] (по имеющимся сведениям ? переписанный в php из asp)
Самый расписанный, самый документированный. Есть примеры использования. А оно достаточно простое. Настраиваете в файле adodb.inc.php параметры базы данных, которые будут использоваться по умолчанию. А можно и не настраивать.
Пример из документации:
include("adodb.inc.php");
ADOLoadCode('mysql');
$db = NewADOConnection();
$db->Connect("localhost", "root", "password", "mydb");
$result = $db->Execute("SELECT * FROM employees");
if ($result === false) die("failed");
while (!$result->EOF) {
for ($i=0, $max=$result->FieldCount(); $i < $max; $i++)
print $result->fields[$i].' ';
$result->MoveNext();
print "
";
}
Если нужна база не MySQL, а Oracle, меняем вторую строку на
ADOLoadCode('oracle');
Программа сама подключает функцией [] файл с классом для нужной базы данных.
Поддержка разных баз осуществляется через дочерние классы.
В общем, все работает и пишется хорошо, и требования тоже "хорошие": объем подключаемого кода для БД MySQL равняется 34 килобайтам кода. Для справки: код для этого сайта включая класс шаблона и сами шаблоны "весит" 66 килобайт.
[] (Database access class).
В использовании ? примерно то же, что и AdoDB. Только в "комплекте" нет руководства. :) Разные базы поддерживаются дочерними классами, которые, насколько я понял, подключаются автоматически. Но здесь функций для выполнения запроса несколько ? select, insert, update и execute. Не знаю, может, это и удобнее с точки зрения обработки результата, но писать программы по моим прикидкам не будет ни удобнее, ни сложнее (по крайней мере, мне не приходилось сталкиваться с ситуацией, когда тип запроса выбирался в зависимости от ситуации).
[] (Database independent class, говорят, что переписан с Perl-овой версии).
Описано хорошо, но на сайте. Функции разделены, но на две (выборка и остальные запросы). Чтобы достать из архива какие-либо файлы, надо проявить хорошую смекалку. Потому что архив не распаковывается ? там в именах файлов двоеточия, и система грязно ругается. Заходим в файл class.DBI-....tgz, копируем оттуда в нужную директорию файл class.DBI-0.3.8.tar и открываем его в UltraEdit. Народным методом copy/paste достаем фрагмент кода под нужную базу. Фуф!
[].
В отличие от предыдущих классов здесь запрос отправляется в базу конструктором класса запроса. В одном из классов нашел ошибку ? вместо str_replace используется ereg_replace.
[] (Database independent object wrapper).
А вот этим классом настоятельно не советую пользоваться. Разделение на разные базы сделано самым глупым способом ? если переменная с типом базы, и такая конструкция:
if ($database_type==1) {
...
}
elseif ($database_type==2) {
...
};
А в ней ? самое страшное. Попробуйте найти это сами.
if ($database_type==2) {
$this->my_temp_resultID = mysql_query($someSQL, $this->my_connection);
$this->my_temp_result_object->numrows = mysql_num_rows($this->my_temp_resultID);
$this->my_temp_result_object->numcols = mysql_num_fields($this->my_temp_resultID);
// fill column_names from resultset
for ($j=0; $j < $this->my_temp_result_object->numcols; $j++) {
$this->my_temp_result_object->column_name[$j] = mysql_fieldname($this->my_temp_resultID, $j);
};
// fill data elements from resultset
for ($i=0; $i < $this->my_temp_result_object->numrows; $i++) {
$x = mysql_fetch_row($this->my_temp_resultID);
for ($j=0; $j < $this->my_temp_result_object->numcols; $j++) {
$this->my_temp_result_object->element[$i][$j] = $x[$j];
};
};
return $this->my_temp_result_object;
mysql_free_result($this->my_temp_resultID);
};
Перед return есть два вложенных цикла, которые тупо берут данные из базы и скидывают их в массив. Конечно же, потом с этим массивом делать можно что угодно, "хоть веревки вейте, хоть ездовую собаку делайте", но это стоит дополнительной занимаемой памяти.
Конечно же, иногда требуется произвести операцию, которая не по силам базе данных, но это один случай из ста. В остальных же нужно просто получать данные и выводить их в документ. При этом сразу после вывода массив строки заменяется на новый, почти такой же. Здесь же этого не происходит.
Тут, конечно, мне можно возразить, мол, сам шаблонами пользуешься, и до конца держишь все в переменной (финал моих скриптов такой: $root->ugh(); ? это выводится готовый документ :). Ну, шаблоны ? это совсем другое дело. А если я прикручу этот класс к своим шаблонам или к другому скрипту, в котором держать все данные в массиве не нужно, памяти будет требоваться еще больше.
Если же кому-то нужно именно скидывать в один массив все результаты запроса, пользуйтесь другими классами ? всего-то четыре строки своего кода.
К тому же, как вы понимаете, освобождения результата в конце функции не произойдет, потому что команда на завершение работы функции ? return ? уже дана. Если скрипт, который вызывает несколько "хороших" запросов, одновременно вызовет много пользователей, ой, как плохо станет серверу!
Пароль на страницу. Часть 1. Скорее теоретическая.
DL21.3.2001
В связи с позапрошлым выпуском (напомню, речь там шла о свободе доступа к чужому почтовому ящику) я решил описать способы закрыть паролем часть сайта. Тема, на самом деле, большая, поэтому на первый раз ограничусь авторизацией php+mysql.
Самый первый вопрос, который обычно встаёт? как закрыть директорию со скрпитами администрирования паролем. При этом не нужно никаких изысков ? один или несколько администраторов имеют одни и те же права, а персоналии меняются редко. Проще всего в данной ситуации использовать стандартную серверную авторизацию ? положить файлы .htaccess и .htpasswd и прописать в них нужные параметры. Про это уже написано много, поэтому я ничего особо нового не скажу, лучше посмотрите [].
Добавлю две вещи. Первое ? это куда класть файл .htpasswd. Экспериментальным путем я выяснил, что если, например, путь к документу с сообщением об ошибке (ErrorDocument) пишется относительно системной переменной DocumentRoot. Но путь к файлу с паролями (UserFile) пишется относительно ServerRoot. Насколько я понял, выше ServerRoot положить .htpasswd нельзя ? "../" не воспринимается. Всё это сделано для того, чтобы можно было поместить файл с паролями, например, одним уровнем выше корневой директории сайта, чтобы из сети доступа к файлу не было вообще.
Второе ? это то, что скрипт может узнать, кто его открывает и пароль: переменные $PHP_AUTH_USER и $PHP_AUTH_PW.
Главный недостаток этого способа ? сервер не может блокировать подбор пароля (это после нескольких неудачных попыток входа пользователю предлагается подождать часок-другой, а в течение этого времени обращения с его IP-адреса игнорируются). Это написано в официальной документации по Апачу.
Ещё один недостаток ? необходимость переписывать файлы с паролями при удалении пользователя или введении нового. Но если это происходит нечасто, этого способа вполне достаточно, к тому же не придётся забивать голову написанием механизма авторизации.
Пишем наш первый HTTP запрос.
Если Вы думаете, что все слишком сложно, то Вы ошибаетесь. Человек так устроен, что просто не способен создавать что-то сложное, иначе он сам в этом запутается :-)Итак, есть браузер и есть Web-сервер. Инициатором обмена данными всегда выступает браузер. Web-сервер никому, никогда просто так ничего не пошлет, чтобы он что-нибудь отправил браузеру? надо, чтобы браузер об этом попросил. Простейший HTTP запрос моет выглядеть, например, так:
GET http://www.php.net/ HTTP/1.0\r\n\r\n
Где GET (В переводе с английского означает "получить") ? тип запроса, тип запроса может быть разным, например POST, HEAD, PUT, DELETE (часть из них мы рассмотрим ниже).
http://www.php.net/ ? URI (адрес) от которого мы хотим получить хоть какую-нибудь информацию (естественно мы надеемся подучить HTML страницу).
HTTP/1.0 ? тип и версия протокола, который мы будем использовать в процессе общения с сервером.
\r\n ? конец строки, который необходимо повторить два раза, зачем, станет понятно немного позднее.
Вы можете выполнить данный запрос очень просто. Запустите программу telnet.exe, введите в качестве хоста www.php.net, укажите порт 80, и просто наберите данный запрос, нажав два раза Enter в качестве \r\n\r\n. В ответ вы получите HTML код главной страницы сайта www.php.net.
Протокол HTTP. Введение.
Сразу хочу уточнить одну маленькую вещь. Страшное слово протокол есть не что иное, как соглашение множества людей, просто в один прекрасный момент люди решили: "Давайте будем делать так, и тогда все будет в порядке". Бояться нечего, все просто до безобразия и это безобразие мы сейчас будем вскрывать. Итак, что же это такое протокол HTTP и с чем его едят?Структура запроса.
Рассмотрим, из чего состоит HTTP запрос. Все достаточно просто. Начнем с того, что HTTP запрос ? это вполне осмысленный текст. Из чего же он состоит в общем случае? Будем рассматривать протокол HTTP 1.0. Итак:Request-Line [ General-Header | Request-Header | Entity-Header ]\r\n[ Entity-Body ]
Формат: "Method Request-URI HTTP-Version\r\n"
Method ? метод, которым будет обрабатываться ресурс Request-URI, может быть GET, POST, PUT, DELETE или HEAD.
Request-URI ? относительная или абсолютная ссылка на страницу с набором параметров, например, /index.html или http://www.myhost.ru/index.html или /index.html?a=1&b=qq. В последнем случае серверу будет передан запрос с набором переменных a и b с соответствующими значениями, а знак "&" ? амперсант служит разделителем между параметрами.
HTTP-Version ? версия HTTP протокола, в нашем случае "HTTP/1.0".
Нам крайне интересны методы обработки GET и POST. Методом GET можно просто передать параметры в скрипт, а методом POST можно эмулировать submit формы.
Для метода GET, Request-URI может выглядеть, например, так: "/index.html?param1=1¶m2=2".
Формат: [Date: value\n | Pragma: no-cache\n]
Может иметь только два параметра: Date или Pragma. Date ? дата по Гринвичу в формате "День недели, Число Месяц Год ЧЧ:ММ:СС GMT", например, "Tue, 15 Nov 1994 08:12:31 GMT" ? дата создания запроса. Pragma может иметь одно значение no-cache, которое запрещает кэширование страницы.
Request-Header ? часть заголовка, описывающая запрос.
Request-Header может иметь следующие параметры: Allow, Authorization, From, If-Modified-Since, Referer, User-Agent.
В данной главе мы не будем рассматривать параметр Autorization, так как он используется для доступа к закрытым ресурсам, что требуется не так уж часто. Вы можете самостоятельно изучить формирование заголовка для авторизованного доступа на сайте www.w3c.org.
Allow ? задает допустимые методы обработки.
Формат: "Allow: GET | HEAD\n".
Параметр игнорируется при указании метода обработки POST в Request-Line. Задает допустимые методы обработки запроса. Прокси сервера не модифицируют параметр Allow и он в неизменном виде доходит до сервера.
From ? e-mail адрес, пославшего запрос.
Формат: "From: adderss\r\n".
Например, "From: myname@mailserver.ru\r\n".
If-Modified-Since ? указывает, что запрос не модифицировался с такого-то времени.
Формат: "If-Modified-Since: date\r\n"
Используется только для метода обработки GET. Дата указывается по Гринвичу в таком же формате, как и для параметра Date в General-Header.
Referrer ? абсолютная ссылка на страницу, с которой произошла инициация запроса, т. е. ссылка на страницу, с которой пользователь перешел на нашу.
Формат: "Referrer: url\n".
Пример: "Referrer: www.host.ru/index.html\n".
User-Agent ? тип браузера.
Например: "User-Agent: Mozilla/4.0\n"
Entity-Header ? часть заголовка, описывающая данные Entity-Body.
В данной части запроса задаются параметры, которые описывают тело страницы. Entity-Header может содержать следующие параметры: Allow, Content-Encoding, Content-Length, Content-Type, Expires, Last-Modified, extension-header.
Allow ? параметр аналогичный Allow из General-Header.
Content-Encoding ? тип кодирования данных Entity-Body.
Формат: "Сontent-Encoding: x-gzip | x-compress | другой тип\n".
Пример: "Сontent-Encoding: x-gzip\n". Символ "|" означает слово "или", то есть то или то или то и.т.д.
Другой тип может указывать на способ кодирования данных, например, для метода POST: "Сontent-Encoding: application/x-www-form-urlencoded\n".
Content-Length ? количество байт, пересылаемых в Entity-Body. Значение Content-Length имеет совсем другой смысл для данных, пересылаемых в формате MIME, где он выступает как параметр описания части данных ? "external/entity-body". Допустимыми являются целые числа от нуля и больше.
Пример: "Content-Length: 26457\n".
Content-Type ? тип передаваемых данных.
Например: "Content-Type: text/html\n".
Expires ? Время, когда страница должна быть удалена из кэша браузера.
Формат: "Expires: date\n". Формат даты алогичен формату даты для параметра Date из General-Header.
Last-Modified ? время последнего изменения пересылаемых данных.
Формат: "Last-Modified: date\n". Формат даты алогичен формату даты для параметра Date из General-Header.
Extention-header ? часть заголовка, которая может предназначаться, например, для обработки браузером, или другой программой, которая принимает документ. В данной части можно описывать свои параметры в формате "ParameterName: parametervalue\n". Данные параметры будут игнорироваться, если программа-клиент не знает, как их обработать.
Например: "Cookie: r=1\r\n" ? устанавливает всем известные печеньки для страницы.
А теперь после таких страшных слов давайте попробуем немного успокоиться и понять, что же нам надо? Понимать мы естественно будем на примерах.
Давайте представим, что нам надо получить страницу с сайта, передав Cookies (Печеньки), иначе нас просто пошлют как незванных гостей, и более того, известно, что на данную страницу пускают только после того, как Вы побывали на главной странице сайта.
Запрос GET.
Напишем наш запрос.GET http://www.site.ru/news.html HTTP/1.0\r\n
Host: www.site.ru\r\n
Referer: http://www.site.ru/index.html\r\n
Cookie: income=1\r\n
\r\n
Данный запрос говорит нам о том, что мы хотим получить содержимое страницы по адресу http://www.site.ru/news.html, использую метод GET. Поле Host говорит о том, что данная страница находится на сервере www.site.ru, поле Referer говорит о том, что за новостями мы пришли с главной страницы сайта, а поле Cookie говорит о том, что нам была присвоена такая-то кука. Почему так важны поля Host, Referer и Сookie? Потому что нормальные программисты при создании динамических сайтов проверяют данные поля, которые появляются в скриптах (РНР в том числе) в виде переменных. Для чего это надо? Для того, например, чтобы сайт не грабили, т.е. не натравливали на него программу для автоматического скачивания, или для того, чтобы зашедший на сайт человек всегда попадал бы на него только с главной страницы и.т.д.
Теперь давайте представим, что нам надо заполнить поля формы на странице и отправить запрос из формы, пусть в данной форме будет два поля: login и password (логин и пароль),- и, мы естественно знаем логин и пароль.
GET http://www.site.ru/news.html?login=Petya%20Vasechkin&password=qq HTTP/1.0\r\n
Host: www.site.ru\r\n
Referer: http://www.site.ru/index.html\r\n
Cookie: income=1\r\n
\r\n
Логин у нас "Petya Vasechkin" Почему же мы должны писать Petya%20Vasechkin? Это из=за того, что специальные символы могут быть распознаны сервером, как признаки наличия нового параметра или конца запроса и.т.д. Поэтому существует алгоритм кодирования имен параметров и их значений, во избежание оштбочных ситуаций в запросе. Полное описание данного алгоритма можно найти [], а в PHP есть функции rawurlencode и rawurldecode для кодирования и декодирования соответственно. Хочу отметеить, что декодирование РНР делает сам, если в запросе были переданы закодированные параметры. На этом я закону первую главу знакомства c протоколом HTTP. В следуючей главе мы рассмотрим построение запросов типа POST (в переводе с английского? "отправить"), что будет гораздо интереснее, т.к. именно данный тип запросов используется при отправке данных из HTML форм.
Content-Type: application/x-www-form-urlencoded.
Пишем запрос, аналогичный нашему запросу GET для передачи логина и пароля, который был рассмотрен в предыдущей главе:POST http://www.site.ru/news.html HTTP/1.0\r\n
Host: www.site.ru\r\n
Referer: http://www.site.ru/index.html\r\n
Cookie: income=1\r\n
Content-Type: application/x-www-form-urlencoded\r\n
Content-Length: 35\r\n
\r\n
login=Petya%20Vasechkin&password=qq
Здесь мы видим пример использования Content-Type и Content-Length полей заголовка. Content-Length говорит, сколько байт будет занимать область данных, которая отделяется от заголовка еще одним переводом строки \r\n. А вот параметры, которые раньше для запроса GET помещались в Request-URI, теперь находятся в Entity-Body. Видно, что они формируются точно также, просто надо написать их после заголовка. Хочу отметить еще один важный момент, ничто не мешает, одновременно с набором параметров в Entity-Body, помещать параметры с другими именами в Request-URI, например:
POST http://www.site.ru/news.html?type=user HTTP/1.0\r\n
.....
\r\n
login=Petya%20Vasechkin&password=qq
Content-Type: multipart/form-data.
Как только интернет мир понял, что неплохо бы было через формы отсылать еще и файлы, так W3C консорциум взялся за доработку формата POST запроса. К тому времени уже достаточно широко применялся формат MIME (Multipurpose Internet Mail Extensions? многоцелевые расширения протокола для формирования Mail сообщений), поэтому, чтобы не изобретать велосипед заново, решили использовать часть данного формата формирования сообщений для создания POST запросов в протоколе HTTP.Каковы же основные отличия этого формата от типа application/x-www-form-urlencoded?
Главное отличие в том, что Entity-Body теперь можно поделить на разделы, которые разделяются границами (boundary). Что самое интересное ? каждый раздел может иметь свой собственный заголовок для описания данных, которые в нем хранятся, т.е. в одном запросе можно передавать данные различных типов (как в Mail письме Вы одновременно с текстом можете передавать файлы).
Итак, приступим. Рассмотрим опять все тот же пример с передачей логина и пароля, но теперь в новом формате.
POST http://www.site.ru/news.html HTTP/1.0\r\n
Host: www.site.ru\r\n
Referer: http://www.site.ru/index.html\r\n
Cookie: income=1\r\n
Content-Type: multipart/form-data; boundary=1BEF0A57BE110FD467A\r\n
Content-Length: 209\r\n
\r\n
--1BEF0A57BE110FD467A\r\n
Content-Disposition: form-data; name="login"\r\n
\r\n
Petya Vasechkin\r\n
--1BEF0A57BE110FD467A\r\n
Content-Disposition: form-data; name="password"\r\n
\r\n
qq\r\n
--1BEF0A57BE110FD467A--\r\n
Теперь давайте разбираться в том что написано. :-) Я специально выделил некоторые символы \r\n жирным, чтобы они не сливались с данными. Присмотревшись внимательно можно заметить поле boundary после Content-Type. Это поле задает разделитель разделов ? границу. В качестве границы может быть использована строка, состоящая из латинских букв и цифр, а так же из еще некоторых символов (к сожалению, не помню каких еще). В теле запроса в начало границы добавляется '--', а заканчивается запрос ? границей, к которой символы '--' добавляются еще и в конец. В нашем запросе два раздела, первый описывает поле login, а второй поле password. Content-Disposition (тип данных в разделе) говорит, что это будут данные из формы, а в поле name задается имя поля. На этом заголовок раздела заканчивается и далее следует область данных раздела, в котором помещается значение поля (кодировать значение не требуется!).
Хочу обратить Ваше внимание та то, что в заголовках разделов не надо использовать Content-Length, а вот в заголовке запроса надо и его значение является размером всего Entity-Body, стоящего после второго \r\n, следующего за Content-Length: 209\r\n. Т.е. Entity-Body отделяется от заголовка дополнительным переводом строки (что можно заметить и в разделах).
А теперь давайте напишем запрос для передачи файла.
POST http://www.site.ru/postnews.html HTTP/1.0\r\n
Host: www.site.ru\r\n
Referer: http://www.site.ru/news.html\r\n
Cookie: income=1\r\n
Content-Type: multipart/form-data; boundary=1BEF0A57BE110FD467A\r\n
Content-Length: 491\r\n
\r\n
--1BEF0A57BE110FD467A\r\n
Content-Disposition: form-data; name="news_header"\r\n
\r\n
Пример новости\r\n
--1BEF0A57BE110FD467A\r\n
Content-Disposition: form-data; name="news_file"; filename="news.txt"\r\n
Content-Type: application/octet-stream\r\n
Content-Transfer-Encoding: binary\r\n
\r\n
А вот такая новость, которая лежит в файле news.txt\r\n
--1BEF0A57BE110FD467A--\r\n
В данном примере в первом разделе пересылается заголовок новости, а во втором разделе пересылается файл news.txt. Внимательный да увидит поля filename и Content-Type во втором разделе. Поле filename задает имя пересылаемого файла, а поле Content-Type ? тип данного файла. Application/octet-stream говорит о том, что это стандартный поток данных, а Content-Transfer-Encoding: binary говорит на о том, что это бинарные данные, ничем не закодированные.
Очень важный момент. Большинство CGI скриптов написано умными людьми, поэтому они любят проверять тип пришедшего файла, который стоит в Content-Type. Зачем? Чаще всего закачка файлов на сайтах используется для получения картинок от посетителя. Так вот, браузер сам пытается определить что за файл посетитель хочет отправить и вставляет соответствующий Content-Type в запрос. Скрипт его проверяет при получении, и, например, если это не gif или не jpeg игнорирует данный файл. Поэтому при "ручном" формировании запроса позаботьтесь о значении Content-Type, чтобы оно было наиболее близким к формату передаваемого файла.
| image/gif | для gif |
| image/jpeg | для jpeg |
| image/png | для png |
| image/tiff | для tiff (что используется крайне редко, уж больно емкий формат) |
EditPlus 2.10c
Редактор ? то, что надо, во всяком случае мне. Подсветка активной строки, перенос по словам (который можно включить кнопкой на панели инструментов, а для себя я ещё переопределил под него сочетание Ctrl+W). И при этом в режиме автопереноса вы видите настоящие номер строки и столбца (UltraEdit такого не умеет, кстати). Кроме того, при включенном автопереносе не портится вид программного кода "лесенкой" (чего не умеет ни один из редакторов, которыми я пользовался). Временами это мешает, но по большей части ? очень даже удобно. Кроме того, при включении и выключении автопереноса курсор не убегает на первую позицию текста, как это происходит в других редакторах. Шаблоны для автозавершения можно скачать с сайта. FTP, естественно. И ещё ? можно указать в настройках путь к корневой директории сервера на данном компьютере, чтобы сервер вызывать страницы и правильно писать пути. Регулярные выражения ? стандарт PCRE (см. архив этого сайта).
Здорово? По-моему, очень здорово! И всё это умещается в инсталляционном пакете ? представьте себе! ? в 970 килобайт! В общем, вы меня понимаете, поработав с EditPlus, я окончательно расстался с UltraEdit.
HTML-Kit (build 290)
Пройдут года два-три, и HTML-Kit станет таким же матёрым редактором, как и Microsoft Word.А если серьезно, то это самый мощный редактор html, который я видел (HomeSite? это отдельная песня).
Предпросмотр в окне редактора (которым фанаты HomeSite чаще всего козыряют), множество инструментов по верстке html (например, диалоговое окно по вставке картинки в страницу а так же созданная W3C программа HTML-tidy, гибко настраиваемая), плагины (либо на внутреннем языке макросов, либо вообще отдельнае программы), автозавершение (это уже все умеют, только ни у кого нет интерфейса по настройке шаблонов автозавершения). Панель инструментов редактируется так же, как в MS Word (там я, кстати взял и перетащил кнопку "Word wrap" из меню на самое видное место). Если вы редактируете php-скрипт, то можно сразу получить справку по функции, переведя на неё курсор (который не мыши) и нажав F1 (всё хорошо, только программа обращается не иначе как к онлайновому руководству по php с официального сайта).

Почему я не пользуюсь HTML-Kit. Причины две. Во-первых, большая задумчивость программы (на K6-266 это ощущается) и... движок редактора. Мне не понравился переход по сочетаниям Ctrl+влево и Ctrl+вправо. Слишком большие "шаги" делает программа, часто приходится возвращаться обратно, а это неприятно и снижает скорость работы. Да впрочем, если это исправят, вряд ли я его поставлю ? по моим прикидкам, несколько полезных функций (а всего их там добрая сотня) понадобятся мне раз в месяц. Не хочется остальное время зря грузить систему.
Метод POST.
В случае HTTP запроса типа POST существует два варианта передачи полей из HTML форм, а именно, используя алгоритм application/x-www-form-urlencoded и multipart/form-data. Различия между данными алгоритмами весьма существенные. Дело в том, что алгоритм первого типа создавался давным-давно, когда в языке HTML еще не предусматривали возможность передачи файлов через HTML формы. Итак, давайте рассмотрим эти алгоритмы на примерах.Об авторе
Не люблю особо о себе распространятся когда люди не знают подробностей о человеке, они вынуждены его уважать ;)) Но на работе комплекса Apache+PHP+Win32 собаку съел, поверьте! ;)) Впрочем, повторюсь, я не претендую этой статьей на универсальность. Просто прочитав несколько противоречивых руководств, и заглянув в несколько конференций, где обсуждается одно и то же ("это вам не линукс, под виндой ничего и не заработает!") многие будут неправильно информированы. Даже если они пытаются сделать так, как кто-то описывает и у них ничего не получается, себя они винят в последнюю очередь. Виноват если не Билл Гейтс и не инопланетяне, то, как минимум, ее величество Судьба, ниспославшая именно одному человеку такой запутанный Случай. Да, бывают Случаи, но они лечатся, как правило, переустановкой системы (это худший вариант и не надо считать это руководством к действию). Все остальные случаи совершенно обычны.Увидев в очередной раз в конференции сообщение типа "PHP + Апач + Win32 = ???" я сел, и написал эту статью. На одном дыхании. Надеюсь, когда еще у кого-то возникнут подобные вопросы, его сразу адресуют к моей статье: "иди, почитай, там все сказано". Впрочем, создание всеобъемлющего руководства по установке и конфигурированию PHP под Win32 невозможно без Ваших откликов: lkx2@mail.ru.
Удачи!
Перепечатка и использование данной статьи для написания различного рода руководств допускаются только с согласия автора и только с обязательной ссылкой на оригинальную статью.
Уважайте чужую интеллектуальную собственность!
(с) Дмитрий Короленко, 2001
PHPEd 1.75 alpha
Идея хорошая, но реализация сырая.Хороших идей много. Можно делать проверку работы скрипта непосредственно через интерпретатор php или perl, можно, прописав путь к DocumentRoot, проверять работу скриптов через сервер (это бывает необходимо при работе с куками и header-ами). Есть встроенный броузер (кстати, весьма старый, не понимает, например, обозначений "\"), сразу же можно посмотреть html-код полученной страницы (внизу две "вкладки" "HTML" и "Output"). Кроме того, справка по функциям php даётся необязательно с официального сайта ? можно указать путь к руководству на жестком диске.

Плохого тоже достаточно. Редактор ? один в один HTML-Kit одно-двухгодичной давности. Не знаю, кто у кого слизал, или этот движок вообще свободно распространяемый, дело даже не в этом. Если движок в последней версии HTML-Kit ещё более-менее пригоден, то этот очень неудобный. Глюков, о которых все пишут, не видел, потому как интенсивно этим редактором не пользовался.
Постскриптум.
Думаю, что о передаче запросов на сервер не стоит рассказывать подробно. Это уже дело чисто РНР техники :-). Достаточно внимательно прочитать раздел о функциях работы с сокетами, или о функциях модуля CURL в официальной документации РНР.Из выше сказанного, надеюсь теперь понятно, почему вопрос: "Как мне сформировать POST запрос, используя функцию header?" ? бессмысленен. Функция header(string) добавляет запись только в заголовок запроса, но никак не в тело запроса.
Есть еще один тип запросов ? Content-Type: multipart/mixed, надеюсь после прочтения данной статьи Вы легко разберетесь с данным типом сами. Подробно изучить его можно []
О запросах других типов можно прочитать в официальной спецификации протокола HTTP 1.0 [].
Практическая часть.
Прежде всего, настоятельно рекомендую проверить работу веб-сервера Apache ДО каких-либо измывательств над бедным животным ;)) Поверьте, это сохранит Вам и многим другим кучу времени, нервов и физических сил. Не торопитесь устанавливать PHP, не проверив работоспособность Апача. Даже если Вам ОЧЕНЬ хочется. Всему свое время. Вам проще будет локализовать причину, если не будете торопиться.Работа с MySQL. Часть 4. Постраничный вывод
DL28.4.2001
Регулярно в форуме задают один и тот же вопрос: как сделать постраничный вывод. И каждый раз человеку отвечают: "Легко! m строк, начиная с n-ной: Select запрос Limit $n,$m". На самом деле не так всё просто.
Я уже про синтаксис параметра LIMIT, однако без толку. Для полноценного постраничного вывода строк из базы требуется большее. Требуется
1. Обработать номер страницы (в том числе проверить, не больше ли он общего количества страниц)
2. Нарисовать навигационную строку (чтобы не просто "вперед-назад", а с ссылками на несколько соседних страниц)
Тут-то и начинаются главные проблемы.
Недавно я работал над сайтом, в котором эти постраничные выводы в статистике были в каждом списке (а списков было много!). Тут-то и созрело решение, как свести все эти штучки к простому и единому решению. Получились четыре функции, которые я теперь использую везде, где нужен постраничный вывод данных, и не напрягаю попусту голову проблемой (как же я делал это там, как бы вынуть этот код оттуда?).
Первая функция? для внутреннего пользования двумя следующими. Берёт номер страницы, общее количество строк и количество строк на странице и выдаёт номер страницы, уже проверенный. Вторая берёт то же самое, проверяет номер страницы и выдаёт парамерт LIMIT либо полный (LIMIT n,m), либо краткий (LIMIT m), если это первая страница, либо ничего не выдаёт. Третья функция из тех же трёх параметров и адреса для ссылки делает навигационную строку. Ещё одна функция выдаёт число для нумерованного списка.
Этого достаточно для нормальной работы с постраничным выводом данных. Посмотрим, что получается в коде программы:
// кол-во строк в страницах
$in_page = 10;
// получаем количество строк
$amount = @mysql_result(mysql_query("SELECT count(id) as goods_total FROM goods"),0);
// рисуем навигационную строку и пишем начало таблицы
print("
| $count. | ${good_row[name]} ${good_row[description]} | ${good_row[price]} |
Это ВСЁ, что нужно для постраничного вывода! Больше напрягаться не надо!
Одно только пояснение ? в качестве параметра функции draw_bar указывается адрес этого скрипта со всеми параметрами так, чтобы он туда только дописывал номер страницы. Если сложная выборка, надо будет ручками формировать этот адрес (всё-таки упрощение жизни вышло относительное: упрощаем одно ? усложняем другое).
Навигационная панель сделана в виде номеров страниц (" 1 | 2 | 3 "). Но привести к виду "0-10 | 11-20 | 21-30" не проблема.
Ниже есть ссылка на файл с этими функциями.
Редакторы PHP и HTML
DL10.4.2001
На нашем потребительском тесте 4 редактора. Это EditPlus, HTML-Kit, PHPEd и UltraEdit. Сперва сводная таблица их возможностей.
![]() | ![]() | ![]() | ![]() | |
| Подсветка синтаксиса HTML и PHP | + | +* | +* | + |
| FTP | + | + | + | + |
| Автозавершение | + | + | + | + |
| Проекты | + | + | + | |
| Проверка орфографии | + | + | + | |
| Макросы | + | + | + | |
| Настройка панелей инструментов (мало ли, вдруг понадобится :) | + | + | + | |
| HTML-Tidy | + | + | ||
| Справка (мануал) по функциям php | + | + | ||
| Встроенный броузер | + | + | ||
| Установочный пакет (килобайт) | 970 | 2900 | 965 | 1004 |
Теоретическая часть.
Итак, как Вы поняли (из моего повествования, документации на PHP либо из других источников) возможны ДВА способа установки PHP на веб-сервере. Как CGI-программу и как модуль сервера. В принципе, подчеркну, в принципе, особой разницы нет. В любом случае, веб-сервер передает ядру PHP скрипт, путь к которому содержится в запросе клиентского браузера. В случае работы PHP как CGI порождается новый процесс, которому, собственно, и передается скрипт. В случае работы модуля, его код уже "висит" в памяти сервера. Это основное принципиальное отличие. Что из этого следует додумайте сами.Итак, как устанавливать PHP для работы совместно с веб-сервером Апач? Как CGI или как модуль Апача? Практически, особой разницы нет, но... Как упомянуто выше, вы ДОЛЖНЫ установить PHP как модуль для реализации функций работы с заголовками и авторизацией. Впрочем, это явления одного порядка. Кроме того, в случае PHP как CGI-программы, переменная $PHP_SELF (которая должна хранить имя выполняемого скрипта) содержит все что угодно, но только не имя. Это не самое страшное, что бывает, но вдруг придется использовать чей-то чужой скрипт, в котором она широко используется... Бывает, что при повторном вызове скрипта по имени, которое содержит переменная $PHP_SELF, скрипт просто прекращает свою работу. Битая ссылка. Как это ни печально. Можно, наверное, назвать еще несколько отличий, но, как мне кажется, и этой разницы вполне достаточно, чтобы сделать выбор в пользу PHP-модуля.
Да, и еще. Установив PHP как модуль, вы получите возможность управлять некоторыми его параметрами из файлов .htaccess Для экспериментального сервера это, пожалуй, не настолько уж важно все-таки, Вы являетесь и администратором. Но дистрибутивы некоторых программ PHP поставляются с файлами .htaccess, которые изменяют параметры PHP, и отказываются работать, если им это не удается. Можно поправить php.ini, но всякий раз для каждого скрипта его править... Нет, это выше человеческих сил!
Итак, короткая теоретическая часть и долгое вступление, вернее, лирическое отступление ;)) закончились. Приступим к практике.
Траблшутинг
Допустим, оно у вас не заработало с первого раза... Что делать? Ну, прежде всего, конечно же, надо отправить во все конференции сообщение типа: "А-а-а-а-а!!! Хелп!!! У меня НИ-ЧЕ-ГО не работает!!!" И, разумеется, письмо автору данной статьи ;)) Самое главное сообщите как можно меньше информации о своей системе ну да, в конференциях ведь участвуют исключительно вундеркинды и телепаты, а Ваш покорный слуга так и вовсе Маг и Волшебник. Таким как он догадаться, на какой системе Вы работаете, и в чем может быть дело вообще пара пустяков! ;)) Да, кстати, в конференциях быстрее всего отвечают на постинги, набранные ЗАГЛАВНЫМИ БУКВАМИ. Ведь от долгой работы с компьютером портится зрение и Ваш постинг, набранный строчными буквами могут просто не заметить. ;))А если серьезно, не надо паниковать. Давайте разберемся.
Симптомы: Апача запустилась. Но скрипты не выполняются либо нагло лезут в окно браузера, либо хотят сохраниться на локальном диске.
Диагноз: Скрипт не передается на выполнение парсеру PHP
Лечение:
1. Проверьте работоспособность самого PHP. Для этого создайте в директории с php.exe файл, допустим, test.php:
<? echo "TEST" ?>
Запустите его командой php.exe test.php. Должны увидеть следующее:
| Content-type: text/html TEST |
Заметьте: между первой и третьей строками есть пустая строка. Так надо, так должно быть. Именно так и никак иначе.
Если это не срабатывает? PHP страшно ругается и плюется, проверьте, есть ли у вас все необходимые файлы. А именно, файл php4ts.dll в директории с PHP либо в директории, содержащейся в переменной окружения PATH. Киньте ее в системную директорию windows и не мучайтесь. (Ну, я же предупреждал ? сначала проверьте работоспособность компонентов!)
Есть, но все равно проблема остается? Пожалуй, следует заглянуть поглубже... Впрочем, это выходит за рамки данной статьи. Скажу лишь, что файл php.ini, включенный в дистрибутив, требует настройки только в исключительных случаях. Поверьте, Ваш случай ? самый ординарный. ;)) Скорее всего, тогда дело в поврежденных выполняемых файлах дистрибутива.
2. PHP сам по себе все-таки работает... Проверим связку Апач + PHP...
Если у вас PHP сконфигурирован для работы в качестве модуля Апача (настоятельно рекомендую настроить его именно так! это дело всей моей жизни), проверьте загрузку модуля и назначение mime-типа application/x-httpd-php
Если PHP работает как внешняя программа, дополнительно к назначению mime-типа, проверьте назначение action, которое ставится в соответствие данному типу (см. Урок 1)
Симптомы: Апач вообще не запускается. Выскакивает черное окошко сеанса MS-DOS и тут же закрывается.
Предварительный диагноз: Неправильная конфигурация Апача. Что-то неправильно прописано в конфигурационных файлах. Либо отсутствует библиотека, необходимая для запуска модуля PHP ? php4apache.dll, либо само ядро php4ts.dll
Лечение: Ставим более точный диагноз:
Если самостоятельно не удастся разобраться, в чем же дело, это здорово поможет, если не Вам, то, возможно, специалисту, к которому Вы обратитесь (Эй! Я не сказал, чтобы все обращались ко мне!) Описания типа "я все нормально установил, но Он тут же вываливается" вряд ли кого удовлетворят. Все-таки, Апач, хоть и довольно невразумительно (а разве бывает в мире программ иначе?), но все же сообщает, почему он не может стартовать. Так вот: СМОТРИТЕ НА ЭКРАН ? там часто сообщается о причинах ошибок программ. Не всегда, но часто. И данный случай ? не исключение.
Итак, сначала попытайтесь вспомнить, ругался ли он так ДО установки PHP. Если нет, то:
В случае установки в виде CGI проверьте правильность написания директив настройки Апача и в тех ли секциях Вы сделали изменения. Других вариантов нет. Дело в этом и только в этом. Ибо PHP вызывается Апачем для выполнения скриптов и не играет никакой роли при запуске самого Апача. Поэтому дальше речь пойдет ТОЛЬКО о PHP как модуле Апача. На CGI больше не останавливаемся.
Если Вы устанавливаете PHP как модуль Апача, следует посмотреть, может, ему чего-то не хватает? ;)) Попробуйте, например, положить php4ts.dll в директорию с Апачем. Иногда это помогает. В общем, дайте простор фантазии. Только не рукам! Не надо бить компьютер за то, что вы такой неумеха ;)) А выглядит оно, бывает, устрашающе ? Апач рушится, вызывая ошибку в apachecore.dll. Но это все неправда. Вам для работы требуется пиво, программам ? библиотеки и файлы конфигурации. Не так ли? ;))
По моим наблюдениям, разные версии Апача ведут себя несколько по-разному при подключении PHP модуля. Пожалуйста, поправьте меня, если у Вас есть данные считать по-другому.
Так, Apache 1.3.12 ищет php4ts.dll сначала в своей директории (где лежит Apache.exe), затем в директориях, указанных в переменной окружения PATH. Вполне может быть, что в Вашем случае в системной директории Windows лежит php4ts.dll от другой версии PHP Как правило, от старой версии ;)) И php4apache.dll не может ее загрузить. А ТУ САМУЮ версию просто не находит. Честно сказать, я терпеть не могу, когда программы при инсталляции кидают в системную директорию свои библиотеки. Я скорее примирись с тем, что одна и та же библиотека будет у меня лежать в двух директориях. Речь идет о директории Апача и о директории с php.exe (иногда полезно) и библиотекой php4apache.dll. Вообще говоря, это все можно положить прямо в директорию Апача ? и далеко ходить не надо, и апдейт проще произвести ? нигде не забудете старую библиотеку.
Apache 1.3.14 ищет php4ts.dll сначала в той директории, где у Вас лежит php3apache.dll. В данном случае все файлы PHP можно сложить в одну директорию. Я так и сделал.
Еще бывают случаи, когда Вы пытаетесь прикрутить к PHP-модулю Апача дополнительные модули и библиотеки (например, пресловутую php_gd.dll) от других версий PHP. Не уверен, что это удастся. Во всяком случае, Апач (точнее, PHP в его составе) шибко ругается, что не совпадают внутренние версии API. Для начала лучше их все отключить. Понятно, где ? в php.ini. В любом случае, присматривайтесь к сообщениям, выдаваемым при запуске.
Да, и еще. Подключая дополнительные модули, обязательно пропишите переменную extension_dir в файле php.ini ? по умолчанию PHP ищет их в той же директории, в которой находится (./) а в дистрибутиве они лежат в директории extensions/ ? странно, да?..
Тоже хороший редактор. Пользовался им
Тоже хороший редактор. Пользовался им больше года. Но сейчас, к сожалению, могу описывать его только с точки зрения пользователя EditPlus.В поиске и замене есть галочка "поиск во всем тексте" (чтоб не "вверх-вниз"). Регулярные выражения в поске - звёздочка и вопросительный знак (как в файловых системах). Подсветка синтаксиса php есть на сайте. В отличие от других редакторов не умеет показывать настоящий номер строки при включенном переносе слов.
Их полезного - удаление пробелов из концов строк и шестнадцатиричный режим редактирования (правил недавно в нём icqcore.dll, чтоб баннеры вырезать). А в остальном - очень хороший редактор.

Почему здесь нет HomeSite? Потому что тех, кто им пользуется, уже не переубедить. А вот к тем, кто собирается его качать и ставить, взываю: одумайтесь!
Сам пользовался им пару месяцев, после чего с чистой совестью удалил. Моё чисто субъективное мнение? очень громоздкая штука, которая к тому же периодически или сама зависает, или подвешивает систему (по крайней мере, такое происходило регулярно после запуска HomeSite+Apache). Не знаю, возможно, сейчас ситуация изменилась, и авторы исправили ошибки в программах, но снова ставить на себе эксперименты не хочу.
Установка PHP а-ля CGI
Данный вопрос неплохо освещен в Сети. Но справедливости ради упомянем его, ограничившись, правда, лишь ключевыми моментами.Альтернативный способ. Прежде всего, рекомендуется все, что связано с локальной копией веб-сервера, сложить в одну директорию, а директорию, в свою очередь, подключить как виртуальный диск командой subst f: путь-к-директории, чтобы все было "как в юниксе". Действительно, мудрое решение. Особенно удобно не лазать по деревьям директорий (мы же не обезьяны, в конце-то концов) все, что относится к веб-серверу, под рукой. Но не все, далеко не все имеют возможность выделить для экспериментов целый логический диск. Поэтому данный совет весьма полезен.
На установке самого веб-сервера я задерживаться, пожалуй, не буду. Ничего сложного нет, все конфигурационные файлы неплохо самодокументированы. Да и руководств по установке и настройке его в Сети МОРЕ.
Итак, Апач уже установлен в директорию типа f:/usr/local/apache (где f буква, соответствующая виртуальному диску) и полностью работоспособен. Соответственно, PHP хорошо бы установить рядышком скажем, в f:/usr/local/php
Распаковываем архив дистрибутива PHP. Нам прямо-таки жизненно необходимо не так уж и много файлов. Это:
php.exe интерпретатор командной строки
php4ts.dll собственно ядро PHP
php.ini-dist конфигурационный файл, версия из дистрибутива
Представьте себе, это ВСЁ! Кто-то спросит: а почему же оригинальный дистрибутив занимает аж по 3-5 мегабайт в архиве (в зависимости от версии и комплектации). А потому что в нем содержатся различные библиотеки расширений для генерации картинок "на лету", для работы с базами данных Postgre SQL, SyBase SQL и т.п. Заметьте: поддержка MySQL в PHP версии 4 встроена прямо в ядро и не требует никаких дополнительных файлов. Все эти расширения вы можете поставить в любой момент.
Установили, скопировали php.ini-dist из дистрибутива в директорию windows и переименовали его в php.ini. Попробовали, работает ли PHP сам по себе? Создайте в директории с PHP текстовый файл с именем, например, test.php:
<? echo "TEST" ?>
И запускаете его из командной строки:
php.exe test.php
Получаете такое вот:
| Content-type: text/html TEST |
Это означает, что PHP сам по себе работает.
Если у Вас не работает либо Apache, либо PHP, то рано нам заниматься конфигурированием связки Апач + PHP. Пусть оно сначала по отдельности все заработает. Обратитесь к разделу "Траблшутинг".
Итак, предыдущий шаг показал нам, что все О.К.
Последующие настройки касаются только файла конфигурации Апача httpd.conf. Добавим mime-тип, соответствующий расширению программ PHP:
AddType application/x-httpd-php .php .php3 .phtml
Причем это следует сделать в секции описания модуля mod_mime? это стандартный модуль апача. Либо добавить в конфигурационный файл mime.types ? но, мне кажется, лучше вносить изменения только в один файл, а не в десять сразу.
Затем поставим действие (action) в соответствие указанному нами типу:
Options ExecCGI
ScriptAlias "/__php_dir__/" "f:/usr/local/php/"
Action application/x-httpd-php "/__php_dir__/php.exe"
Это рекомендуется сделать непосредственно перед секцией Virtual Hosts.
Ну и все на этом... Скопировали созданный нами тестовый файл в корневой каталог веб-сервера и набрали его URL в строке веб-браузера. Должно работать. Если нет ? обратитесь к секции "Траблшутинг" ниже. Может, поможет. Также посмотрите настройки Апача ? особенно секцию Virtual Hosts, если вы пытались обратиться к своему серверу по имени (ну, типа http://vasjapupkin.com/test.php).
Установка PHP как модуля Апачи
Необходимые материалы (тот минимум, с которым все работает):Ход работы.
LoadModule php4_module "путь-к-директории-php/php4apache.dll"
Если Вы следовали приведенным выше рекомендациям назначения имен директорий, то она должна выглядеть как:
LoadModule php4_module "f:/usr/local/php/php4apache.dll"
#
# Options ExecCGI
#
#ScriptAlias "/__php_dir__/" "f:/usr/local/php/"
#Action application/x-httpd-php "/__php_dir__/php.exe"
Установка PHP + Apache: CGI vs. module
Дмитрий Короленко20.4.2001
от многих забот и проблем избавитесь,
установив пхп в виде модуля апачи
(вау! прямо как в рекламном ролике
получилось, надо же) -
тогда и хэдеры будут высылаться,
и переменная $PHP_SELF
будет указывать именно на скрипт...
(из постинга в форум на phpclub)
последнее изменение: 10.01.02
Данное руководство (?) ни в коей мере не претендует на полноту отражения затронутых вопросов. Оно не описывает нюансов настройки веб-сервера Апач. Только самое необходимое. И только связанное с работой PHP совместно с веб-сервером Апач. И, естественно, возможны различные неточности и ошибки. Просьба указать на них (контактная информация? в конце статьи).
Сразу предупреждаю ? те, кто дорожат своим временем (либо те, кому побыстрее хочется заполучить работающий веб-сервер) могут пропустить мои излияния (включая вступление, теоретическую и добрую половину практической части) и сразу перейти к Уроку 2. Там описывается установка PHP как модуля Apache под Win32. А остальное… Ну, кто-то, наверное, прочитает. ;)) Надеюсь, таких будет немало.
Вступление
Итак, почему я написал эту статью? И зачем нам вообще такая экзотика как Апач и PHP под Win32?Для отладки скриптов PHP на локальной машине мне понадобилось установить веб-сервер + PHP под Win32 (принципиально пользуюсь MS Windows ? ибо, на мой взгляд, продуктивная работа, связанная с веб-дизайном, под Unix и его клонами попросту невозможна ? при этих словах я увернулся от летящего в меня Тухлого Помидора ? эти системы хороши как серверная основа, но не более того). Спорить со мной не надо, да я и не буду. Кроме того, вы же читаете эту статью, а, значит, планируете установить Апач + PHP именно под всеми ненавистной Виндой. ;))
Не помню уж, что это был за первый испытанный мной веб-сервер, но запуск парсера PHP мои скрипты осуществляли строчкой #!c:/php/php.exe что не есть очень удобно. Хотя… Авторы PHP (в официальной документации!) указывают на то, что с точки зрения безопасности такой способ ? наилучший. При этом сам парсер "живет" далеко от самих файлов со скриптами. И скрипт передается ему на выполнение не веб-сервером, а интерпретатором командной строки. Но я не думаю, что этому способу установки вообще есть смысл обращать пристальное внимание. Мы же с Вами не параноики и не мазохисты.
Памятуя о том, что большинство провайдеров использует в качестве веб-сервера Апач, решил установить его виндовую версию на локальной машине. Да и не только в провайдерах дело. Пожалуй, это, если и не самый, то один из самых конфигурируемых серверов. Сказано ? сделано. Осталось прикрутить PHP. В Сети нашлось много руководств, подписанных разными авторами, но их содержание было подозрительно схожим... Чуть ли не с точностью до знаков препинания. Видимо, бОльшая часть этих статей представляет собой оригиналы разной свежести и клоны статей Дмитрия Котерова [] и Александра Бугакова []. Если кого-то пропустил, I beg your pardon.
Итак, в них описывается способ установки PHP как CGI-программы. Этот способ заполонил всю сеть и многие даже не подозревают, что можно сделать иначе. Но мы его назовем "альтернативным". Да-да, именно так! Более того ? я скажу, что это вредный способ. Он ссорит между собой тех, кто работает под виндой, и линуксоидов. Ибо когда первые жалуются: "у меня PHP как-то не так работает", вторые с кривой ухмылкой заявляют "Виндовз ? маст дай! Линукс ? рулез форева! Ставьте линукс ? и таких проблем не будет. Потому что под виндовз НЕВОЗМОЖНО установить PHP как модуль Апача. Хоть в лепешку расшибись." Нет, я не хочу дискутировать о достоинствах или недостатках той или иной операционной системы. Тем более, не в данной статье. Но я сам чуть было не обратился в эту веру.
Теперь впечатления. Запускаем скрипт, который пытается послать заголовки (header) ? неа, не работает :(( Заглядываем в мануал: в чем дело? Ах, какая досада... Оказывается, PHP должен работать как модуль Апача. Жалко... Ведь под винду нам модуль не установить... Так, а как насчет авторизации по коду 403? Тоже не работает... Придется и от этого отказаться...
Придется ли? Скорее всего, нет! Конечно, универсального решения я выдать не смогу, но постараюсь объединить в данной статье все (ну, или почти все) что известно мне о работе Апача + PHP под Win32. Возможно, статья будет исправлена и дополнена. Пишите письма.
Файл srm.ini
####### modules { path = "/usr/local/srm/lib/"; load "auth"; load "http"; load "php4"; }srm { sockname = "/tmp/srm.socket"; port = 7777; log_level = 100; logfile = "/var/log/srm"; }
store { application_items = 2048; session_items = 4096; application_lock_items = 1024; session_lock_items = 1024; session_save_path = "/var/state"; }
http { port = 7780; }
banana { class_path = "/usr/local/srm/banana/lib"; function_library = "/usr/local/srm/banana/lib/library.php"; } #######
Пояснения:
modules? подгружаемый модули SRM. Для работы с PHP нужен специфически собранный PHP (об этом позже). Без него работают модули auth & http.
srm ? надеюсь, что тут комментарии не нужны.
store ? установки количества элементов, хранимых в памяти и в сессиях (у SRM свои собственные сессии, в которые он пишет дамп при shutdown и откуда читает при старте).
banana ? настройки виртуального класса, которыя является родителем для всех классов, которые можно использовать с SRM.
Как поладить дизайнеру с программистом
DL29.5.2002
Продолжение темы дизайнеров, программистов и XSL, который должен бы их связывать, но так пока и не может.
Понятно, что XSL - это небольшой язык программирования, который неопытные программисты глубоко освоить не могут. Если для программиста XSL - это ещё один роман, то для верстальщика - проблема. Но давайте не будем кидаться определениями. Вместе с программистами и дизайнерами могут работать также верстальщики и кодеры.
Разделим технологии на несколько ступенек:
Автор работает в фирме, где есть только программисты и дизайнеры. При этом дизайнеры имеют широкие обязанности - не только делать рыбу в HTML, но и вставляют форматирование в код. Обязанности распределяются следующим образом:
| программист |
Схема дизайнер-верстальщик-программист. Здесь дизайнер не знает HTML, либо знает на слабом уровне. Макеты форматирует верстальщик. Программист занимается скриптами и сервером. Структура, свойственная большим фирмам.
| программист | |
| верстальщик | |
В совсем больших фирмах среди программистов выделяют также кодеров.
| программист | |
| кодер | |
| верстальщик (есть ли?) | |
Разговоры о том, что дескать ни верстальщик, ни дизайнер не знают и не захотят изучать XSL, бессмысленны. Проблемы начинаются из-за неправильного распределения обязанностей.
Пытаемся сделать сайт на XML+XSLT, отводим под это экспериментальный или малозначимый проект, делаем соответствующую организацию этого проекта (плохую то есть). Потом, когда ничего не работает, никто не может сделать то, что нужно, оставляем затею с XSL, переделываем всё старыми испытанными способами. После этого пишем или .
А в начале надо ответить для себя на вопрос: надо ли пробовать делать сайт на XML+XSLT? Если вы, не дай бог, ответили "да", то молитесь! Шутка.
Дальше, поскольку это новая технология для фирмы, надо определить, кто будет заниматься ей каждодневно. После этого идёт анализ, по силам ли такое фирме. Всё.
Проще всего с применением XSL будет большим фирмам, где программисты разделяются на разработчиков и кодеров. Превращение HTML-рыбы и образца XML-документа в работающий XSLT-лист - работа именно по части кодера. На первой схеме я поместил XSL между "приладами" HTML и PHP - и идея поручить XSL кодеру в эту схему красиво вписывается (сперва предположил про кодера, а схему нарисовал потом, честно! :).
В других ситуациях будет сложнее. Например, мне встречалась "фирма", где всё делает один и тот же человек. Иногда внешний вид страницы рисует дизайнер, но режет макет на отдельные картинки не он.
Другая фирма: дизайнер рисует макет внешнего вида страницы, остальное делают несколько программистов. Парни эти - мастера на все руки, причём очень высокого уровня. Над одним проектом работает один человек. Тоже нет смысла применять XML, потому что одному человеку, наверное, проще разобраться в одном программном "слое" - php смешанном с HTML, - чем с двумя или тремя (php + шаблон XML + XSL).
Выводы:
1. XML+XSL применимы там, где есть разделение труда
2. где разделение труда хорошо организовано
3. проект соответствующего масштаба (мелкий можно и в виде HTML смешанного с PHP, для крупного XSL тоже не очень подходит)
Пароль на страницу. Часть 2. Блокировка подбора (отредактированная)
DL15.5.2001
Когда я выложил этот выпуск в прошлый раз, меня запинали на месте, мол такой блокировкой можно и сервер "пустить под откос".
Но сначала о блокировке подбора. Банальности, но всё-таки. Пароль длинной десять символов из букв латиницы и цифр - это очень много вариантов. Если подбирать пароль по 1000 000 вариантов в секунду, понадобится несколько тысяч лет. Но поскольку такую абракадабру запомнить сложно, мы чаще делаем пароль из осмысленных слов. Несколько лет назад оказалось, что большинство паролей можно подобрать при помощи словаря из 10 000 слов. В своё время в сети появился червь (вирус такой), который лазил по юниксовым серверам, используя их дырки в защите, и подбирал пароли привелигированых пользователей при помощи... системного орфографического словаря Юникса. Ничего таскать не надо было!
Каждый пользователь, пока он не ввёл правильный логин и пароль, считается злобным хакером. С чем же мы имеем дело, когда пользователь вводит что-либо неправильно?
1. забывчивость (на это на приличных сайтах есть формочка "забыл пароль", чтобы отправить на введёный в системных настройках email этот самый пароль)
2. баловство ("ибо нефиг")
3. подбор пароля по словарю (вероятность удачного подбора велика, поэтому закрывать надо, тем более, если сайт коммерческого характера)
4. DoS-атака (чтобы не перегрузить сервер, надо минимизировать действия, которые будет выполнять скрипт в таком случае)
Я долго думал, как можно вызвать перегрузку на сервере, если механизм защиты стоит на файлах. Оказалось, несложно (сколько это будет стоить - другой вопрос). Итак, допустим, сервер не выдержит, если скрипт будет пытаться 1000 раз в секунду открывать файлы на запись и писать в них данные. Поскольку после 5 неудачных попыток войти в систему пользователь будет сразу получать отказ в доступе (без какой-либо записи данных в файл), надо найти 200 уникальных IP, с которых по пять раз и обратиться. Это возможно. Вешаем в баннерокрутилке html-баннер с пятью тегами:
Пользователь моментально делает пять обращений сервер пять раз пишет в файл (кстати, в некоторых броузерах, возможно, выскочит окно для ввода логина и пароля). Можно сделать html-страницу с пятью такими картинками, а саму страницу вставить через iframe на посещаемый сайт (через iframe - чтобы по полю referer не нашли. Вряд ли служба поддержки халявного хостинга будет заниматься такими вещами как копание в лог-файлах в поисках рефереров). Те примеры, которые я привёл, разумеется, натянуты, но сам факт того, что можно воспользоваться таким недостатком системы, доказан. Кстати, нечто подобное уже было.
Но всё-таки приведу этот способ - зря писал, что ли? Его, кстати, можно без особого страха применять для ограниченного количества адресов (например, для локальной сети фирмы), положив в директорию файл .htaccess такого содержания:
order deny,allow
deny from all
allow from xxx.xxx.xxx
А вот код программы:
$errors = 0;
$fn = "ignore/". preg_replace("[^\d\.]", "", $REMOTE_ADDR. ".". $HTTP_FORWARDED_FOR);
if (is_file($fn)) {
if (filectime($fn) < time()-3600)
unlink($fn);
else
$errors = fread(fopen($fn, "r"), 2);
};
if ($errors>5) {
print ("Доступ закрыт. Зайдите через час.");
exit();
};
// здесь происходит установка связи с сервером БД. чтобы не трогать зря, если пользователя сразу же "отлупили".
$result = mysql_query("SELECT * FROM user WHERE login='". preg_replace("/[^\w_\-]/", "", $PHP_AUTH_USER). "' AND pass='". md5($PHP_AUTH_PW). "'");
if (@mysql_num_rows($result)!=1) {
header("WWW-Authenticate: Basic realm=\"secret area\"");
header("HTTP/1.0 401 Unauthorized");
print ("Authorization required");
fwrite(fopen($fn, "w"), ++$errors);
exit();
};
$current_user = mysql_fetch_array($result);
mysql_free_result($result);
Впрочем, грех работать с файлами, если есть база. Шутка.
Для непрошедших авторизаций создаём таблицу:
CREATE TABLE unauth (username VARCHAR(64) NOT NULL, pass VARCHAR(64) NOT NULL, ip VARCHAR(255), logintime TIMESTAMP)
И вместо обращения к файлам работаем с базой.
$errors = @mysql_result(mysql_query("SELECT count(username) as falses FROM unauth WHERE logintime>DATE_SUB(NOW(),INTERVAL 1 HOUR) AND ip='$REMOTE_ADDR $HTTP_X_FORWARDED_FOR'"),0);
if (mysql_error())
die(mysql_error());
if ($errors>5) {
print ("Доступ закрыт. Зайдите через час.");
exit();
};
$result = mysql_query("SELECT * FROM user WHERE login='". preg_replace("/[^\w_\-]/", "", $PHP_AUTH_USER). "' AND pass='". md5($PHP_AUTH_PW). "'");
if (@mysql_num_rows($result)!=1) {
header("WWW-Authenticate: Basic realm=\"secret area\"");
header("HTTP/1.0 401 Unauthorized");
print ("Authorization required");
mysql_query("INSERT INTO unauth (username, pass, ip) VALUES ('$PHP_AUTH_USER', '$PHP_AUTH_PW', '$REMOTE_ADDR $HTTP_X_FORWARDED_FOR')");
exit();
};
$current_user = mysql_fetch_array($result);
mysql_free_result($result);
Хранить ли старые записи для статистики или нет - дело хозяйское. Если что, их можно удалять, выполняя перед авторизацией запрос:
DELETE FROM unauth WHERE logintime
Такой механизм при больших нагрузках будет работать быстрее и надёжнее, чем файлы - в базе часто используемые данные буферизуются и обрабатываются непосредственно в оперативной памяти.
На этом позвольте попрощаться.
P.S. Хотел начать писать про механизм сессий, но меня опередил DiMA.
Php в деталях // полная версия статьи:
| © , 2000-2001 © , 1999-2001 | document.write(''); |
Пишем свой форум?
DL30.5.2001
В форумах регулярно появляются сообщения, в которых автор мимоходом роняет: "хочу написать форум/доску объявлений/контент-менеджер/т.п., чтобы научиться программировать на php". Тем, кто хочет делать то же самое, собирается или уже делает, советую: плюньте на эту затею. Не получится!
Вы либо почти ничему новому не научитесь, либо не сделаете свой форум. Либо вы и более-менее изучите язык и сделаете систему, потратив на это прорву времени.
Конечно же, вас не убедит аргумент, что дорожные знаки и ПДД учат до того как садятся за руль (по крайней мере в автошколах). Вы на это скажете, что берётесь за большое дело только для себя. Не, если за это обучение платят ("покопайся, напиши форум, если получится")? я молчу, флаг в руки!
А всем остальным советую не браться за большие вещи ради обучения. Учить язык лучше на мелких задачах (и гонять тестовые программы не на рабочем веб-сервере, а ). При написании больших приложений вас ждут такие препядствия как
Очень много повторяющихся операций. Изучить работу функций и модулей языка очень сложно, потому что вы будете иметь дело с рабочим кодом. А изучать их лучше на мелких задачах ? они гораздо нагляднее, и не давит груз большого проекта.
Разве можно грамотно спланировать проект, не имея представление о том, как он будет работать внутри?!
Обязательно придётся переписывать готовые фрагменты (ой, как неохота! они ведь уже работали!). Если переделывать ничего не надо, значит вы не видите структурных ошибок.
Ладно, пусть вы своевременно прочитали "Разработку Web-приложений" Ратшиллера и Геркена, и чего-то выяснили, на ходу поменяли структуру таблиц БД, выделили что-то в отдельный модуль, какой-то модуль упразднили. Всё равно, в приложении остаются множественные ошибки, неэффективные приёмы. Их отлавливать сложнее.
Ещё один соблазн ? забыть про изучение нового и писать, писать, писать свой проект. Новых знаний нет, поэтому мировоззрение не меняется, никаких дилемм, конфликтов подходов, и вам кажется, что всё сделано правильно.
Сам сделал похожую вещь ? большой проект, "не догоню, так согреюсь". Убил кучу времени, не дописал проекта, бросил. А затем, спустя месяц обнаружил колоссальный рывок в изучении php.
Отсюда и получаются те три "либо", которые я упомянул выше: не научился, зато доделал до конца (потому что новые знания не мешали, и проект делался, как по маслу), научился, но не написал (понял, что надо всё переписывать почти с нуля и бросил), или и научился и написал (потратив кучу времени на переделку готовых частей).
Чтоб жизнь мёдом не казалась, добавлю, что и на мелких программках вы не овладеете языком в совершенстве ? существуют эффект масштаба и взаимодействие с другими программами.
Буквально вчера в [] один человек сказал, что хочет написать гостевую книгу, потому что устанавливать готовую очень сложно. Не факт. Способность прикрутить чужую программу целиком, частично или взять оттуда нужные функции ? ценная вещь в работе программистом. Экономия большого количества времени. Зачем изобретать велосипед, когда можно взять готовый работающий модуль, который распространяется бесплатно? Для того, чтобы сделать возможным пользователям в доске сообщений выделять текст полужирным шрифтом или курсивом, или выделять цитаты, преформатированный текст (
), я не стал писать свой механизм, а скачал форум [] и взял из него готовые функции (в их лицензионном соглашении такое разрешено. Кстати, смотрите в эти соглашения, чтобы не иметь проблем с копирайтами!).
Написано много . Написать такой класс самому ? несложно (правда, в опубликованных, как я писал, бывают грубые ошибки). Многие из нас пользуются собственными классами шаблонов. А что же всё-таки делать с форумом? Поставьте []. Очень удобный форум, поддерживает несколько баз данных, модерирование, отправка ответов на email (а не просто уведомления), и никаких картинок-смайликов!!!Проект open source, поэтому ошибки быстро находятся и исправляются. Скачайте этот форум, посмотрите на объём файлов. Вам всё ещё хочется писать свой?
Работа с БД. Анализ логов.
DL17.5.2001
Хочу снова похвалиться своим "творчеством".
В декабре я описал, как проще всего собирать логи. Сейчас я покажу, как их можно анализировать.
Что там было в таблице? Дата, адрес от корня виртуального хоста ($PHP_SELF), броузер, реферер, ip-адрес пользователя и имя хоста. Строка вставлялась так:
@mysql_query("INSERT INTO logs (date, ip, host, address, referer, browser) VALUES (NOW(), '$REMOTE_ADDR', '". gethostbyaddr($REMOTE_ADDR). "', '$PHP_SELF', '$HTTP_REFERER', '$HTTP_USER_AGENT')");
А что мы хотим видеть в статистике? Просто посмотрим разные варианты: посещения по дням, распределение посещений по времени суток. Затем эти же две выборки, только не для всех логов, а для главной страницы. Распределение по дням недели, посещаемость за последние недели и месяцы. Количество посетителей, пришедших с других сайтов. Бывает интересно посмотреть, сколько посетителей приходило с определённой ссылки в разные дни. Распределение по времени за определённый день (неделю, месяц). Адреса, куда уходят с такой-то страницы.
Систематизировать это не так сложно, как кажется. Всё вышеописаное укладывается в восемь вариантов группировки таблицы. Адрес, реферер, броузер (увы, тут получается сравнение строчек "HTTP_USER_AGENT", включающих в себя и версии, и ОС, а не отдельных броузеров. Тут без обработки на входе не обойтись), день, неделя, месяц, день недели, час.
На статистику по странам, городам, а так же маршруты пользователей не замахиваемся? тоже нужна дополнительная обработка.
Добавим к выбору группировки выбор ограничения по дням (последние n дней) а так же условия выборки для поля WHERE, которые можно ввести в текстовое поле, и получим систему, в которую укладывается те выборки и распределения, которые я описал.
На самом деле, не так просто оказалось составить список выборок и привести его к удобному для обработки в программе виду. В конце концов, я взял и запихал все запросы в массив, элемент которого выглядит так:
$selection[0] = array(
"name" => "график по дням",
"select" => array("date_format(date,'%e.%m.%Y') as dday", "count(date) as visits"),
"group" => array("dday"),
"order" => array("date DESC"),
"type" => 1
);
Элемент "name" ? это название для крутилки, "type" ? тип таблицы. Типов таблиц два ? просто список и список со "столбиками". Если список сортируется по количеству посещений, то столбики в принципе не нужны, а для удобства восприятия, например, графика посещений по дням, график желателен. Остальные элементы можно не комментировать.
В запросе учитываются так же и временные ограничения, и условие, которое ввёл пользователь. Переменная $type ? номер запрашиваемой выборки.
$days = intval($days);
if ($days>0)
$selection[$type]["where"][] = "date>DATE_SUB(NOW(),INTERVAL $days DAY)";
$where = stripslashes(trim($where));
if (strlen($where)>0)
$selection[$type]["where"][] = "($where)";
После этого рисуется форма (в крутилках первыми строчками выводятся выбранные значения. Затем строится запрос, которым узнаётся общее количество строк. Это, надо признать, скользкое место, потому что в нём никаких упрощений, просто в отличие от основного запроса, здесь просто отсутствует сортировка. Но количество строк узнаётся "в лоб" ? выбирается всё то же самое и потом делается mysql_num_rows. Если у кого будут идеи, можете прислать мне или публиковать свой анализатор (только ссылку на меня поставьте, пожалуйста).
$amount_request = "SELECT ". implode(", ", $selection[$type]["select"]). " FROM logs ";
if (sizeof($selection[$type]["where"])>0)
$amount_request .= " WHERE ". implode(" AND ", $selection[$type]["where"]);
$amount_request .= " GROUP BY ". implode(", ", $selection[$type]["group"]);
$request = "SELECT ". implode(", ", $selection[$type]["select"]). " FROM logs ";
if (sizeof($selection[$type]["where"])>0)
$request .= " WHERE ". implode(" AND ", $selection[$type]["where"]);
$request .= " GROUP BY ". implode(", ", $selection[$type]["group"]). " ORDER BY ". implode(", ", $selection[$type]["order"]). " ". get_limit($page, $amount, $in_page);
Как видите, я использую описанный в предыдущем выпуске модуль постраничного вывода запросов (get_limit).
Но мало дать неограниченные возможности построения запросов. Вот, допустим, смотрю я на статистику рефереров по популярности и хочу посмотреть, как народ шёл ко мне с []. Что делать? Выбирать нужные параметры крутилок и писать в текстовом поле "referer like '%hackzone%'" ну просто влом!
Оказывается, это тоже несложно сделать. Два часа мыслительных усилий и редактирования текста, и вот вышла некое подобие возможности детализации выборки. Нажимаю "список рефереров" в закладках (в закладках, потому что в адресе передаётся параметр "referer not like 'http://detail.phpclub.net%'"), получаю таблицу, в ней в строке "hackzone.ru" нажимаю на ссылку и вот оно, распределение зашедших с этого сайта по дням. Можно нажать на другую ссылку и получить распределение по времени суток ? как угодно.
Какие ещё делать выборки ? зависит от вашей фантазии. Если написанного не хватает, можно добавить свои. Обещаю по мере сил обновлять и улучшать программу.
В принципе, имея логи у себя на офисной или домашней машине, можно делать с ними всё, что душе угодно. Надо только написать обработчик. Делов-то! :) На [] я не претендую. По крайней мере, для себя я написал такую вещь, которая позволяет делать многое из того, что угодно душе. И, кстати, не грузить лишнюю информацию и рекламу Спайлог-информера и прочего.
Relax this is PHP
Junkie Doodle Profile27.5.2002
Message # 1010670:
Date: 04/06/02 20:42
By: Junkie Doodle Profile
Subject: RE: What's wrong with XML?
I am working as a coder. And [please understand me] what a pitty i have to work with designers too. And we used to have the same problem in our projects.
1. XSL is not for designers. XSL requires programming knowledge, which is NOT for designers. And I dont want to do it myself or be bugged once in ten minutes to help a designer about it.
2. Using FastTemplate like solutions can be problemful sometimes. And if we have to think about the best way, the very best intelligent way for our projects, sometimes it may be much better to not to use external components, for example if you need to code a critical mission. Then i'm telling you what we are doing...
- Have meetings with designers before the project is started and documentate your projects. Let everyone knows what will be published on each page. Give the names of variables you will insert in HTML [$this_var_is_dummy]. Do NOT write guerilla code. Just code for efficiency of project and do NOT add extra items just because you CAN. Dont forget that nobody is judging your abilities, people just look for their job is done AS they wanted.
- Start coding on your dummy pages with real page names. And use your documentation to see what datas you need to create on each page. Use "br"s instead of tables, seperate your rows with "hr"s... forget about design and think about creating your datas. - DO ALL YOU HAVE TO DO ABOVE HTML and just put your variables inside html like
- Train your designers about loops that we usually use to publish query results. And prepare your code for embedding as little packages.
- Tell designers to put the variable names instead of live data on their designs like
- Make your code running on your own dummy interface
- Leave it to your designers to embed it for you.
- And go out, have fun, call your girl/boy friend
- Relax this is PHP
--
Спасибо Юрию Бохонковичу за присланный текст.
Хранение переменных, глобальных для всех
Демон и экстеншен с API для него к PHP.Разработчик: [] () [].
Язык: C
Предназначение:
Те, кто работал в ASP с объектом Application (я не работал), сразу поймут, зачем нужен SRM. Действительно, для ASP программеров это вещь привычная. Не работавшим с ним постараюсь объяснить. Через пару часов разбирания с SRM у меня появилась четкая ассоциация ? "хранение на Слове" (кто читал С.Лукьяненко "Холодные берега" ? тот поймет сразу).
Действительно четкая аналогия:
Варианты использования SRM:
Принцип работы SRM:
Объекты классов-детей Banana не подбираются сразу (), а только при первом обращении. При этом, как и у обычных объектов, выполняется конструктор и др. Далее доступны методы и свойства этого объекта, однако через var_dump вы их не увидите, как я уже сказал.
Скрипты, в которых объявляются классы удаленных объектов имеют требование к наименованию: так, класс Users должен содержаться в Users.banana.php и быть наследником класса Banana. Так же в конце класса добавляются две строчки (см. пример), необходимые для инициализации удаленного объекта.
Наглядный пример: класс удаленного объекта:
/* Club.banana.php */ /* Класс Club */ Class Club extends Banana { var $members;
function Club () { $this->members="0"; }
function increase () { $this->members++; } }
$club = new Club(); $club->run(); ?>
и клиентский скрипт:
/* club.php */ $srm = new SRM ("/tmp/srm.socket", 7777);
$t = new SRMApp ($srm,"Club"); $t->increase(); echo $t->members."_n";
print_r($t); ?>
Кладем класс в class_path, рестартим srmd и выполняем клиентский скрипт. На выходе получем:
--=-- [цифра] srmapp Object ( [conn_id] => Resource id #2 [handle] => 1 ) --=--
Цифра будет увеличиваться с каждым рефрешем. Естественно, если закомментить строку с increase(), то ее значение останется прежним. Похоже на сессию, основное различие в том, что она одна на всех.
Подобный класс можно прикрутить к форуму, вместо просто присвоения свойства members в конструкторе можно сделать подсчет членов клуба в базе (незачем эту выборку делать каждому посетителю на каждой странице), в случае добавления или удаления ? вызывать соответствующий метод класса ? inrease или decrease (увеличение на 1 и уменьшение соотв-но).
По-моему очень удобно.
Аналогично организуется класс UsersOnSite. Принцип прост ? выставлять человеку уникальную куку, проверять есть ли она у нас, нет ? +1 человек, проверяем таймауты у юзеров, кого-то удаляем и т.п.
Пока не работает, но в ближайшем будущем обещается (по словам Derick Rethans):
Также НЕ поддерживается (и скорее всего не будет): видимость переменных среды PHP ($_SERVER, $_COOKIE etc.) в удаленных объектах по чисто техническим причинам ? демон srmd работает, не зная об апаче ничего.
Не знаю как вас, а меня появление такого extension, несмотря на его сырость, само по себе уже очень радует. Может, и вправду "PHP goes to the enterprise level"?
Если это кого-то кроме меня радует ? могу написать также, как я ставил и настраивал демона и экстеншен к ПХП.
(1) если объектов нет в дампе после последнего шатдауна, которые он при прошлом выходе записал (srmd дампит свой "карман" при выходе и при запуске его подхватывает)
Ручная сортировка в веб-интерфейсе
DL30.5.2002
Для новостного сайта или каталога чаще всего применяется структура базы данных с отдельной таблицей рубрик. Это удобно и просто: автоматически считается количество статей (сайтов) в рубриках, рубрики легко редактируются, создаются или удаляются. Идилию нарушает часто возникающая необходимость сортировки списка рубрик в заданном порядке.
Приходится либо писать этот список в HTML вручную, либо создавать в таблице в базе данных дополнительное поле для сортировки, в которое нужно писать числа опять же вручную. Это делается либо через интерфейс управления базой данных, либо дополнительное текстовое поле делается в форме редактирования рубрики. Это, конечно, неудобно для пользователя, а тем более если пользователей, которым иногда требуется менять порядок сортировки, несколько.
Решения для данной задачи есть. Из того, что я видел, это [] фирмы []. Однако он требует настройки ActiveX или VB Script и работает только в IE 6.0.
На самом деле, можно обойтись меньшими жертвами, используя простой JavaScript, который будет работать и в IE 5.0, и в Опере 5.12.
Итак, что нужно для удобного управления сортировкой рубрик? Впрочем, не только рубрик, сортировать вручную данные из базы приходится часто, просто это не делается из-за отсутствия удобного инструмента. Во-первых, порядок сортировки задаётся в отдельном поле в таблице. Во-вторых, его содержимое должно меняться автоматически, скриптами, а не людьми. Пользователям гораздо удобнее видеть не цифры, а список рубрик и кнопки "вверх" и "вниз". Эта схема широко применяется в самых разных программах:

Ещё один пример потенциального использования. На ныне почившем сайте [] был конкурс "10 слов, которые потрясли мир". Надо было в нормальном предложении, состоящем из 10 слов так переставить их, чтобы получилось смешно. Реализовано это было неважно: пользователю приходилось вводить в текстовое поле номера слов, причём нумерация шла с 0(!), что вызывало большие затруднения.
Рисуем вот такую форму:
|
рот командира отдающего приказ должен быть открыт на ширину приклада | ||
При нажатии кнопки "Отправить" запускается скрипт, который записывает в спрятанную переменную id_set значения из списка через запятую. Именно переменную $id_set разбирает получающий данные скрипт.
В демонстрационном скрипте разбирается и проверяются все значения. Но для закрытого механизма администрирования этого не нужно.
В MySQL есть очень полезная функция FIND_IN_SET. Возвращает номер значения в ряде. Вот пример её использования:
mysql> SELECT FIND_IN_SET('b','a,b,c,d'); -> 2
mysql> SELECT FIND_IN_SET('e','a,b,c,d'); -> NULL
Таким образом можно записать в поле sort_order результат выполнения функции FIND_IN_SET(id,'id1,id2,id3,...'). При этом если в списке не окажется какого-то идентификатора, функция вернёт пустое значение (NULL). При сортировке значения NULL окажутся вверху. Чтобы этого избежать, добавляем функцию IFNULL, которая будет заменять NULL на количество id в строке + 1. Получаем простой скрипт:
if (strlen(trim($id_set))>0) {
$id_set = preg_replace("/,\$/", "", trim($id_set));
mysql_query("UPDATE heading SET sort_order=IFNULL(FIND_IN_SET(id, '{$id_set}'),". (substr_count($id_set, ",") + 2). ")");
форма, которая приведена здесь, работает в IE 5.0 и выше, а так же в Опере 5.12 и более новых. На NN и Mozilla не проверялось.
Script Running Magic или хранение объектов на Слове
Антон Довгаль26.5.2002
В качестве эпиграфа:
?Тяжело тащить?
? Да нет, что ты. На Слово можно что угодно подвесить, разницы не чувствуешь.
(с) С.Лукьяненко, "Холодные берега"
Новый проект: экономическая игра "Монополист"
DL13.6.2001
Друзья! Представляю вам свой новый проект. Экономическая игра [] (не надо плеваться на название, уж какое есть, лучше не придумал :). Разумеется, только с живыми участниками, и, разумеется, на PHP. Задумывалась она ещё полгода-год назад, писалась в общей сложности неделю, и вот запущена в работу.
Что из себя представляет игра []? Игрок имеет фирму на рынке, у фирмы есть производственные мощности. Каждый ход вы указываете, какой объем товаров производить, по какой цене продавать, расходы на рекламу, и информационную безопасность. Программа подсчитывает спрос на товары фирм, продажи, затраты и доходы. Задача игрока - опережать конкурентов по объёмам продаж (цена * количество), а в идеале - победить всех и стать монополистом. Впрочем, конкуренты могут появляться когда угодно - по умолчанию в играх вход открыт в любое время. Фирмы, которые долго сидят в долгах, банкротятся. Общий спрос на товары фирм определяется... правильно, экономической коньюнктурой. Естественно, этот показатель не будет виден никому из игроков, а игроки делают прогнозы, читая новости. Грубо говоря, хорошие новости - спрос вырастет, плохие - упадёт.
И, конечно же, поскольку игра сетевая, для общения участников игр есть форум и приват. Да, кстати! Разумеется, можно переводить друг другу наличные средства, делиться технологиями и объединяться в лиги.
На сервере сейчас []. Каждая игра будет запущена, когда в неё запишутся десять участников.
В Сети подавляющее большинство игр - военные. В принципе, если в игре нет рулетки артефактов или магий, военная игра от экономической не отличается. Война - это управление особым предприятем. Управление предприятием - это особая война.
Однако, в экономической игре в случае кризиса перепроизводства игрокам самим придётся сокращать объемы производства (а если спад затяжной, то и производственные мощности), а так же затраты на научные исследования и маркетинг. Приходится останавливать гонку вооружений. В военной же игре не надо проводить сокращение - соперники сделают это за вас.
Но что я всё о печальном? Чего я нигде ещё не видел, так это четкой градации научных уровней и гибкости цен исследований. Как бывает в военных играх? "Апгрейд такого-то оружия стои столько-то." И так всегда, независимо от твоего или чужого уровня развития. Разве что генератор случайных чисел прикрутят и цены будут время от времени колебаться. Этому есть рациональное объяснение: никто не делится секретами с врагами. Но почему тех же правил придерживаются и авторы экономических игр? Ведь если какой-то предприниматель берётся производить сумки-тележки, он не изобретает колесо заново. А если одна автомобильная фирма представляет автомобиль с новой системой, конкуренты быстренько покупают его, изучают устройство этой системы и патенты и думают, как им сделать подобное. Ноу-хау производства тоже не вечны - ушел специалист к конкурентам, и они получили вашу технологию. Одним словом, идти в разработках впереди всех дорого. Дешевле догонять. Исходя из этого в моей игре и подсчитываются расходы на исследования.
Научные исследования сделаны просто: есть один единственный показатель - "технический уровень". Для каждой фирмы стоимость достижения n-го уровня своя. Фирма тратит деньги на исследования, и её уровень постепенно повышается (если денег на достижение очередного уровня не хватает, они всё равно зачтутся - есть отдельный показатель "прогресс исследований"). В зависимости от технического уровня определяется спрос на товары и себестоимость.
И ещё одна вещь, которую я нигде не видел - это системный шпионаж (может, мало играл?). Если подумать, то всё, что есть у соперника, нам интересно. И его технологии, и экономические показатели, и вообще всё, что он сам знает. В общем, чем больше платит фирма на шпионаж, и чем меньше платят конкуренты, тем больше ей известно о них. При определенном соотношении затрат можно и украсть технологию (бывает дешевле, чем исследовать самому). Военные ведомства большинства стран называются министерствами обороны, поэтому в игре эта графа расходов названа "информационная безопасность".
[]
Пароль на страницу. Часть 3. Пароль от базы
DL24.6.2001
Была у меня в своё время проблема: надо закрыть администрационную часть сайта, но при этом я не могу положить файл .htpasswd выше корневой директории сайта. Врождённая подозрительность не позволяла положить файл с паролем и отдельную директорию и заблокировать доступ к ней по http. Решил попробовать сделать защиту как в phpMyAdmin: у пользователя спрашиваются логин и пароль, с которыми скрипт соединяется с базой. В своём я сделал именно так. Удобство метода в том, что файл можно складывать куда угодно? никаких кук, никаких директив сервера для директории. Заодно, если поменяется пароль в базе данных, не надо ничего исправлять в скрипте.
Распишу метод на примере MySQL.
Пишем функцию, например, mysql_die:
function mysql_die() {
header("HTTP/1.0 401 Unauthorized");
header("WWW-authenticate: basic realm=\"Statistics\"");
print ("Access denied. User name and password required.");
exit();
}
В начале программы указываются хост сервера БД и, если надо, имя базы:
$db_host = "localhost";
$db_name = "somedatabase";
А для соединения с базой берутся переменные сервера: $PHP_AUTH_USER и $PHP_AUTH_PW.
$db_connect = @mysql_connect($db_host, $PHP_AUH_USER, $PHP_AUTH_PW)
or mysql_die();
И всё. Теперь о недостатках. Разумеется, с такой защитой можно пробовать подбирать пароль (в принципе, можно приделать , но тогда потерятеся вся красота метода). Пароль, как и в случае защитой средствами сервера, пересылается в открытом виде. Но для простых задач такое вполне сгодится.
На днях в форуме разгорелась дискуссия, что лучше где применять ? защиту через 401-й код, свои куки или сессии. Я не стал ввязываться, чтобы не растерять красноречия, но в ближайших выпусках опишу в общих чертах последние два метода (а пока рекомендую читать ) [].
Пароль на страницу. Часть 4. Печенюшки
DL26.6.2001
Способ этот применим там, где, во-первых, пользователей много, и их контингент постоянно меняется. Во-вторых, где нужно сделать удобный вход - чтобы можно было зайти в систему, введя логин и пароль в форме на странице.
Рисуем форму и делаем файл, который получает логин и пароль (защиту от подборки я уде описывал, допишите её сюда сами).
// обработка строки с логином
$login = str_repalce("'", "", $login);
$login_result = mysql_query("SELECT id FROM user WHERE login='$login' AND pass='". md5($pass). "'");
if (!mysql_error() && @mysql_num_rows($login_result)==1) {
/* выдача кук. Имена кук и путь лучше во избежание путаницы определять в едином подключаемом файле. */
setcookie($COOKIE_LOGIN_NAME, $login, time()+3600, $COOKIE_PATH);
setcookie($COOKIE_PASSW_NAME, $pass, time()+3600, $COOKIE_PATH);
/* Сразу же после входа пользователя перенаправляют на закрытый паролем адрес. */
header("Location: /somepath/");
exit;
}
elseif (!mysql_error()) {
/* вывод сообщения об ошибке и формы для повторного ввода */
print ("Неправильный логин или пароль.");
}
else
print (mysql_error());
Все закрытые страницы вызывают файл, в котором проверяется правильность пароля, полученного из куки:
$login = str_repalce("'", "", $HTTP_COOKIE_VARS[$COOKIE_LOGIN_NAME]);
$login_result = mysql_query("SELECT id FROM user WHERE login='$login' AND pass='". md5($HTTP_COOKIE_VARS[$COOKIE_PASSW_NAME]). "'");
if (!mysql_error() && @mysql_num_rows($login_result)!=1) {
/* Если такой строки в таблице нет, пользователь перенаправляется на страицу входа в систему. */
header("Location: /login.php");
exit;
}
else
print (mysql_error());
Имена кук будут использоваться в нескольких местах, поэтому лучше заранее поместить их в одном месте (например, объявив константы), чтобы потом не исправлять по нескольку раз.
Как видите, пароль будет бегать по каналу и лежать в файле с куками в открытом незакодированном виде. Это очень небезопасно. В отсутствие хозяина можно подойти к компьютеру, заглянуть в файл, где броузер держит куки, и записать пароль на бумажку (а если в локальной сети всё общее, то и подходить не надо, и стащить пароль можно прямо при хозяине).
Чтобы этого не произошло, пароль нужно кодировать. Как приемлемый вариант, хэш md5. Тут уже нельзя увидеть пароль и зайти в систему, записав его на бумажку или copy-paste-нув. Кстати, именно так можно залазить под паролем и без ведома друга в web-интерфейсы, строящие авторизацию на сессиях. Поэтому последнее, что можно сделать в этом направлении - это менять куку при каждой загрузке страницы.
Сам когда-то делал такую схему: в таблице пользователей есть колонка с датой последнего обращения. Эту дату последнего обращения и пароль, закодированные через md5, пользователь получает при каждом обращении. Система берёт куку с логином, вытаскивает из базы эту строку, генерирует хэш от полей last_log и passwd и сравнивает его с полученным. Если они совпадают, значит посетителя можно впускать. Для пущей безопасности можно добавить проверку на истечение куки - кука должна истечь после получаса неактивности, и, соответсвенно, в базе дата последнего лога должна быть менее чем полчаса назад.
$login = str_repalce("'", "", $HTTP_COOKIE_VARS[$COOKIE_LOGIN_NAME]);
$login_result = mysql_query("SELECT * FROM user WHERE login='$login' AND last_log>DATE_SUB(NOW(), INTERVAL 30 MINUTE)");
if (!mysql_error() && @mysql_num_rows($login_result)==1) {
/* Получаем строку таблицы и формируем хэш от нужных полей. */
$current_user = mysql_fetch_array($login_result);
$hash_to_check = md5($current_user["passwd"]. " Ы - чтоб никто не догадался ". $current_user[log_time]);
if ($hash_to_check == $HTTP_COOKIE_VARS[$COOKIE_HASH_NAME]) {
$current_time = time();
/* Обновление поля последнего входа и выдача новой куки. */
mysql_query("UPDATE user SET last_log='". date("Y-m-d H:i:s", $current_time). "' WHERE login='$login'");
setcookie($COOKIE_HASH_NAME, md5(date("Y-m-d H:i:s", $current_time). " Ы - чтоб никто не догадался ". $current_user["passwd"]), $current_time + 1800, $COOKIE_PATH);
}
else {
/* В случае несовпадения хэша пользователь перенаправляется на страицу входа в систему. */
header ("Location: /login.php");
exit;
};
}
elseif (!mysql_error() && @mysql_num_rows($log_result)!=1) {
header("Location: /login.php");
exit;
}
else
print (mysql_error());
Разумеется, " Ы - чтоб никто не догадался " лучше тоже выделить в отдельную переменную, а лучше использовать вместо этой строки ip-адрес посетителя (или, для обрывающегося диалапа, первые два/три числа ip-адреса).
Кстати, насчёт IP-адреса. Его лучше проверять, но не весь адрес, а только первые два (для ip, начинающихся на число меньше 127) или три (соответственно, больше 127) числа адреса. Это спасёт пользователей плохого и обрывающегося диалапа от необходимости заново авторизовыватсья после обрыва связи, и в то же время, не даст зайти взломщику, укравшему куку. Конечно же, он не сможет перезвонить и зайти через другого провайдера - адрес пула не тот, но это не наши проблемы ("в такую погоду свои дома сидят"). Как не наша проблема и воровство паролей внутри фирмы. Мы защитили от любопытных товарищей и неграмотных взломщиков, а против троянов и снифферов, которые можно поставить жертве, ничего сделать не можем.
На этом "навороты" закончились. Надёжнее защиту уже не сделать. Никто не будет лазить в файл кук за хэшем и подбирать его. Проще будет поместить между пользователем и веб-интерфейсом сниффер и при помощи него найти пароль. Можно поместить трояна, который будет запоминать всё, что пользователь ввёл на клавиатуре, но это уже не наши проблемы. Чтобы защититься от прослушивания канала, надо использовать соединения типа SSL или шифрование данных.
Работа с MySQL. Часть 7. Деревья
DL3.6.2002
Работа с MySQL. Часть 7. Деревья
Необходимость вывода данных структурированных в форме деревьев возникает при написании собственного форума или каталога сайтов. Готовых каталогов и форумов в сети можно найти предостаточно, однако иногда чужое в готовом не годится, а переделывать написанное другим займёт гораздо больше времени, чем написать своё.
Структуру данных лучше взять общепринятую - в записи сообщения или рубрики форума содержится идентификатор родителя. Для организации вывода дерева напрашивается рекурсивная функция. Именно так сделано в []. Файл include/multi-threads.php содержит функцию thread, которая строит вызывается для каждого корневого сообщения и рекурсивно вызывает себя для ответов на них:
function thread ($seed = 0) {
...
if(@is_array($messages[$seed]["replies"])) {
$count = count($messages[$seed]["replies"]);
for($x = 1;$ x
$key = key($messages[$seed]["replies"]);
thread ($key);
next ($messages[$seed]["replies"]);
}
}
}
Но вызов рекурсивной функции при выводе вызывает у меня сомнения: повторять построение дерева сообщений при каждом выводе нерационально. Структура дерева меняется только при добавлении, изменении и удалении сообщений. Данную процедуру лучше было бы вызывать при таких действиях, хранить структуру в таблице и при выводе дерева не делать никаких вычислений.
Для построения дерева достаточно знать последовательность вывода рубрик и их уровень в дереве. Добавим два поля с этими данными в таблицу: level (TINYINT(4). 127 уровней - хватит?) и sortorder (VARCHAR(128)).
Всё, что нам нужно для построения дерева - это идентификатор рубрики и её родителя. Допустим, мы имеем в каталоге несколько рубрик такого содержания:
--------- id parent --------- 3 0 5 0 7 0 10 3 11 7 12 5 13 3 16 10 21 16 26 11 30 3 47 7 60 10 73 13 75 47 ---------
Структура дерева, подобие которой мы хотим получить такова:
o- 3 | +-o- 10 | | | +-o- 16 | | | | | +-o- 21 | | | +-o- 60 | +-o- 13 | | | +-o- 73 | +-o- 30
o- 5 | +-o- 12
o- 7 | +-o- 11 | | | +-o- 26 | +-o- 47 | +-o- 75
Правда, данный алгоритм позволит нарисовать дерево, но без веток виде линий, как сделано на этом рисунке. Структура дерева будет нарисована при помощи отступов слева.
Вернёмся ещё раз к таблице id-parent. Это рубрики, уже отсортированные по некоторому признаку, по которому мы хотим сортировать элементы одинакового уровня. Например, по убыванию числа сайтов. Кроме id и родительской рубрики мы знаем и номер каждой из них в данном списке. Выровняем эти номера до нужной длины, добавив слева нули. После этого для каждой рубрики сделаем текстовую строку с номерами всех её родителей от самого корня:
------------------------------ id sort parent sortorder level ------------------------------ 3 1 0 01 0 5 2 0 02 0 7 3 0 03 0 10 4 3 0104 1 11 5 7 0305 1 12 6 5 0206 1 13 7 3 0107 1 16 8 10 010408 2 21 9 16 01040809 3 26 10 11 030510 2 30 11 3 0111 1 47 12 7 0312 1 60 13 10 010413 2 73 14 13 010714 2 75 15 47 031215 2 ------------------------------
При сортировке по полю sortorder мы получим именно то, что нам нужно:
------------------------------ id sort parent sortorder level ------------------------------ 3 1 0 01 0 10 4 3 0104 1 16 8 10 010408 2 21 9 16 01040809 3 60 13 10 010413 2 13 7 3 0107 1 73 14 13 010714 2 30 11 3 0111 1 5 2 0 02 0 12 6 5 0206 1 7 3 0 03 0 11 5 7 0305 1 26 10 11 030510 2 47 12 7 0312 1 75 15 47 031215 2 ------------------------------
Отступ слева делается, учитывая поле level.
Важно так же отметить, что нам не нужно ничего сортировать в самом скрипте. Для формирования полей sortorder и level нужно заблокировать таблицу от записи (чтобы не произошло изменения структуры веток), выбрать из базы идентификатор рубрики и её родителя, отсортировав по нужному признаку, и записать их в простой двухмерный массив. Затем обработать массив последовательно от первого до последнего уровня и записать поля sortorder и level в таблицу.
Для формирования sortorder не нужно рекурсии (хотя можно, и, вероятно, она работать будет даже быстрее). Достаточно пройтись по массиву одним и тем же циклом. В нём, если рубрика не обработана, для неё формируется поле sortorder из поля sort и родительского sortorder. Если родительская рубрика ещё не обработана, поднимается флаг $unprocessed_rows_exist и цикл запускается ещё раз.
mysql_query("LOCK TABLES dir WRITE");
$result = mysql_query("SELECT id, IFNULL(parent,0) as parent FROM dir ORDER BY sites DESC, title");
while ($row = mysql_fetch_array($result)) {
$count++;
$data["parent"][$row["id"]] = $row["parent"];
$data["sort"][$row["id"]] = $count;
}
reset($data);
$unprocessed_rows_exist = true;
while($unprocessed_rows_exist) {
$unprocessed_rows_exist = false;
while (list($i, $v) = each($data["parent"])) {
if(($data["parent"][$i] == 0 !isset($data["sort"][$data["parent"][$i]])) && !isset($data["sortorder"][$i])) {
$data["sortorder"][$i] = str_pad($data["sort"][$i], $max, "0", STR_PAD_LEFT);
$data["level"][$i] = 0;
}
elseif(!isset($data["sortorder"][$i]) && isset($data["sortorder"][$data["parent"][$i]])) {
$data["sortorder"][$i] = $data["sortorder"][$data["parent"][$i]]. str_pad($data["sort"][$i], $max, "0", STR_PAD_LEFT);
$data["level"][$i] = $data["level"][$data["parent"][$i]] + 1;
}
elseif(!isset($data["sortorder"][$i]) && isset($data["sort"][$data["parent"][$i]])) {
$unprocessed_rows_exist = true;
}
}
reset($data);
Отмечу, что данный алгоритм не зацикливается при наличии строк с битым полем parent и не пропускает их, а делает корневыми. Рекурсивный алгоритм их просто пропустит.
После выполнения этого цикла мы имеем массивы "id => level" и "id => sortorder". Отправляем в базу всего один запрос, пользуясь внутренними функциями MySQL ELT и FIND_IN_SET:
mysql_query("UPDATE dir SET sortorder=ELT(FIND_IN_SET(id,'". implode(",", array_keys($data["sortorder"])). "'),". implode(",", $data["sortorder"]). "), level=ELT(FIND_IN_SET(id,'". implode(",", array_keys($data["level"])). "'),". implode(",", $data["level"]). ") WHERE id IN (". implode(",", array_keys($data["sortorder"])). ")");
Конечно же, в отличие от дерева рубрик каталога, в большом форуме много сообщений. Передёргивать их всех при добавлении одного нового нет смысла.
В форумах чаще всего используется сортировка по дате написания сообщения. Поэтому поле sortorder можно смело делать из своего и родительского timestamp'а, выровненного функцией [] до 11-значной длины.
Валим свой сервер и флудим поисковики
DL14.6.2001
Баннер "Поговори со мной о PHP", который красовался три дня на всех страницах сайта, как вы поняли, был шуткой. Началось это с того, что в форуме появилась жалоба, что от рекламы книг разъезжаются таблицы, и предложение повесить баннер с голой бабой и надписью "поговори со мной о php". А как раз в эти дни постоянные участики IRC-канала #phpclub открыли [].
Я решил поизголяться (не, на баннере был не я!) и похвастаться баннером на новостной ленте []. Признаться, такого количества интересующихся рекламой я не ожидал. Две недели назад я подправил код страницы с выпуском, чтобы там выводились только проверенные отзывы. И правильно сделал? находятся хулиганы, которые почти на каждый выпуск постят флуд или абракадабру из букв. Если вы всё-таки хотите увидеть все отзывы на выпуск, включая ещё не модерированные, допишите в адресной строке "comment/" ? они все там. Привести в нерабочее состояние сервер Apache ? нет ничего проще! Разумеется, испытания рекомендуется проводить на домашнем или офисном компьютере, который не жалко "повесить". Методика простая: кладёте в какую-нибудь директорию файл .htaccess следующего содержания:
Options All
RewriteEngine On
RewriteRule [a-z_.]+/?$ /путь к директории/somefile.php
И набираете в адресной строке
http://хост/путь к директории/bla_bla_bla
Сервер ничего не выдаёт броузеру, потому что... запрос "/путь к директории/bla_bla_bla" он переписал, получил "/путь к директори/somefile.php", снова глянул в .htaccess и снова переписал запрос на "/путь к директори/somefile.php". И так до бесконечности. При этом сервер быстренько забивает память компьютера.
Веб-сервер хостера вам "завалить" вряд ли удастся, но попортить _себе_ жизнь легко. Первый раз столкнувшись с такой проблемой, я бился, наверное, час. Поэтому если при отладке директив Rewrite* сервер перестаёт выдавать документы, проверьте, доходят ли запросы до "адресатов". [] Вот читаю и думаю, чего орёт человек... У меня ВСЁ лежит в базе данных, движок ? даже не от «php-nuke» до «parser», а самопальный!
Правда, в большинстве случаев, автору сайта это скорее на руку, т.к. он получает лишнюю возможность дать проиндексировать свой ресурс именно так, как он хочет и никак иначе.
Ну, это вообще можно записывать в коллекцию перлов! Кто угодно в сети может заставить поисковик проиндексировать себя как хочется, знать бы только ip-адреса роботов.
Между тем, Яндекс уже две недели индексирует не совсем то, что вы видите на моём сайте. На страницах с выпусками мой самопальный движок выдаёт ему только текст выпуска, никаких отзывов, никакой навигации (она есть на других страницах, в частности, в архиве). Судя по статистике, мои материалы стали больше находить по правильным запросам, а не по "как открыть в php новое окно". Вообще, непонятно, зачем поисковый движок Яндекса откровенно "светится", имея читаемый адрес (slovo.yandex.ru) и "Yandex" в поле USER_AGENT. Чтобы вебмастеру было виднее, что заботливый Яндекс индексирует его раз в две недели?
Лично мне от этого теплее не становится, зато становится теплее тем, кто хочет "зафлудить" поисковую машину (вспомним сайт Zhopa.ru, который в поисковиках на самые популярные запросы появлялся в первой строчке, пока его админы не забанили _ручками_). Сейчас Яндекс заявляет, что сделал добавление к поисковому механизму, которое не позволяет "флудильным" страницам вылезать в первых строках (сам проверил по запросу "реферат", вроде работает), но я могу флудить с умом. Например, смотреть, что Яндекс крутит в рекламе ("все вопросы кЫ..."), и держать табличку в базе данных с этими вопросами... Так или иначе, пока нет специального удобоваримого для поисковиков формата, им пристало маскироваться под обычного посетителя.
XML: свет в конце туннеля
DL19.6.2001
В январе я написал на тему классов шаблонов. В ответ мне настоятельно порекомендовали изучить XML и XSL, и не прошло и полгода, как я попробовал таки работать с XML.
Первое, что предлагают желающим? это функции парсинга XML. Я спросил работающий пример. Код своего xml-парсера мне прислал Михаил Владимиров (автор сайта "How IT works"). Принцип работы этих функций такой: вы пишете функцию, например, для тегов, открывающих контейнер ? ? функция start_elem, которая определяет, на что заменить данный тег. Сообщаете php, что именно её надо использовать для обработки XML, а php, скомпилировав код, будет вызывать её сам. Сама функция состоит из одной большой конструкции "switch $tag { ... case ... }". Мне такое не очень понравилось (наверное, я бы проще ориентироватлся в своём шаблоне). Я спросил, нет ли чего более удобного. "Используй Sablotron, там всё цивильно." ? ответил мне Михаил.
Sablotron ? это XML-процессор, который из XML-документа и таблицы стилей XSLT (это не как CSS, а набор специальных тегов) делает документ любой разметки, какой пожелаете (можно даже текстовый файл сделать). Просто берёте и пишете XML-документ, используя удобную вам разметку, а затем для этой структуры пишете XSLT. XML можно формировать скриптом динамически, а можно опять же через шаблоны. Думаю, что если я в скором времени переделаю этот сайт под Sablotron, от шаблонов по-прежнему не откажусь. Лень мне два раза в разных файлах написать один и тот же SQL-запрос. Пользовался когда-то FastTemplate, но слишком глупое это занятие объявлять в разных скриптах одни и те же блоки, поэтому я написал свой собственный класс, который считывает запросы из самих же шаблонов. А типовые схемы XSLT можно сохранить в файлы. В скрипте вызывается обработчик XML+XSLT и результат его работы выдаётся пользователю. Никаких проблем с совместимостью броузеров, XSLT, как и PHP хранится только на сервере (читал где-то вариант работы с XML, где он разбирается на компьютере у пользователя при помощи яваскрипта ? но это же маразм!).
Содержимое отделено от его представления. Давняя мечта программиста. В принципе, ничего нового по сравнению с классами шаблонов нет ? те же места вставки переменных или дочерних шаблонов, те же блоки строк, однако это прорыв. Прорыв по следующм причинам:
1. Обработчик шаблонов ? бинарная программа, она не компилируется при каждом запуске скрипта, а постоянно сидит в памяти, причём в одном экземпляре (если php установлен как модуль).
2. Sablotron ? программа с открытым кодом, и поэтому в ней (по идее) должно быть меньше багов на килобайт кода, чем в самопальном классе.
3. Стандарт XSLT един для всех людей на планете.
4. Консоль Sablotron-а может внятно сообщить о возникшей ошибке, значит отлаживать XSLT гораздо проще.
Недостатки XML+XSLT есть, и для маленьких проектов они могут даже перевесить достоинства.
1. Ещё один язык разметки, который надо изучить (на самом деле, не так уж сложно)
2. Ещё один модуль в архитектуре сервера. Больше времени на настройку, больше багов.
3. Фривар ? это никакой гарантии работоспособности.
4. Да, кстати! Отлаживать схемы XSLT можно только на саблотроновской консоли ? сервер будет просто говорить "Fatal error in line #n".
Из очень полезного в данной схеме работы ? значительно проще реализовать разные варианты представления данных, проще говоря скины. Если на сайте есть неколько типов страниц (как на этом), приходится разделять шаблоны на уровни. Один ? для всего сайта, содержит таблицу стилей, шапку страницы, служебную информацию. А вот в колонке где сейчас вы видите текст, может быть информация совершенно другой структуры. Под каждую структуру свой файл с шаблоном. В каждом скрипте (разумеется, их количество близко к количеству этих самых структур данных) выбирается нужный шаблон второго уровня. На этом сайте файлов шаблонов всего 14. Плюс к тому часть шаблонов лежит в базе данных. С файлами можно изловчитсья и разделить их, скажем, на директории. А как быть с заготовками в базе? Вводить дополнительные поля? Или держать в одном и разделять спецсимволами? В общем, чтобы сделать несколько видов страницы, придётся сильно попотеть.
Вспоминаю, как в конце 99-го года я думал, как сделать на своём сайте выбор цветовой схемы ? как в WebClub.ru ? и прикидывал, как лучше объявлять переменные для разных цветов и как найти и заменить все цвета в странице. Разумеется, выбор цвета я тогда не сделал, не сделал и сейчас, хотя и подумывал.
В этом плане XSLT значительно упрощает жизнь. Если типовые страницы не сильно отличаются друг от друга (например, как на "php в деталях"), можно ограничиться одним XSLT-файлом для всего скина. Тогда будет гораздо проще реализовать выбор скина для броузера или цветовой схемы.
Дизайнеры могут смело редактировать XSLT, не боясь испортить вам ? программистам ? php-код. Так же отныне такую вещь как подсветка строк то белым то серым цветом можно спихнуть на XSLT ? в описании есть пример. Если номер строки нужен только ради самого себя, нумерацию строк тоже можно отдать обработчику. Операции "если есть такая-то значение, вывести её с таким обрамлением" забываем ? на это есть кострукция "
Я сознательно не стал давать рекомендации, где применять XML и XSLT. Так же я не стал давать в тексте примеров кода. Я лишь постарался описать видимые удобства и недостатки этой схемы обработки данных, потому что более детально рассказывать не позволяет совесть ? слишком мало знаю. Результаты моих опытов и ссылки на документацию прилагаются.
Не прощаемся
DL3.7.2002
"Юбилейный" 60-й выпуск.
Ничто на свете не вечно, творческий порыв тоже когда-нибудь заканчивается. Проект "php в деталях", как я считаю, на сегодня исчерпал себя.
Если говорить о причинах, то главная? моя собственная оценка полезности сайта. Несмотря на название сайта ? "в деталях" ? и моё сильное желание писать подробно и раскрывать тему глубоко, статьи всегда получались довольно поверхностными. Нет такой темы, которую я освещал, чтобы можно было сказать "Я знаю её хорошо." Знания поверхностны, и на вопросы в комментариях мне отвечать затруднительно. За полтора года работы сайта появилось множество сайтов, где пишут о программировании на php. Немного среди них хороших, но таких, которые можно читать вместо моего ? достаточно.
Теперь можно свободно вздохнуть и расслабиться. Учить тому, чем профессионально не занимаешься ? очень сложно. :) Да, я забыл написать, что я не программист. Не заканчивал спецшкол, не участвовал в олимпиадах по программированию, не учусь на такой специальности в универе, не работаю программистом. В данный момент я учусь (точнее, сдав сессию, прохлаждаюсь) на экономическом факультете НГУ.
А как всё красиво начиналось! В начале 99-го я скачал мануал и стал писать скрипты для своего сайта и отлаживать их на веб-сервере по халявному диалапу. В августе того же года попробовал искать книги по PHP (напомню, система появилась в 96-м году). Это, по сути, баловство проолжалось до начала 2000-го, когда я поставил сервер apache дома и несколько поменял сайт, в т.ч. наконец-то перевёл данные в MySQL. Дело пошло живее. Осенью я решил, что имею некоторый опыт, которым могу делиться с другими. Сферы php-программирования, которые я более-менее освоил, были неважно описаны в сети. Например, инструкция как создать свой каталог сайтов на базе данных была очень простой ? большие куски кода с описаниями вроде "надо создать базу, вот скрипт, который это делает", "вот скрипт, который выводит записи из каталога". Захотелось это описать человеческим языком (возможно, тогда таких описаний было уже некоторое количество, но найти их было сложно).
Самое приятное впечатление и самое большое событие связанное с сайтом ? это поездка на празднование двухлетия PHP-Клуба 18 мая 2001 года в Москву, которую спонсировали Александр Смирнов и Антон Калмыков. Ребята, это был самый большой подарок мне, ещё раз огромное спасибо!
Я очень благодарен читателям за отзывы на сайте и по электронной почте. На сайте их набралось 600 штук, почтой, насколько помню, около 400.
Чем займусь дальше? Во-первых, сделаю, наконец, свою домашнюю страничку. С июля 98-го в Сети, а так её и не сделал ? непорядок! Но это не будет авторским проектом ? чур меня! Продолжит свою работу игра [], доведу до человеческой кондиции игру "Пьяница". Ещё несколько задумок в стадии разработки, которые рано открывать.
Поимённые благодарности:
[]
[]
[]
[]
[]
[]
Филипп Воробьёв (Voodoo)
Haba-Haba
Евгений Климов (Slach)
[]
Антон Довгаль (tony2001)
[]
[]
[]
| [] |
[]
[]
Наиль Кашпаров (Nail)
Алексей Тарков (grizly)
Константин Шевченко (Сat)
Идейная поддержка и ссылки:
[]
[]
[]
[]
Кого забыл, звиняйте.
Дата и время
Функций много, отмечу только некоторые самые важные: неправда, что MySQL считает дни недели только с воскресенья, как принято в Америке. Нужно использовать не функцию DAYOFWEEK, а WEEKDAY, тогда понедельнику будет соответствовать номер 0, вторнику? 1, воскресенью ? 6.Для сложного форматирования даты (например, для вывода даты в виде 18.08.01), есть функции DATE_FORMAT (для даты и времени) и TIME_FORMAT (только для времени). Работа с этими функциями удобнее, чем использование своих собственных (потому что это средство стандартное и универсальное, чего в самопальном приспособлении добиться очень сложно), а так же быстрее (используются встроенные функции mysql-сервера, которые уже сидят в памяти, вместо компиляции при каждом запуске скорипта собственного кода).
Юниксовский timestamp MySQL тоже поддерживает ? переводы в него и из него через функции UNIX_TIMESTAMP и FROM_UNIXTIME:
UNIX_TIMESTAMP([дата-время]) ? выдаёт дату в юниксовом формате (если аргумент пропущен ? текущую дату).
FROM_UNIXTIME(дата [, формат]) ? выдаёт дату в обычном формате (во втором аргументе может быть указан формат по правилам как в DATE_FORMAT).
Кроме того, основные параметры даты ? число, день недели и месяц (возможно и словом), год, квартал (!), неделя и многое другое доступно не только через общую функцию DATE_FORMAT, но и через отдельные специальные функции.
Фильтруй базар?
DL10.8.2001
- Застрахерите мою жизнь.
- Не "застрахерите", а "застрахуйте"...
- Не вижу в этом принципиальной разницы.
Анекдот
Разгорелась дискуссия в форуме? "как отслеживать маты". Вопрос этот обсуждался уже не раз, и сейчас снова пошёл по классическому сценарию:
? Как отслеживать маты?
? Регулярным выражением можно. А лучше брось эту затею.
? Дайте регулярное выражение.
? А если он введёт "с.л.о.в.о", что тогда делать будешь? Ещё сложнее. Плюнь на это дело.
? Дайте регулярное выражение.
? А если латиницей? Брось.
? Дайте регулярное выражение.
? Брось эту затею.
? Дайте регулярное выражение.
? Брось эту затею.
(и так далее)
Вопрос, конечно же, не праздный, и многие умы человечества над ним бьются, но какого-нибудь удачного решения я не видел. Грустно читать форум по Формуле-1 с именами "Михаэль Шума***" или "Джонни ***берт". Интересно, а как этот форум будет реагировать на слово "застрахуйте"?
Появилась как-то в ураинском интернете страница "проверка слова на маты". Предлагалось ввести слово или фразу и посмотреть, что скажет программа, а если программа неправильно определяла результат, написать авторам. И таким образом авторы хотели силами веба протестировать программу, найти-таки универсальный алгоритм распознавания русских матов. Больше об этом тестере никто ничего не слышал... Сам проверял ? Шумахера и Херберта считает матерками, почта к авторам не идёт ? не может соединиться с их сервером.
Читал документацию по White Tiger WWW Board ? там предусматривалась защита от матов включая ввод латинскими буквами и, например, двух слэшей вместо буквы "л": "/\". Не думаю, что это сильно поможет. Через такой фильтр спокойно пройдёт слово через точки.
Стоит ли говорить про то, что замена слов на звёздочки подтолкнёт посетителей на разные изыскания с целью обойти защиту. Поведение целиком зависит от манер участников и атмосферы форума. Стоит сравнить хотя бы форум о Формуле-1 сайта телекомпании [] и сайта f1news.ru ? форум f1news.ru, по-моему, самый уютный форум данной тематики, а в ПТП ? проходной двор и грызня по мелочам. Так что программы-резалки облико морале не поднимут это точно.
И, всё-таки, попробуем написать и поймать им хоть что-то. Первое, что приходит в голову:
"сука|блядь|мудак"
Вспоминаем про латиницу. Та-ак, лучше сразу применить другой подход: писать слова в массив, а из него формировать строку регулярного выражения. Делаем так же массив одинаковых символов кириллицы и латиницы и заменяем через preg_replace:
$letter_cyr[] = "/а/"; $letter_lat[] = "[аa]";
$letter_cyr[] = "/л/"; $letter_lat[] = "[". preg_quote("л/\"). "]";
$letter_cyr[] = "/к/"; $letter_lat[] = "([kк]|\|
Последний вариант ? буква к в виде "|
"[cс][yу]([kк]|\|
Почти все символы имеют латинский аналог. По-моему, этого достаточно, чтобы отказаться от такой затеи. Нет? Пойдём дальше. Перед тем, как заменять русские символы на комбинации, предусмотим возможность ввода слова через точки, тире, подчёркивания или пробелы:
$word = preg_replace("/./", "\\0[^\w]*", $word);
Слова, написанные с ошибками ? "блять" ? просто внесём в словарь вместе с правильными (кстати, у немецких шифровальщиков во Вторую Мировую Войну проблема была ? русские шифровки раскодировались в несколько раз дольше, чем шифровки англичан и французов, потому что наши шифровальщики часто совершали ошибки). Правда, есть ещё возможность написать мат заглавными буквами и разделить его строчными: "БаЛЯТЬ", "МАаНДаАВОШКА" ? простор для фантазии богатый, правда? Прикрыть такое уже невозможно ? если сделать, скажем
$word = preg_replace("/./", "\\0[^ ]*", $word);
То будет коцать уже приличные слова ? как в анекдоте про Вовочку "вообще-то я имел в виду фамилию Хэммингуэй, но ход ваших мыслей мне нравится". Дальше фантазировать можно, но это уже граничит с маразмом. Если дискуссии в вашем форуме говнистые, резалка не поможет. Разве что можно попробовать сделать некоторую разумную проверку сообщения на входе и посылать сообщения модератору с подсветкой подозрительных слов. Однако фильтр может пропустить какое-то слово, а говнюки из форума будут специально маскировать маты в сообщениях, чтобы модератор их не увидел.
Итак, резюмируя всё это и выводя мораль, скажу, что не вижу смысла в такой проверялке матов. Она будет работать ненадёжно. А если матёрые матершинники узнают, что таковая работает, то держитесь крепче ? будут маскироваться, искать пробелы в вашем фильтре. Пока никакой алгоритм не может определять лучше человека такие вещи как употребление табуированной лексики или ругань, а в правилах большинства конференций есть так же запрет на личные оскорбления, вызывающее поведение, спам ? без модератора всё равно не обойтись
Функции условий
IFNULL(x,y) ? если x не NULL, тогда выдаёт x, иначе ? y.NULLIF(x,y) ? если x и y равны, выдаёт NULL, если не равны ? x.
IF(x,y,z) ? если x = true (вернее, если x не равен 0 и не NULL), выдаёт y, если нет ? z.
К примеру, в форуме хранится информация о пользователях и есть возможность не показывать другим пользователям свой Email. Делается поле show_email, в котором лежит 0, если пользователь не хочет показывать адрес, и 1, если разрешает.
SELECT ..., IF (show_email,CONCAT(''),'адрес не указан') AS email, ...
Эхо боя, оптимизация логов
DL16.8.2001
Дал знакомому посмотреть сайт, который дома тестирую. Он посмотрел, похвалил, а я решил посмотреть, что он видел. Открываю логи Апача, смотрю и через некоторое время обнаруживаю, что кроме товарища ещё кто-то лазил на мой домашний сервер.
Вот он, вирус CodeRed, о котором так долго твердили большевики.
"GET /default.ida?XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX%u9090%u6858%ucbd3%u780
1%u9090%u6858%ucbd3%u7801%u9090%u6858%ucbd3%u7801%u90
90%u9090%u8190%u00c3%u0003%u8b00%u531b%u53ff%u0078%u0
000%u00=a HTTP/1.0" 404 270
В те моменты, когда я подключался к сети по диалапу со включенным Апачем, ко мне, словно звуки далёкой канонады, приходили такие запросы. Учитывая то, что в IPv4 2^32 адресов, по которым вирус рассылается случайным образом, можно восхищаться масштабами его распространения.
Заодно перенастроил правила в Atguard, чтобы не разрешать соединяться с Апачем откуда-то ни было кроме 127.0.0.1.
Кстати, именно как по эху? вернее как по кругам на воде ? недавно замерили активность и направленность DoS-атак во всём интернете (русский комментарий к результатам ? ) []. При отправке запроса на соединение хулиган, чтобы не быть пойманным, меняет обратный адрес IP-пакета на случайный. Имея много ip-адресов, можно собирать приходящие ответы от атакуемых серверов и делать расчёты. Интересно, можно ли, слушая запросы из сети и имея алгоритм выбора случайного числа вируса CodeRed (насколько я понял из сетевых обзоров, две его версии можно отличить по строке запроса), подсчитывать количество заражённых? RomikChef прислал описание оптимизации работы с логами.
О! Какая статья интересная, а я и не читал :-( А может, и хорошо, что не читал, потому что всё по-другому сделал. Мне кажется, что у Димы не очень удачная организация базы.
У меня запись занимает ровно 20 байт. может быть, это тоже не самая удачная, но, сами понимаете, размер на порядок, а то и на два меньше.
id ? 4 байта
ip ? 4 байта
таймстамп ? 4 байта
дальше идут ссылки на словари, каждая по 2 байта
page ? $PHP_SELF
agent ? бровзер
реферер ? реферер
баннер ? баннер
Теперь по пунктам.
- ip, упакованный в 4 байта, это, конечно, короче, чем хост. Тем более, что толку от хоста в базе нет. И группировать по int, мне кажется, база будет быстрее, чем по строкам. Да, вывод информации происходит медленнее, поскольку надо ресолвить сразу кучу адресов. Но я могу и подождать ? при просмотре торопиться некуда. Имхо. А вот при записи на ресолвинг времени не тратится.
- таймстамп не родной, а юниксовский. И не потому, что родной занимает на 1 байт больше, а потому, что когда пришлось логи малость подправить, сами понимаете, стампы все сбились.
- page. Это понятно. Словарь всех страниц сайта. Таблица небольшая, всё летает.
- агент. То же самое.
- баннер ? тоже понятно. Какой баннер был показан.
- реферер. Самое шаткое место. Да, таблица растет. но, как вы понимаете, 90% рефереров ? со своего сайта. В принципе, одиночные реферера можно и почистить. Query_string у внутренних рефереров отсекается. У чужих ? отсекается тоже и пишется отдельно, для анализов.
Теперь по замечаниям в обсуждениях. Да, я пишу все хиты. Во-первых, при моем размере записи это не напряжно. во-вторых, если чувак нажал релоад, ему показался другой баннер, мне это надо учитывать.
И ещё. Вопрос. Кто как отличает роботов от людей? Я определяю по включенной графике. И в принципе, моя статистика не сильно разнится со спайлоговской.
Что там у меня было раньше?
CREATE TABLE logs ( date datetime DEFAULT '0000-00-00 00:00:00' NOT NULL, (можно TIMESTAMP)
ip varchar(16) NOT NULL, host varchar(255) NOT NULL, browser varchar(64) NOT NULL, referer varchar(255) NOT NULL, request_uri varchar(255) NOT NULL, );
Признаться, я делал эту систему, когда не знал существования функций [] и []. Потом переделывать руки не доходили. А преобразуется ip-адрес в целое число переведением всех четырёх чисел адреса в двоичный код (с дополнительными нулями: 255 переводится в 11111111, а 4 ? в 00000100), убираются точки, и получившееся число переводится обратно в десятичный. В IPv4 может быть 2^32 адресов, т.е. (2^8)^4 ? итого число занимает четыре байта.
Timestamp из Юникса пишется просто ? функция UNIX_TIMESTAMP. Выбирается так же просто ? для группировки по дням, неделям, дням недели или чему-то ещё используется стандартная функция MySQL FROM_UNIXTIME:
SELECT DATE_FORMAT(FROM_UNIXTIME(date),'%e.%m.%Y') AS date_group, COUNT(ip) AS visits FROM logs GROUP BY date_group ORDER BY DATE_FORMAT(FROM_UNIXTIME(date),'%Y%m%d');
Чем больше узнаёшь возможностей MySQL, тем больше кажется, что ты ничего не знаешь. Каких там функций только нет!
В чём я не уверен, так это в целесообразности держания словарей адресов, рефереров и броузеров. Надо при записи лога выбирать идентификаторы, проверять, вставлять новую запись, если таковой не нашлось.
$page_result = mysql_query("SELECT id FROM page WHERE address='$PHP_SELF'");
if (@mysql_num_rows($page_result)==1)
$page_id = mysql_result($page_result, 0);
elseif (!mysql_error()) {
mysql_query("INSERT INTO page (address) VALUES ('$PHP_SELF')");
$page_id = mysql_insert_id();
};
Не хочется объединять таблицы при запросе, хотя меньше данных надо обрабатывать ? в главной таблице объём файла становится гораздо меньше, что ускоряет работу. Неясно, правда, сколько дополнительного времени это займёт при записи логов. По этому вопросу и мне, и Ромику хотелось бы услышать мнение общественности. Прошу высказываться в отзывах.
Математические функции
MOD(N,M) или "%"? остаток от деления N на M.FLOOR и CEILING ? округление до целого вниз и вверх.
ROUND ? округление до целого или до определенной десятичной дроби.
LEAST (X,Y,...) и GREATEST(X,Y,...) ? минимальное и максимальное числа из указанных.
Не упоминаю стандартные математические функции взятия модуля, знака, работы с углами.
Остальные функции
LAST_INSERT_ID() ? как и [] выдаёт последний идентификатор, который сгенерировала база данных по запросу с данного соединения.MD5(строка) ? поскольку говорят, что зашифрованный функцией PASSWORD() пароль легко расшифровать, я храню хэш md5 от пароля.
FORMAT(X, D) ? форматировать число X в виде "#,###,###.##", округлённое до D знаков после запятой. Подумал, что неплохо бы в моей [] сделать форматированные для удобного чтения числа, глянул в руководство, вот оно. Всё уже написано.
Ещё две функции, про которые я забыл, когда писал про оптимизацию работы логов.
INET_NTOA(число) ? аналог [].
INET_ATON(ip-адрес) ? аналог [].
А я только начал думать, как отделять статистику по ip-адресам от всех других выборок в . Оказывается, всё проще, чем кажется.
Пароль на страницу. Часть 5. Сессии
DL9.8.2001
Зачем я писал заметку про куки? "Не понимаю, зачем писать про куки, когда в php есть сессии?!" Затем, чтобы у читателей не образовывалась перед глазами плоская картина. Не везде ещё стоит php 4-й версии, а в третьей они не поддерживаются. Более того, не везде сессии так необходимы? за редким исключением алгоритм авторизации проверяет правильность логина/пароля и правильность данных сессии, а затем либо отфутболивает клиента на страницу входа, либо берёт массив (или объект) с данными о пользователе.
Случаев, когда работа сессиями необходима, не так уж и часты. Например, в своей игре "Монополист" я сразу стал использовать сессии, потому что пользователь может играть в нескольких играх и одна и та же страница в одном и том же сеансе работы может содержать разные данные. Там лучше данные для одной из игр, в которых пользователь участвует, хранить в сессии и сделать страницу для перехода между играми.
В общем, я не утверждаю, что сессиями пользоваться не нужно. Нужно, только всему своё место. К вопросу применимости трёх способов авторизации ? через 401-й заголовок ("realm"), куки или сессии ? я вернусь позже. Сейчас поговорю о сессиях.
Сессии в php ? это на самом деле не метод авторизации (само понятие неправильное, но в форумах спрашивают именно "как авторизовывать пользователя через сессии?"). Встроенный в php механизм пользовательских сессий лишь идентифицирует этих пользователей, авторизовывать ? опять же, работа вашего скрипта.
Много про механизм сессий рассказывать не буду ? уже рассказано. В самом простом виде (вернее в самом dafault-ном) механизм этот работает так: система держит на сервере файл сессии, в котором содержатся её переменные. Пользователь при запуске сессии получает уникальный идентификатор (обычно через куку), и при обращении к другим страницам отправляет её. При запуске механизма сессий в вашем скрипте обработчик php проверяет, существует ли файл соответствующий пришедшему идентификатору сессии ? если существует, то скрипт сможет прочесть данные из файла, если нет ? будет запущена новая сессия и создан файл. Разумеется, имя данной переменной опеределено в установках php.
Теперь о том, какими функциями мы пользуемся.
session_start(). Запускает сам механизм сессий. От пользователя должна быть переменная и соответствующий ей файл. Если нет файла, он создаётся, и сессия запускается с нуля. Если нет ни файла, ни переменной, то генерируется переменная (например, посылается заголовок с кукой) и создаётся файл.
session_register(имя1, имя2, имя3...). Указание, какие переменные запомнить в файле по окончании работы скрипта. После того как пользователь перейдёт к другой странице, можно запустить механизм сессий, и после вызова данной функции переменные будут доступны.
session_destroy(). Удаляет файл данных сессии (при использовании кук надо удалять их вручную, выставив пустую куку: "setcookie(session_name())").
session_end(). Если после авторизации данные о пользователе менять не надо, лучше сразу "выключить за собой свет" ? закрыть файл и освободить доступ к нему.
session_set_cookie_params(жизнь, путь, домен). Установка параметров куки с идентификатором сессии (по умолчанию кука выставляется на корень сервера и на 0 секунд ? до закрытия браузера).
Пока всё. Подробно про сессии будут отдельные выпуски. Пока опишу механизм авторизации и идентификации пользователя при помощи сессий.
Итак, имеем три файла ? вход (login), проверка (auth) и выход (logout).
// вырезка всех нежелательных символов
$login = preg_replace("/[^\w_\.\-]/", "", $HTTP_POST_VARS["login"]);
$pass = trim($HTTP_POST_VARS["pass"]);
// проверка переменных
if (strlen($login)==0 strlen($pass)==0)
$error = "Введите логин и пароль";
else {
// проверка логина и пароля
$user_result = mysql_query("SELECT * FROM user WHERE login='$login' AND pass='". md5($pass). "'");
/* если возникла ошибка в базе (например, пользователь всунул в сессию дли-и-инную переменную, которую база переваривать не захотела) или получилась не одна строка, отфутболиваем пользователя */
if (mysql_error())
die(mysql_error());
elseif (@mysql_num_rows($user_result) != 1)
$error = "Неверное имя пользователя или пароль.";
// если всё нормально, выбираем данные, запускаем сессию
else {
$user = mysql_fetch_assoc($user_result);
session_set_cookie_params(1800, "/");
session_start();
// запоминаем данные о пользователе
session_register("user");
// и дальше отправляем его куда-нибудь
if (isset($HTTP_POST_VARS["return"]))
header("Location: {$HTTP_POST_VARS['return']}");
else
header("Location: /");
exit();
};
};
/* здесь пользователь уже не прошёл авторизацию, но может отправить куку из закрытой сессии. очистим её. */
if (isset($HTTP_COOKIE_VARS[session_name()]))
setcookie(session_name());
// дальше рисуем форму, это неинтересно.
Данный скрипт является и обработчиком и формой для ввода данных. При получении логина и пароля он обрабатывает их и, если они правильные, прекращает работу, отправив пользователя на нужную страницу. Если данные неправильные или вообще отсутствуют, рисует форму.
/* убиваем переменную user, чтобы нельзя было, нарисовав форму, отправить данные в post-запросе. */
unset($user);
// флаг "ошибка сессии" ? если он включён, работа прекратится.
$session_error = false;
// если не существует куки с идентификатором сессии, поднять флаг
if (!isset($HTTP_COOKIE_VARS[session_name()]))
$session_error = true;
// если существует, запускаем механизм сессий и регистрируем переменную $user.
else {
session_start();
session_register("user");
/* если случайно в массиве нет логина и пароля, работа тоже прекращается ("ничего не знаем, мы вам их давали") */
if (!isset($user["login"]) !isset($user["pass"]))
$session_error = true;
};
/* если пользователю до сих пор удалось геройски избежать ошибок, делается проверка через базу так же, как и на входе. */
if (!$session_error) {
$check_result = mysql_query("SELECT uid FROM user WHERE login='{$user[login]}' AND pass='{$user[pass]}'");
if (mysql_error() @mysql_num_rows($user_result) != 1)
$session_error = true;
};
// если была какая-то ошибка, то
if ($session_error) {
// уничтожаем данные сессии
session_destroy();
// уничтожаем куку, если она была
if (!isset($HTTP_COOKIE_VARS[session_name()]))
setcookie(session_name(),"","/");
/* отправляем пользователя на вход, с возможностью вернуться на запрошенный адрес */
header("Location: /login.php?return=$REQUEST_URI");
// прекращаем работу
exit();
};
mysql_free_result($check_result);
Пользователь проверен и в массиве $user ? все данные о нём, можно, например, поприветствовать его по имени-отчеству:
include("auth.inc");
?>
[skip]
И выход:
if(isset($HTTP_COOKIE_VARS[session_name()])) {
// запуск механизма сессий
session_start();
// удаление файла
session_destroy();
// удаление куки
setcookie(session_name());
};
// выход со страницы
header("Location: /login.php");
Пара замечаний: закрываемая паролем часть в данном примере - весь сервер (например, service.firm.ru), для закрытия директории нужно исправить пути. Вместо PHPSESSID используется session_name(), чтобы можно было свободно менять имя идентификатора. Кстати, на одном физическом сервере можно делать разные имена идентификаторов сессий - достаточно в нужную часть положить файл .htaccess со строкой php_value session.name "ABRACADABRA".
На этом всё. Ждите статьи со сравнением приведённых способов авторизации (свои куки, сессии и 401-й код) и нескольких статей по сессиям.
Работа с MySQL. Часть 5. Функции обработки данных
DL21.8.2001
Должен признаться, много времени на изучение руководства по MySQL я не уделял, а брал его только когда было совсем необходимо что-то узнать. Как выяснилось, я многое потерял? вещи, над которыми иногда задумывался, оказывается, уже в базах данных MySQL реализованы.
Посмотрев в перечень математических функций, я несколько переделал подсчёт данных в своей []. Данные вынимались из базы запросом, проводились вычисления, затем возвращались обратно. Теперь количество запросов для этой операции сведено к одному ? отправляется сразу UPDATE-запрос, внутри которого указываются все вычисления и сопутствующие данные.
Пока что расскажу про функции (не все, конечно), которые нужно знать каждому, кто много работает с базой.
Строковые функции
CONV(N,система_из,система_в) ? конвертация числа из ондой системы исчисления в другую:select CONV("ff",16,10); => 255
Кстати, конвертировать можно не только в стандартных системах (2,8,10,16), но и в любых других от 2 до 36 ? насколько хватает букв латинского алфавита.
CONCAT(X,Y,...) ? объединение строк и чисел в одну строку (пример приведён выше).
CONCAT_WS(разделитель,X,Y,...) ? аналог функции [].
LENGTH(строка) ? [].
LOCATE(подстрока, строка) ? [].
SUBSTRING(строка, отступ, длина) ? [].
TRIM() ? удаление лишних символов из начала и конца строки. В отличие от функции php [], позволяет не только пробелы, а любые символы и даже комбинации символов.
REPLACE (строка, X, Y) ? заменяет в строке X на Y (не перепутайте порядок с порядком параметров в str_replace).
XML + XSLT: ещё раз о специализации программных средств
DL28.8.2001
На днях на выпуск об XML мне написал свой отзыв человек с ником вЪедливый. Мы решили продолжить обсуждение, и хочу дать ответ на [] в форум.
У xml xsl есть много подводных камней. Грамотно использовать его может лишь уже поднаторевший в этом.
(лирическое отступление? попробуйте поискать {Fatal error: Call, Warning: MySql} в яндексе. Результаты просто поразительны. И после этого вы будете всех перетаскивать на xml xsl ?).
1. Создавать xsl шаблоны ? это изучение верстальщиками еще одного языка. Это для программиста новый язык ? новый роман, верстальщикам сложнее.
2. Дополнительные трудности по написанию в строгом синтаксисе чтобы xml был валидным (кавычки, регистр, слэш на одинарных тагах, закрывать параграф, когда наконец все проги будут понимать русские название элементов и аттрибутов ???)
3. Выдавать xml на php сложнее чем html. На больших проектах это существенно замедляет отладку.
4. Уродская ситуация с xml и русским языком. Примерно такая же как была в jave на заре развития. Хотя конечно принципиально поддержка разных языков продумана, но новичков отпугивают первоначальные сложности. Каждая новая прога начинает с игнорирования всего кроме ascii и utf8. Php "Supported target encodings are ISO-8859-1, US-ASCII and UTF-8". Конечно это не проблема для тех кто уже почуствовал запах xml.
5. Создавать html на лету по xsl это ресурсоемко. Есть принципиальные проблемы с буферированием и получением лишь части xml. Пусть нам нужно выдать большую страницу полученную по большому xml (условно xml ? 1 Мб). Придется загрузить ВЕСЬ xml трансформировать и выдать ВЕСЬ ответ зараз. Без xml мы можем читать файл частями и выдавать по мере прочтения а не все сразу. С xml придется использовать SAX парсер ? опять доп трудности.
6. Вместе с xml все равно необходим скриптовый язык. Простейший пример ? показать N символов текста node, причем N выбирает клиент (т.е. N параметр представления а не структуры), счетчики (цифры либо буквы). Php либо jscript либо ... Кстати xml любит раздувать конструкции xsl
echo <<
CONF;
Что из этого проще и понятнее ? Это я еще не сравнивал
7. Трудности по загонке данных в xml. Например при экспорте в xml из excel я столкнулся с проблемой с символами ® ©. Кроме того xml создан для написания структурированных документов. Даже в ворде не все способны создавать структурированные документы. А сколько человек работают с tex который так же создан для создания структурированных документов?
Примечание. Я использую xml в своей работе. Просто иногда цена за использование xml выше чем отдача от него. Поравьте меня если ошибся. Жду возражений дополнений
И наконец самое главное
8. Ни одна база данных не поддерживает xml на нормальном уровне (мб Оracle9i XMLType ?). XML существует на начальном уровне хранения в виде файлов. Конкретнее нужно чтобы: База данных индексировала xml и позволяла быстро выдернуть часть дерева в естественном для xml синтаксисе не просматривая все. Причем желательно чтобы в случае если xml содержит ссылки на др xml то из них тоже выдиралась лишь необходимая часть. Позволяла заменить/вставить целую часть дерева за раз. Хочется смотреть на базу не как на совокупность плоских таблиц а один иерархический документ. (а лучше обоими способами).
Конечно мы можем хранить xml в clob и blob и т.д. а отдельные элементы, атрибут вытаскивать в поля, но это ИЗВРАТ.
Это нарушение изначальной концепции ? МЕДЛЕННЫЙ (как и положено интерпретатору) php использует быструю написанную на C базу данных для вычленения НЕБОЛЬШИХ кусочков информации, на обработку которых у него хватает сил.
Складываестя впечатление, что мы говорим на разных языках. Особое удивление вызывают пункты 6 и 8. Никто не предлагает заменить языки скриптования XML-ем. Это разделение труда и специализация. Тебе же не придёт в голову придумывать свои форматы записи больших объемов данных, если есть база? Ответ по пунктам приведу ниже, потому что это не самое главное.
Твоя схема работы на подключаемых файлах с верхом, низом страницы и действиями вроде записи логов, конечно удобна на проектах разного масштаба. Но она косвенно ограничивает функциональность сайта. По своему опыту знаю: с увеличением функциональности (например, сделать шапку страницы с подсветкой текущей позиции, сделать там динамические меню, которые появляются не везде и т.п.) начинается путаница. И ладно, если только ты и работаешь над таким проектом. Куда хуже будет, если такой чужой проект дадут тебе или если над ним работают двое. А если смена дизайна всего сайта, что делать? Сидеть и ковырять код? Класс шаблона тоже не очень спасает. Как мне и предсказывали, на сложном проекте я тону в тегах. Верстальщику работать с шаблонами тоже неудобно.
Об XML говорят много, и преимущественно ничего ? пустые фразы. "Отделение контента от дизайна" (или "данных от их представления") превратилось в ничего не значащее заклинание.
Работа с XML позволяет, во-первых, иметь дело в программе только с данными, во-вторых, работать с ними как с []. Вполне естественно, что за это надо платить увеличением ресурсоёмкости. А вот писать код валидно, в строгом синтаксие ? это не проблема, а вопрос культуры производства. Php-скрипт ты же пишешь по всем правилам.
Применение XSLT для трансформации XML в HTML облегчает работу программиста тем, что XSLT как специфический язык программирования берёт на себя такую мелочёвку как подсветку строк через одну, нумерацию списков и многочисленные if?else, необходимые только для правильного вывода информации (например, подсветка в меню навигации текущего места).
Связка XML+ XSLT избавляет тебя, программиста, от такой мелкой рутины и позволяет заняться только обработкой данных (при этом убрав из кода, с которым работаешь, весь шум вёрстки). Она требует большей квалификации от верстальщика и/или дизайнера, но зато теперь они не только придумывают формат документов, но сами и воплощают его в реальность. Больше верстальщик не будет бегать к тебе с просьбой: "Надо, чтоб у нового анонса значок мигал." Теперь он сам этим занимается и не морочит тебе голову этой дурью. :) Ты можешь заняться высшими материями.
В конце концов, тебе самому проще будет работать с чётко разделёнными узлами: здесь данные, здесь их обработка, здесь их форматирование. При смене дизайна сайта не надо будет хвататься за голову от того, что придётся вручную перебрать все скрипты. В мае дорожники положили новый асфальт, в июне жилищники стали ковырять теплотрассу. Теперь же конфликты задач упразднены. Теплотрасса лежит в другом измерении.
Разумеется, применять или не применять XML+XSLT ? это личное дело. Просто поражает подход многих людей, которые не попробовали, а заранее говорят: "да нафига нам это?!", "что там такого особенного?!".
Подводя итог, скажу: php давно (больше года назад) вырос из простого интерпретатора. На php делают более сложные задачи, чем "одинамичивание" домашних страниц. Он уже не медленный интерпретатор, как ты сказал. Скрипт компилируется и только после этого запускается ? рост производительности значительный. В дополнение к мощной системе php есть обработчики XML+XSLT, которые снимают с программиста, работающего над большим проектом, заботы о красивом оформлении страниц.
Если ещё остался интерес, отвечаю по пунктам:
1. Пускай верстальщики изучают XSLT ? не такой уж он сложный, да и сами они должны знать не только HTML, но и CSS, а так же немного JavaScript.
2. Разве валидность написания раньше не требовалась? Ну, да, я понимаю, что тег многие писали только из уважения к былому величию компании Netscape. Но что поделаешь ? больше возможностей, нужно больше точности в работе, выше культура производства. Ведь если вы ездите, скажем, на "Тойоте-Марк-2", заправиться бензином на шоссе из ведра у пьянчуги будет плохой приметой, не так ли?
3. Не факт, что применение XML+XSLT замедляет отладку. Во-первых, в отличие от смеси HTML+PHP, на программисте не висит, например, вывод нумерованных списков, подсветка строк и множество прочей мелочёвки. По своему опыту могу сказать, что на эту мелочёвку тратится существенная доля времени. Кстати, XML+XSLT позволяет программисту и верстальщику отлаживать свои части проекта параллельно.
Во-вторых, анализатор синтаксиса в броузере будет молчать, как партизан, и просто ничего не выведет, если будет синтаксическая ошибка. А программа-валидатор умеет грамотно сообщать, что не так.
В-третьих, если XSLT лежит в файле, а XML формируется "на лету", то сделать возможность посмотреть XML-код несложно. Если новая версия сайта делается по адресу test.site.ru, можно сделать виртуальный хост xml-test.site.ru, и пусть php-скрипт смотрит, через какой виртуальный хост его вызывают. Если через "test", то вызывает обработчик XML+XSLT, если "xml-test", тогда выдаёт просто XML.
4. Настройка русского Саблотрона ? это, конечно, шаманство, но не настолько ужасная, как кажется.
5. Кто спорит, что это ресурсоёмко. Наименее ресурсоёмкая для сервера технология ? это Фронтпейдж и заливка готовых html-страниц на сервер. Скпритовые языки, модули Apache (например, mod_rewrite) ? это всё увеличивает ресурсоёмкость, но зато упрощает разработку.
6. Этот пункт вызывает особое удивление. Никто не предлагает заменять скриптовые языки на XSLT.
7. Ну, да, с там надо возиться отдельно (честно говоря, мне не приходилось, надо смотреть документацию). Но, по-моему, под "структурированными документами" ты имеешь в виду сложно структурированные.
8. Будут новые версии баз, будет и возможность работать с иерархическим документом. Пока что доступных технических средств достаточно, чтобы переводить данные из "линейных" таблиц в древовидные документы.
По следам поиска ещё раз
DL28.9.2001
Комментарии к предыдущему материалу Олега Юсова.
Упражнения в настройках и запросах.
Сначала как добавить FULLTEXT-индекс:
mysql> alter table articlea add fulltext(ztext);
ERROR 1073: BLOB column 'ztext' can't be used in key specification with the used table type
mysql> alter table articlea type=myisam;
Query OK, 36 rows affected (0.60 sec)
Records: 36 Duplicates: 0 Warnings: 0
mysql> alter table articlea add fulltext(ztext);
Query OK, 36 rows affected (10.00 sec)
Records: 36 Duplicates: 0 Warnings: 0
Текстовые индексы можно делать только в таблицах типа MyISAM. Тексты берутся из таблицы и скидываются в файл индекса, и растёт объём базы.
По поводу запросов.
Нельзя поле relev использовать в клаузе WHERE:
SELECT *, MATCH field AGAINST ('$searchwords') as relev FROM table WHERE relev>0 ORDER BY relev DESC
хотя можно:
SELECT *, MATCH field AGAINST ('$searchwords') as relev FROM table WHERE MATCH field AGAINST ('$searchwords')>0 ORDER BY relev DESC
Вычисленное поле, конечно же, нельзя использовать в WHERE по всем правилам синтаксиса, но можно использовать в HAVING:
SELECT *, MATCH field AGAINST ('$searchwords') as relev FROM table HAVING relev>0 ORDER BY relev DESC
Поиск через MATCH, как писал Олег, делается только по слову целиком. ...Впрочем, по релевантности можно только сортировать, а выбирать по LIKE (это, конечно, скажется на производительности, даже не знаю, насколько).
Убираем условие "relev>0", оставляем сортировку. Остальное, как и раньше? рубим полученную строку и превращаем в запрос с несколькими операторами LIKE:
SELECT *,MATCH field AGAINST ('$searchwords') AS relev FROM table WHERE field LIKE '%$word1%' OR field LIKE '%$word2%' ORDER BY relev DESC, datefield DESC
Логи, которые я теперь собираю по технологии, которую предложил RomikChef, работают гораздо лучше. Скорость выборки на удивление осталась прежней, зато объём файлов, в которых лежат таблицы, сократился в 10 раз.
Кому надо, могут скачать () [].
Если не лень будет, выложу скрипт для автоматической отправки по почте логов с хостинга на дом, запакованных в формате GZip (Windows Commander понимает). Для полной автоматизации можно будет сделать канал для WatzNew, и логи будут идти сами, например, раз в неделю (получится письмо объёмом около 300 килобайт).
Найдено []:
mailto:serforester@peren.ru 23.08.2001 10:01
Дима, прости уж мне мою банальность (хотя кто я такой, чёб меня прощать), но все эти разговоры о Лебедеве ... Ну первооткрыватель, ну сделал кучу сайтов, есть у него чувство формы, да и программульки писать могЁт. Ну и хватит :-) Просто когда кого-то постоянно захваливают ? хочется отойти подальше и не слышать этого. И чёрт с ним, что не будешь понимать о чём потом будут разговоры ? зато хоть уважение и интерес к этой личности останется. Короче, это как с Сибирским Цирюльником и его Мэйкером. Уважаю я Никиту Михалкова и поэтому не могу смотреть на то, как он превращается в холёную "звезду". Кстати, "цирюльника" так и не посмотрел ? у Н.М. и так хватает интересных фильмов. Вообщем, поступило типа предложение: все на [] или [] , а про автора ни-ни. Пропадёт ? будут о нём, как об арханизме Рунета говорить.
mailto:nail@bowling.nsk.su 26.08.2001 16:43
2 serforester@peren.ru:
Что-то вы напутали, Семен Семеныч.
[] и   [];? дело рук двух разных людей ? Дмитрия Лебедева и Артемия Лебедева соответственно.
Оказывается, благодаря моим усилиям Артемий "программульки писать могёт". Мне лестно.
По следам поиска в "деталях" - релевантность
25.9.2001
Помнится в статье "Безопасный и удобный поиск" была такая фраза:
"Наверное, в том же Яндексе все видели ссылочку "сортировать по релевантности". Это оно и есть. Сортировка результатов по количеству совпадений слов. Отчасти, кстати, такая сортировка снимает проблему обработки логики поиска. Но с БД MySQL делать такую сортировку очень сложно. Надо сперва выбрать, где есть все слова, потом записи, где разные слова (исключив предыдущие). Если у вас постраничный вывод? то вообще дело труба!"
На данный момент (начиная с MySQL 3.23.23) это не совсем так. Можно сказать, что с точностью до наоборот ? с БД MySQL делать такую сортировку очень просто с использованием FULLTEXT полей.
Для вывода результатов поиска по релевантности необходимо:
1. Требуемые поля VARCHAR, либо любые из разновидностей полей TEXT (SMALLTEXT, MEDIUMTEXT и т.п.) сделать ключами FULLTEXT:
ALTER TABLE table ADD FULLTEXT(field)
Или во всеми любимом phpMyAdmin'е, который я слегка подправил для работы с FULLTEXT полями (архив прилагается ? вдруг кому пригодится).
2. Дальше ? еще проще:
$query = "SELECT *, MATCH field AGAINST ('$searchwords') as relev FROM table ORDER BY relev DESC"
Далее можно навешивать всякие LIMIT'ы и прочее для удобного вывода.
3. Все::)
Заметки:
1. По умолчанию установлен поиск слов, содержащих не менее 4 символов. Правится установкой #define MIN_WORD_LEN 4 в исходнике ft_static.c, хотя на мой взгляд править это не нужно.
2. Недоступны символы % в поисковой фразе, слова в поисковой фразе парсятся с использованием списка разделетелей.
3. Список разделителей слов правится в исходнике ft_static.c.
4. Необходимо минимум десяток записей в таблице для начала вычисления релевантности.
5. Нельзя поле relev использовать в клаузе WHERE:
SELECT *, MATCH field AGAINST ('$searchwords') as relev FROM table WHERE relev>0 ORDER BY relev DESC
хотя можно:
SELECT *, MATCH field AGAINST ('$searchwords') as relev FROM table WHERE MATCH field AGAINST ('$searchwords')>0 ORDER BY relev DESC
6. При создании индексов FULLTEXT возможны 2 варианта:
CREATE TABLE table ( field1 VARCHAR (255), field2 TEXT, FULLTEXT (field1, field2) )
и
CREATE TABLE table ( field1 VARCHAR (255), field2 TEXT, FULLTEXT (field1), FULLTEXT (field2) )
В первом случае возможен запрос:
SELECT *, MATCH field1, field2 AGAINST ('$searchwords') as relev FROM table ORDER BY relev DESC
релевантность вычисляется у всех полей сразу.
Во втором случае такой запрос выдаст ошибку. Здесь вычисляем релевантность следующим образом:
SELECT *, MATCH field1 AGAINST ('$searchwords')+MATCH field2 AGAINST ('$searchwords') as relev FROM table ORDER BY relev DESC
Второй вариант несколько сложнее в запросах, однако, на мой взгляд лучше, т.к. увеличивается гибкость поиска ? к каждому из полей можно задать, например, коэффициент значимости и при суммировании релевантностей полей умножать их на этот коэффициент. Поисковая фраза будет "больше" искаться в полях с большим коэффициентом. Например, если мы делаем поиск по проиндексированным страницам каталога ресурсов, то поле имени страницы обычно задают с большим коэффициентом, чем поля мета-тегов описаний или ключевых слов.
7. Скорость достаточно высокая ? даже в некоторых случаях быстрее like поиска
8. Все вышесказанное работает начиная с версии MySQL 3.23.23
Прячем исходный код. Zend Optimizer & Zend Encoder
10.9.2001
Возникла как-то задачка: показать проект заказчикам, но исходник не показывать.
После долгих раздумий, как "попортить" свой стройный код (вспоминая еще те времена, когда были проги для исходников на турбо-паскале, которые убирали переводы строк, табуляторы, лишние пробелы, делая код нечитаемым) по совету умных товарищей с канала #phpclub набрел на Зендовские утилиты, а именно Zend Optimizer и Zend Encoder.
Комплекс, в принципе, предназначен для кодирования и раскодирования, некой оптимизации и пр. пр., но мне было интересно именно сокрытие исходных текстов от шаловливых рученок...
Краткое описание работы: Zend Optimizer работает как модуль к PHP и делает кучу работы, оптимизирует там что-то и понимает файлы, обработанные прогой Zend Encoder. Прога эта делает некую компиляцию исходника ПХП, после чего он становится абсолютно нечитабельным и нередактируемым. Пример: файл test.php с содержимым:
---
phpinfo () ?>
---
после обработки выглядит так:
---
Zend..2000112001..1..88..179.xЫ
Ў2..еpHjb-B+-KfH%"-.kШ¦рээJ+䦦"hг4б.-.'Є .и+TЛR-Q-Єўv.L.п.7.L -х.f.J¦¦
х.ц|k-@.еe.
---
Вызывая оба файла, как кодированный, так и не кодированный, получим один и тот же результат на выводе(тоже фича Optimizera: понимать кодированные и не кодированные файлы одинаково).
Для Encoder'a в комплекте поставки есть шелл-скрипт, который позволяет кодить целый сайт, включая поддиректории.
Перед тем, как приступить к описаную непосредственно установки комплекса стоит отметить, что Zend Optimizer - свободно распространяемая программа, а вот Zend Encoder - это не бесплатная утилита, за нее требуют денег, но с сайта можно скачать пробную версию, а пообщавшись с обитателями канала #phpclub, поиметь ключик для неё и пользоваться абсолютно бесплатно...
Установка ПО.
Установка производилась на 2 машины с конфигурациями:1. RedHat 6.2, Apache 1.3.20, PHP 4.0.6.
2. Linux Mandrake 8.0, Apache-AdvancedExtranetServer/1.3.19, PHP
4.0.4pl1.
Самое важное и главное - достать с Зенда нужные дистрибуты (для той ПХП, которая стоит на машине). Большинство собак зарыто именно здесь.
Установка сводится к следующему:
1. скачать 2 архива: один с Zend Optimizer, другой с Zend Encoder.
2. создать директорию /usr/local/Zend.
3. используя mc, или кто со строки любит, распаковать в директорию /usr/local/Zend файлы: из архива Optimizer'a - ZendOptimizer.so, из архива Encoder'a - все.
4. Добавить в самый конец файла php.ini 2 строки:
zend_extension="/usr/local/lib/Zend/ZendOptimizer.so"
zend_optimizer.optimization_level=15
5. Перегрузить апач. Все должно работать. Если работает - смотреть phpinfo(), где должны быть надписи:
This program makes use of the Zend scripting language engine:
Zend Engine v1.0.4, Copyright (c) 1998-2000 Zend Technologies
with Zend Optimizer v1.1.0, Copyright (c) 1998-2000, by Zend Technologies
или похожие на эти, разница только может быть в версиях. Если все ОК - замечательно, идем и кодируем: "zendenc файл.исх файл.кон", причем имя у них должно быть обязательно разное. Есть скрипт encode.sh, который копируется в корень проекта, настраиваются пути к исходной и конечной директории, путь вызова к zendenc, и все. Скрипт запускается и после некоторого(у кого какой проект) времени в конечной директории создается копия всех без исключения файлов, только вот файлы с расширениями php, php3, phps, и еще там можно настроить, будут в закодированном виде. Усе. Исходники можно прибивать, либо наоборот не прибивать, а тащить к хостеру закодированный проект, и исходник хранить в сухом прохладном месте до следующей правки.
Проблемы: в принципе быть не должно, но если возникли, то надо перепроверить все: основная глюка ловится из-за несоответствия версии PHP. Внимательней читайте phpinfo(), логи ошибок апача, они - рулез! Самое плохое - это когда при рестарте апача говорится, что не могу найти библиотеку libm.so.5. Я решал проблему так: у меня была библиотека libm.so.6, находилась в /lib и была симлинком к libm-2.1.1.so. Берем и создаем новый симлинк к libm-2.1.1.so под именем libm.so.5 в /lib.
Вот, собственно, и все.
Администрирование базы данных
Способы администрирования БД в порядке убывания удобства:Особенно рекомендую первый способ. С ним не придётся изучать запросы ALTER TABLE, ADD COLUMN и т.п. Я их не знаю до сих пор. Тем более, что "такие вопросы, товарищ посол, с кондачка не решаются" - когда вам понадобится автоматически изменить структуру базы или таблицы? Пару слов о втором способе. Это так сказать обходная технология, которую я применял, не зная про phpMyAdmin и утилиту mysqldump. В скрипте пишутся команды, удаляющие базу и создающие её вновь. Когда-то помогало, но вообще это, ещё раз скажу, обходная технология, "подпорка".
На будущее: если у вас будет несколько сайтов, использующих БД, то хотя бы в пределах домашнего сервера создайте несколько баз. Это облегчит работу серверу и исключит возможность путаницы таблиц. В общем, правила работы с БД те же, что и с сайтом - держать в отдельной директории от других.
Обработка ошибок запросов
Сообщение о последней ошибке можно получить через функцию mysql_error:echo "Ошибка базы данных. MySQL пишет:", mysql_error();
Если результат функции пишется в переменную, можно проверить её:
$result = mysql_query($request);
if (!$result)
echo "Ошибка базы данных. MySQL пишет:", mysql_error();
else {
echo "
| ", $row["field1"], " | ", $row["field2"], " |
};
Если в переменную не пишем, то так:
$request = "UPDATE (...)";
mysql_query($request);
if (!mysql_error())
echo "Обновление данных прошло успешно!";
else echo "Ошибка базы данных. MySQL пишет:", mysql_error();
Если запрос генерируется автоматически, можно выводить и сам запрос (полезно создавать переменную, которая бы его содержала, и использовать её в качестве параметра функции).
Работа с базами данных. Начало.
DL21.11.2000
Всё, что я могу сказать в качестве рекомендации к использованию БД - это то, что жизнь без них - просто смерть! База данных - луч exe-шного света в тёмном царстве обработки данных интерпретируемой программой. База данных приносит немножко головной боли, но снимает гораздо больше.
В качестве примера взят сервер баз данных MySQL (полагаю, что освоив его, вы без большого труда освоите и другие. Когда-нибудь я напишу и о них, если сам освою :).
Первый разговор пойдет о функциях PHP, используемых для работы с MySQL. Итак, начнём.
Соединение с сервером БД
...осуществляется при помощи функции mysql_connect: $connect = mysql_connect(<хост>, <логин>, <пароль>); По умолчанию, на mysql-сервере в таблице пользователей есть пользователь root, который может иметь доступ только с localhost-а, то бишь с того же самого компьютера, где стоит сервер mysql. ВНИМАНИЕ! "Иметь доступ с localhost-а" значит, что доступ имеет ваш скрипт PHP, а вы можете обращаться к нему с любого другого компьютера.Что происходит, когда мы вызываем функцию mysql_connect? С началом выполнения вашего скрипта, php выделяет в своей памяти место для информации о нём и его переменных. В информации о выполняемом скрипте хранится, в том числе, и информация о соединениях с базами данных. Переменная $connect - грубо говоря указатель на место, где данная информация хранится. Переменная эта точно такая же, как и остальные - если вы используете функции, то надо объявлять глобальные переменные, чтобы обратиться к ней.
Почему вообще используется переменная? Это на случай, если для работы вам необходимо использовать несколько серверов баз данных (или, например, для обеспечения бОльшей безопасности вы используете разные логины, у которых могут быть разные привилегии). В таких случаях в каждом запросе нужна определённость, по какому, так сказать, каналу идёт команда. Но если вы используете только одно соединение, указывать его в параметрах функций запросов (о них - ниже) не нужно - php находит первое (и в данном случае единственное) установленное соединение и использует его.
Запрос-выборка и обработка результатов
Механизм работы функций запросов к БД такой же, как и у функции соединения: функции передаются параметры запроса и (если надо) соединения, а результат записывается в переменную:$result = mysql_db_query(string база данных, string запрос [, переменная соединения]);
или
$result = mysql_query(string запрос [, переменная соединения]);
ВНИМАНИЕ! Чтобы использовать функцию mysql_query, в которой база данных не указывается, надо предварительно выбрать используемую базу данных: mysql_select_db(string база данных);
Теперь у нас есть переменная $result. Это указатель на результат выполнения запроса. Там есть сколько-то строк таблицы. Получить эти строки можно через функции mysql_fetch_row и mysql_fetch_array:
echo "
| ", $row["field1"], " | ", $row["field2"], " |
Функция mysql_fetch_array выдаёт в указанную переменную (в данном случае $row) массив, индексы которого - имена полей (причём, если вы в списке полей запроса пишете table.field, то индекс массива будет field). mysql_fetch_row выдаёт массив, индексы которого - числа, начиная с 0.
Какой функцией лучше пользоваться? Если вы запрашиваете звёздочку, т.е. все поля таблицы, а выводить поля нужно в определённой последовательноси (когда, например, у таблицы рисуется шапка), лучше пользоваться mysql_fetch_array. Если вы запрашиваете одно-два-три поля, чётко зная их последовательность, можно делать mysql_fetch_row - это уменьшит объем кода программы.
Запросы-действия
Это команды DELETE и UPDATE. Подобные запросы - в "правах" такие же, как и SELECT, поэтому отправка команды серверу происходит тем же способом - mysql_query (mysql_db_query). Но в данном случае функция не возвращает результата:$result = mysql_query("SELECT * FROM sometable");
но
mysql_query("DELETE FROM sometable WHERE id=...");
Соответственно, если мы выполним запрос-выборку и не запишем результат в переменную, данные не будут храниться нигде.
Безопасный и удобный поиск
DL18.12.2000
Обещаный рассыльщик почты откладывается до пятницы, а пока - поговорю про безопасный поиск по базе данных.
Главное, с чем сталкиваешься при написании скрипта для поиска - то, что все кажется простым, но объем кода быстро нарастает.
и не дать ему а)
Применяя такие приемы, можно, во-первых, ограничить свободу действий пользователя и не дать ему а) узнать программную структуру сайта б) вызвать перегрузку сервера (например, отправив мегабайт текста, состоящего из слов длиной в три буквы (фраза получилась двусмысленная, но переписывать не буду :), чтобы скрипт 250 тысяч раз лазил в базу) в) увидеть сообщение об ошибке в результате попадания в строку спецсимволов языка запросов. Во-вторых, некоторое удобство для пользователя - постраничный вывод и подсветка.Разделив получение адресов подписчиков и сам процесс рассылки в разные классы, мы получаем много полезного. В том числе
1) все операции на своих местах ("Ты не установил соединение с базой в главной программе!")
2) отсюда? упрощение процесса отладки ("Почему здесь переменная пустая?!")
2) читаемый код ("А что делается в этих строках?!")
3) легко модифицируемый код ("Там, сверху, замени коннект к базе, и снизу в пяти местах фетч исправь на новый.")
Если вы используете не MySQL (у меня, к сожалению, поддерживается только он), можно взять класс Database и подправить под нужную вам базу. Конечно, можно было в программе замутить наследственные классы (один класс Database, а остальные ? дочки), но рассыльщик имеет скорее рабочую, чем демонстрационную задачу. К тому же, это не лучший способ поддержки разных баз данных: удобнее держать все в одном классе, который знает функции разных серверов. Да, и, к тому же, здесь сделано два класса лишь для поддержки хранения адресов в файлах.
Скачать рассыльщика можно, нажав на ссылку ниже.
Как делать неправильно
Например, вот так:$res1 = mysql_query("SELECT id, name FROM rubs");
while ($row = mysql_fetch_row($res1))
$rub[$row[0]] = $row[1];
Из запроса получены имена рубрик и записаны в массив $rub.
$res2 = mysql_query("SELECT id, url, name, rub FROM sites WHERE какое-то-там-условие");
while ($row = mysql_fetch_array($res2)) {
echo "", $row["name"], "";
echo "(рубрика ";
echo $rub[$row["rub"]], "
";
};
Теперь выбираются новости, и вместо выбранного номера рубрики вставляем соответствующий элемент массива рубрик.
На самом деле, можно избавить себя от необходимости тащить сквозь всю программу этот массив $rub (а если у нас к рубрикам обращаются функции - что, через GLOBAL брать?), от возможности ошибиться с $rub[$row["rub"]] - если на странице несколько подобных запросов, то сделать опечатку где-нибудь легко.
Кроме того, под массив $rub требуется некоторый объем памяти (а если рубрик много?). В третьей версии PHP такой скрипт будет выполняться дольше, чем при использовании объединений таблиц, потому что он интерпретирует программу построчно при выполнении (в отличие от 4-го, который компилирует программу и только потом выполняет).
Как надо
В приведённом выше примере можно применить объединение таблиц и избавиться от описанных недостатков.$res = mysql_query("SELECT sites.id, url, sites.name as sitename, rubs.name as rubsname, rubs.id as rub_id FROM sites, rubs WHERE sites.rub=rubs.id и-какое-то-там-условие");
while ($row = mysql_fetch_array($res2)) {
echo "", $row["sitename"], "";
echo "(рубрика ";
echo $row["rubsname"], "
";
};
Итак, здесь лучше использовать запрос "SELECT sites.id, url, sites.name as sitename, rubs.name as rubsname, rubs.id as rub_id FROM sites, rubs WHERE sites.rub=rubs.id". Получается, что мы имеем готовый массив, заботимся о выводе только его элементов и пишем меньше кода.
Логика
Допустим, мы хотим предоставить пользователю возможность выбирать логику поиска - искать все слова или только одно из нескольких. Если вы хотите сделать как в [] - два амперсанта означают "И" (слово1&&слово2&&слово3) или как-то еще, то я не советчик. Шаманство со строками на небольшом сайте imho не оправдывает затраченного времени. Поэтому форму для поиска рисуем так:искать любое из слов
искать все слова
А в поисковом скрипте лишний раз проверяем, что пользователь ввел:
if ($logic!="AND" && $logic!="OR")
$logic = "OR";
Как будет использоваться логика ? ниже.
Механизм администрирования - теория
В первую очередь рекомендую заняться именно им, чтобы потом не было мучительно больно: новостная лента "почти" готова, только редактирование новости никак не написано, и приходится писать запросы "INSERT INTO news..." каждый раз вручную. Можно, конечно, и через хвалёный phpMyAdmin писать новости, но это тоже "не то".В директории с сайтом создаём директорию admin (можно более оригинально - например chief, nachalnik...). Когда сайт будет выложен на ЦЦЦ, положим в эту директорию .htaccess и поставим пароль, чтоб кто попало не лазил.
А теперь думаем, что же надо для нормальной работы с новостями. Форма для ввода новых новостей, обрабочтик формы. Так, а ещё? Список новостей с возможностью тыкнуть в неё, чтобы тут же отредактировать. Потом можно сделать галочки напротив заголовков новостей, чтоб можно было отметить неугодные и грохнуть их тут же (а лучше, чтоб было подтверждение "да/нет"). А потом сделать ввод даты ограниченным: вместо текстовых полей - раскрывающиеся списки, которые бы спасли от опечаток. Много... Но чтобы потом не было мучительно больно...
Детально описывать здесь это не имеет смысла - скачайте файл и разберитесь. К этому выпуску планируется продолжение - новостная лента с рубриками. В общем, заходите ещё.
Миллениум: магия чисел. Счетчик для страницы
DL25.12.2000
Ожидание нового века всех завораживает! Кто успел, назвал свои проекты "-2000" [] (это с регулярным выражением написано. по-русски - "название-2000" :), кто-то - с приставкой "XXI". На радио (телик уже месяц не смотрю) выходят последние в этом году еженедельные передачи, и ведущие непременно напоминают: следующая передача выйдет в следующем тысячелетии. Магия чисел системы исчисления времени завораживает!
Поэтому сегодня мне тоже хочется поговорить о магии чисел, но другой.
Итак, вы решили, что хватит генерировать трафик [], [] или [] (а сколько еще их развелось!), пора делать собственный счетчик!
Сделаю лирическое отступление для тех, кто все еще считает, что нет ничего лучше значка "Участник Rambler's top 100". Про рекламу и говорить нечего - со страниц рейтингов большой приток посетителей только у тех, кто на первой-второй странице (предполагаю, что вам, как и мне это в ближайшем будущем не светит). А регистрироваться ради зашедших с последних страниц - все равно, что кидать в океан бутылки с бумагой (послания капитана Гранта - и то только через пару лет нашли). Эстетики в пузомерках тоже никакой - что-то инородное посреди страницы болтается (во загнул! :), хотя [] ими не брезгует...
А теперь о главном - скажите-ка, можно ли в вашем счетчике посмотреть статистику по конкретному хосту (когда с него впервые зашли, как часто ходят, на какие страницы)? То-то же! Все сервера статистики держат минимально приемлемые для пользователя цифры. Рамблер - хосты за сегодня и числа за два месяца. eXtreme - последние 20 хостов, числа за 20 дней, 20 недель, 20 месяцев. Естественно, что держать логи чуть ли не всего рунета никому не захочется.
Поэтому мы будем писать их сами.
Что мы знаем про пользователя, зашедшего на сайт?
ip-адрес ($REMOTE_ADDR)
броузер ($HTTP_USER_AGENT)
адрес, откуда он пришел ($HTTP_REFERER) *если он не запретил рефереры при помощи, например, atguard.
адрес запрошенной страницы ($REQUEST_URI)
Подробнее о заранее определенных в PHP переменных - см. в [].
К данному списку можно добавить, разумеется, дату-время лога и текстовый адрес пользователя (например p123.bass4.sinor.ru вместо 123.4.56.78). Последний можно получить через функцию []:
$REMOTE_TXT_ADDR = gethostbyaddr($REMOTE_ADDR);
Хранить логи надо в базе данных (не лучше всего, а надо, иначе выяснить из них что-то полезное будет очень трудно). Благо, сейчас на нормальных хостингах стоит базовый набор PHP+MySQL. У меня на этом сайте логи пишутся одной строкой в конце каждого доступного пользователю файла (их всего 5).
@mysql_query("INSERT INTO log (date, ip, host, browser, address, referer) VALUES (NOW(), '$REMOTE_ADDR', '". gethostbyaddr($REMOTE_ADDR). "', '$HTTP_USER_AGENT', '$REQUEST_URI', '$HTTP_REFERER')");
"Собачка" перед командой - для того, чтобы пользователь не получал ругательств сервера по поводу данной команды (одна из особенностей PHP4). А зачем ругаться? :) Это только наша проблема - логи, пользователь пусть смотрит содержимое страницы спокойно, может быть, довольный, зайдет на сайт снова и снова.
Но вернемся к счетчику. Еще одной полезной вещью является подсчет уходов пользователя на другие сайты. Тем, кто работает с шаблонами, проще: перед выводом страницы делать примерно следующее:
$page = str_replace("
А в файле jump.phtml написать следующее:
if (strlen(ereg_replace("[^0-9a-zA-Z]", "", $url))>0) {
// здесь должна была быть связь с базой данных
// вставка строки в таблицу логов
@mysql_query("INSERT INTO log (date, ip, host, browser, referer, url, jump) VALUES (NOW(), '$REMOTE_ADDR', '". gethostbyaddr($REMOTE_ADDR). "', '$HTTP_USER_AGENT', '$HTTP_REFERER', '$REQUEST_URI', '". addslashes($url). "')");
header ("Location: http://$url");
}
else
header ("Location: http://АДРЕС_НАШЕГО_САЙТА");
В моей таблице логов есть поле jump - адрес, куда пользователь уходит.
Механизм просмотра статистики - самое сложное место в данной схеме (впрочем, как и во всех остальных) - можно писать самому, а можно писать запросы на выборку в или терминалке. Ваша фантазия здесь неограничена :).
Кстати, посмотрев логи своего сайта, я убедился, что ставить ссылки на другие ресурсы вовсе не вредно, а даже полезно. Слова мои имеют больший вес, а пользователь, если ему интересно все равно вернется. И открывать ссылки в новом окне вовсе не обязательно - если надо, посетитель нажмет Shift и сам откроет в новом окне, а если нет, то зачем его удерживать?
Но это все мое ворчание. Главное, что статистику вашего сайта теперь можно будет сделать именно такой, какая вам нужна.
Народная самодеятельность . связи таблиц в MySQL
DL6.12.2000
Первоначально я предполагал сделать выпуск "Народной самодеятельности" со сборником ошибок, допущеных в основном мною :). Кое-что я собирал из статей о традиционных ошибках и т.п. Но жизнь оказывается куда интересней! Пишу про очередную самодеятельность прямо по горячим следам.
Итак, некоторый человек, назовем его Вася, спрашивает Общественность форума про MySQL.
В.: Не совсем понятно как создать связь между таблицами, чтобы в поле первой автоматически вставлялись данные второй (есно выборочно и есно по ИД).
Варивант типа SELECT db.user, db.delete_priv, user.user, user.delete_priv FROM db,user WHERE db.user = user.user не совсем подходит, так как связью это можно назвать с большой натяжкой...
O.: Честно говоря, не понимаю, почему эта связь тебе не подходит? Чем эта связь натянута?
В.: Ну не нравится мне связь, основанная на синтаксисе запросов... хотелось бы чего-нибудь более существенного, иначе я вообще не вижу смысла в ключах и индексах.
O.: Более существенное - ты имеешь в виду графический интерфейс как в MS Access?
К сожалению, сам Access работает точно так же? достаточно нажать кнопочку "SQL", и вы увидите эти связи "с большой натяжкой".
В.: Просто мне проще программно все это делать, благо таблицы небольшие... Раз в самом MySQL это не работает.
O.: Через вложенные циклы, рекурсии? Скажи зачем тогда тебе вообще база данных и таблицы?
В.: Никаких вложенных циклов и рекурсий ? читаю н-ое колмчество массивов и с ними уже работаю.... А делать связь на основе селекта ? не совсем то что мне нужно...
O.: Правильно... храни все в файликах, тогда вообще вопросов не будет.
Он не знал велосипеда,
Слепо верил в чудеса,
Потому что не изведал
Всех достоинств колеса.
В общем, считаю своим долгом разложить по полочкам связи таблиц.
Кстати, насчёт связей - вот мнение признанного авторитета - Voodoo ;)
> хотелось бы чего-нибудь более существенного...
более существенное - это поддержка reference
[]
create table.....
reference_definition: REFERENCES tbl_name [(index_col_name,...)]
[MATCH FULL | MATCH PARTIAL]
[ON DELETE reference_option]
[ON UPDATE reference_option]
Только надо учесть что это нихрена в МуСКЛе не работает :)
The FOREIGN KEY, CHECK, and REFERENCES clauses don't actually do anything. The syntax for them is provided only for compatibility, to make it easier to port code from other SQL servers and to run applications that create tables with references. See section 5.4 Functionality Missing from MySQL.
"Не делай этого!"
Чего не стоит делать при помощи MyAdmin, так это вести текущую работу с базой. Здесь лучше использовать готовые скрипты или писать свои, потому что интерфейс данной утилиты непригоден для быстрой работы с данными и неудобен для этой "текучки".И, конечно же, лишняя программа значит лишнюю дыру в защите. Цитирую форум "" []:
From: magistr <>
Вот какая штука:
я тут полез в ya.ru ввел запрос phpmyadmin
она выдала ссылки (внизу)
так я захожу - смотрю а мне дают доступ к phpmyadmin просто !!
я конечно понимаю что на пароль ставить не обязательно, но все-таки как-то даже непривычно - как при коммунизме -- даже двери на ночь не закрывали :0)
это еще вчера было
я пишу на пробу админу lucky.ru мол так и так
так он это дело вроде прикрыл но даже ни слова благодарности!!!!!!!!!!!!
вот так и делай людям потом доброе! ((((
вот так
жду комментариии
phpMyAdmin | Показать найденные слова
Добро пожаловать в phpMyAdmin 2.0.5 MySQL 3.22.23b запущено на 212.164.0.129:3306 Создать новую БД [ Документация ] Показать состояние MySQL [ Документация ] Показать системные переменные MySQL
... Добро пожаловать в phpMyAdmin 2.0.5 ...
... phpMyAdmin-Homepage ...
http://ignispc8.cs.nstu.ru/sql/main.php3?server=1 - 3К - строгое соответствие
Похожие документы | Все с сервера не менее 976 док.From: Vadim <>
Спасибо добрый человек за информацию :) Думаю что первые 20-30 ссылок выданные поисковиком уже лишились баз.
А идея кстати классная, надо будет еще WebAdmin поискать :)From: leosha <>
а каких комментариев ты ждешь?
Знаешь сколько таких деятелей? И это не только по phpMyAdmin, естественно..
А потом газеты пишут "еще один сгорел на работе" (с) Остап Бендер.
Кстати, закрывать доступ от посторонних надо не только на рабочем веб-сервере. Если на домашнем или рабочем компьютере вы держите базу для испытаний, лучше иметь какой-нибудь файрволл ( [], например). Иначе злоумышленник (или просто хулиган), узнав ваш IP-адрес, может зайти на ваш сервер по протоколу HTTP и поадминистрировать вашу базу.
Это всё был рассказ про phpMyAdmin версии 2.1.0. Его разработчики обещают к концу этого года (видимо, уже не получится) выпустить версию 3.0.0, в которой обещаются такие красивые вещи, как абстрактный класс базы данных, который бы позволял системе быть независимой от сервера базы данных (там, ниже - ещё две версии phpMyAdmin специально для других баз данных), а так же сервис настроек и хранения собственных запросов. Про абстрактный класс БД уже собираюсь написать в продолжение темы ООП - уже изучаю.
Объектно-ориентированное программирование и классы, часть 2. Рассыльщик почты
DL21.12.2000
Сперва немного новостей. Вышел .
From: Antonio
[]
[] или .
Возрадуйтесь! Теперь gd работает, даже если php поставлен как модуль апача, ну и ест-но gd знает все (gif,jpeg,png,wbmp).
Добавлен новый модуль php_printer.dll? вероятно теперь из пхп можно выводить чен-ть на печать и еще куча всего гового.
Насчет принтера не знаю, а вот gd с gif ? это хорошо!
Скачал и поставил. Ура! GD под Win32 работает! Когда у меня стоял php/4.0.2 release, я попробовал проверить работу gd. Расскомментировал extention php_gd.dll ? а php мне "unsupported or undefined function Imagecreate". После этого работать с раскомментированной строкой php_gd.dll было невозможно ? php просто повисал при выполнении любого скрипта. Теперь все работает, и это главное!
Теперь по теме.
В я описал его главные преимущества ? структура, группировка используемых вместе функций и переменных, упрощение адресации. Теперь ? о том, как можно применять объекты и классы в программах.
ООП, оно, конечно, вещь хорошая. Но где же его применять? Вот какие условия использования ООП у меня получаются:
1. Необходимо одновременная обработка нескольких переменных (например, передавать их в качестве параметров функции ? писать все переменные не много ли чести? :). То есть, переменным нужно придать некоторую структуру, ассоциацию. Чтобы функция без дополнительных указаний сразу видела, с чем ассоциирована данная переменная, и что с ней делать.
2. Подобных структур данных может быть несколько. Если использовать шаблоны, большинство методов можно использовать повторно. Когда структуры данных несколько различаются, можно использовать механизм наследования.
Описанный мною в прошлом выпуске класс CMail я использовал для рассылки новостей. Но когда количество подписчиков перевалило за 50, программа не успевала выполниться за минуту (именно такое ограничение было у провайдера). Причина ? в том, что рассылался файл с аттачментом, и каждый раз программа его кодировала в base64 и рисовала переносы строк. Класс, правда, не был предназначен для рассылки почты подписчикам. Но это так, детали...
Объектно-ориентированное программирование, классы
DL10.12.2000
Мой путь к пониманию объектов шел слишком долго. Надо сказать, что закончился он чрезвычайно неожиданно - я прочел... мануал PHP 4. Где можно найти толковое описание, только не там, казалось бы... Правда, уже до этого я кое-что знал ("...объект, сочетающий в себе как совокупность данных, так и действий над ними." (с) Епанешников, "Программирование в среде Turbo Pascal 7.0"), но это уже детали.
Что же такое класс и объект. Сперва об объекте. Определение "...сочетающий в себе как совокупность данных, так и действий над ними" - вполне подходящее. Если говорить "приземленно", то объект в PHP - это переменная особого типа. В ней содержатся специально объявленные под-переменные и функции этого объекта (то, что объект содержит переменные и функции, в научной литературе называется инкапсуляцией). Функция is_object на эту переменную выдает true:
if (is_object($objectname)) {
do_something();
};
Обращение к под-переменной объекта производится следующим образом (название, конечно же неправильное, правильно "свойство объекта").
$objectname->property
print ($objectname->property);
Вызов функции (метода) объекта:
$objectname->format_output($format);
Конечно же неудобно писать имя объекта и "стрелочку" ("->") перед нужной переменной, но это только поначалу. Зато потом можно сэкономить большой объем программного кода (и избежать лишней головной боли).
Теперь что такое класс. Класс - значит класс объектов. В PHP-скриптах описывается не объект. Сначала описывается класс объектов, и затем можно создавать сколько угодно объектов этого класса.
class Public_Transport {
var $capacity = 0;
var $passengers = 0;
var $stop = array;
var $current_stop = 0;
var $vehicle = "unknown";
function say_stop () {
echo $this->stop[$this->current_stop];
if ($this->current_stop==sizeof($this->stop)-1)
echo ". Конечная.";
else
echo " следующая - ", $this->stop[$this->current_stop+1];
}
function stop () {
$this->passengers += intval(rand((-1*$this->passengers),100));
if ($this->passengers > $this->capacity) {
echo "Освобождаем двери!";
$this->passengers = $this->capacity;
};
}
function go () {
$this->current_stop++;
}
}
?>
ВНИМАНИЕ! Закрывающая скобка класса должна быть без точки с запятой (""), как и все описания функций внутри описания класса.
Программа, работающая с классом Общественный_Транспорт будет выглядеть так:
$bus = new Public_Transport;
$bus->capacity = 200;
$bus->vehicle = "Лиаз-767";
$bus->stop = array ("Торговый центр", "Поликлиника", "Институт теплофизики", "Вычислительный центр", "Институт ядерной физики", "Институт гидродинамики", "Морской проспект", "Дом ученых", "ул. Жемчужная", "Цветной проезд");
while ($bus->current_stop < sizeof($bus->stop)) {
$bus->say_stop();
$bus->stop();
$bus->go();
};
?>
В этом примере запущен только один автобус, а можно и два, и три, и сколько угодно. Понятно, что это можно повторить и без помощи объектов, но это сложнее, и полученный код не так легко читается, как с объектами, тем более, когда "предметов" несколько.
Объект и его свойства являются обычными переменными. Например, можно работать с динамическими именами переменных:
$a = "bukva a";
$b = "bukva b";
$c = "a";
echo $$c;
?>
Такой код выведет "bukva a". И то же самое можно делать с объектами и их свойствами:
class someclass1 {
var $a = 1;
var $b = 2;
var $c = 3;
}
class someclass2 {
var $a = 4;
var $b = 5;
var $c = 6;
}
$d = new someclass1;
$e = new someclass2;
$f = "d";
$g = "c";
echo ${$f}->{$g};
?>
(такой код выдаст "3")
То же касается и динамических имен функций.
В руководстве по PHP4 написано подробнее о [] и [].
И на прощанье вот что. В начале года мне надо было написать скрипт для рассылки новостей и прайс-листов подписчикам. Зашел я на сайт PHP и заглянул в [], чтобы найти что-нибудь про аттачмент. В комментариях к функции я нашел то, что искал - класс для вложения файла в письмо. За восемь месяцев туда накидали много ссылок на такие классы, а в феврале он был единственный - []. Так вот, как они делают - делать не надо (я тогда в классах разбирался смутно, и просто вырезал функции, несколько упростив код). Процитирую заголовки функций:
class CMailFile {
var $subject;
var $addr_to;
var $text_body;
var $text_encoded;
var $mime_headers;
var $mime_boundary = "--==================_846811060==_";
var $smtp_headers;
function CMailFile($subject,$to,$from,$msg,$filename,$mimetype = "application/octet-stream", $mime_filename = false) {
/* если функция имеет то же имя, что и класс, то это будет конструктор класса (см. ниже) */
function attach_file($filename,$mimetype,$mime_filename) {
/* Вот это не понимаю! attach_file вызывается из функции CMailFile - зачем? Только для красоты. А так - можно было этот кусок кода вставить прямо в главную функцию, раз уж решено сделать единовременный вызов функции. Далее идут несколько функций того же назначения и характера. */
function encode_file($sourcefile) {
function sendfile() {
function write_mimeheaders($filename, $mime_filename) {
function write_smtpheaders($addr_from) {
}
/* А вот пример использования класса. */
// usage - mimetype example "image/gif"
// $mailfile = new CMailFile($subject,$sendto,$replyto,$message,$filename,$mimetype);
// $mailfile->sendfile();
Зачем было оформлять это как класс - непонятно. Только для красоты и разделения функции на несколько штук. А вообще-то, если я захочу на ходу поменять адресата (например, для той же рассылки, когда я прохожу циклом по массиву адресов), надо снова вызывать функцию CMailFile, которая перекодирует файл снова, требуя определенных системных ресурсов.
Раз уж я начал ругать другого, покажу, как по моему мнению правильнее использовать ООП для рассылки писем. В следующем выпуске посвященном приемам ООП (а перед, думаю, ним будуд два других) я хочу привести пример использования класса для отправки почтовых сообщений, в том числе для рассылок.
Обработка строки
Первым делом надо порезать ручками строку.$search = substr($search, 0, 64);
64 символов пользователю будет достаточно для поиска. Теперь каленым железом выжжем все "ненормальные" символы.
$search = preg_replace("/[^\w\x7F-\xFF\s]/", " ", $search);
По идее, нельзя давать пользователю возможности искать по слишком коротким словам - кроме всего прочего, это сильно загружает сервер. Итак, разрешим искать только по словам, которые длиннее двух букв (если ограничение больше, надо заменить "{1,2}" на "{1, кол-во символов}").
$good = trim(preg_replace("/\s(\S{1,2})\s/", " ", ereg_replace(" +", ""," $search ")));
А после замены плохих слов - надо сжать двойные пробелы (они были сделаны специально для корректного поиска коротких слов).
$good = ereg_eplace(" +", " ", $good);
ООП на службе почты
Что возьмём за объект? Правильно, письмо. При рассылке почты многим подписчикам надо будет поменять лишь адрес получателя.class Message {
Свойства класса: содержимое письма, тема, тело письма и служебные заголовки.
class Message {
var $text = "";
var $to = "";
var $subject = "Новости сайта Васи Пупкина";
var $headers = "
From: \"Вася Пупкин\"
Reply-to: \"Вася Пупкин\"
Organization: Студия веб-дезигна Васи Пупкина
X-Mailer: PHP/4
X-Priority: 3 (Normal)
Content-Type: text/plain; charset=koi8-r
";
Конструктор объекта.
function Message($text) {
$this->text = $text;
}
Функция отправки одного письма.
function send() {
mail(convert_cyr_string($this->to,"w","k"), convert_cyr_string($this->subject, "w", "k"), convert_cyr_string($this->text, "w", "k"), convert_cyr_string($this->headers, "w", "k"));
}
Функция отправки писем всем подписчикам.
function send_all($maillist) {
if (is_object($maillist))
if ($maillist->fetch())
for ($a=0;$a
$this->to = $maillist->addr[$a];
$this->send();
};
}
}
И это всё, что есть в классе Message. Список подписчиков хранится в объекте $maillist, который мы передаем в качестве параметра функции (естественно, в теле программы назвать можно его как угодно).
Вместе с классом Message я использую классы Database (пока поддерживает только MySQL, если кто-то пришлет то же самое для другой базы, буду много благодарен) и класс File, которые используются для получения списка адресатов.
class File {
var $filename = "";
var $addr = array();
function File($filename) {
$this->filename = $filename;
}
function fetch() {
$this->addr = file($this->filename);
if ($this->addr)
return true;
else
return false;
}
}
Это класс файла. Класс базы данных ? сложнее. Помимо считывания информации там есть функции конфигурации соединения с сервером (например, программа может устанавливать соединение, а потом его закрывать, или использовать имеющееся), установка логина/пароля/хоста сервера БД. А аргументами к функции-конструктору являются имя таблицы, имя поля и условия выборки.
PhpMyAdmin . бережет нервы и время
DL14.12.2000
Если кто-то говорит "Не надо ставить этого монстра" - не верьте! Надо и очень надо!
phpMyAdmin - утилита для управления базами данных через интерфейс броузера (формы, кнопки, ссылки). Распространяется бесплатно ("open source"), первая публичная версия появилась в октябре 98 года. За более чем два года программа доведена до высокого уровня - она умеет почти всё, что можно делать с базами данных.
В принципе, версией 2.1.0 может пользоваться человек, совсем не знающий SQL (на кой он тогда нужен - другой вопрос). Все операции по созданию, удалению и настройке баз и таблиц можно выполнять именно через эту утилиту.
Разумеется, необходимости знания языка запросов никто не отменял, но досконально учить синтаксис всех запросов (CRETE TABLE, ALTER TABLE, например) не требуется. Тому, кто не очень хорошо знает язык запросов (SQL), эта утилита будет очень полезна. По крайней мере, я больше не пишу запросы в "терминалке" mysql.exe, а создаю таблицы через форму phpMyAdmin. Это, кстати, исключает возможность опечатки в атрибутах полей.
Кстати, он умеет не только выполнять запросы - им можно заменить даже mysqldump.
Отступление. Что такое mysqldump. При разработке сайта дома, имея под рукой MySQL, встаёт проблема - всё готово, скрипты закачаны, а как переносить базу? Смотрим свойства полей базы, и пишем скрипт, который создаёт базу и рисует таблицы. Потом закачиваем скрипт на сервер и запускаем... А можно гораздо проще. Специально для облегчения работы разработчиками MySQL написана программа, которая читает всю указанную базу, ее таблицы и пишет файл с SQL-запросами. Это запросы на создание базы, создание таблиц и записи в таблицы строк. Запустив программу mysql на другом сервере и указав в качестве параметра этот файл, получаем точную копию базы.
Но здесь надо сделать небольшую ремарку: все-таки это PHP, а значит команды интерпретируемые. Поэтому программы "*.exe" работают гораздо быстрее. Если объем данных в таблице большой, дамп базы лучше писать и считывать через программы пакета MySQL.
Что еще хорошего в нем, так это возможность сделать русский интерфейс. В файле config.inc.php3 исправьте "english.inc.php3" на "russian-win-1251.inc.php3" (или "rusian-koi8.inc.php3"). Правда, русский язык там не русский, а машинно-переведенный, поэтому я правил ляпы вручную (забавно то, что в документации написана благодарность кому-то за перевод!).
Все настройки утилиты (в т.ч. хост, логин и пароль) хранятся в том же config.inc.php3.
По следам виртуальных директорий, или как можно настроить VirtualHost
11.12.2001
От автора. Если завтра мне понадобится настроить Apache для работы с очередным виртуальным сервером, я буду использовать именно эту "рыбу".
Здесь просуммирован опыт нескольких специалистов с которыми в свое время мне приходилсь общаться, за что огромное спасибо!
Конечно жизнь не стоит на месте? будут появляться новые проблемы, их решения, будут меняться взгляды, и обстоятельства, но надеюсь мои развернутые комментарии помогут Вам понять суть и обоснование всех пунктов, а значит Вы сможете и дальше уже самостоятельно двигаться в нужном направлении...
"На берегу" хотелось бы отметить один момент ? автор давно уже придерживается идеи о том, что обращения ко всем документам (исключая статические), в пределах одного сайта, должны сводиться в одну точку (н.п. /index.html). Исключения, конечно, бывают, но они, как обычно, лишь подтверждают правило.
Сделать это можно несколькими путями:
- созданием нужной файловой структуры, где все файлы делают только лишь
include(файл_движок);
- созданием нужной файловой структуры, с помощью линков* на файл_движок (кстати, это самый привычный и высокопроизводительный вариант, хотя и имеющий организационные ограничения в плане поддержания достаточно динамической структуры, например, дерево каталога большой торговой фирмы);
- поддержание виртуальной файловой структуры с помощью Web-сервера (здесь тоже возможны варианты, и один из них, для Apache, самый высокопроизводительный, эффективный и гибкий, описан ниже);
* ? "жесткие" линки, работают не только в Unix/Linux-системах, но и в Windows NT, Windows 2000, Windows XP (используйте Far с Alt+F6 или специальные утилиты).
# обратите внимание - для сайта отведена домашняя директория
# при этом всё, что видно через браузер пользователю находится в поддиректории /html/
DocumentRoot "/www/имя_сайта/html" ServerAdmin webmaster@имя_сайта.ru # хороший тон, когда сайт откликается и на полное и на укороченное имя
ServerName www.имя_сайта.ru ServerAlias имя_сайта.ru
# вот еще использование домашней директории - поддиректория для лог-файлов
# удобно, что они всегда под рукой разработчика (некоторые провайдеры
# ВООБЩЕ не предоставляют доступ к логам! :( - это очень неудобно),
# и в тоже время, они недоступны пользователю, т.к. находятся вне DocumentRoot
ErrorLog "/www/имя_сайта/logs/error_log" CustomLog "/www/имя_сайта/logs/access_log" common # Кстати, "common" конечно не единственный формат логов, более того -
# есть возможность самому задать его структуру
# (см. http://httpd.apache.org/docs/mod/mod_log_config.html).
# пользоваль комфортнее себя чувствует видя привычное расширение .html
# в пользу расширения .html и другие аргументы:
# - при сохранение в браузере страницы, пользователь получит файл с
# таким расширением, а потом легко его откроет по Enter`у;
# - если Вы пойдете на поводу у провайдера и воспользуетесь расширением php3, к примеру,
# то не факт, что у другого будет такое же (.phtml - как вариант).
# ну и что - мне то какая проблема, скажете Вы,
# изменить расширения у своих файлов... в итоге после запуска проекта на новом месте
# Вы с удивлением обнаружите падение посещаемости и,
# стремительно увеличивающийся /www/имя_сайта/logs/error_log
# а дело в том, что поисковые системы уже давно проиндексировали Ваш сайт,
# и ещё долго будут водить пользователей на сайт-призрак - они же не знают, что всего то и нужно,
# это изменить расширение скриптовых файлов!
# - в конце концов, это просто больше соответствует истине -
# ведь в итоге получается действительно HTML-файл.
AddType application/x-httpd-php .html
# Кстати, если нагрузка на сайт предполагается большая,
# а часть страниц можно сохранить в "компилированном" виде, т.е. в чистом HTML-е,
# то можно сэкономить немного процессорного времени "отсадив" такие страницы
# в отдельную директорию, и отключить там PHP, например так:
#
#
# RemoveType .html
#
#
# или использовать для "чистых" HTML-документов другое расширение, н.п. .htm,
# но тут могут вылезти проблемы, описанные выше - вдруг эти документы
# нужно будет сделать динамическими, позже, по каким либо причинам,
# и что - менять расширение?
# очень удобно - этот файл будет исполнятся перед каждым обращением к PHP,
# в пределах данного сайта, и сюда удобно вынести присвоение переменных и define,
# а так же include связанные с загрузкой библиотек т.д. и т.п.,
# которые привязывают к физическому расположению, чего-либо.
# этот документ также находится вне DocumentRoot,
# что повышает безопасность и логично отражает иерархию
# возможно Вы скажете, что это ненужный файл - ведь все обращения итак
# обрабатываются одним файлом (н.п. index.html), и именно в нем легко можно сделать
# привязку к физическому положению! можно - но я все-таки считаю, что правильнее,
# когда в единой точке входа сосредоточена логика сайта, а физика - отдельно.
# кроме того, возможны несколько точек входа - в случае с
# виртуальными сайтами или физическими разделами (см. ниже настройку mod_rewrite)
php_admin_value auto_prepend_file "/www/имя_сайта/.startup.html" # расширение .html предлагаю использовать для единообразия картины
# Кстати, это не "стопудовый" PHP -
# или кому больше нравится ?>, всё таки нужно писать...
# Для подобных же вещей (т.е. для комфортной привязки проекта,
# к конфигурации сервера) может служить вот эта директива
#php_admin_value include_path "/www/имя_сайта/include"
# она перечисляет директории, которые будут отрабатываться
# по командам серии include(). Конечно на "домашнем" и рабочем
# сервере это будут разные каталоги (если вы конечно не на одной
# платформе и там и там работаете :)
# для ошибок, происходящих в пределах PHP логичнее всего
# тоже завести отдельный файл, в пределах нашей logs директории
php_admin_flag log_errors On php_admin_value error_log "/www/имя_сайта/logs/php_errors"
# после запуска сайта в плановую эксплуатацию имеет смысл
# выключить показ сообщений о ошибках, т.к. в некоторых случаях
# в сообщениях об ошибках есть информация о конфигурации и путях сервера,
# а это прямой удар по безопасности!
php_admin_flag display_errors Off
# данный флаг управляет автоматической регистрацией в PHP всех переменных
# переданных методами GET или POST
# т.е. фактически это глобальные переменные на ряду с переменными
# определёнными в теле скрипта - вот тут и кроется "дыра" в безопасности сайта,
# ведь если злоумышленник узнает какие переменные Вы используете как внутренние,
# то он сможет управлять Вашим сайтом, через передачу нужных значений в запросах!
# второй аргумент в пользу такого решения - стиль программирования,
# на который стоит обращать внимание при сложном по структуре
# (много связанных модулей, например) проекте - обращаясь к переменной в тексте программы
# программист (имеется в виду тот, который не писал другие модули) может только догадываться
# откуда берется эта переменная: от пользователя, или из программного кода
php_admin_flag register_globals Off # конечно в небольших проектах, которые пишутся на одном дыхании,
# автоматическая регистрация может только облегчить жизнь программисту,
# и многие считают это большим плюсом, тем не менее я данный флаг ставлю
# в Off всегда и получаю значения через свои функции
# хотя бы потому, что код малого проекта может быть использован в большом,
# а там _не_использование_ автоматической регистрации (глобальных переменных то бишь)
# считаю более чем оправданным, особенно при использовании ООП
RewriteEngine On
#RewriteLog "/www/имя_сайта/logs/rewrite_log"
#RewriteLogLevel 9
# произведем обработку запроса
# уберём подряд идущие слэши
RewriteRule (.*)/(/.*) $1$2 [N]
# если какой либо элемент, название директории или имя файла, начинается с точки [.]
# то это считается служебным ресурсом и доступ к нему с клиентской стороны НЕвозможен
# кстати "так думают" ещё ls НЕ показывая по умолчанию такие файлы и samba, которая
# добавляет к таким файлам атрибут hidden
RewriteRule ^(.*/)\.(.*) %{REQUEST_FILENAME} [G]
# физические файлы и директории (в случае наличия DefaultIndex)
# имеют абсолютный приоритет
# если то, что просят, это физический файл,
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} -f # то отдаем как есть и останавливаемся
RewriteRule ^ %{REQUEST_FILENAME} [L]
# это не файл - пытаемся думать, что это директория
# если не было слеша в конце, то делаем редирект
# прилепляя слеш в конец запроса - т.о. пользователь увидит "красивый" адрес
# фактически это может произойти только один раз - если запрос не файл,
# и в конце нет слеша (то, что после ? и сам ? НЕ учитывается!)
# кстати - [R,L] можно убрать,
#RewriteRule [^\/]$ %{REQUEST_FILENAME}/
# и тогда итоговый URL для пользователя будет больше походить на путь к
# виртуальному файлу, чем к виртуальной директории :) и это можно красиво использовать!
RewriteRule [^\/]$ %{REQUEST_FILENAME}/ [R,L]
# если то, что просят, это физическая директория,
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} -d # и в ней есть DefaultIndex файл
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME}index.html -f # то передаём на исполнение этот файл
RewriteRule ^ %{REQUEST_FILENAME}index.html [L]
# если то, что просят, это физическая директория,
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} -d # НО в ней НЕТ DefaultIndex файл
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME}index.html !-f # то передаём на исполнение сообщение об отказе в доступе
RewriteRule ^ %{REQUEST_FILENAME} [F]
# и, если запрос содержит более одной директории,
# то, зацикливаем анализ, откинув последнюю директорию
#RewriteRule (/.*/)(.*/)$ $1 [N]
# ОБРАТИТЕ ВНИМАНИЕ! Данная конструкция может добавить ненужную нагрузку на сервер,
# если Вы используете достаточно длинные (с большим количеством вложенных поддиректорий)
# URL`ы, ведь на каждый запрос она будет производить разложение на составляющие
# и анализ каждой из них, по вышеописанным правилам, поэтому,
# если вам нужны виртуальные сайты с виртуальными директориями в пределах сайта
# (что это такое - см. ниже :), то только тогда "раскоментарьте" это правило!
# а тут оседают все остальные обращения к сайту
RewriteRule ^ /index.html [L]
# Принцип работы:
# 1. Если то что просят файл - всё остаётся как есть.
# 2. Если то что просят директория, и там есть DefaultIndex файл, то управление на него.
# 3. Если дошли до этого шага? значит это и есть "виртуальная" директория -
# теперь нужно теперь найти обработчик для неё. Можно задать его жестко,
# н.п. как DefaultIndex-файл, находящийся в корне сайта,
# но можно добавить нашему ReWrite`у немного интеллекта -
# пусть он попытается подняться вверх по предполагаемому дереву каталогов,
# и там поискать, по правилу п/п 1 и 2, т.е. зациклим анализ;
# 4. Если подниматься больше некуда (или мы и находились в корне), то данную
# "виртуальную" директорию будет обрабатывать DefaultIndex-файл корня сайта.
#
# В итоге мы получим сайт с "виртуальными" директориями, но если часть структуры
# сайта построена физически, то это будет как бы сайт в сайте, т.е. если URL выглядит как
# /test/бла/бла/, и директория /test/ физическая и в ней есть DefaultIndex-файл,
# то именно он получит управление, а не корневой DefaultIndex-файл.
#
# Конечно для Ваших задач может понадобиться
# другое решение - ReWrite наверняка с ним справится! :)
# Если уж тема безопасности затронута, то хотелось бы упомянуть ещё несколько моментов:
php_admin_flag safe_mode On # - safe_mode в PHP это тот режим, который практически полностью позволяет исключить
# возможность несанкционированного доступа пользователя к ресурсам доступ к которым
# (хотя бы даже и только на чтение) имеет PHP, запущенный как модуль Apache:
# 1. Запрещается изменять переменные окружения для запускаемых программ, через putenv();
# 2. Запрещается запускать программы через exec(),
# находящиеся вне указанной директории, см. safe_mode_exec_dir в php.ini;
# 3. Файловые операции корректируются:
php_admin_value open_basedir "/www/имя_сайта/" php_admin_value doc_root "/www/имя_сайта/" # ни одна из файловых функций не сможет открыть файл, если он не входит
# в указаное поддерево (их, кстати, можно указать несколько).
# Подробнее на http://www.php.net/manual/en/features.safe-mode.php
# - disable_functions данная директива позволяет отключить некоторые функции
# (конечно можно это сделать при компиляции, но "геморроя" тогда больше), которые могут
# слишком много и быстро рассказать о конфигурации сервера, например phpinfo()
# или слишком много позволить пользователю, н.п. dl()
# Кстати, данная конструкция (как и некоторые другие, safe_mode_exec_dir)
# должна устанавливаться только из php.ini, т.е. глобальна в пределах одного Apache
# - На тему загрузки модулей есть специальная директива
# php_admin_flag enable_dl Off
# хотя в safe_mode функция dl() и так не доступна
# Кстати, программисту создающему сайт на домашнем сервере имеет смысл установить
# всё настройки (в том числе и по вопросам безопасности) так, как будто этот сервер,
# доступен всем Сетевым Ветрам - просто для того, чтобы потом на реальном сервере
# не вылезла какая-либо несовместимость конфигурации
# Подробности по настройке PHP
# см. http://www.php.net/manual/en/configuration.php#ini.safe-mode
# И последнее, что хотелось бы упомянуть на этут тему: если на Вашем сервере
# размещается несколько десятков или сотен серверов, и многое в их конфигурации
# однотипно, то стоит подумать над использованием одного из механизмов, подробнее
# описанных тут http://httpd.apache.org/docs/vhosts/mass.html
# а конкретно http://httpd.apache.org/docs/mod/mod_vhost_alias.html
#VirtualDocumentRoot "/www/%0/html"
# данная директива используется как шаблон в одном
# для отработки нескольких серверов вместо DocumentRoot
# в нескольких
# где %0 полное имя сервера, т.о. для заведения одного из типичных серверов
# достаточно будет только в нужном месте создать директорию... и все!
# Есть только одна проблема - в директиве php_admin_value %0 останется неизменным :(
# Ну, что ждём от Apache Team создание virtual_php_admin_value или напишем сами?
Яцевич Роман, неважно кто по жизни
отдельное спасибо Бохонковичу Юрию, за консультации по безопасности систем на базе Unix/Linux-серверов а также Лебедеву Дмитрию, за то, что он открыл мне глаза на отнюдь не виртуальную проблему виртуальных директорий!
Подсветка
Чтобы подсвечивать светом или жирным шрифтом искомые слова в тексте, надо сделать всего лишь следующее:$highlight = str_replace(" ", "|", $good);
Пробелы (а они у нас между словами стоят поодиночке, и нигде двойной пробел не встречается, к тому же с концов строки мы их тоже вырезали) достаточно заменить на вертикальную черту - разделитель вариантов в регулярных выражениях. "Плохие" слова мы не подсвечиваем, потому что в базе их не ищем :).
В коде, который выводит текст пишем:
$row["text"] = ereg_replace($highlight, "\\0", $row["text"]);
После написания выпуска я кинулся, было, писать и себе "подсветку". Не тут-то было! У меня в тексте встречаются теги HTML, поэтому пришлось много подумать... Получилась вот такая вещь (строка со словами для подсветки есть):
$text = eregi_replace(">([^<]*)($words)", ">\\1\\2", $text);
Приходится смотреть, нет в теге ли это слово. Однако тут встает проблема ресурсоемкости такой замены (мой K6-266 над текстом в 5 килобайт думал целых семь секунд). Печально.
Поиск в MySQL. Релевантность своими руками
DL9.12.2001
Продолжаю начатую в сентябре тему поиска с сортировкой по релевантности в базе MySQL.
MySQL предлагает в последних версиях базы данных использовать для полнотекстового поиска индексацию FULLTEXT и конструкцию MATCH field AGAINST. Однако не на всех серверах стоит последняя версия MySQL, и не все хостинг-провайдеры хотят обновлять софт по соображениям надежности системы.
В своё время я предполагал, что поиск с сортировкой по релевантности надо будет делать в несколько запросов, и, следовательно, лучше вовсе не браться за это. Мысли, что релевантность можно подсчитывать в самом запросе отдалённо меня посещали, но я боялся и представить такую конструкцию.
Однако же, работник одной из сайтостроительных фирм Н-ска похвастался мне системой поиска, которую они применяют на своих сайтах. Я точно не запомнил запрос, попробую так воспроизвести его:
SELECT title, date_format(material_date,'%e.%c.%y') AS date1, IF(text like '%word1 word2 word3%', 3*10, 0) + IF(text LIKE '%word1%', 9, 0) + IF(text LIKE '%word2%', 9, 0) + IF(text LIKE '%word3%', 9, 0) AS relevance FROM table WHERE text LIKE '%word1%' OR text LIKE '%word2%' OR text LIKE '%word3%' ORDER BY relevance DESC, material_date DESC
Ужасно выглядит, но работает даже на старых версиях MySQL. Попробовал сравнить скорость работы с вот таким запросом:
SELECT title, date_format(material_date,'%e.%c.%y') AS date1, MATCH text AGAINST('word1 word2 word3') AS relevance FROM table WHERE text LIKE '%word1%' OR text LIKE '%word2%' OR text LIKE '%word3%' ORDER BY relevance DESC, material_date DESC
В среднем скорость универсального запроса в два раза меньше, чем использующего новые конструкции. Что вполне логично? чем больше универсальность, тем больше ресурсоёмкость.
Попробуем построить такой запрос автоматически. Как в , отрезаем длинную строку, а так же все неправильные символы и короткие слова. Рисуем запрос.
$query = "SELECT title, date_format(material_date,'%e.%c.%y') AS date1, IF(text like '%". $good_words. "%', ". (substr_count($good_words, " ") + 1). "*10, 0) + IF(text LIKE '%". str_replace(" ", "%', 9, 0) + IF(text LIKE '%", $good_words). "%', 9, 0) AS relevance FROM table WHERE text LIKE '%". str_replace(" ", "%' OR text LIKE '%", $good_words). "%' ORDER BY relevance DESC, material_date DESC";
Не очень-то сложно. Для надёжности и защиты от флуда можно ограничить количество слов в запросе.
Некоторые дополнения к прежним публикациям.
Общее количество найденных строк в таблице. Для вывода результатов поиска, разумеется, надо пользоваться оператором LIMIT (чтобы не писать каждый раз формирование этого параметра, пользуйтесь ). Если никаких операций группировки в запросе не делается, лучше подсчитать количество строк сразу в запросе ? COUNT(*), а не через функцию php mysql_num_rows(). Можете проверить на больших таблицах. Если производятся групповые операции, делаем запрос с COUNT(DISTINCT()), но без GROUP BY.
Подсветка. Если в текстах не бывает html-тегов, жить проще.
$text = preg_replace("/word1|word2|word3/i", "\\0", $text);
Если в тексте теги используются, то есть три варианта а) не делать подсветку б) поскольку теги пользователь не видит (разве что очень любопытный пользователь), то можно сделать поле индекса, в котором не будет тегов а символы [^\w\x7F-\xFF\s] будут заменены на пробелы (именно эти символы вырезаются из поисковой строки в самом начале, так что поиск по ним не производится). Поиск и подсветку в таком случае сделать именно по индексу. в) делать подсветку текста из обычного поля, предварительно вырезав теги функцией [].
Полная версия поискового кода, как всегда, в списке файлов.
Постраничный вывод результатов
Ну, когда у нас есть макет для поиска и количество строк результата поиска, сделать постраничный поиск - пара пустяков. Проверяем переменную $page (не меньше 0, не больше $results_amount/$rows_in_page).В запрос, который подсчитывает количество строк (смотри выше), пишем нужные нам поля и поля для сортировки. А потом дописываем
if ($page==0)
$request .= "LIMIT $rows_in_page";
else
$request .= "LIMIT ". $page*$rows_in_page. ",". $rows_in_page;
(синтаксис: LIMIT <кол-во строк> либо LIMIT <кол-во строк отступа>, <кол-во строк>)
В результате выполнения подобного запроса мы получим именно те самые строки, которые надо выводить на странице.
Для навигации можно либо рисовать ссылки на следующую и предыдущую страницы, либо, что сложнее, делать панель навигации на несколько страниц.
if ($page>0)
print ("предыдущая страница");
if ($page
print ("следующая страница");
Пустой запрос. Повторите попытку.
");Тада скрипт будет гибнуть, если будут пробелы и всяка шняга. А регулярные выражения ГРАМОТНО составлены. Я б так не смог. Пытался повторть, но пока не смог.
| архив | ссылки | форумы | что такое php |
| © , 2000-2002 © , 1999-2002 |
Релевантность
Наверное, в том же [] все видели ссылочку "сортировать по релевантности". Это оно и есть. Сортировка результатов по количеству совпадений слов.Отчасти, кстати, такая сортировка снимает проблему обработки логики поиска. Но с БД MySQL делать такую сортировку очень сложно. Надо сперва выбрать, где есть все слова, потом записи, где разные слова (исключив предыдущие). Если у вас постраничный вывод - то вообще дело труба!
Синтаксис объединений таблиц
Простое соединение - INNER JOIN:SELECT
или
SELECT
или
SELECT
если таблицы объединяются по полю field1.
В таком соединении выбираются только те строки таблиц, которые соответствуют условию объединения - равенство значений полей. Если для строки table1 нет соответствующей строки из table2, строка не попадает в итог запроса. Если же надо подсчитать количество сайтов в рубрике (продолжаю пример с каталогом), такой запрос не совсем подходит - в списке появятся только рубрики, в которых есть сайты. Для подобной операции нужно использовать LEFT JOIN.
SELECT
или
SELECT
если таблицы объединяются по полю field1.
При этом соответствующей строки в table2 может и не быть, тогда в полях из table2 мы получим NULL, а если это групповая операция, как в случае с количеством сайтов в рубрике, тогда в поле будет 0:
SELECT rubs.id, name, COUNT(sites.id) AS sites FROM rubs LEFT JOIN sites ON rubs.id=sites.rub GROUP BY rubs.id
Заметьте: поля id есть в обеих таблицах, поэтому в их обозначении надо использовать имя таблицы. Кстати, если при объединении не используются групповые операции, всё равно лучше менять имя поля оператором AS, чтобы не возникало путаницы.
AleX, 06-12-2000 10:31
Да, и еще, объясните тогда дураку принципиальное назначение ключей, индексов? Ведь при связи селектром они не сильно то используются...
Darth, 06-12-2000 10:44
индексы для быстрого поиска
первичные ключи дла однозначной идентефикации строки в таблице
внешние ключи для связи таблиц
ну конечно MySQL не все поддерживает :(
Немного позже я собираюсь вернуться к этой теме и продолжить публичную разработку новостной ленты.
Создание таблицы
Итак, что нам нужно в новостной ленте? Сам текст новости, дата... ну и пусть ещё у новости будет заголовок. Берём phpMyAdmin, создаём базу данных (можно и не создавать). Жмём на её название в списке баз. В правом окне помимо (пустого) списка таблиц текущей БД есть формы действий. Находим форму "создать таблицу". Пишем имя таблицы news, и число полей - нам нужно 4. Вводим информацию о полях таблицы:Имя поля Тип данных Пустое Дополнительно news_id MEDIUMINT NOT NULL AUTO_INCREMENT (для этого поля надо поставить галочку "первичный")
ntext TEXT NOT NULL ntitle VARCHAR(255) ndate DATETIME NOT NULL
Поле news_id не нужно для вывода ленты, но необходимо для администрирования новостей. AUTO_INCREMENT означает автоматическое задание значения поля при создании новой строки таблицы.
В ntext будет храниться сам текст новости. Длина одной заметки может быть до 65 килобайт. Пометка NOT NULL означает, что поле не может быть пустым, а при попытке вставить в таблицу строку, в которой поле ntext будет пустым, MySQL сильно руганётся.
Заголовок новости - ntitle - пусть будет опциональным. Иногда заголовок просто неуместен, или придумать его сложно. Максимальная длина заголовка в нашей таблице будет 255 символов.
Поле ndate содержит дату и время новости. Естественно, что оно не может быть пустым - по нему идёт сортировка таблицы.
А вот так выглядит запрос на создание таблицы:
CREATE TABLE news (news_id MEDIUMINT NOT NULL AUTO_INCREMENT, ntext TEXT NOT NULL, ntitle VARCHAR(255), ndate DATETIME NOT NULL, PRIMARY KEY(news_id));
Программный код:
$request = "CREATE TABLE ... ";
mysql_query($request);
if (mysql_error())
echo "Ошибка БД в запросе "$request". MySQL пишет: ". mysql_error();
else
echo "Таблица создана";
Статистика поиска
Неплохо будет сразу информировать пользователя, сколько он нашел строк таблицы. Для этого делается дополнительный запрос в базу:$query = "SELECT id FROM table WHERE field LIKE '%". str_replace(" ", "%' OR field LIKE '%", $good). "%'";
Для статистики по отдельным словам можно сделать следующее:
$word = explode(" ", $search);
while (list($k, $v) = each($word)) {
if (strlen($v)>2)
$stat[]="$v:". mysql_num_rows(mysql_query("SELECT id FROM table WHERE field LIKE '%$v%'"));
else
$stat[]="$v: короткое";
};
$word_stats = "Статистика слов: ". implode("", $stat). "
";
unset($stat);
Связь без большой натяжки
Вообще, никакой натяжки в описании связей в запросе не было и нет - испокон веков БД работали именно так. Access со стрелочками и формопостроителями появился намного позднее.А теперь об "основательном".
Связь между таблицами - суть реляционной базы данных. В идеале вне базы данных не держится никакая информация. Внутри базы разные по сути вещи разделяются на разные таблицы - например, сообщения и форумы, сообщения и их авторы (если есть регистрация участников), возможно даже в отдельную таблицу выносится разрешения доступ к форумам персонально для каждого участника. При этом данные лежат там, где надо, не смешиваются друг с другом, и не повторяются лишний раз. Это и есть основной смысл реляционных БД. За исключением сложных задач (например, построить дерево обсуждений форума), выборка данных производится одним запросом. Никакие массивы использовать не надо.
Вывод новостной ленты
Итак, мы уже соединились с сервером БД и выбрали базу (mysql_connect и mysql_select_db). Теперь осталось вывести записи. Отправляем запрос, проверяем на возможную ошибку и выводим строки.$request = "SELECT ntext, ntitle, ndate FROM news";
Теперь бы только отсортировать данные... Изменим в запрос:
$request = "SELECT ntext, ntitle, ndate FROM news ORDER BY ndate DESC";
это означает, что сортировка идёт по полю ndate в порядке убывания (DESCending) даты (2000-12-05, 2000-12-04, ...). Сортировка по возрастанию - префикс ASC (ASCending), обычно сортировка по возрастанию установлена default и этот префикс использовать не надо.
Теперь выводим ленту:
$request = "SELECT ntext, ntitle, ndate FROM news ORDER BY ndate DESC";
$result = mysql_query($request);
if (!mysql_error()) {
// Цикл, вынимающий строку как массив с числовым индексом
while ($row = mysql_fetch_row($result)) {
print("
". $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)
Забавно читать, как в форуме пишут:
- Как мне быть с генератором случайных чисел?! неправильно работает!
- А зачем тебе?
- Да в базе id использовать...
В общем, не надо самодеятельности.
Советы, кажется, уже исчерпаны. Напоследок. С недавних пор я стал думать, что при написании скриптов, работающих с БД, надо ориентироваться не только на глупого и шаловливого посетителя, но и на криворукого администратора. Даже если мы внимательно будем следить за текстом, который вставляем в текстовое поле (одинарные кавычки не писать, делать их автозамену в Word-е, белое не носить), вероятность попадания служебных символов в запрос ненулевая.
Запросы на выборку данных (SELECT)
Даже если вам не грозит "падение" от наплыва посетителей, лучше взять себе в привычку, чтобы потом не было проблем с адаптацией к новым задачам. Теперь о безопасности работы.
К тому же это лишний барьер на пути взломщиков вашего сайта. Пример "взлома" простой:
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 '%'"); То есть все пароли будут одинаковые. Это только один пример. Итак,
Имитация файлов и директорий
DL16.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, нужно немного изменить строку шаблона. Пусть сервер ищет файлы без расширения, то есть те, у которых в имени нет точки:
Очень удобно! Когда-нибудь поставлю такое же и себе.
Бизнес в интернете: Сайты - Софт - Языки - Дизайн
- Киберсантинг
- Киберсантинг как бизнес
- Виды Киберсантинга
- Создание игр
- Дизайн как бизнес
- Dreamweaver
- PHP
- Homesite
- Frontpage
- Studio MX
- Сайтостроительство
- Citrix MetaFrame
- Стили сайта
- ActiveX на сайте
- HTML как основа сайта
- Adobe GoLive
- Что такое WEB
- Мобильные WAP сайты
- 3D графика на сайтах
- 3DS MAX графические решения
- Графика в 3D Studio MAX и на сайте




Проект open source, поэтому ошибки быстро находятся и исправляются. Скачайте этот форум, посмотрите на объём файлов. Вам всё ещё хочется писать свой?
$title