PHP в деталях

         

Комментарии к статье ""


11.9.2001 10:26  Валера
А под WIN будет работать? т.е. я все делаю дома, и это надо скрыть от хитрого народа?
11.9.2001 11:42  Max  []
Привет!

У тебя есть вот этот самый "ключ" для этой утилиты?

Если есть, то на каких условиях можно его получить?

Успехов!

Пока.

Макс.
18.9.2001 08:20  John Forest  []
Кстати а почему бы и правда ключиком не поделиться!? :o) Так заманчиво все описанно! :o)
19.9.2001 12:01  Vitaliy N. Kravchenko  []
И еще: Если будут проблемы с установкой, то можно написать на optimizer@zend.com, ребята шустрые,- сразу же отвечают..

:)

Удачи!
24.9.2001 14:21  PHPClub  []
Я так думаю - ничего нестоит добавить в Optimizer проверку

if fake_serial exit;

И все кодированные ворованным энкодером скрипты пошлют тебя :)))
6.10.2001 10:59  Dennis  []
"тащить к хостеру закодированный проект" - я не понял, а что у любого хостера закодированные скрипты будут работать??? а разве у хостера не должен быть установлен такой же энкодер?
10.10.2001 19:14  Soyuz  []
Может кто ссылочку кинет на ключик, то? Буду примного благодарен.
16.10.2001 03:14  [=COM=]  []
Ну народ ну детский сад... Естественно будет везде работать где установлен Zend Optimizer и под любой OS под которую есть Optimizer. А он стоит практически у всех провайдеров. Енкодер не нужен для раскодирования тоесть у провайдера его не нужно...
21.10.2001 16:03  Максим  []
Работает Linux,Windows,etc... У хостера должен только Optimizer стоять, а вот если у кого ключ есть то уж поделитесь не жадничайте...
20.2.2002 10:03  Роман  []
Валера, да, работать будет!



ДАЙТЕ КТО НИТЬ """КЛЮЧИК"""

ПЛИИИИИЗ.
20.2.2002 10:06  Роман  []
Под вин работает.

дайте кто нить КЛЮЧ плиз.
23.6.2002 18:29  IM-WIZARD  []
граждане однодумцы .... неумеете вы искать то в инете!

если вы найдете ... вам будет очень смешно!

на видном месте лежит!

но пока ничего не скажу ... кому надо ... обращайтесь!
архив | ссылки | форумы | что такое php
© , 2000-2002
© , 1999-2002

Комментарии к статье ""


25.9.2001 17:09  hsw  []
В случае, если в WHERE используется функция MATCH,

"relev>0" и "order by relev desc" выполняются автоматически.

см. http://www.mysql.com/doc/F/u/Fulltext_Search.html
Ответ DL:

Как раз нет - сам проверял и по урлу, который ты написал, из примеров видно.
27.9.2001 17:55  Slach  []
Это ... Дима...

Ты погорячился немного...

во-первых ищет оно СТРОГО по совпадению слова... по части слова не ищет...

Во-вторых, такой полезной штуки как индекс цитирования тоже нет.. .увы...

Вот ты бы лучше про UdmSearch рассказал... т.е. откуда что качать (и про win32 тоже)... и главное как ставить настраивать... компилить...
Ответ DL:

См. следующий материал. А до UdmSearch руки никак не дойдут :(
28.9.2001 15:04  Gray Angel  []
Я скачал файл pma.rar, архиватор отказался его открывать, сказал, что битый архив.
28.9.2001 16:40  Максим Деркачев  []
> 5. Нельзя поле relev использовать в клаузе WHERE:

Подозреваю, результата, подобного WHERE можно добиться используя HAVING - он накладывается уже на результирующий датасет, а не на саму таблицу, как делает WHERE.

Еще несколько замечаний.

1. FULLLTEXT SEARCH не ищет фрагменты слов - только целые слова. Т.е. AGAINST('Linux') выдаст все про Linux (вне зависимости от case), тогда как AGAINST('Linu') не вернет ничего про Linux.

2. FULLTEXT SEARCH ищет всё по "или", т.е. записи, в одной из которых есть "linux", а в другой - "RedHat", будут обе видны в результате запроса "Linux RedHat".

3. Конструкция MATCH .. AGAINST(...) направленная на поля в присоединенной (правой) таблицы при LEFT JOIN, вставленая в WHERE для ограничения выборки, не ограничивает эту выборку. Сумбур конечное, но попробую объяснить на простом примере.

Запрос:

SELECT a.id, b.txt FROM a LEFT JOIN b ON (a.id = b.id) WHERE MATCH b.text AGAINST('something')

Мы ожидаем, что в результат войдут поля из объединения, где поле txt содержит 'something'. На самом деле это не так. В результате будут все id из таблицы a, и что придется - из таблицы b - MATCH AGAINST в WHERE работает не так, как всё остальное.

Если же мы поменяем LEFT JOIN на RIGHT JOIN (сначала ограничим выборку по b, а потом прицепим то что следует из a), то всё заработает так как надо.

4. Что-то было еще, но вылетело из головы :)

Резюмирую: FULLTEXT - мощная штука, но очень специфическая. Посему, пользуйтесь, но don't cut off your eggs by an accident.
<

Комментарии к статье ""


1.10.2001 09:33  hsw  []
По поводу смеси LIKE + MATCH: посмотрите на эти запросы с помощью EXPLAIN.

Без использования индексов идет просмотр всей таблицы, а это грустно и медленно.
19.10.2001 13:58  100  []
Я протащился от письма... =)
13.11.2001 13:05  Vlad  []
Привет.

Люди, помогите решить задачку...

В MySQL'ой табличке лежит Blob. А надо слить его клиенту в виде файла...

Причём имя файла не должно совпадать с именем сливающего его скрипта. Вот.

Буду благодарен.
архив | ссылки | форумы | что такое php
© , 2000-2002
© , 1999-2002

По следам поиска ещё раз


DL
28.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:

Что-то вы напутали, Семен Семеныч.

[] и &nbsp [];? дело рук двух разных людей ? Дмитрия Лебедева и Артемия Лебедева соответственно.

Оказывается, благодаря моим усилиям Артемий "программульки писать могёт". Мне лестно.


По следам поиска в "деталях" - релевантность



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.

Вот, собственно, и все.



Администрирование базы данных


Способы администрирования БД в порядке убывания удобства:

[] (весьма рекомендую!)

Написать скрипт, который бы передёргивал базу (см. пример)

mysql.exe в пакете mysql

mysql_manager.exe (там, вроде, как-то можно, только на грани шаманства)

Особенно рекомендую первый способ. С ним не придётся изучать запросы ALTER TABLE, ADD COLUMN и т.п. Я их не знаю до сих пор. Тем более, что "такие вопросы, товарищ посол, с кондачка не решаются" - когда вам понадобится автоматически изменить структуру базы или таблицы? Пару слов о втором способе. Это так сказать обходная технология, которую я применял, не зная про phpMyAdmin и утилиту mysqldump. В скрипте пишутся команды, удаляющие базу и создающие её вновь. Когда-то помогало, но вообще это, ещё раз скажу, обходная технология, "подпорка".

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



Комментарии к статье ""


28.12.2000 12:01  tony2001  []
Да. Для новичков очень нужная вещь.

Раньше такого ресурса явно не хватало.

Молодцы!
Ответ DL:

Стараемся...
28.12.2000 14:41  laborant  []
Спасибо
28.1.2001 19:49  Татьяна Сергеевна Карпова  []
Я руковожу коллективом студентов и мы выполнили с использованием PHP инициативный серьезный проект, который условно назвали "Виртуальный университет". Это не просто информационная система - наши пользователи: преподаватели, администрация и студенты, каждая группа имеет свой сложный интерфейс и массу функций. В качестве СУБД выбрана Oracle8i.

Начинали разработку на PHP3.0 и на MS NTserver4.0, за несколько дней почти без труда перенесли проект на LINUX+Apach+PHP4.0 - все заработало, только с кодировкой немного пришлось повозиться. Позавчера проводили комплексное тестирование, 25 человек сдавали экзамен.

Когда начинали заниматься проектом - более всего было проблем с документацией, с советами и примерами. Поэтому считаю, что Ваш сайт очень нужен, пока не даю ссылку на наш проект, т.к. гостя мы убрали на реальное тестирование, там очень серьезные задачи студенты решают, если тема вас заинтересовала, напишите, пришлю логин и пароль и адрес.

В издательстве Питер уже в печати находится перевод книги"Web Application Development with PHP4.0" Tobias Ratschiller, Till Gerken к ней прилагается диск, я думаю, что это первая книга по PHP на русском, или я не права?
Ответ DL:

Искренне рад за вас. Что касается книги, то в о них была информация. По-моему, книги на русском о php уже появились.
28.4.2001 01:02  Peredozzz  []
Да круто все, вааще не вопрос!
7.10.2001 18:09  Владислав  []
А как быть человеку, который хочет разобраться во всем это великолепии (базы, запросы, встраивание результатов их деятельности в страничку и т.д.), НЕ ЗНАЯ вообще НИЧЕГО о Linux, Apache???
Ответ DL:

Ну, я по большому счёту не знаю ничего о Linux - и ничего :)
8.10.2001 16:00  Олег  []
Недавно я сконфигурировал PHP для работы с MSSQL , но ненашел не одного мало мальского русского ресурса по этой теме, не хотели бы вы взять на себя раскрытие этой темы. Да и еще, не плохо было бы если у вас на сайте вся справочная информация включая архив была бы структуирована в один файл(например zip), скачал и на печать, и не надо было бы бегать по сайту и собирать страницы в один файл!

Заранее благадарю.
<

Обработка ошибок запросов


Сообщение о последней ошибке можно получить через функцию mysql_error:

echo "Ошибка базы данных. MySQL пишет:", mysql_error();

Если результат функции пишется в переменную, можно проверить её:

$result = mysql_query($request);

if (!$result)

echo "Ошибка базы данных. MySQL пишет:", mysql_error();

else {

  echo "<table>";

  while ($row = mysql_fetch_array($result))

    echo "<tr><td>", $row["field1"], "</td><td>", $row["field2"], "</td></tr>";

  echo "</table>";

  };

Если в переменную не пишем, то так:

$request = "UPDATE (...)";

mysql_query($request);

if (!mysql_error())

  echo "Обновление данных прошло успешно!";

else echo "Ошибка базы данных. MySQL пишет:", mysql_error();

Если запрос генерируется автоматически, можно выводить и сам запрос (полезно создавать переменную, которая бы его содержала, и использовать её в качестве параметра функции).



Работа с базами данных. Начало.


DL
21.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 "<table>"; while ($row = mysql_fetch_array($result))   echo "<tr><td>", $row["field1"], "</td><td>", $row["field2"], "</td></tr>"; echo "</table>";

Функция 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=...");

Соответственно, если мы выполним запрос-выборку и не запишем результат в переменную, данные не будут храниться нигде.



Безопасный и удобный поиск


DL
18.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 "<a href=", $row["url"], ">", $row["name"], "</a>";

  echo "(рубрика <a href=rub.phtml?id=", $row["id"], ">";

  echo $rub[$row["rub"]], "</a><br>";

  };

Теперь выбираются новости, и вместо выбранного номера рубрики вставляем соответствующий элемент массива рубрик.

На самом деле, можно избавить себя от необходимости тащить сквозь всю программу этот массив $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 "<a href=", $row["url"], ">", $row["sitename"], "</a>";

  echo "(рубрика <a href=rub.phtml?id=", $row["rub_id"], ">";

  echo $row["rubsname"], "</a><br>";

  };

Итак, здесь лучше использовать запрос "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". Получается, что мы имеем готовый массив, заботимся о выводе только его элементов и пишем меньше кода.



Комментарии к статье ""


28.12.2000 10:09  Антон Сурженко  []
Огромное спасибо за информацию - я полностью перепечатал для себя все содержимое Вашего сайта и изучаю его пошагово. Большое дело делаешь, товарищ!
Ответ DL:

Сколько народу весь сайт качает - вы бы видели! :)
29.12.2000 12:28  Тимофей  []
Спасибо! С Новым Годом всех.
18.5.2001 14:10  ioanna  []
A ya muchaus s novostami srazu v neskolko tem(punktov menu), chtob avtomaticheski opredelallos v kakoi punkt menu, da esche i s pdf-file, vrode v bd vse est', no insert kakoi-to nesuraznii, ne mogu ponat' pochemu vivodit srazu v neskolko punktov menu.

Site u vas milii ochen', pravda nekotorie stranitsi nemnozhko medlenno, zato i dla uma skolko, i udobno navigirivat, slovom, SPASIBO
4.6.2001 11:07  yu  []
> Этот формат - для типа DATETIME. Если тип данных DATE, то надо использовать "Y-m-d", и если TIME, >соответственно "H-i-s". Переменная $date здесь содержит дату/время, определенные функцией mktime.

если TIME то формат не "H-i-s", а "Н:i:s" - по меншей мере по умолчанию.
Ответ DL:

По фигу, какой разделитель - и тире и двоеточие съест.
21.8.2001 00:07  Rodriges
Все работает как часы !!!

Но как быть с новостями большого объема? Пока не понятно. Как выводить красиво в виде абзацев на страницу? Понятно, что вопрос глупый, но все же...
3.5.2002 15:58  Voland  []
Все гениальное просто! Большое спасибо всем, кто помогает новичкам освоиться в новом мире PHP! Отдельное спасибо создателям сего сайта!
архив | ссылки | форумы | что такое php
© , 2000-2002
© , 1999-2002

Комментарии к статье ""


29.12.2000 16:26  addslashes
http://www.php.net/addslashes - read!
Ответ DL:

Ну, да, Oracle - есть Oracle :)
21.1.2001 17:34  BOLK
> Данные типа DATE, TIME, DATETIME и TIMESTAMP можно форматировать с помощью функции
> date_format (см. руководство по mysql). Используйте его, и не форматируйте данные
> через php - это не просто "самодеятельность", а ещё и растрата системных ресурсов.

Можно подумать, что MySQL, форматируя дата не тратит те же системные ресурсы... хотя если сервер СУБД на другой машине, то тратятся конечно другие. Но все равно, те же яйца.
Ответ DL:

Тратит, но на порядок меньше ? а это существенно. Эта функция полезна не только этим. Например, построить из логов распределение посещений по дням недели (группировка нужна) ? это только через date_format можно.
21.1.2001 17:37  BOLK
"обработать его с помощью intval: $id = intval($id))" Будет короче: $id = (int) $id. А еще забыли о крайне полезной штуке - LIMIT
Ответ DL:

В одном из следующих выпусков LIMIT описан.
8.2.2001 16:43  Олег
$somefield = str_replace("'", "'", $somefield).

Хммм... А может быть правильно $somefield = str_replace("'", "", $somefield)?
1.3.2001 10:18  pv@  []
>>>> По возможности минимально используйте LEFT JOIN для объединения таблиц. Это весьма трудоёмкая операция для базы данных.

На самом деле - это утвержденгие не совсем корректно. LEFT JOIN работает быстро при условии что правильно построены индексы :).

А проверить насколько сложен запрос и какие ресурсы будут задествованы можно при помощи:

explain YOUR_SQL_CODE_HERE ;

Enjoy!
Ответ DL:

И если таблицы не очень большие :)
19.3.2001 19:45  Mike  []
> $somefield = str_replace("'", "'", $somefield). Хммм... А может быть правильно $somefield = str_replace("'", "", $somefield)?

А можно просто пресечь все(кроме a-z 0-9 - кто-нибудь серьезно пользуется логинами типа ##kolya или $%^vasya?) Из полученной строки исключаются все ключевые слова SQL.

Если это имя пользователя, и при регистрации он захотел себе странноватое имя, напоминающее SQL запрос, то вываливается сообщение типа "Это имя уже занято..." :)

Проще надо быть, а то живете почти что на физическом уровне OSI, про простейшие вещи забываете. И про методологию проектирования БД почти ничего нет. Так в основном, примитивные вещи. Скажете - а зачем это "им"? "Нам" это было бы полезно... Местами...

После нас эта "БЭДЭ" достанется другим. Очень не хочется, чтобы кто-нибудь плевался из-за того, что я когда-то считал себя Программистом.
Ответ DL:

Ой, давно пора там исправления делать, руки не доходят. :)
<

Комментарии к статье ""


28.12.2000 10:16  Роман З.  []
только изучаю PHP+MySQL, Ваша статья оказалась крайне полезной, спасибо!

SELECT FROM table1 LEFT JOIN table2 USING (field1) этой команды я не знал до прочтения (почему то в MySQL Reference Manual ее не нашел, но теперь буду пользовать...
Ответ DL:

Хм... я её именно там и нашел.
11.1.2001 01:50  Андресон  []
Уважаемый DL. Все это красиво - спору нет. и знакомом с детсва. Но не замечал ли ты, что два простых запроса обрабатываются Мускулом быстрее чем один сложный!?... понаблюдай, советую.

с уважением, Андресон.
Ответ DL:

Это так, только в задачу входит количество строк в результате выполнения запроса. Так, например, выборка всех логов по главной странице сайта заняла 2.25 секунд, а count(*) этих строк заняла всего 0.22 секунды. Если вы предлагаете делать два простых запроса вместо одного сложного, а потом их результаты обрабатывать черезе php, то imho это зря. Такая схема будет дольше работать, если строк много.
11.1.2001 11:05  Artem  []
to: Андресон

А что понимается под двумя простыми запросами и одним сложным?

Можно мне пример привести, где сложный выполняется медленее?

Я лично использую весьма сложные запросы (было и по 10 таблиц)

И все они выполняются достаточно быстро.

Я видел тормоза если в условиях есть оператор OR - он очень плохо оптимизируется MySQL'ем.

А пока связи типа AND MySQL оптимизирует запросы очень хорошо.

Правда я в последнее время живу на v 3.23.
15.1.2001 16:33  TEMiK  []
Join это просто, код выглядит прекрасно. Я делал обработчик базы 200 тыс записей, объединение таблиц жутко тормозило, пришлось все переделать через простые запросы. Прирост скорости 10-20 раз.
архив | ссылки | форумы | что такое php
© , 2000-2002
© , 1999-2002

Комментарии к статье ""


2.1.2001 13:16  F.D.K  []
есть ли еще где-нибудь информация по классам на русском кроме переведенного мануала ?
Ответ DL:

Например, . Но это описание PHP3, так что оно весьма отстает от жизни.
4.1.2001 10:23  Ministry  []
Опа,

а я и не знал что в ПХП есть классы )))

Спасибо,

былобы интересно еще почитать что-то подобное
Ответ DL:

Например, :) Ведь там ясно написано: Classes and Objectt
4.1.2001 18:54  mick  []
Я думаю, что автор класса

просто отдал дань традиции написания классов, когда

считалось хорошим тоном разделять конструкотры и метода вплоть до того, что

для каждой публичной переменной существовала функция для получения ее значения и

функция для занесения в нее.

А так же исключительно для читаемоти кода удобнее разделять функции между собой, причем это касается не только классов.
Ответ DL:

Да, возможно, это так и есть.
30.1.2001 07:46  Дмитрий  []
Здравствуйте!

есть такой вопрос.

если сможете помогите. Очень надо.

есть

class A {

var a;

var b;

.....

function pr(){

echo "a";

}

}

class B extends A{

function pr(){

echo "b";

}

}

Так вот если создадим объкт класса B

и напишем b->pr(); то напечатается b, но надо

чтобы вызвалась сначала функция из класса A а потом из класса B.

т.е.

class B extends A {

function pr(){

a = convert(b)//вот такую фунцию необходимо написать

a->pr();

echo "b";

}

}

я конечно понимаю что задание не имеет смысла и что на php4

можно просто написать a::pr();

но если сможете пожалуйста ответьте

можно на почту.
21.2.2001 16:06  lodevar  []
Господа! Проверяйте грамматику перед опубликованием на сайте странички, а не после того, как вас об этом попросят.

На этой странице после формы отзыва есть слова:

"Нажимая эту кнопку вы соглашаетесь со следующим:

1)ваш отзыв - публичное высказывание, всю ответственность за него несете вы."

Насколько я знаю, после деепричастного оборота «нажимая эту кнопку» должна следовать ЗАПЯТАЯ!

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

С наилучшими пожеланиями.
Ответ DL:

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

Комментарии к статье ""


28.12.2000 01:45  evilpunk  []
На счет локальных firewall - я бы рекомендовал ZoneAlarm от ZoneLabs (http://www.zonelabs.com - по-моему так(?)), так как она управляет доступом для каждой программы и связывает с ней некий ключик - потом троян уже не сможет прикинуться ICQ и вылезти в сеть.
Ответ DL:

Не пробовал, так что комментировать не буду :) Но адрес в ссылки добавлю.
28.12.2000 13:04  eagle  []
Не совсем ясно, а зачем оно надо.

Того же можно добиться, используя

MySQL Administrator for Windows.

И риска при перехвате меньше и сервер меньше грузится и быстрее?
Ответ DL:

Ну, на вкус и цвет...
29.12.2000 19:29  Leonid  []
Спасибо.

Информация пришлась кстати. Возможно спас свою базу ...
Ответ DL:

:))))
9.1.2001 10:48  iGORmnemonic  []
А как быть людям, у которых нет своего сервера и они хостятся у удаленного провайдера? Стоящий у него phpMyAdmin 2.0.1 предлагает не слишком широкий набор функций - даже пароль на базу не сменишь.
Ответ DL:

Закачать свой phpMyAdmin версии 2.1.0 и поставить пароль на него.
17.1.2001 14:38  cat  []
Очень все интересно. И про то как какой то Вася забыл закрыть доступ к PHPAdminu :))

Я вот посоветовал бы автору действительно очень серьезно обсудить возможность написания абстрактных классов для доступа к любой базе не обязательно к MySql. Тут можно многое наговорить :)) Да и былобы интересно вообще такой проэктик замутить на общественных началах. Очень нужнаю вещь.
Ответ DL:

Такой класс уже существует. Собираюсь его описать.
23.2.2001 19:18  Сергей  []
У меня несколько вопросов.

Можно отвечать и на email

1.Непонятно как в phpPgAdmin задавать названия колонок, типа "Наименование предмета" у него можно только "Наименование_предмета" что понятно совсем не хорошо.

2. Где и что надо указать что-бы он в левом фрейме показывал так-же таблицы созданные через create view ?
2.6.2001 09:25  Константин  []
Поставил phpMyAdmin.

php 4.0.5+mysql-3.23.38+winME;

При попытке создать новую базу данных

MySQL said: You have an error in your SQL syntax near '' at line 1

В чем дело?

Да, на сайте MySql есть MySqlGI хоть он пока и кривоват но всетаки работает. Интересно знать мнение других об этом
Ответ DL:

Если вместо одной кавычки в запросе появляются две - значит у ваc стоит sybase_quote=1 (в SyBase кавычка эскейпится второй кавычкой).
<

Комментарии к статье ""


30.12.2000 06:16  Bls
mysql_num_rows(mysql_query("SELECT id FROM table WHERE field LIKE '%$v%'"));

ну кто же так делает. есть в sql конструкция count: select count(id) as kolichestvo from table where field LIKE '%$v%'
Ответ DL:

...А потом придется лишний раз вытаскивать массив и из него брать элемент.
, пункт 18. Лишние переменные - лишняя память.
31.12.2000 03:31  Bls
В той конструкции которой пользуешься ты нагружаетя не только память:

mysql приходится выполнить выборку всех id удовлетворяющих условию. Потом сохранить их в ожидании пока Вы к ним не обратитесь. (предположим что это 20 целых чисел).

В противовес этому при использвании count mysql хранит 1 целое число. Убедил ? :)
31.12.2000 03:33  Bls
получился разнабой (ты, Вы)

надеюсь Вы не в обиде :))
Ответ DL:

Да нет. Просто imho код более читаем, а внутри mysql производительность та же - либо делается группировка, либо подсчет кол-ва строк. К тому же не надо делать mysql_free_result (а если база большая, это необходимо). Так что, это дело вкуса, и того, кому как удобнее.
6.1.2001 23:43  Sergey  []
Если говорить о поиске, то интересно было бы узнать не о SELECT из БД, а о поиске как таковом. К примеру - если требуется быстро искать на PHP в "куче текстовых файлов", то как это делается (индексация в той же базе и тд)
Ответ DL:

Мне не совсем понятно определение "поиск как таковой". Чем SELECT не поиск? :) А для быстрого поиска в файлах есть хорошо отработанные утилиты, как .
12.1.2001 13:22  Axel  []
А почему символы с x7F по xFF стали "ненормальными" ?

или неправильно понял смысл функции $search = preg_replace("/[^(w)|(x7F-xFF)|(s)]/", " ", $search); ?

Best regards.
Ответ DL:

Там есть символ '^', инвертирующий строку, то есть все кроме этих символов будет резаться.
3.2.2001 02:18  falcon  []
Кое что в "обработке строки" все таки не предусмотренно...

А именно выход, если были введены одни пробелы.

После trim переменная станет равной нулю и mySQL в запросе это не понравится.
Ответ DL:

Посмотрите в - там это предусмотрено.
1.3.2001 18:21  Смоляное Чучелко  []
К вопросу о count(id): ежели так уж хочется поэкономить память, можно ведь написать mysql_result($result,0,1)
Ответ DL:

Кстати, экспериментальным путем установлено, что

$r = mysql_query("select id from ... where ...");

$num_rows = mysql_num_rows($r);

при большом количестве строк в результате выборки медленнее, чем

$r= mysql_query("select count(id) as num_rows from ... where ...");

$num_rows = mysql_fetch_row($r);

$num_rows = $num_rows[0];

(можно проверить, выполнив запросы через терминал).
<

Комментарии к статье ""


5.1.2001 12:30  Igor  []
Все понятно и доступно! Стиль изложения в этой статье относится к самым ценным и редким, и, естественно, полезным.
Ответ DL:

Спасибо!
5.2.2001 17:31  StanD  []
А если я буду рассылать этим мэйлером что-то около 10000 писем?
Ответ DL:

Будет тормозить. Основное время уйдет, правда, не на работу скрипта (в смысле переваривание команд), а на выполнение команд - именно рассылку писем. В одном форуме php-клуба недавно поднималась тема.
27.2.2001 20:36  GROL  []
А как быть если надо приатачить файл(ы)?
Ответ DL:

В сети полно скриптов по отправке письма с аттачментом ().
11.3.2001 21:26  Alexander Garbuz  []
Очень хорошо и подробно написано. Единственное что я бы изменил - текстовые комментарии оформил бы как коментарий в теле программы. Типа // комментарий. А в остальном Cool!!!
31.3.2001 21:17  fish  []
По классам вопрос. Если я хочу обьявить переменные которые используются только в методах класса то есть промежуточные, как мне их обьявлять и пользовать, и где про это почитать можно?
Ответ DL:

Так же, как и обычные переменные в основной программе или обычной функции. Это общие переменные класса надо объявлять специально, и к ним специальная адресация ($this->variable), а с такими - всё просто.
9.4.2001 11:13  Lvetal  []
Самый простой метод избавится от нагрузки на скрипт при рассылке - нагрузить mail сервер...

Есть такая замечательная вещь как BCC - загоняем туда всех подписчиков и отсылаем на mail сервер... а он пусть грузится... :)
Ответ DL:

Уже пробовал. Ждёшь отправки ровно столько же времени.
архив | ссылки | форумы | что такое php
© , 2000-2002
© , 1999-2002

Комментарии к статье ""


28.12.2000 10:46  leosha  []
Может я и ошибаюсь, но ценность вот этой строки достаточно сомнительна:

$REMOTE_TXT_ADDR = gethostbyaddr($REMOTE_ADDR)

т.к. есть переменная окружения $REMOTE_HOST

и если она пустая, то gethostbyaddr($REMOTE_ADDR) тоже ничего не даст...

Также, вызывает недоумение вот эта фраза:

"Естественно, что держать логи чуть ли не всего рунета никому не захочется. "

Позвольте, а что тогда делает SpyLog? ;-)
Ответ DL:

Сейчас посмотрю в мануал насчет REMOTE_HOST. А про Спайлог я и не говорил :) Когда его услуги были бесплатными, я его не ставил, держал старый добрый eXtreme.
28.12.2000 16:51  leosha  []
Хотя... Не погорячился ли я. Если в Апаче выключена опция "Ресолвить IP" то тогда действительно хост надо определять именно так как Вы и говорите. Я совсем забыл об этой фиче (чаще всего она включена).
Ответ DL:

Возможно, у меня просто документация по PHP устарела - там про это не написано.
29.12.2000 08:00  Jen  []
А HTTP_X_FORWARDED_FOR? Весьма полезная штука. Если существует, то лучше от нее gethostbyaddr брать. Через прокси нынче многие ходят.
Ответ DL:

Можно и его. Проверю.
29.12.2000 13:14  Vitaliy  []
Спасибо большое за то что прикрутили "Версию для печати"..

Очень полезный и интересный сайт...

Хотелось бы еще по е-mail получать новости об обновлениях на этом сайте...

какую-нибудь рассылочку...

Но это, конечно, по желанию создателей..

Спасибо.
Ответ DL:

Скоро прикручу.
5.1.2001 11:14  Corwin  []
Дмитрий, по некоторому совпадению, я тоже весьма активно занимаюсь рассмотренной тобой темой, по этому поводу есть следующие комментарии. Просто сбор статистики как правило проблем не вызывает, важнее то. как ее анализировать и предоставлять пользователю. Очень интересно было бы услышать об опыте использования browcap. Также помимо простого построения гистограмм показывающих, что IE больше чем NN... можно анализировать сессии, цепочки запросов близлежащих по времени, строить путь перемещения пользователя по карте сайта и находить дизайнерам и разработчикам закутки, в которые кроме них самих никто не заглядывает. Есть еще множество идей и вопросов, если это интересно, то можно продолжить.
Ответ DL:

Полностью согласен. Когда я написал эту заметку, сразу понял, что мало объяснить, как собирать данные.
5.1.2001 11:21  Corwin  []
Кстати маленькое уточнение на счет собачки :о)

Это не фича PHP4 - такая возможность была еще и в PHP3, если не ошибаюсь с версии 3.0.2a
<

Комментарии к статье ""


16.12.2001 18:59  Iv An  []
Вы уж меня простите, но я опять над MySQL поиздеваюсь.

Как и в прошлое издевательство, беру OpenLink Virtuoso в её халявном варианте (на халяву только три коннекции, но нам больше и не надо).

Пишу

CREATE TEXT [XML] INDEX ON docs_table ( docs_text_column )

WITH KEY docs_id_column

[USING my_filtering_function] ...

[тут ещё много всякого можно понаписать]

и индекс создан.

А теперь пишу например

SELECT чего_надо

FROM docs_table

WHERE CONTAINS( docs_text_column,

'[__enc=''Widonws-1251''] (("сортиров*" or "ранжирова*" or "sorting") and not "сортировка пузырьком")'

SCORE_LIMIT 0.1 )

или вот намедни выпендривался

-- вернуть XML вида заголовок статьисписок авторов
-- для каждой свежей статьи

-- у которой внутри тэга упоминается поиск или сортировку, но не сортировка пузырьком.

-- (сортировку пузырьком любят верификаторщики в качестве демо-примера, а нам это не в тему)

select

ARTICLES.ARTICLE_ID as HIT_ID,

xquery_eval('{title,authors}', FREETEXT_HIT) as HIT_DATA

from ARTICLES

where

xcontains(

'article[text-contains(abstract,

''("search" or "sort*") and not "bubble* sort*")'')]',

ARTICLES.ARTICLE_XML,

0,

FREETEXT_HIT

)

and

ARTICLES.ARTICLE_PUBLISHING_DATE >= '2000-01-01'

А можно документы в DAV положить и галочку поставить, чтобы проиндексировались...
19.12.2001 18:40  Slach  []
Кстати... по поводу full_text...

вот еще одна ссылочка

http://evolt.org/article/Boolean_Fulltext_Searching_with_PHP_and_MySQL/18/15665/index.html
14.4.2002 03:16  pk  []
"Попробовал сравнить скорость работы с вот таким запросом"

Как делается подобного рода сравнение? Есди в перле есть спец бенчмарки, то как в php делается сравнение скоростей работы 2-х разных скриптов? Разъясните поподробнее, плиз, и если не сложно, с примерами.
Ответ DL:

Это делается просто в консоли mysql.
19.4.2002 17:14  Павел
Народ, а как организовать регистро независимый поиск в БД ?
Ответ DL:

Он и так регистронезависимый, если ты о MySQL
14.6.2002 12:39  Artem  []
Ну я блин с вас фигею!

Кто же так полнотекстовый поиск использует!

вместо

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

надо писать:

SELECT title, date_format(material_date,'%e.%c.%y') AS date1, MATCH text AGAINST('word1 word2 word3') AS relevance FROM table WHERE MATCH text AGAINST('word1 word2 word3')

Вот тогда скорость будет не в 2 раза выше, а в гораздо выше! (может и в 1000000 раз - зависит от БД и данных)

Учите, блин, матчасть, блин.

Другое дело что "fulltext" все еще полная alpha. (что-бы не сказать жопа) - и индексирование тормозит совсем не по детски. Для нормального его использования все ждут выхода 4.x.x из стадии alpha в стадию хотя-бы beta.
<

Комментарии к статье ""


11.12.2001 08:19  DiMA  []
Хм, тут ни слова нет о действительно важных проблемах. Где линки на русский ман по апачу? Все это и более того можно прочитать в формате документации на русском языке.

1. Важная проблема - Location и его скрытая угроза в безопасности. Товарищ, спец по безопасности, я хотел бы услышать комментарии, если не затруднит. Хотя тут вообще ни слова о Location. Такая команда есть, 100% :)

2. Не решённая хотя бы для меня лично проблема - как в DIRECTORY писать локальный путь от DOCUMENT_ROOT? Вот ответите, как это сделать, спасибо скажу. Типа написать нечто такое: ...

Вот это чушь:

AddType application/x-httpd-php .html

Если у нас нет какого-то модуля, из-за которого AddType будет не выполнен, то получиться, что PHP программы станут простыми текстовыми файлами и станут доступны для скачивания. Это как бы сделать ошибку, влекущую следующие ошибки. А если апач из-за первой ошибки свалится, то так ему и надо. Зато пхп код на месте.
12.12.2001 05:58  DVA
Ммм.. а можно положить этот код в файл, доступный для скачивания ?
13.12.2001 04:27  Шурик  []
АddType совсем не чушь. Очень помогает от дураков хакеров.

А чтобы не были твои пхп и другие файлы доступны для скачивания

отредактируй .htaccess файл. И ничего не будет доступно.

Options -Indexes

вполне решает эту проблему.
13.12.2001 15:28  Роман Яцевич  []
Действительно - тут нет про террористов, про курс доллара, про морозы и т.д. А ведь это такие серьёзные и Важные проблемы!

Кстати, а действительно: "Где линки на русский ман?". В том то и проблема - что там можно прочитать ВСЁ! И это ВСЁ нужно единицам! Я же предлагаю один из реальных и проверенных вариантов решений с "виртуальными" директориями... Не более и не менее...

1. Зачем рассказывать как "бороться" с дырой в безопасности при использовании Location, если её(директиву :) можно просто не использовать! Убедите меня (и читателей) хотя бы парой-тройкой аргументов, что без Location нельзя жить - и, возможно, комментарии появятся на данной странице.

2. Насколько я понимаю - подобного механизма в официальной поставке просто нет. Но буду рад ошибиться... :(

Про "чушь":

Насколько я понимаю Вы про расширение .html. Ну, для начала я не понимаю, причем здесь само расширение? Положите файл .php на сервер с отключенным PHP и вы получите то же самое! А вот для решения той проблемы, которую Вы описали, я предлагаю серию действий, и все они описаны в статье (Вы просто невнимательно читали):

а) Вынести весь PHP-код за пределы DOCUMENT_ROOT. Исполнение кода осуществляется выполнением include(нужный_файл) из единственного index.html файла доступного из DOCUMENT_ROOT. Конечно это не догма - возможны несколько точек входа, но это детали - главное, что в пределах DOCUMENT_ROOT находятся только "оболочки", которые вызывают нужный код из недр сервера, которые недоступны по HTTP.

б) Даже если, по каким либо причинам, реальный PHP-код временно находится в пределах DOCUMENT_ROOT, я предлагаю названия всех файлов, которые хочется спрятать от посторонних глаз, начинать с ".", а Apache настроить на то, чтобы он такие файлы не отдавал пользователю ни при каких условиях (см. второе правило в настройке Rewrite).
<

Логика


Допустим, мы хотим предоставить пользователю возможность выбирать логику поиска - искать все слова или только одно из нескольких. Если вы хотите сделать как в [] - два амперсанта означают "И" (слово1&&слово2&&слово3) или как-то еще, то я не советчик. Шаманство со строками на небольшом сайте imho не оправдывает затраченного времени. Поэтому форму для поиска рисуем так:

искать любое из слов

искать все слова

А в поисковом скрипте лишний раз проверяем, что пользователь ввел:

if ($logic!="AND" && $logic!="OR")

  $logic = "OR";

Как будет использоваться логика ? ниже.



Механизм администрирования - теория


В первую очередь рекомендую заняться именно им, чтобы потом не было мучительно больно: новостная лента "почти" готова, только редактирование новости никак не написано, и приходится писать запросы "INSERT INTO news..." каждый раз вручную. Можно, конечно, и через хвалёный phpMyAdmin писать новости, но это тоже "не то".

В директории с сайтом создаём директорию admin (можно более оригинально - например chief, nachalnik...). Когда сайт будет выложен на ЦЦЦ, положим в эту директорию .htaccess и поставим пароль, чтоб кто попало не лазил.

А теперь думаем, что же надо для нормальной работы с новостями. Форма для ввода новых новостей, обрабочтик формы. Так, а ещё? Список новостей с возможностью тыкнуть в неё, чтобы тут же отредактировать. Потом можно сделать галочки напротив заголовков новостей, чтоб можно было отметить неугодные и грохнуть их тут же (а лучше, чтоб было подтверждение "да/нет"). А потом сделать ввод даты ограниченным: вместо текстовых полей - раскрывающиеся списки, которые бы спасли от опечаток. Много... Но чтобы потом не было мучительно больно...

Детально описывать здесь это не имеет смысла - скачайте файл и разберитесь. К этому выпуску планируется продолжение - новостная лента с рубриками. В общем, заходите ещё.



Миллениум: магия чисел. Счетчик для страницы


DL
25.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("<a href=http://", "<a href=http://", $page);

А в файле 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


DL
6.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. Рассыльщик почты


DL
21.12.2000

Сперва немного новостей. Вышел .

From: Antonio <anton@concord.ru>

[]

[] или .

Возрадуйтесь! Теперь 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 и рисовала переносы строк. Класс, правда, не был предназначен для рассылки почты подписчикам. Но это так, детали...



Объектно-ориентированное программирование, классы


DL
10.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: \"Вася Пупкин\" <vasya@pupkin.ru>

Reply-to: \"Вася Пупкин\" <vasya@pupkin.ru>

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<sizeof($addr);$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 . бережет нервы и время


DL
14.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 или специальные утилиты).

<VirtualHost *>

# обратите внимание - для сайта отведена домашняя директория

# при этом всё, что видно через браузер пользователю находится в поддиректории /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-файл.

<IfModule mod_mime.c>

AddType application/x-httpd-php .html </IfModule>

# Кстати, если нагрузка на сайт предполагается большая,

# а часть страниц можно сохранить в "компилированном" виде, т.е. в чистом HTML-е,

# то можно сэкономить немного процессорного времени "отсадив" такие страницы

# в отдельную директорию, и отключить там PHP, например так:



#<Directory /www/имя_сайта/html/article/>

# <IfModule mod_mime.c>

# RemoveType .html

# </IfModule>

#</Directory>

# или использовать для "чистых" HTML-документов другое расширение, н.п. .htm,

# но тут могут вылезти проблемы, описанные выше - вдруг эти документы

# нужно будет сделать динамическими, позже, по каким либо причинам,

# и что - менять расширение?

# очень удобно - этот файл будет исполнятся перед каждым обращением к PHP,

# в пределах данного сайта, и сюда удобно вынести присвоение переменных и define,

# а так же include связанные с загрузкой библиотек т.д. и т.п.,

# которые привязывают к физическому расположению, чего-либо.

# этот документ также находится вне DocumentRoot,

# что повышает безопасность и логично отражает иерархию

# возможно Вы скажете, что это ненужный файл - ведь все обращения итак

# обрабатываются одним файлом (н.п. index.html), и именно в нем легко можно сделать

# привязку к физическому положению! можно - но я все-таки считаю, что правильнее,

# когда в единой точке входа сосредоточена логика сайта, а физика - отдельно.

# кроме того, возможны несколько точек входа - в случае с

# виртуальными сайтами или физическими разделами (см. ниже настройку mod_rewrite)

php_admin_value auto_prepend_file "/www/имя_сайта/.startup.html" # расширение .html предлагаю использовать для единообразия картины

# Кстати, это не "стопудовый" PHP - <script language="php"></script>

# или кому больше нравится <? ?>, всё таки нужно писать...

# Для подобных же вещей (т.е. для комфортной привязки проекта,

# к конфигурации сервера) может служить вот эта директива

#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 всегда и получаю значения через свои функции

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

# а там _не_использование_ автоматической регистрации (глобальных переменных то бишь)

# считаю более чем оправданным, особенно при использовании ООП

<IfModule mod_rewrite.c>

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 наверняка с ним справится! :)

</IfModule>

# Если уж тема безопасности затронута, то хотелось бы упомянуть ещё несколько моментов:

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"

# данная директива используется как шаблон в одном <VirtualHost *></VirtualHost>

# для отработки нескольких серверов вместо DocumentRoot

# в нескольких <VirtualHost *></VirtualHost>

# где %0 полное имя сервера, т.о. для заведения одного из типичных серверов

# достаточно будет только в нужном месте создать директорию... и все!

# Есть только одна проблема - в директиве php_admin_value %0 останется неизменным :(

# Ну, что ждём от Apache Team создание virtual_php_admin_value или напишем сами?

</VirtualHost>

Яцевич Роман, неважно кто по жизни

отдельное спасибо Бохонковичу Юрию, за консультации по безопасности систем на базе Unix/Linux-серверов а также Лебедеву Дмитрию, за то, что он открыл мне глаза на отнюдь не виртуальную проблему виртуальных директорий!

Подсветка


Чтобы подсвечивать светом или жирным шрифтом искомые слова в тексте, надо сделать всего лишь следующее:

$highlight = str_replace(" ", "|", $good);

Пробелы (а они у нас между словами стоят поодиночке, и нигде двойной пробел не встречается, к тому же с концов строки мы их тоже вырезали) достаточно заменить на вертикальную черту - разделитель вариантов в регулярных выражениях. "Плохие" слова мы не подсвечиваем, потому что в базе их не ищем :).

В коде, который выводит текст пишем:

$row["text"] = ereg_replace($highlight, "<font color=#cc0000>\\0</font>", $row["text"]);

После написания выпуска я кинулся, было, писать и себе "подсветку". Не тут-то было! У меня в тексте встречаются теги HTML, поэтому пришлось много подумать... Получилась вот такая вещь (строка со словами для подсветки есть):

$text = eregi_replace(">([^<]*)($words)", ">\\1<font color=#cc0000>\\2</font>", $text);

Приходится смотреть, нет в теге ли это слово. Однако тут встает проблема ресурсоемкости такой замены (мой K6-266 над текстом в 5 килобайт думал целых семь секунд). Печально.



Поиск в MySQL. Релевантность своими руками


DL
9.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", "<b>\\0</b>", $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 ("<a href=search.php?search=". rawurlencode($good). "&page=". ($page-1). ">предыдущая страница</a>");

if ($page

  print ("<a href=search.php?search=". rawurlencode($good). "&page=". ($page+1). ">следующая страница</a>");



Пустой запрос. Повторите попытку.


");

Тада скрипт будет гибнуть, если будут пробелы и всяка шняга. А регулярные выражения ГРАМОТНО составлены. Я б так не смог. Пытался повторть, но пока не смог.

архив | ссылки | форумы | что такое php
© , 2000-2002
© , 1999-2002



Релевантность


Наверное, в том же [] все видели ссылочку "сортировать по релевантности". Это оно и есть. Сортировка результатов по количеству совпадений слов.

Отчасти, кстати, такая сортировка снимает проблему обработки логики поиска. Но с БД MySQL делать такую сортировку очень сложно. Надо сперва выбрать, где есть все слова, потом записи, где разные слова (исключив предыдущие). Если у вас постраничный вывод - то вообще дело труба!



Синтаксис объединений таблиц


Простое соединение - INNER JOIN:

SELECT <fields> FROM table1 INNER JOIN table2 ON table1.field1=table2.field2

или

SELECT <fields> FROM table1, table2 WHERE table1.field1=table2.field2

или

SELECT <fields> FROM table1 INNER JOIN table2 USING (field1)

если таблицы объединяются по полю field1.

В таком соединении выбираются только те строки таблиц, которые соответствуют условию объединения - равенство значений полей. Если для строки table1 нет соответствующей строки из table2, строка не попадает в итог запроса. Если же надо подсчитать количество сайтов в рубрике (продолжаю пример с каталогом), такой запрос не совсем подходит - в списке появятся только рубрики, в которых есть сайты. Для подобной операции нужно использовать LEFT JOIN.

SELECT <fields> FROM table1 LEFT JOIN table2 ON table1.field1=table2.field2

или

SELECT <fields> FROM table1 LEFT JOIN table2 USING (field1)

если таблицы объединяются по полю 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: <font color=#cc0000>короткое</font>";

  };

$word_stats = "Статистика слов: ". implode("", $stat). "<br>";

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("<tr><td><h3>". $row[1]. "</h3>");

    pring("<font size=-1>". $row[2]. "</font>");

    print("<p align=justify>". $row[0]. "</p>");

    };

  }

/* в случае ошибки БД программа выводит сообщение сервера (конечно, можно обойтись без такой проверки, но тогда пользователю посыплются ругательства PHP). */

else {

  print ("Ошибка БД в запросе "$request". MySQL пишет ". mysql_error());

};

/* если дальше предусмотрено выполнение каких-либо операций, лучше всего сразу очистить память */

mysql_free_result ($result);

Ещё одна полезная вещь: если это новостная лента, скажем, на главной странице сайта, нужно ограничить количество новостей (скаджм 10). И ещё надо, чтоб дата отображалась не как "2000-12-05 22-26-47", а "5.12.2000 22:46":

$request = "SELECT ntext, ntitle, date_format(ndate,'%e.%m.%Y %H:%i') as ndate1 FROM news ORDER BY ndate DESC LIMIT 10";

Ограничиваем количество новостей ровно десятью, и не надо никаких условий в цикле while. Функция date_format форматирует дату так, как нам надо (буквы - как в функции PHP date, а "e" - это число месяца без нуля в начале). Почему пишу "as ndate1", а не "as ndate"? Проверьте сами :) (подсказка: см. директиву ORDER BY в том же запросе).

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



Запись новостей


Создаётся новость путём отправки запроса точно так же, как и запроса на создание таблицы или выборки строк. Перед вставкой данных в запрос, их лучше обработать функцией addslashes, чтобы одинарные кавычки MySQL воспринял, как часть текста:

mysql_query ("INSERT INTO news (ntitle, ntext, ndate) VALUES ('". addslashes($ntitle). "', '". addslashes($ntext). "', NOW())");

В поле типа DATE вставляем текущее время функцией now. Если нужно вставить не текущее время, можно сделать так:

..., '". date("Y-m-d H-i-s", $date). "', ...

Этот формат - для типа DATETIME. Если тип данных DATE, то надо использовать "Y-m-d", и если TIME, соответственно "H-i-s". Переменная $date здесь содержит дату/время, определенные функцией mktime.



Запросы на вставку строки (INSERT)


Поле идентификатора вставлять не нужно. На это есть свойство поля AUTO_INCREMENT.

Забавно читать, как в форуме пишут:

- Как мне быть с генератором случайных чисел?! неправильно работает!

- А зачем тебе?

- Да в базе id использовать...

В общем, не надо самодеятельности.

Если в поле формата DATE, TIME, DATETIME или TIMESTAMP надо вставить текущее время, используйте встроенную в mysql функцию NOW: "INSERT INTO vote (ip, date) VALUES ($REMOTE_ADDR, NOW())"

Хранимые в базе пароли лучше прикрыть функцией php md5: "INSERT INTO user (login, pass) VALUES ('$login', ". md5($pass). ")" "SELECT * FROM user WHERE login='$login' AND pass=". md5($pass)

Советы, кажется, уже исчерпаны. Напоследок. С недавних пор я стал думать, что при написании скриптов, работающих с БД, надо ориентироваться не только на глупого и шаловливого посетителя, но и на криворукого администратора. Даже если мы внимательно будем следить за текстом, который вставляем в текстовое поле (одинарные кавычки не писать, делать их автозамену в Word-е, белое не носить), вероятность попадания служебных символов в запрос ненулевая.



Запросы на выборку данных (SELECT)


Во избежание путаницы полей (если встречаются поля с одинаковыми названиями) используйте в запросах оператор AS: "SELECT table1.id as id1, table2.id as id2". Это поможет избежать ошибок в запросе (например, если не указана таблица, а поле с таким названием есть в нескольких запрашиваемых таблицах, mysql выдаёт ошибку), а так же вы избежите недоразумений при работе с полученными данными (echo $row["id1"] писать гораздо проще, чем $row[$x]).

Данные типа DATE, TIME, DATETIME и TIMESTAMP можно форматировать с помощью функции date_format (см. руководство по mysql). Используйте его, и не форматируйте данные через php - это не просто "самодеятельность", а ещё и растрата системных ресурсов.

По возможности минимально используйте LEFT JOIN для объединения таблиц. Это весьма трудоёмкая операция для базы данных.

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

Вместо "WHERE id=1 OR id=3 OR id=232" можно использовать встроенную функцию IN: "WHERE id IN (1,3,232)".

Если нужен текстовый поиск, осторожней со знаком "%". Во всяком случае, запросы типа somefield LIKE '%a%' лучше не делать - опять же слишком трудоёмкая операция. По крайней мере, надо фильтровать слова и отрезать те, которые короче 3 символов.

Используйте минимум необходимых полей в запросе. "SELECT * FROM sometable" выполняется медленнее, чем "SELECT id FROM sometable", тем более если в таблице много данных. Для подсчёта количества строк в таблице вообще (или подпадающих под некоторое условие) достаточно одного поля.

Разбивайте данные на страницы, используя оператор LIMIT. Это экономит время выполнения запроса и уменьшает объем страницы, которую получает пользователь.

Даже если вам не грозит "падение" от наплыва посетителей, лучше взять себе в привычку, чтобы потом не было проблем с адаптацией к новым задачам. Теперь о безопасности работы.


Старайтесь не допускать внесения в базу данных символа одинарной кавычки ("'"), поскольку это служебный символ запросов БД. Перед внесением в базу поле можно обработать функцией str_replace: $somefield = str_replace("'", "'", $somefield);

К тому же это лишний барьер на пути взломщиков вашего сайта. Пример "взлома" простой:

mysql_query("UPDATE users SET password=PASSWORD('$passwd') WHERE login='$login'");

Если кавычку не обработать на входе, злоумышленник может в качестве логина сунуть строку "vasya_pupkin' OR login LIKE '%". В базу данных залетит запрос: mysql_query("UPDATE users SET password=PASSWORD('$passwd') WHERE login='vasya_pupkin' OR login LIKE '%'"); То есть все пароли будут одинаковые. Это только один пример. Итак,

Обрабатывайте данные, получаемые из адресной строки или из формы, и приводите их к нужному типу во избежание ошибок и "взломов" сайта. (ещё пример: если требуется идентификатор, то есть целое число, надо обработать его с помощью intval: $id = intval($id)).