Абсолютный и относительный путь к сценарию

Обратим внимание на поле action тэга
. Поскольку он не предваряется слэшем (/), то представляет собой относительный путь к сценарию.

То есть браузер при анализе тэга попытается выдать запрос на запуск сценария, имеющего имя
script.cgi и расположенного в том же самом каталоге, что и форма (точнее, HTML-документ с формой).
Абсолютный и относительный путь к сценарию

Как вы, наверное, догадались, термин "каталог" здесь весьма условен. На самом-то деле имеется в виду не реальный каталог на сервере (о котором браузер, кстати, ничего не знает), а часть URL, предшествующая последнему символу / в полном URL файла с формой. В нашем случае это просто http://www.somehost.com. Заметьте, что здесь учитывается имя хоста. Как видим, все это мало похоже на обычную файловую спецификацию.
Однако можно указать и абсолютный путь, как на текущем, так и на другом хосте. В первом случае параметр action будет выглядеть примерно следующим образом:

Браузер определит, что это абсолютный путь в пределах текущего хоста (точнее, хоста, на котором расположен документ с формой) по наличию символа / впереди пути. Рекомендуется везде, где только возможно, пользоваться таким определением пути, всячески избегая указания абсолютного URL с именем хоста — конечно, за исключением тех ситуаций, когда ресурс размещен сразу на нескольких хостах (такое тоже иногда встречается).
Во втором случае получится приблизительно следующее:

Еще раз обратите внимание на то, что браузеру совершенно все равно, где находится запускаемый сценарий — на том же хосте, что и форма, или нет. Это позволяет создавать сайты, расположенные на нескольких хостах, "прозрачно" для их посетителей. Вся идеология сети Интернет и службы World Wide Web построена на этой идее — возможности свободного перемещения (и ее легкости) по гиперссылкам, где бы ни находился сервер, на который они указывают.



Accept

r Формат: Accept: text/html, text/plain, image/gif, image/jpeg
r Переменная окружения: HTTP_ACCEPT
В этом заголовке браузер перечисляет, какие типы документов он "понимает". Перечисление идет через запятую. К сожалению, в последнее время браузеры стали несколько небрежны и часто присылают в этом заголовке значение */*, что обозначает любой тип.
Существует еще множество заголовков запроса (часть из них востребуются только протоколом HTTP 1.1), но мы не будем на них задерживаться.



Адресация с Сети

Машин в Интернете много, это факт. Так что вопрос о том, как можно их эффективно идентифицировать в пределах этой сети, оказывается далеко не праздным. Кроме того, практически все современные операционные системы работают в многозадачном режиме (поддерживают одновременную работу нескольких программ). Это значит, что возникает также вопрос о том, как нам идентифицировать конкретную систему или программу, желающую обмениваться данными через Сеть. Эти две задачи решаются стеком TCP/IP при помощи IP-адреса и номера порта. Давайте посмотрим, как.
Адресация с Сети

Все, о чем рассказано далее, не является непреложной истиной. Скорее даже наоборот. Местами может показаться, что я "ломлюсь в открытую дверь"— пытаюсь доказать существование того, что и так существует. И все-таки, на мой взгляд, чтобы понять что-то, нужно сначала проникнуться мыслью, что основы этого "что-то" довольно просты, пусть даже они и абстрактны.



Арифметические операции

r a + b — сложение
r a — b — вычитание
r a * b — умножение
r a / b — деление
r a % b — остаток от деления a на b
Операция деления / возвращает целое число (то есть, результат деления нацело), если оба выражения a и b — целого типа (или же строки, выглядящие как целые числа), в противном случае результат будет дробным. Операция вычисления остатка от деления % работает только с целыми числами, так что применение ее к дробным может привести к, мягко говоря, нежелательному результату.



Array

Ассоциативный массив (или, как его часто называют, хэш, хотя для PHP такое понятие совсем не подходит[В.О.17] ). Это набор из нескольких элементов, каждый из которых представляет собой пару вида ключ=>значение (символом[В. О.18] => я обозначаю соответствие определенному ключу какого-то значения). Доступ к отдельным элементам осуществляется указанием их ключа. В отличие от массивов Си, ключами здесь могут служить не только целые числа, начиная с нуля, но и любые строки. Например, вполне возможно существование таких команд:
// ñîçäàñò ìàññèâ ñ êëþ÷àìè "0", "a", "b" è "c"
$a=array(0=>"zzzz", "a"=>"aaa", "b"=>"bbb", "c"="ccc[В. О.19] ");
echo $a["b"];      // âûâåäåò "bbb"
$a["1"]="qq";      // ñîçäàñò íîâûé ýëåìåíò â ìàññèâå è ïðèñâîèò åìó "qq" $a["a"]="new_aaa"; // ïðèñâîèò ñóùåñòâóþùåìó ýëåìåíòó "new_aaa";
Забегая вперед, скажу, что оператор array() создает массив, элементы которого перечислена в его скобках.

Массив, в общем случае ассоциативный (см. ниже). То есть набор пар ключ=>значение. Впрочем, здесь может быть передан и список list.



Ассоциативные массивы

Возможно, вы уже догадались, что ассоциативные массивы — один из самых мощных инструментов в PHP. Массивы — нечто, что довольно часто реализовывается в интерпретаторах типа PHP (в Perl ассоциативные массивы устроены даже немного хуже, чем в PHP). Давайте рассмотрим чуть подробнее, как с ними работать.
Массивы — это своеобразные контейнеры-переменные для хранения сразу нескольких величин, к которым можно затем быстро и удобно обратиться. Конечно, никто не запрещает вам вообще их не использовать, а, например, давать своеобразные имена переменным, такие как $a1, $a2 и т. д., но представьте, что получится в этом случае, если вам нужно держать в памяти, скажем, тысячу таких переменных. Кроме того, такой способ организации массивов имеет и еще один недостаток — очень трудно перебрать все его значения в цикле, хотя это и возможно:
for($i=0; ; $i++) {
  $v="a$i";
  if(!isset($$v)) break;
..äåëàåì ÷òî-íèáóäü ñ $$v
}
Ассоциативные массивы

Никогда так не делайте! Этот пример приведен здесь лишь для иллюстрации. Если вдруг при написании какого-нибудь сценария вам все-таки мучительно захочется применить этот "трюк", выключите компьютер, подумайте минут 15, а затем снова включите его.
Здесь мы используем возможность PHP по работе с ссылочными переменными, которую я категорически не рекомендую где-либо применять. Все это представлено здесь для того, чтобы проиллюстрировать, насколько неудобно бывает работать без массивов.
Давайте теперь начнем с самого начала. Пусть у нас в программе нужно описать список из нескольких человеческих имен. Можно сделать это так (листинг 10.1):
Листинг 10.1. Инициализация массива
$NamesList[0]="Dmitry";
$NamesList[1]="Helen";
$NamesList[2]="Sergey";
. . .
Таким образом, мы по одному добавляем в массив $NamesList
элементы, например, пронумерованные от 0. PHP узнает, что мы хотим создать массив, по квадратным скобкам (нужно заметить, что для этого переменная $NamesList

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

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

Давайте теперь посмотрим, как можно распечатать наш список. Самый простой способ — воспользоваться циклом for:

echo "À âîò ïåðâûé ýëåìåíò ìàññèâà: ".$NamesList[0]."
";

for($i=0; $i<êîë-âî_ýëåìåíòîâ; $i++)

  echo $NamesList[$i]."
";

Количество элементов в массиве легко можно определить, задействуя функцию count() или ее синоним sizeof():

for($i=0; $i
  echo $NamesList[$i]."
";


Автоматическая генерация названий

Если пользователь находится на сайте "Книжный магазин" в разделе "Философия" на заинтересовавшей его странице "Современность", то, конечно, в заголовке окна браузера ему бы хотелось видеть что-то вроде "Книжный магазин | Философия | Современность", а не просто "Современность". Мы уже договорились хранить название страницы в блоке Title. Но, конечно, мы бы не хотели записывать в каждой странице название полностью, потому что:
r в будущем мы можем перенести страницу в другой раздел;
r мы, возможно, захотим сменить разделитель | на /;
r это нарушает концепцию минимальной избыточности данных, что, как мы уже неоднократно убеждались, не приводит ни к чему хорошему.
Специально для решения такого рода задач в нашем шаблонизаторе предусмотрим механизм, который я далее буду называть автоматической склейкой тел блоков. Вот как он работает. Если при обработке очередного блока шаблонизатор видит, что его тело начинается с подстроки [Клей], он определяет, что текст должен быть "пристыкован" к предыдущему телу одноименного блока, но не должен заменить его. В качестве "строки-клея" выступает значение блока с именем Клей. Это позволяет нам в будущем изменить символ "склейки" лишь в одном месте, чтобы это затронуло весь сайт. В случае, если указана пустая пара квадратных скобок [] (то есть имя блока было опущено), используется тело блока с именем DefaultGlue (см. листинг 30.12), а если и он отсутствует — то |.
Теперь при загрузке страницы /phil/index.html из листинга 30.11, пользователь увидит ее полное название, составленное из блоков Title всех "надкаталогов"
и самого файла страницы. Мы добиваемся этого, определив блок Title следующим образом:




Автоматическое подключение библиотекаря

Из листинга 29.2 можно видеть, что пока нам не удалось полностью избавиться от указания абсолютного пути к библиотекам. Вот строка, которая мне не нравится:
include "$DOCUMENT_ROOT/lib/librarian.phl"; // подключаем библиотекарь
Действуя привычным способом, нам придется вставлять ее в каждый сценарий, который планирует использовать библиотекаря. Этих сценариев может быть довольно много, так что если мы вдруг захотим изменить lib на, скажем, ../libraries, то придется править все программы. По закону Мэрфи где-нибудь да ошибетесь — обязательно. А значит, такое решение нам, как дотошным программистам, не подходит. К счастью, существует еще по крайней мере два способа решить проблему с абсолютными путями, и который из них выбрать — зависит от ситуации.
Автоматическое подключение библиотекаря

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



Базовые функции

int strlen(string $st)
Одна из наиболее полезных функций. Возвращает просто длину строки, т. е., сколько символов содержится в $st. Как уже упоминалось, строка может содержать любые символы, в том числе и с нулевым кодом (что запрещено в Си). Функция strlen() будет правильно работать и с такими строками.
int strpos(string $where, string $what, int $fromwhere=0)
Пытается найти в строке $where подстроку (то есть последовательность символов) $what и в случае успеха возвращает позицию (индекс) этой подстроки в строке. Первый символ строки, как и в Си, имеет индекс 0. Необязательный параметр $fromwhere можно задавать, если поиск нужно вести не с начала строки $from, а с какой-то другой позиции. В этом случае следует эту позицию передать в $fromwhere. Если подстроку найти не удалось,
функция возвращает false. Однако будьте внимательны, проверяя результат вызова strpos() на false — используйте ля этого только оператор ===.
string substr(string $str, int $from [,int $length])
Данная функция тоже востребуется очень часто. Ее назначение — возвращать участок строки $str, начиная с позиции $start и длиной $length. Если $length не задана, то подразумевается подстрока от $start до конца строки $str. Если $start больше, чем длина строки, или же значение $length равно нулю, то возвращается пустая подстрока.
Однако эта функция может делать и еще довольно полезные вещи. К примеру, если мы передадим в $start отрицательное число, то будет считаться, что это число является индексом подстроки, но только отсчитываемым от конца $str (например, -1 означает "начиная с последнего символа строки"). Параметр $length, если он задан, тоже может быть отрицательным. В этом случае последним символом возвращенной подстроки будет символ из $str с индексом $length, определяемым от конца строки.
int strcmp(string $str1, string $str2)
Сравнивает две строки посимвольно (точнее, побайтово) и возвращает: 0, если строки полностью совпадают; -1, если строка $str1 лексикографически меньше $str2; и 1, если, наоборот, $str1 "больше" $str2. Так как сравнение идет побайтово, то регистр символов влияет на результаты сравнений.[E52]
int strcasecmp(string $str1, string $str2)
То же самое, что и strcmp(), только при работе не учитывается регистр букв. Например, с точки зрения этой функции "ab" и "AB" равны.
Базовые функции

Если ваша строка состоит только из "английских" букв, проблем не будет. Однако в случае использования "русских" букв результат (точнее, правильность) работы функции strcasecmp() сильно зависит от настроек текущей локали (см. ниже).



Безымянные временные файлы

Иногда всем нам приходится работать с временными файлами, которые при завершении программы хотелось бы удалить. При этом нас интересует лишь файловый дескриптор, а не имя временного файла. Для создания таких объектов в PHP предусмотрена специальная функция.
int tmpfile()
Создает новый файл с уникальным именем (чтобы другой процесс случайно не посчитал этот файл "своим") и открывает его на чтение и запись. В дальнейшем вся работа должна вестись с возвращенным файловым дескриптором, потому что имя файла недоступно.
Безымянные временные файлы

Фраза "имя файла недоступно" может породить некоторые сомнения, но это действительно так по одной-единственной причине: его просто нет. Вот как такое может произойти? В большинстве систем после открытия файла его имя можно спокойно удалить из дерева файловой системы, продолжая при этом работать с "безымянным" файлом через дескриптор, как обычно. При закрытии этого дескриптора блоки, которые занимает файл на диске, будут автоматически помечены как свободные.
Пространство, занимаемое временным файлом, автоматически освобождается при его закрытии и при завершении работы программы.



Библиотекарь

Ту часть кода, которая будет содержать функцию Uses() (а мы реализуем ее именно в виде функции) и другие функции, нужные для загрузки модулей, назовем библиотекарем. Этот библиотекарь, очевидно, сценарию придется загружать первым, а каким именно образом, мы поговорим чуть позже.
Теперь немного о том, как мы будем реализовывать Uses(). Это довольно несложно. Помните, я подчеркивал, что поскольку PHP является интерпретатором, то на нем осуществимы такие приемы, как описание функций внутри функций и многое другое. Так мы и сделаем: функция Uses() вначале будет проверять, не загружался ли уже модуль с таким именем, затем искать затребованный модуль в специальных "каталогах для модулей", фиксировать во внутреннем массиве факт, что указанный файл загружен, и, наконец,
вызывать include_once для файла с модулем. Кроме того, на время загрузки текущий каталог будет сменяться на тот, в котором находится модуль, чтобы стартовые части всех модулей запускались в "своих" каталогах. Это как раз та возможность, которая отсутствует в Perl, и которая оказывается довольно удобной на практике.
Раз библиотекарь всегда подключается к программе в первую очередь, разумно доверить ему выполнение еще некоторых действий.
r Поместим в файл библиотекаря функции, чаще всего необходимые почти каждому сценарию. Таким образом, мы как бы "расширим"
набор встроенных в PHP функций. Однако помните, что встроенные функции переопределять все же нельзя, можно лишь создавать новые с уникальными именами.
r Библиотекарь, как никто другой, должен приложить максимум усилий, чтобы сделать сценарии переносимыми с одной платформы на другую. Для нас это будет заключаться в корректировке некоторых переменных, которые PHP создает перед выполнением программы. Первым кандидатом на такую правку будет $SCRIPT_NAME (а также одноименная переменная окружения), которая, как мы знаем, в Windows-версии PHP содержит не то значение, которое мы ожидаем.
r И еще нам хочется, чтобы на момент загрузки модуля текущий каталог сменялся на тот, в котором расположен файл модуля. Таким образом, стартовая часть библиотеки всегда сможет определить, где она находится, — например, при помощи вызова getcwd().

Вот что у нас получится в результате:

Листинг 29.1. Библиотекарь: librarian.phl


define("LIBRARIAN_LOADED",1);

// Расширение библиотечных файлов по умолчанию

define("LibExt","phl");

// Пути поиска библиотек. Если начинаются с точки, то поиск

// ВСЕГДА ведется относительно текущего каталога, даже если его

// сменят, в противном случае при следующем вызова Uses() будет

// выполнен перевод пути в абсолютный.

$INC=array(".","./lib");

// Функция преобразует указанный относительный путь в абсолютный.

// Если путь уже является абсолютным (т. е. отсчитывается от корневого

// каталога системы), то с ним ничего не происходит, в противном случае

// используется имя текущего каталога (или заданного в $cur) с

// необходимыми преобразованиями. Существование файла с полученным полным

// именем не проверяется. Функция лишена некоторых недостатков

// встроенной в PHP realpath() и имеет по сравнению с ней несколько

// большие возможности, работая, правда, чуть медленнее.

function GetAbsPath($name,$cur="") { return abs_path($name,$cur); }

function abs_path($name,$cur="")

{  // Преобразуем обратные слэши в прямые

   $name=strtr(trim($name),"\\","/");

   // Сначала разбиваем путь по знакам "/"

   $Parts=explode("/",$name);

   $Path=($cur===""?getcwd():$cur); // начальный каталог поиска

   foreach($Parts as $i=>$s) if($s!=".") {     

     // Признак корневого каталога?

     if(!$i && (strlen($s)>1&&$s[1]==":"||$s=="")) $Path=$s;

     // Ссылка на родительский каталог?

     elseif($s=="..") {

       // Если это уже корневой каталог, то куда спускаться?..

       if(strlen($Path)>1 && $Path[1]==":") continue;

       // Иначе используем dirname()

       $p=dirname($Path);

       if($p=="/"||$p=="\\"||$p==".") $Path=""; else $Path=$p;


     }

     // Иначе просто имя очередного каталога

     elseif($s!=="") $Path.="/$s";

   }

   return ($Path!==""?$Path:"/");

}

// Преобразует URL в абсолютный файловый путь.

// Т. е. если адрес начинается со слэша, то результат рассматривается

// по отношению к каталогу DOCUMENT_ROOT, а если нет — то относительно

// dirname($SCRIPT_NAME). Конечно, функция не безупречна (например, она

// не умеет обрабатывать URL, заданные Alias-директивами Apache, но в

// большинстве случаев это и не нужно.

function Url2Path($name)

{  $curUrl=dirname($GLOBALS["SCRIPT_NAME"]);

   $url=abs_path(trim($name),$curUrl);

   return getenv("DOCUMENT_ROOT").$url;

}

// Превращает все пути в списке $INC в абсолютные, однако делает это

// не каждый раз, а только если массив изменился с момента последнего

// вызова.

function AbsolutizeINC()

{  global $INC;

   static $PrevINC="";   // значение $INC при предыдущем входе

   // Сначала проверяем — изменился ли $INC. Если да, то преобразуем

   // все пути в массиве в относительные, иначе ничего не делаем.

   // Нам это нужно только из соображений повышения производительности

   // функции.

   if($PrevINC!==$INC) {

     // Мы не можем использовать foreach, т. к. нам надо

     // модифицировать массив

     for($i=0; $i
       $v=&$INC[$i];

       if($v[0]=="." && (strlen($v)==1 || $v[1]=='\\' || $v[1]=='/'))

         continue;

       $v=abs_path($v);

     }

     // Запоминаем текущее состояние массива

     $PrevINC=$INC;

   }

  }

// Загружает указанную библиотеку функций. Для поиска файла

// просматривает каталоги в массиве $INC.

function Uses($libname)

{  global $INC;

   static $PrevINC="";   // значение $INC при предыдущем входе

   static $LastFound=0;  // для ускорения работы

   // Переводим все пути в $INC в относительные — вдруг вызывающая

   // программа добавила что-нибудь в массив?..


   AbsolutizeINC();

   // Теперь просматриваем пути, начиная с того, по которому была

   // найдена какая-нибудь предыдущая загруженная библиотека. Скорее

   // всего, там окажется загружаемый сейчас модуль. Если нет —

   // что же, просмотрим весь список...

   $l=$LastFound;

   do {

     // В очередном каталоге есть файл модуля?..

     $dir=$INC[$LastFound];

     if(@is_file($file="$dir/$libname.".LibExt)) {

       // Сменить каталог на тот, в котором расположен модуль

       $cwd=getcwd();

       chdir(dirname($file));

       // Делаем доступными для модуля все глобальные переменные

       foreach($GLOBALS as $k=>$v) global $$k;

       // Включаем файл

       $ret=include_once($file);

       // Пока не вернулись в предыдущий каталог, перевести

       // добавленные (возможно?) пути в $INC в абсолютные

       AbsolutizeINC();

       // Вернуться

       chdir($cwd);

       return $ret;

     }

     $LastFound=($LastFound+1)%count($INC);

   } while($LastFound!=$l);

   // Ничего не вышло — "умираем"...

   die("Couldn't find library \"$libname\" at ".join(", ",$INC)."!");

}

// Корректируем некоторые переменные окружения, которые могут иметь

// неверные значение, если PHP установлен не как модуль Apache

@putenv("SCRIPT_NAME=".

  $GLOBALS["HTTP_ENV_VARS"]["SCRIPT_NAME"]=

  $GLOBALS["SCRIPT_NAME"]=

  ereg_Replace("\\?.*","",getenv("REQUEST_URI"))

);

@putenv("SCRIPT_FILENAME".

  $GLOBALS["HTTP_ENV_VARS"]["SCRIPT_FILENAME"]=

  $GLOBALS["SCRIPT_FILENAME"]=

  Url2Path(getenv("SCRIPT_NAME"))

);

// На всякий случай включаем максимальный контроль ошибок

Error_reporting(1+2+4+8);

// ВНИМАНИЕ! После следующего закрывающего тэга

// не должно быть НИКАКИХ ПРОБЕЛОВ! В противном случае

// сценарий, подключающий библиотекаря, будет выводить в самом

// начале своей работы этот пробел, что недопустимо при


// работе с Cookies.

}?>

Обратите внимание на то, что весь код библиотекаря помещен в блок оператора if. Это сделано специально, чтобы при возможной (ошибочной) повторной загрузке библиотекаря по include все работало корректно.

Библиотекарь


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

Пожалуй, в приведенном коде есть и еще одно интересное место. Я имею в виду инструкции, помеченные комментарием: "Делаем доступными для модуля все глобальные переменные". Зачем это нужно? Разве глобальные переменные по определению не доступны подключаемому модулю? К сожалению, это так, и вот почему. Мы вызываем include_once в теле функции Uses(), а не в глобальном контексте. Неудивительно, что подключенный файл работает не в нем, а в области видимости тела функции. Указанный цикл перебора всех глобальных переменных и их "глобализация"

с помощью global решает проблему.

Библиотекарь


Здесь есть еще одна тонкость. Если модуль "захочет" определить какую-либо новую глобальную переменную, он не сможет сделать это никак иначе, чем через массив $GLOBALS. Однако изменять имеющиеся переменные напрямую он все же способен.


Бинарные данные

Бинарные данные — это почти то же самое, что и данные в формате TEXT, но только при поиске в них учитывается регистр символов ("abc"
и "ABC" — разные строки). Вего имеется  4 типа бинарных данных (табл. 26.4).
Таблица 26.4. Типы бинарных данных, используемые в MySQL

Тип
Описание
TINYBLOB
Может хранить максимум 255 символов
BLOB
Может хранить не более 65 535 символов
MEDIUMBLOB
Может хранить максимум 16 777 215 символов
LONGBLOB
Может хранить 4 294 967 295 символов

BLOB-данные не перекодируются автоматически, если при работе с установленным соединением [E134] включена возможность перекодирования текста "на лету"
(см. ниже).



Битовые операции

Эти операции предназначены для работы (установки/снятия/проверки) групп битов в целой переменной. Биты целого числа— это не что иное[В. О.36] , как отдельные разряды того же самого числа, записанного в двоичной системе счисления. Например, в двоичной системе число 12 будет выглядеть как 1100, а 2 — как 10, так что выражение 12|2 вернет нам число 14 (1110 в двоичной записи). Если переменная не целая, то она вначале округляется, а уж затем к ней применяются перечисленные ниже операторы.
r a & b — результат — число, у которого установлены только те биты, которые установлены и у a, и у b одновременно.
r a | b — результат — число, у которого установлены только те биты, которые установлены либо в a, либо в b (либо одновременно).
r ~ a   — результат, у которого на месте единиц в a
стоят нули, и наоборот.
r a << b — результат — число, полученное поразрядным сдвигом a на b битов влево.
r a >> b — аналогично, только вправо.



Блочные чтение/запись

string fread(int $f, int $numbytes)
Читает из файла $f $numbytes символов и возвращает строку этих символов. После чтения указатель файла продвигается к следующим после прочитанного блока позициям (это происходит и для всех остальных функций, так что дальше я буду пропускать такие подробности). Разумеется, если $numbytes больше, чем можно прочитать из файла (например, раньше достигается конец файла), возвращается то, что удалось считать. Этот прием можно использовать, если вам нужно считать в строку файл целиком. Для этого просто задайте в $numbytes очень большое число (например, сто тысяч). Но если вы заботитесь об экономии памяти в системе, так поступать не рекомендуется. Дело в том, что в некоторых версиях PHP передача большой длины строки во втором параметре fread() вызывает первоначальное выделение этой памяти в соответствии с запросом (даже если строка гораздо короче). Конечно, потом лишняя память освобождается, но все же ее может и не хватить для начального выделения.
int fwrite(int $f, string $st)
Записывает в файл $f все содержимое строки $st. Эта функция составляет пару для fread(), действуя "в обратном направлении".
Блочные чтение/запись

Например, с помощью описанных двух функций можно копировать файлы (правда, в PHP есть для этого отдельная функция — copy()), считав файл целиком посредством fread() и затем записав в новое место при помощи fwrite().
[E66] При работе с текстовыми файлами (то есть когда указан символ t
в режиме открытия файла) все \n автоматически преобразуются в тот разделитель строк, который принят в вашей операционной системе.



Блокирование файла

При интенсивном обмене данными с файлами в мультизадачных операционных системах встает вопрос синхронизации операций чтения/записи между процессами. Например, пусть у нас есть несколько "процессов-писателей"
и один "процесс-читатель". Необходимо, чтобы в единицу времени к файлу имел доступ лишь один процесс-писатель, а остальные на этот момент времени как бы "подвисали", ожидая своей очереди. Это нужно, например, чтобы данные от нескольких процессов не перемешивались в файле, а следовали блок за блоком. Как мы можем этого достигнуть?
Здесь на помощь приходит функция flock(), которая устанавливает так называемую "рекомендательную блокировку" для файла. Это означает, что блокирование доступа осуществляется не на уровне ядра системы, а на уровне программы. Поясню на примере.
Однажды я прочитал одно замечательное сравнение рекомендательной блокировки с перекрестком, на котором установилось довольно оживленное движение, регулируемое светофором. Когда горит красный, одни машины стоят, а другие проезжают. В принципе, любая машина может, так сказать, проехать наперекор правилам дорожного движения, не дожидаясь зеленого сигнала, но в таком случае возможны аварии. Рекомендательная блокировка работает точно таким же образом. А именно, процессы, которые ей пользуются, будут работать с разделяемым файлом правильно, а остальные… как-нибудь да будут, пока не произойдет "столкновение".
С другой стороны, "жесткая блокировка"
(которая в PHP не поддерживается) подобна шлагбауму: никто не сможет проехать, пока его не поднимут.

О жесткой блокировке мы в этой книге говорить не будем.
Единственная функция, которая занимается управлением блокировками в PHP, называется flock().
bool flock(int $f, int $operation [, int& $wouldblock])
Функция устанавливает для указанного открытого дескриптора файла $f режим блокировки, который бы хотел получить текущий процесс. Этот режим задается аргументом $operation и может быть одной из следующих констант:

r LOCK_SH (или 1) — разделяемая блокировка;

r LOCK_EX (или 2) — исключительная блокировка;

r LOCK_UN (или 3) — снять блокировку;

r LOCK_NB (или 4) — эту константу нужно прибавить к одной из предыдущих, если вы не хотите, чтобы программа "подвисала" на flock() в ожидании своей очереди, а сразу возвращала управление.

В случае, если был затребован режим без ожидания, и блокировка не была успешно установлена, в необязательный параметр-переменную $wouldblock будет записано значение истина true.

В случае ошибки функция, как всегда, возвращает false, а в случае успешного завершения — true.

Блокирование файла


Хотя в документации PHP и сказано, что flock() работает во всех операционных системах (в том числе и под Windows), мои тесты показали, что как раз для Windows это не так. А именно, в этой системе функция всегда возвращает индикатор провала, независимо от того, правильно она вызывается, или нет. Возможно, в будущих версиях PHP это досадное недоразумение будет исправлено.


Блокировки с запретом "подвисания"

Как следует из описания функции flock(), к ее второму параметру можно прибавить константу LOCK_NB для того, чтобы функция не ожидала, когда программа может "двинуться в путь", а сразу же возвращала управление в основную программу. Это может пригодиться, если вы не хотите, чтобы ваш сценарий бесполезно простаивал, ожидая, пока ему разрешат обратиться к файлу. В эти моменты, возможно, лучше будет заняться какой-нибудь полезной работой — например, почистить временные файлы, память, или же просто сообщить пользователю, что файл заблокирован, чтобы он подождал и не думал, что программа зависла. Вот пример использования исключительной блокировки в совокупности с LOCK_NB:
$f=fopen("file.txt","a+");
while(!flock($f,LOCK_EX+LOCK_NB)) {
  echo "Пытаемся получить доступ к файлу
";
  sleep(1); // ждем 1 секунду
}
// Ðàáîòàåì
Эта программа основывается на том факте, что выход из flock() может произойти либо в результате отказа блокировки, либо после того, как блокировка будет установлена — но не до того! Таким образом, когда мы наконец-то дождемся разрешения доступа к файлу и произойдет выход из цикла while, мы уже будем иметь исключительную блокировку, закрепленную за нашим файлом.



Bool

Логический тип, который будет восприниматься либо как ложь (нулевое число, пустая строка или константа false), либо как истина (все остальное). Обычно редко указывается этот тип (вместо него пишут int, хотя это и неверно), но я все же постараюсь применять его там, где это возможно.



Целые числа

Существует несколько разных типов целых чисел, различающихся количеством байтов данных, которые отводятся в базе данных для их хранения. Все эти типы рознятся только названиями и (с некоторыми сокращениями) записываются так:
префиксINT [UNSIGNED]
Необязательный флаг UNSIGNED задает, что будет создано поле для хранения беззнаковых чисел (больших или равных 0).
Имена типов, в вобщем виде обозначенные здесь как префиксINT, приводятся в табл. 26.1.
Таблица 26.1. Типы целочисленных данных MySQL.

Тип
Описание
TINYINT
Может хранить числа от –128 до +127
SMALLINT
Диапазон от –32768 до 32 767
MEDIUMINT
Диапазон от –8 388 608 до 8 388 607
INT
Диапазон от –2 147 483 648 до 2 147 483 647
BIGINT
Диапазон от –9 223 372 036 854 775 808 до 9 223 372 036 854 775 807




CGI изнутри



CGI изнутри


До сих пор мы рассматривали лишь теоретические аспекты CGI. Мы знаем в общих чертах, как и что передается пользователю сервером и наоборот. Однако как же все-таки должна быть устроена CGI-программа (CGI-сценарий), чтобы работать с этой информацией? Откуда она ее вообще получает и куда должна выводить, чтобы переслать текст пользователю?
И это только небольшая часть вопросов, которые пока остаются открытыми. В этой главе я постараюсь вкратце описать, как же должны на самом деле быть устроены внутри CGI-сценарии. На мой взгляд, каждый программист обязан хотя бы в общих чертах знать, как работает то, что он использует — будь то операционная система (ОС) или удобный язык-интерпретатор для написания CGI-сценариев (каким является PHP). А значит, речь пойдет о программировании на Си. Я выбрал Си, т. к. это одно из самых лучших и лаконичных средств; кроме того, именно на Си чаще всего пишут те сценарии, которым требуется максимально критичное быстродействие (базы данных, поисковые системы, системы почтовой рассылки с сотнями тысяч пользователей и др.). В пользу этого языка говорит также и то, что его компиляторы можно встретить практически в любой сколько-нибудь серьезной ОС.
Тем не менее, вы не найдете в этой главе ни одной серьезной законченной программы на Си (за исключением разве что самой простой, типа "Hello, world!"). Несмотря на это, я попытаюсь описать практически все, что может понадобиться при программировании сценариев на Си (кроме работы с сокетами, — это тема для отдельной книги, да и, пожалуй, лишь косвенно примыкает к Web-программированию). По возможности я не буду привязываться к специфике конкретной ОС, ведь для CGI существует стандарт, независимый от операционной системы, на которой будет выполняться сценарий. Вооружившись материалом этой главы, можно написать самые разнообразные сценарии — от простых до самых сложных (правда, для последних потребуется также недюжинная сноровка).
И все-таки, моя цель — набросать общими мазками, как неудобно (повторюсь — именно неудобно!) программировать сценарии на языках, обычных для прикладного программиста (в том числе на Си и Си++). Как только вы проникнетесь этой идеей, мы плавно и не торопясь двинемся в мир PHP, где предусмотрены практически все удобства, так необходимые серьезному языку программирования сценариев.

Если вы не знакомы с языком Си, не отчаивайтесь. Все примеры хорошо комментированы, а сложные участки не нуждаются в непременном понимании "с первого прочтения". Еще раз оговорюсь, что материал этой и следующей глав предназначен для того, чтобы вы получили приблизительное представление о том, как же устроен протокол HTTP и как программы взаимодействуют с его помощью. Думаю, что без этих знаний невозможна никакая профессиональная работа на поприще Web-программирования. Так что не особенно расстраивайтесь, если вы совсем не знаете Си — ведь эта глава содержит гораздо больше, нежели просто описание набора Си-функций. В ней представлен материал, являющийся связующим звеном между CGI и HTML, детально описываются тэги форм и их наиболее полезные атрибуты, приемы создания запросов и многое другое. Все это, безусловно, понадобится нам и при программировании на PHP.

В то же время, изучая приведенные в этой главе примеры, вы можете и не проверять их на практике (особенно если у вас еще нет работающего Web-сервера), т. к. они (как это ни парадоксально звучит) предназначены лишь для теоретических целей. Сам не знаю, как это получилось, но, в конце концов, мне это нравится. Вам, надеюсь, понравится тоже…


Чего хочет программист от своей профессии

Давайте попробуем разобраться, чего хочет Web-программист, когда он выбирает свою профессию. Возможно, он считает, что эта стезя довольно прибыльна? Но деньги приходят, что называется, "сами собой" с накоплением опыта и получением определенных навыков, по мере того, как человек становится профессионалом. Так происходит с любой профессией, а не только с программированием. Кстати, как я немного выше упоминал, никто из профессиональных программистов не работает исключительно за деньги, основной стимул — это все-таки интерес к работе.
Идем дальше. Может быть, ему нужна известность? Конечно, этот фактор не является третьестепенным, учитывая то, что известность — гарантия, что программист всегда легко сможет найти работу. Однако, как и деньги, слава и известность также не бывают самими по себе — их необходимо заслужить. И, к тому же, много ли вы знаете известных имен программистов, действительно заслуживших свое признание практикой (Билл Гейтс не в счет, потому что он уже давно этим не занимается)? Правильно — ни одного. Разве что, может быть, кто-нибудь вспомнит доблестных создателей игры Doom, ставшей уже историей.
Но есть нечто такое, на что я уже намекал, и именно этим Web-программирование (да и вообще любая работа, происходящая в Web) резко отличается в лучшую сторону от всех доселе известных видов программирования. Вы можете быть очень хорошим прикладным или системным программистом. Однако вряд ли ваши программы будет использовать такое количество людей, которое ежедневно посещает даже и не самую популярную страничку в Интернете, "подкрепленную" Web-сценарием. Вряд ли вы получите такое количество отзывов, приобретете такое число бесплатных тестеров, усердно шлющих вам гору писем с сообщениями о неточностях и ошибках в вашем продукте, а также с отзывами и предложениями. А ведь, как известно, заметить ошибку в программе означает "отрубить ей голову". Наконец, иногда приятно отметить для себя, что сценарий, написанный вами несколько лет назад, о котором вы почти уже и забыли думать, продолжает исправно работать "сам по себе", без всякого человеческого вмешательства.



Чтение CSV-файла

Программа Excel из Microsoft Office стала настолько популярна, что в PHP даже встроили функцию для работы с одним из форматов файлов, в которых может сохранять данные Excel. Часто она бывает довольно удобна и экономит пару строк дополнительного кода.
list fgetcsv(int $f, int $length, char $delim=’,’)
Функция читает одну строку из файла, заданного дескриптором $f, и разбивает ее по символу $delim. Параметр $delim должен обязательно быть строкой из одного символа, в противном случае принимается во внимание только первый символ этой строки. Функция возвращает получившийся список или false, если строки кончились. Параметр $length
задает максимальную длину строки точно так же, как это делается в fgets(). Пустые строки в файле не игнорируются, а возвращаются как список из одного элемента — пустой строки.
Функция fgetcsv() работает чуть быстрее пары fgets()/explode(), но зато она, как мы можем видеть, гораздо менее универсальна. Применяйте ее в таком контексте:
$f=fopen("file.csv","r") or die("Ошибка!");
for($i=0; $data=fgetcsv($f, 1000, ";"); $i++) {
  $num = count($data);
  if($num==1 && $data[0]==="") continue;
  echo "

Строка номер $i ($num полей):

";
  for($c=0; $c<$num; $c++)
    print "[$c]: $data[$c]
";
}
fclose($f);



Чтение и запись

Для каждого открытого файла (точнее, для каждого файлового дескриптора, ведь один и тот же файл может быть открыт несколько раз, т.е. с ним может быть связано сразу несколько дескрипторов) система хранит определенную величину, которая называется текущей позицией ввода-вывода, или указатель файла. Функции чтения и записи файлов работают именно с этой позицией. А именно, функции чтения читают блок данных, начиная с этой позиции, а функции записи — записывают, также отсчитывая от нее. Если указатель файла установлен за последним байтом и осуществляется запись, то файл автоматически увеличивается в размере. Есть также функции для установки этой самой позиции в любое место файла.
После того как файл успешно открыт, из него (при помощи дескриптора файла) можно читать, а также, при соответствующем режиме открытия, писать. Обмен данными осуществляется через обыкновенные строки и, что важнее всего, начиная с позиции указателя файла. В следующих разделах рассматриваются несколько функций для чтения/записи.



Что такое CGI?

Итак, мы набираем в нашем браузере
http://www.somehost.com:80/path/to/document.ext
Мы ожидаем, что сейчас получим HTML-документ (или документ другого формата — например, рисунок). Иными словами, мы рассчитываем, что на хосте в каталоге /path/to/ расположен файл document.ext, который нам сейчас и доставят (передаст его, кстати, Web-сервер, подключенный к порту 80 на сервере).
Однако на самом деле ситуация несколько иная. По двум причинам.
r Путь /path/to/, ровно как и файл document.ext на хосте может вообще не существовать. Ведь администратор сервера имеет возможность задать псевдоним
(alias) для любого объекта на сервере. Кроме того, даже если и не назначено никакого псевдонима, все равно имеется возможность так написать программы для Web-сервера, что они будут "перехватывать" каждое обращение к таким путям и соответствующим образом реагировать на это (пример рассмотрен в главе 1 ).
r Файл document.ext может быть вовсе не текстовым документом, а программой, которая в ответ на наш запрос молниеносно запустится, не менее стремительно выполнится и возвратит пользователю результаты своей работы, хотя бы в том же HTML-формате (или, разумеется, в любом другом, — например, это может быть изображение). Пользователь и не догадается, что на самом деле произошло. Для него все равно, загружает ли он документ или невольно запускает программу. Более того, он никак не сможет узнать, что же на самом деле случилось.
Последний пункт особенно впечатляющ. Если вы прониклись его идеей, значит, вы поняли в общих чертах, что такое CGI. Как раз CGI обеспечивает все то, что выглядит так прозрачно для пользователя. Традиционно программы, работающие в соответствии с соглашениями CGI, называют сценариями — скорее всего из-за того, что в большинстве случаев их пишут на языках-интерпретаторах, подобных Basic (например, на Perl или PHP).
Задумаемся на мгновенье. Мы получили довольно мощный механизм, который позволяет нам, в частности, формировать документы "на лету". К примеру, пусть нам нужно, чтобы в каком-то документе проставлялись текущая дата и время. Разумеется, мы не можем заранее прописать их в документе — ведь в зависимости от того, когда он будет загружен пользователем, эта дата должна меняться. Зато мы можем написать сценарий, который вычислит дату, вставит ее в документ и затем передаст его пользователю, который даже ничего и не заметит!
Однако в построенной нами модели не хватает одного звена. Действительно, предположим, нам нужно, чтобы время в нашей странице проставлялось на основе часового пояса пользователя. Но как сценарий узнает, какой часовой пояс у региона, в котором живет этот человек (или какую-нибудь другую информацию, которую может предоставить пользователь)? Видимо, должен быть какой-то механизм, который позволит пользователю не только получать, но также и передавать информацию серверу (в данном случае, например, поправку времени в часах относительно Москвы). И это тоже обеспечивает CGI. Но вернемся прежде снова к URL.



Что такое формы и для чего они нужны

Итак, мы знаем, что наиболее распространенными методами передачи данных между браузером и сценарием являются GET и POST. Однако вручную задавать строки параметров для сценариев и к тому же URL-кодировать их, согласитесь, довольно утомительно. Давайте посмотрим, что же язык HTML предлагает нам для облегчения жизни.
Сначала рассмотрим метод GET. Даже программисту довольно утомительно набирать параметры в URL вручную. Всякие там ?, &, %... Представьте себе пользователя, которого принуждают это делать. К счастью, существуют удобные возможности языка HTML, которые, конечно, поддерживаются браузерами. И хотя я уже замечал, что в этой книге будет лишь немного рассказано о HTML, о формах
мы поговорим очень подробно.
Итак, пусть у нас на сервере в корневом каталоге размещен файл сценария script.cgi (наверное, вы уже заметили, что расширение cgi принято присваивать CGI-сценариям, хотя, как уже упоминалось, никто не мешает вам использовать любое другое слово).
Этот сценарий распознает 2 параметра: name и age. Где эти параметры задаются, мы пока не решили. При переходе по адресу http://www.somehost.com/script.cgi он должен отработать и вывести следующую HTML-страницу:

Ïðèâåò, name! ß çíàþ, Âàì age
ëåò!

Разумеется, при генерации страницы нужно name и age заменить на соответствующие значения, переданные в параметрах.



Что такое шаблонизатор?

Итак, мы вновь столкнулись с множеством трудноразрешимых накладок (возможно, выглядящих для многих с первого взгляда как надуманные). Когда же они закончатся, спросите вы? Отвечаю: прямо сейчас.
Давайте взглянем "в корень" описанных выше сложностей. Почему они вообще возникают в этой задаче? Нетрудно догадаться: опять же из-за излишних зависимостей данных. Помните, эти зависимости привели нас в свое время к необходимости перехода от двухуровневой схемы построения сценариев к трехуровневой? Теперь они подводят нас к идее шаблонизатора.
Вспомним, что мы сделали тогда, чтобы убрать зависимости. Мы поменяли местами "поставщика" и "исполнителя". Идея выполнить обратную перестановку кажется абсурдной, т.к. мы опять придем к тому, с чего начали. Конечно, мы не будем так делать. Зададимся отвлеченным вопросом: что предпринимает общество, когда перед ним возникает чересчур большое количество нерешенных и необъяснимых задач? Оно придумывает себе богов. В программировании — то же самое. Раз мы не можем больше сослаться ни на генератор данных, ни на шаблон, значит, настало время реализовать нечто третье, "бога", управляющего всей системой в совокупности и распределяющего обязанности. Вы, наверное, догадались, что я снова имею в виду шаблонизатор.
Итак, шаблонизатор — это программный код, держащий "под контролем" все файлы на нашем сайте. Ни одно обращение к странице, ни один запуск сценария не может пройти без его непосредственного участия. В то же время шаблонизатор "маскирует" себя, создавая у пользователя впечатление, будто бы его и нет. Этим он похож на генератор данных в трехуровневой модели построения сценариев.
Что такое шаблонизатор?

Теперь вы почувствовали, почему я применил здесь аналогию с богом? Ведь бог как раз удовлетворяет тем описаниям, которые даны в предыдущем абзаце!
Впрочем, идеология "вездесущего" кода не является для нас новой: нечто похожее мы уже рассматривали в главе 29, правда, с целью гарантированного подключения библиотекаря ко всем сценариям сайта. В рамках реализуемой "религии"  мы применим точно такой же подход, только вместо библиотекаря будет подключаться и запускаться шаблонизатор.



Content-length

r Формат: Content-length: длина
r Переменная окружения: CONTENT_LENGTH
Заголовок содержит строку, являющуюся десятичным представлением длины данных в байтах, передаваемых методом POST. Если задействуется метод GET, то этот заголовок отсутствует, и значит, переменная окружения не устанавливается.

Количество байтов данных, присланных пользователем. Эту переменную необходимо анализировать, если вы занимаетесь приемом и обработкой POST-формы.



Content-type

r
Формат: Content-Type: application/x-www-form-urlencoded
r Переменная: CONTENT_TYPE
Данный заголовок идентифицирует тип передаваемых данных. Обычно для этого указывается значение application/x-www-form-urlencoded, что означает формат, в котором все управляющие символы (отличные от алфавитно-цифровых и других отображаемых) специальным образом кодируются. Это тот самый формат передачи, который используется методами GET и POST. Довольно распространен и другой формат, и называется он multipart/form-data. Мы разберем его, когда будем обсуждать вопрос, касающийся загрузки файлов на сервер.
Хочу обратить ваше внимание на то, что сервер никак не интерпретирует рассматриваемый заголовок, а просто передает его сценарию через переменную окружения.

r Формат: Content-type: mime_тип; charset=koi8-r
Задает тип документа и его кодировку. Параметр charset задает кодировку документа (в нашем примере это KOI8-R). Поле mime_тип определяет тип информации, которую содержит документ:
r text/html — HTML-документ;
r text/plain — простой текстовый файл;
r image/gif — GIF-изображение;
r image/jpeg — JPG-изображение;
r еще несколько десятков других типов.



Cookie

r Формат: Cookie: значения_Cookies
r Переменная окружения: HTTP_COOKIE
Здесь хранятся все Cookies в URL-кодировке (о Cookies мы подробнее поговорим в следующей главе).



Дата и время

MySQL поддерживает несколько типов полей, специально приспособленных для хранения дат и времени в различных форматах (табл. 26.5).
Таблица 26.5. Представление дат и времени в базах данных MySQL

Тип
Описание
DATE
Дата в формате ГГГГ-ММ-ДД
TIME
Время в формате ЧЧ:ММ:СС
DATETIME
Дата и время в формате ГГГГ-ММ-ДД ЧЧ:ММ:СС
TIMESTAMP
Время и дата в формате timestamp. Однако при получении значения поля оно отображается не в формате timestamp, а в виде ГГГГММДДЧЧММСС, что сильно умаляет преимущества его использования в PHP

Надо заметить, что в PHP будет проще самостоятельно генерировать дату и время при вставке данных в таблицу, а не задействовать встроенные в MySQL типы. Например, привлекательный с виду тип TIMESTAMP на деле оказывается довольно неудобным, потому что отображается не в том виде, который мы ожидаем.



Date

Формат: Date: Sat, 08 Jan 2000 11:56:26 GMT
Указывает браузеру дату отправки документа.



Действия с переменными

Вне зависимости от типа переменной, с ней можно делать три основных действия.



Деструктор

По аналогии с конструкторами обычно рассматриваются деструкторы. Деструктор— тоже специальный метод объекта, который вызывается при уничтожении этого объекта (например, после завершения программы). Деструкторы обычно выполняют служебную работу — закрывают файлы, записывают протоколы работы, разрывают соединения, "форматируют винчестер" — в общем, освобождают ресурсы. К сожалению, из-за "щедрости" PHP на выделение памяти, которая никогда не будет освобождена, деструкторы в нем не поддерживаются. Так что, если вам нужно выполнить нечто необычное после того, как вы перестали использовать какой-то объект, определите в нем метод, который будет это делать, и вызовите его явно.



Диаграммы двухуровневой и трехуровневой моделей

Наверное, пришло время нарисовать схему взаимодействия частей программы при использовании двухуровневой и трехуровневой модели построения, а также еще раз подчеркнуть их различия. Стрелками (рис.30.1 и 30.2) обозначены зависимости, которые можно охарактеризовать словами как "предоставляет данные". Пунктирные стрелки отмечают зависимости, реализуемые достаточно редко. На схемах это не что иное, как переадресация на другую страницу, возможно, выполняемая генератором данных.
Диаграммы двухуровневой и трехуровневой моделей

Рис. 30.1.
Двухуровневая схема
Мы видим, что в случае двухуровневой схемы связи между компонентами сценария исключительно циклические (см. рис. 30.1). Каждая часть программы взаимодействует на равных с другой ее частью.
Легко заметить, что рис. 30.2 гораздо сложнее, чем рис. 30.1. Его "загруженность"
объясняется тем, что трехуровневая схема более, чем это может показаться с первого взгляда, сложна и универсальна по сравнению с двухуровневой. Обратите внимание на то, что практически все связи стали двусторонними, а циклические — исчезли. Это позволяет работать блоком более независимо, чем для случая двухуровневой модели. А значит, работу над сценарием можно распределить по нескольким исполнителям более эффективно, — к чему мы и стремились.
Диаграммы двухуровневой и трехуровневой моделей

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

Рис. 30.2.
Трехуровневая схема



Динамическая смена кодировки

Приведенное в предыдущем примере письмо можно будет прочитать в 90% существующих почтовых программ. Для "удовлетворения"
остальных желательно посылать письма не в Windows-кодировке, а в KOI-8R. Для перекодирования можно воспользоваться уже известной нам функцией convert_cyr_string(). И, конечно, нужно в Content-type заменить charset на koi8-r. Вот что у нас получится:
$message=
"From: Лист ðàññûëêè
To: Èâàíîâ Èâàí Èâàíîâè÷
Subject: Ïðîáíàÿ ðàññûëêà
Content-type: text/plain; charset=koi8-r
Óâàæàåìûé òîâàðèù! Ýòî ïèñüìî ïîñëàíî ïî÷òîâûì ðîáîòîì.
Âñåãî õîðîøåãî!";
$message=convert_cyr_string($message,"w","k");
Mail("ivanov@ivan.ivanovich.ru","",$message);
Теперь вы понимаете, почему я говорил о том, чтобы все заголовки и Subject находились внутри тела письма? Этим мы достигаем того, что одной командой convert_cyr_string() перекодируется сразу все письмо, включая поля From, To, Subject и др. Иначе пришлось бы применять эту функцию дополнительно для перекодировки параметров $subject и $headers...



Domain

Параметр domain=имя_хоста задает имя хоста, с которого установили Cookie. Ранее я уже говорил про этот параметр. Так вот, оказывается, его можно менять вручную, прописав здесь нужный адрес, и таким образом "подарить" Cookie другому хосту. Только в том случае, если параметр не задан, имя хоста определяется браузером автоматически.



Доменное имя

И все-таки обычным людям довольно неудобно работать с IP-представлением адреса. Действительно, куда как проще запомнить символьное имя, чем набор чисел. Чтобы облегчить простым пользователям работу с Интернетом, придумали систему DNS
(Domain Name System
— Система имен доменов).
Доменное имя

Общемировая DNS представляет собой распределенную базу данных, способную преобразовать доменные имена машин в их IP-адреса. Это не так-то просто, учитывая, что скоро Интернет будет насчитывать десятки миллионов компьютеров. Поэтому мы не будем в деталях рассматривать то, как работает служба DNS, а займемся больше практической стороной вопроса.
Итак, при использовании DNS любой компьютер в Сети может иметь не только IP-адрес, но также и символическое имя. Выглядит оно примерно так:
www.somehost.msu.su
То есть, это набор слов (их число произвольно), опять же соединенных точкой. Каждое такое сочетание слов называется доменом N-го уровня
(например, su— домен первого уровня, msu.su — второго, somehost.msu.su — третьего и т. д.)
Вообще говоря, полное DNS-имя выглядит немного не так: в его конце обязательно стоит точка, например:
www.somehost.msu.su.
Именно такое (вообще-то, и только такое) представление является правильным, но браузеры и другие программы часто позволяют нам опускать завершающую точку. В принятой нами терминологии будем называть эту точку доменом нулевого уровня, или корневым доменом.
Доменное имя

Интересно, и почему так популярна в компьютерной технике точка? В именах файлов — точка. В IP- и DNS-адресе — точка. Практически во всех языках программирования для доступа к объединениям данных — тоже точка. Существуют и другие примеры. Похоже, точка прочно въелась в наши умы, и мы уже не представляем, что бы могло ее заменить...
Нужно заметить, что одному и тому же IP-адресу вполне может соответствовать сразу несколько доменных имен. Каждое из них ведет в одно и то же место — к единственному IP-адресу. Благодаря протоколу HTTP 1.1 (мы вскоре кратко рассмотрим его особенности) Web-сервер, установленный на машине и откликающийся на какой-либо запрос, способен узнать, какое доменное имя ввел пользователь, и соответствующим образом среагировать, даже если его IP-адресу соответствует несколько доменных имен. В последнее время HTTP 1.1 применяется практически повсеместно — не то, что несколько лет назад, поэтому все больше и больше серверов используют его в качестве основного протокола для доступа к Web.

Интересен также случай, когда одному и тому же DNS-имени сопоставлены несколько разных IP-адресов. В этом случае служба DNS автоматически выбирает тот из адресов, который, по ее мнению, ближе всего расположен к клиенту, или который давно не использовался, или же наименее загружен (впрочем, последняя оценка может быть весьма и весьма субъективна). Эта возможность часто задействуется, когда Web-сервер становится очень большим (точнее, когда число его клиентов начинает превышать некоторый предел) и его приходится обслуживать сразу нескольким компьютерам. Такая схема используется, например, на сайте компании Netscape.

Как же ведется поиск по DNS-адресу? Для начала он преобразуется специальными DNS-серверами, раскиданными по всему миру, в IP-адрес. Давайте посмотрим, как это происходит. Пусть клиентом выдан запрос на определение IP-адреса машины www.host.ru. (еще раз обратите внимание на завершающую точку! — это не конец предложения).

Чтобы его обработать, первым делом посылается запрос к так называемому корневому домену (точнее, к программе — DNS-серверу, запущенному на этом домене), который имеет имя "." (на самом деле его база данных распределена по нескольким компьютерам, но для нас это сейчас несущественно). Запрос содержит команду: вернуть IP-адрес машины (точнее, IP-адрес DNS-сервера), на котором расположена информация о домене ru. Как только IP-адрес получен, по нему происходит аналогичное обращение с просьбой — определить адрес, соответствующий домену host внутри домена ru внутри корневого домена ".".

В конце у предпоследней машины запрашивается IP-адрес поддомена www в домене somehost.ru.

Важно, что каждый домен "знает"

все о своих поддоменах, а те, в свою очередь — о своих, т. е. система имеет некоторую иерархичность. Корневой домен, как мы уже заметили, принято называть доменом нулевого уровня, домен ru. (в нашем примере) — первого, host.ru. — второго уровня, ну и т. д. При изменении доменов некоторого уровня об этом должны узнать все домены, родительские по отношению к нему, для чего существуют специальные протоколы синхронизации. Нам сейчас нет нужды вникать, как они действуют — скажу только, что распространяются сведения об изменениях не сразу, а постепенно, спустя некоторое время, задаваемое администратором DNS-сервера, и рассылкой также занимаются DNS-серверы.


Просто? Не совсем. Представьте, какое бы произошло столпотворение на корневом домене ".", если бы все запросы на получение IP-адреса проходили через него. Чтобы этого избежать, практически все машины в Сети кэшируют информацию о DNS-запросах, обращаясь к корневому домену (и доменам первого уровня — ru, com и т. д.) лишь изредка для обновления этого кэша. Например, пусть пользователь, подключенный через модем к провайдеру, впервые соединяется с машиной www.host.ru. В этом случае будет передан запрос корневому домену, а затем, по цепочке, поддомену host и, наконец, домену www. Если же пользователь вновь обратится к www.host.ru., то сервер провайдера сразу же вернет ему нужный IP-адрес, потому что он сохранил его в своем кэше запросов ранее. Подобная технология позволяет значительно снизить нагрузку на DNS-серверы в Интернете. В то же время у нее имеются и недостатки, главный из которых — вероятность получения ложных данных, например, в случае, если хост host.ru. только что отключился или сменил свой IP-адрес. Так как кэш обновляется сравнительно редко, мы всегда можем столкнуться с такой ситуацией.

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

Доменное имя


Здесь и далее я иногда буду подразумевать, что одной машине в Сети всегда соответствует уникальный IP-адрес, и наоборот, для каждого IP-адреса существует своя машина, хотя это, разумеется, не так. Просто так получится немного короче.


Дополнительные функции

bool eregi(string $expr, string $str [,list &$Matches])
То же, что и ereg(), только без учета регистра символов.
Дополнительные функции

Хотя регистр и не учитывается при поиске, в карманах $Matches все найденные подстроки все же запишутся с точным сохранением регистра букв.
string eregi_replace(string $expr, strint $str, string $strToChange)
То же, что и ereg_replace(), но без учета регистра
áóêâåííûõ ñèìâîëîâ.
int quotemeta(string $str)
Часто бывает нужно гарантировать, чтобы в какой-то переменной-строке ни один символ не мог трактоваться как метасимвол. Этого можно добиться, предварив каждый из них наклонной чертой, что и делает функция quotemeta(). А именно, она "заслэшивает"
следующие символы: . , \\, +, *, ? , [ ^ ]
, ( $ ).
Дополнительные функции

Перед | слэш почему-то не ставится. Будьте особо внимательны!
list split(string $pattern, string $string [,int $limit])
Эта функция очень похожа на explode().
Она тоже разбивает строку $string на части, но делает это, руководствуясь регулярным выражением $pattern. А именно, те участки строки, которые совпадают с этим выражением, и будут служить разделителями. Параметр $limit, если он задан, имеет то же самое значение, что и в функции explode() — а именно, возвращается список из не более чем $limit элементов, последний из которых содержит участок строки от ($limit-1)-го совпадения до конца строки.
Наверное, вы уже догадались, что функция split()
работает гораздо медленнее, чем explode(). Однако она, вместе с тем, имеет впечатляющие возможности, в чем мы очень скоро убедимся. Тем не менее, не стоит применять split()
там, где прекрасно подойдет explode(). Чаще всего этим грешат программисты, имеющие некоторый опыт работы с Perl, потому что в Perl для разбиения строки на составляющие есть только функция split().
list spliti(string $pattern, string $string [,int $limit])
Аналог функции split(), который делает то же самое, только при сопоставлении с регулярным выражением не учитывается регистр символов.



Доступ объекта к своим свойствам

Как ни странно, но при изучении ООП "с нуля" программисты, привыкшие к структурному программированию, часто с трудом понимают, каким образом объект может добраться до своих собственных свойств. Рассмотрим, например, такую программу:
$Obj1=new Mysqltable;
$Obj2=new MysqlTable;
. . .
echo $Obj1->TableName, " ", $Obj2->TableName;
Здесь никаких проблем не возникает— ясно, что выводятся свойства разных объектов — мы же сами указали их до стрелки. Однако давайте посмотрим, что будет, если вызвать какой-нибудь метод одного из объектов:
$Obj1->Drop();
Как видите, при вызове метода так же, как и при доступе к свойству, нужно указать объект, который должен "откликнуться на запрос". Действительно, этой командой мы удаляем из базы данных таблицу $Obj1, а не $Obj2. Рассмотрим теперь тело метода Drop():
class MysqlTable {
 function Drop()
 { сюда интерпретатор попадет, когда вызовется Drop() для
 какого-то объекта
 }
}
По логике, Drop() — функция. Эта функция, конечно, едина для всех объектов класса MysqlTable. Но как же метод Drop() узнает, для какого объекта он был вызван? Ведь мы не можем Drop() для $Obj1 сделать одним, а для $Obj2 — другим, иначе нарушился бы весь смысл нашей объектной ориентированности. В том-то вся и соль, что два различных объекта-таблицы являются объектами одного и того же класса...
Оказывается, для доступа к свойствам (и методам, т. к.
один метод вполне может вызывать другой) внутри метода используется специальная предопределенная переменная $this, содержащая тот объект, для которого был вызван метод. Теперь мы можем определить Drop() внутри класса так:
function Drop()
{ // сначала удаляем все записи из таблицы
 $this->Delete("1=1"); // всегда истинное выражение
 // а затем удаляем саму таблицу
 mysql_query("drop table ".$this->TableName);
}
Если мы вызвали Drop() как $Obj1->Drop(), то $this будет являться тем же объектом, что и $Obj1 (это будет ссылка на $Obj1), а если бы мы вызвали $Obj2->Drop()[E151] , то $this был бы равен $Obj2. То есть метод всегда знает, для какого объекта он был вызван. Это настолько важно, что я повторю еще раз: метод всегда знает, для какого объекта он был вызван.
Использование ссылок говорит о том, что $this — не просто копия объекта-хозяина, это и есть
хозяин. Например, если бы в $Obj1->Drop() мы захотели изменить какое-то свойство $this, оно поменялось бы и у $Obj1, но не у $Obj2 или других объектов.
В синтаксисе PHP есть один просчет: запись вида
$ArrayOfObjects["obj"]->DoIt();
считается синтаксически некорректной. Вместо нее применяйте следующие две команды:
$obj=&$ArrayOfObjects["obj"]; $obj->DoIt();
Доступ объекта к своим свойствам

Не забудьте про &
сразу после оператора присваивания (то есть создавайте ссылку на элемент массива), иначе метод DoIt()
будет вызван не для самого объекта, присутствующего в массиве, а для его копии, полученной в $obj!



Доступ по ключу

Как мы уже знаем, ассоциативные массивы— объекты, которые наиболее приспособлены для выборки из них данных путем указания нужного ключа. В PHP è для всех массивов, и для списков (которые, еще раз напомню, также являются массивами) используется один и тот же синтаксис, что является очень большим достоинством. Вот как это выглядит:
echo $Arr["anykey"]; // âûâîäèò ýëåìåíò ìàññèâà $Arr ñ êëþ÷îì anykey
echo $Arr["first"]["second"]; // так используются двумерные массивы
echo (SomeFuncThatReturnsArray())[5]; // ОШИБКА! Так нельзя!
// Вот так правильно:
$Arr= SomeFuncThatReturnsArray();
echo $Arr[5];
Последний пример показывает, что PHP сильно отличается от Си с точки зрения работы с массивами: в нем нет такого понятия, как "контекст массива", а значит, мы не можем применить [] непосредственно к значению, возвращенному функцией.
Величина $Arr[ключ]
является полноценным "левым значением", т. е. может стоять в левой части оператора присваивания, от нее можно брать ссылку с помощью оператора &, и т. д. Например:
$Arr["anykey"]=array(100,200); // присваиваем элементу массива 100[E47]
$ref=&$Arr["first"]["second"]; // $ref — синоним элемента массива
$Arr[]="for add"; // добавляем новый элемент



Double, float

Вещественное число, или целое число, или строка, содержащая одно из таких чисел.



Double

Вещественное число довольно большой точности (ее должно хватить для подавляющего большинства математических вычислений).



Дробные числа

Точно так же, как целые числа подразделяются в MySQL на несколько разновидностей, MySQL поддерживает и несколько типов дробных чисел.

В общем виде они записываются так:
ИмяТипа[(length,decimals)] [UNSIGNED]
Здесь length — количество знакомест (ширина поля), в которых будет размещено дробное число при его передаче в PHP, а decimals — количество знаков после десятичной точки, которые будут учитываться. Как обычно, UNSIGNED задает беззнаковые числа.  Строка ИмяТипа замещается на предопределенные значения, соответствующие возможным вариантам представления вещественных чисел (табл. 26.2).
Таблица 26.2. Типы рациональных чисел в MySQL

Тип
Описание
FLOAT
Число с плавающей точкой небольшой точности
DOUBLE
Число с плавающей точкой двойной точности
REAL
Синоним для DOUBLE
DECIMAL
Дробное число, хранящееся в виде строки
NUMERIC
Синоним для DECIMAL




Другие функции

bool ftruncate(int $f, int $newsize)
Эта функция усекает открытый файл $f до размера $newsize. Разумеется, файл должен быть открыт в режиме, разрешающем запись. Например, следующий код просто очищает весь файл:
ftruncate($f,0);  // î÷èñòèòü ñîäåðæèìîå ôàéëà
void fflush(int $f)
Заставляет PHP немедленно записать на диск все изменения, которые производились до этого с открытым файлом $f. Что это за изменения? Дело в том, что для повышения производительности все операции записи в файл буферизируются: например, вызов fputs($f,"Это строка!") не приводит к непосредственной записи данных на диск — сначала они попадают во внутренний буфер (обычно размером 8K). Как только буфер заполняется, его содержимое отправляется на диск, а сам он очищается, и все повторяется вновь. Особенный выигрыш от буферизации чувствуется в сетевых операциях, когда просто глупо отправлять данные маленькими порциями. Конечно, функция fflush()
вызывается неявно и при закрытии файла.
int set_file_buffer(int $f, int $size)
Эта функция устанавливает размер буфера, о котором мы только что говорили, для указанного открытого файла $f. Чаще всего она используется так:
set_file_buffer($f,0);
Приведенный код отключает буферизацию для указанного файла, так что теперь все данные, записываемые в файл, немедленно отправляются на диск или в сеть.
Другие функции

Буферизированный ввод/вывод придуман не зря. Не отключайте его без крайней надобности — это может нанести серьезный ущерб производительности.

В крайнем случае используйте fflush().

void usleep(int $micro_seconds)
Вызов этой функции позволяет сценарию "замереть"
не указанное время

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

Существует также функция sleep(), которая принимает в параметрах не микросекунды, а секунды, на которые нужно задержать выполнение программы.
int uniqid(string $prefix)
Функция uniqid() возвращает строку, при каждом вызове отличающуюся от результата предыдущего вызова. Параметр $prefix задает префикс (до 114 символов длиной) этого идентификатора.
Зачем нужен префикс? Представьте себе, что сразу несколько интерпретаторов на разных хостах одновременно вызвали функцию uniqid(). В этом случае существует вероятность того, что результат работы функций совпадет, чего нам бы не хотелось. Задание в качестве префикса имени хоста решит проблему.
Чтобы добиться большей уникальности, можно использовать uniqid()

"в связке"
с функциями mt_rand()
и md5(), описанными в предыдущих главах.


Другие функции




Здесь мы для полноты картины рассмотрим функции для работы с сессиями, которые применяются гораздо реже, чем уже описанные.
bool session_is_registered(string $name)
Функция session_is_registered() возвращает значение true, если переменная с именем $name
была зарегистрирована в сессии, иначе возвращается false.
bool session_unregister(struing $name)
Эта функция отменяет регистрацию для переменной с именем $name
для текущей сессии. Иными словами, при завершении сценария все будет выглядеть так, словно переменная с именем $name
и не была никогда зарегистрирована. Возвращает true, если все прошло успешно, и false
— в противном случае.
Другие функции

После вызова функции session_unregister()
глобальная переменная, которая была "аннулирована", не уничтожается, а сохраняет свое значение.
void session_unset()
Функция session_unset(), в отличие от session_unregister(), не только отменяет регистрацию переменных (кстати говоря, всех
переменных сессии, а не какой-то одной), но и уничтожает глобальные переменные, которые были зарегистрированы в сессии.
string session_save_path([string $path])
Эта функция возвращает имя каталога, в котором будут помещаться файлы— временные хранилища данных сессии. В случае, если указан параметр, как обычно, активное имя каталога будет переустановлено на $path. При этом функция вернет предыдущий каталог.
К сожалению, функции, которая бы возвращала список всех зарегистрированных в сессии переменных, почему-то нет. Во всяком случае, в PHP версии 4.0.3.



Дуга сектора

int imageArc(int $im,int $cx,int $cy,int $w,int $h,int $s,int $e,int $c)
Функция imageArc() рисует в изображении $im дугу сектора эллипса от угла $s до $e (углы указываются в градусах против часовой стрелки, отсчитываемых от горизонтали). Эллипс рисуется такого размера, чтобы вписываться в прямоугольник ($x,$y,$w,$h), где $w и $h задают его ширину и высоту, а $x и $y— координаты левого верхнего угла. Сама фигура не закрашивается, обводится только ее контур, для чего используется цвет $c.



Двухуровневая схема

Итак, мы желаем максимально отделить работу программистов и дизайнеров. Давайте будем делать это не сразу, а постепенно, детализируя ситуацию. Вначале решим более простую проблему: разделим код сценария и шаблон его страницы (что я называю двухуровневой схемой
построения сценария). Это довольно несложно. Мы уже поступали так в главе 28, когда писали сценарий простейшего фотоальбома. Теперь мы поставим задачу более точно.



Error_reporting

Устанавливает уровень строгости для системы контроля ошибок PHP. Значение этого параметра должно представлять из себя целое число, которое интерпретируется как десятичное представление двоичной битовой маски. Установленные в 1 биты задают, насколько детальным должен быть контроль. Можно также не возиться с битами, а использовать константы.
Таблица 24.1. Биты, управляющие контролем ошибок

Бит
Константа PHP
Назначение
1
E_ERROR
Фатальные ошибки
2
E_WARNING
Общие предупреждения
4
E_PARSE
Ошибки трансляции
8
E_NOTICE
Предупреждения
16
E_CORE_ERROR
Глобальные предупреждения (почти не используются)
32
E_CORE_WARNING
Глобальные ошибки (не используется)

Наиболее часто встречающееся сочетание — 7 (1+2+4), которое, как мы можем видеть, задает полный контроль, кроме некритичных предупреждений интерпретатора (таких, например, как обращение к неинициализированной переменной). Оно часто задается по умолчанию при установке PHP. Я же рекомендую первым делом устанавливать значение этой настройки равным 255 (соответствует битовой маске со всеми единичками), т. е. включить абсолютно все сообщения об ошибках, или же воспользоваться константой E_ALL, делающей то же самое.



Expires

Необязательная пара expires=дата задает время жизни нашего Cookie. Точнее, Cookie самоуничтожится, как только наступит указанная дата. Например, если задать expires=Friday,31-Dec-99 23:59:59 GMT, то "печенье" будет "жить" только до 31 декабря 1999 года. Кстати, вот вам и вторая неприятность: хорошо, если мы знаем наверняка время "смерти" Cookie. А если нам нужно его вычислять на основе текущего времени (например,
если мы хотим, чтобы Cookie существовал 10 дней после его установки, как в подавляющем большинстве случаев и происходит)? Придется использовать функцию, которая формировала бы календарную дату в указанном выше формате. Кстати, если этот параметр не указан, то временем жизни будет считаться вся текущая сессия работы браузера, до того момента, как пользователь его закроет.



Файл конфигурации Apache httpd.conf

Это приложение содержит полный текст файла конфигурации сервера Apache httpd.conf
с комментариями на русском языке.
Файл конфигурации Apache httpd.conf

Содержимое листинга П1.1 полностью соответствует указаниям по настройке Apache, приведенным в части II книги. Если у вас по какой-то причине не получится правильно установить Apache и PHP версии 4, руководствуясь этими указаниями, представленный ниже текст файла httpd.conf решит все проблемы.
Несколько слов о формате httpd.conf. Файл состоит из строк, содержащих директивы Apache. В одной строке может быть расположено не более одной директивы. Текст от # äо конца строки считается комментарием и не берется в рассмотрение. Также игнорируются пустые строки.
При изменении начальной конфигурации файла возможно группирование нескольких директив в блоки, или контейнеры. При этом Apache поддерживает только ограниченное количество допустимых типов контейнеров. Любой блок-контейнер начинается строкой вида <ИмяКонтейнера>, расположенной, как обычно, на отдельной строке, и завершается тэгом . Некоторые (но не все) блоки могут быть вложенными.
Директивы, касающиеся индивидуальных настроек для каталогов или файлов, могут также помещаться в специальные файлы .htaccess, расположенные в соответствующих местах дерева каталогов сайта. Эти файлы должны иметь тот же формат, что и httpd.conf. Однако для них имеются особые ограничения на использование директив и блоков — список недопустимых можно найти в документации, поставляемой с Apache.
Листинг П1.1. Файл конфигурации Apache httpd.conf
# Основан на конфигурационных файлах сервера NSCA, созданных
# Робом МакКулом.
#
# Главный файл конфигурации сервера Apache, содержащий директивы,
# управляющие работой сервера. За более детальной информацией
# обращайтесь по адресу http://www.apache.org/docs/.
#
# Не стоит читать эти директивы без понимания их роли. Они
# приведены здесь лишь в качестве примера одного из возможных
# вариантов. В случае сомнений обращайтесь к сопроводительной

# документации. Считайте, что вас предупредили.

#

# После просмотра и анализа файла httpd.conf сервер

# попробует найти и обработать файлы:

# C:/Program Files/Apache Group/Apache/conf/srm.conf, а затем

# C:/Program Files/Apache Group/Apache/conf/access.conf,

# если вы не переопределили эти имена директивами ResourceConfig

# и/или AccessConfig.

#

# Директивы конфигурации сгруппированы в три основных раздела:

#

# 1. Директивы, управляющие процессом Apache в целом (глобальное

#    окружение).

# 2. Директивы, определяющие параметры "главного" сервера, или

#    сервера "по умолчанию", отвечающего на запросы, которые

#    не обрабатываются виртуальными хостами. Эти директивы задают

#    также установки по умолчанию для всех остальных виртуальных хостов.

# 3. Установки для виртуальных хостов, позволяющие обрабатывать

#    запросы Web одним-единственным сервером Apache, но направлять

#    по раздельным IP-адресам или именам хостов.

#

# Файлы конфигурации программы и журналы регистрации событий

# (в программисткой среде они чаще называются "конфигами" и "логами",

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

# придерживаться этой терминологии и здесь).

# Если имена файлов, определенных вами для управления сервером,

# начинаются с символа / (или "диск:/" для Win32), сервер будет

# использовать явно указанный в этом имени полный путь. Если же имена не

# начинаются с "/", то для определения пути будет задействовано значение

# директивы ServerRoot. Так, logs/foo.log при значении ServerRoot,

# равном /usr/local/apache, будет интерпретироваться сервером как

# /usr/local/apache/logs/foo.log.

#

# Внимание: В определении имен файлов вы должны использовать прямые слэши

# вместо обратных (т. е. c:/apache вместо c:\apache). Если не указано

# имя диска, по умолчанию будет выбран диск, на котором размещен

# Apache.exe; тем не менее, во избежание путаницы, рекомендуется, чтобы


# вы всегда явно указывали в абсолютных путях имя диска.

#

### Раздел 1: Глобальное окружение

#

# Директивы в этом разделе определяют общие параметры Apache, такие как,

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

# или где ему искать свои файлы конфигурации.

#

# Директива ServerType может иметь значения inetd или standalone.

# Режим inetd поддерживается только на платформах Unix.

ServerType standalone

#

# ServerRoot: вершина дерева каталогов, в которых содержатся файлы

# конфигурации, регистрации и отслеживания ошибок.

#

# В конце строки добавлять слэш не следует!

ServerRoot "C:/Program Files/Apache Group/Apache"

#

# PidFile: Файл, куда сервер при запуске должен записывать свой

# идентификатор процесса.

PidFile logs/httpd.pid

#

# ScoreBoardFile: Учетный файл, предназначенный для хранения внутренней

# информации процесса сервера. Он необходим не для всех архитектур.

# Если для вашей он нужен (об этом можно судить по тому, будет ли создан

# такой файл, когда вы запустите Apache), то вы должны

обеспечить, чтобы

# никакие два экземпляра процесса Apache не использовали один и тот же

# учетный файл.

ScoreBoardFile logs/apache_runtime_status

#

# В стандартной конфигурации сервер обработает при запуске файлы

# httpd.conf, srm.conf и access.conf (именно в таком порядке).

# Последние два файла в настоящее время поставляются пустыми, поскольку

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

# одном файле (httpd.conf).

# Закомментированные ниже значения встроены в сервер по умолчанию.

# Если вы используете другие имена файлов, отредактируйте и

# раскомментируйте "умолчальные". Если потребуется, чтобы сервер

# проигнорировал эти файлы, вы можете указать значения /dev/null (для

# Unix) или nul (для Win32).

#ResourceConfig conf/srm.conf

#AccessConfig conf/access.conf

#

# Timeout: Время ожидания в секундах, прежде чем сервер примет или

# отправит сообщение о тайм-ауте.


Timeout 300

#

# KeepAlive: Признак, позволено или нет устанавливать долговременные

# соединения (persistent connections) (т.е. когда обрабатывается более

# одного запроса на соединение). Для запрета укажите значение Off.

KeepAlive On

#

# MaxKeepAliveRequests: Максимальное число запросов, допустимое в одном

# долговременном соединении. Для снятия ограничений обнулите параметр,

# но для максимального быстродействия мы рекомендуем указать заведомо

# большое конкретное значение.

MaxKeepAliveRequests 100

#

# KeepAliveTimeout: Время ожидания в секундах следующего запроса от

# одного и того же клиента в одном подключении.

KeepAliveTimeout 15

#

# Для обработки запросов Apache для Win32 всегда порождает один дочерний

# процесс. Если он по каким-либо причинам будет преждевременно завершен,

# другой дочерний процесс создается автоматически. Поступающие запросы

# внутри такого дочернего процесса обрабатываются отдельными потоками.

# Следующие две директивы управляют поведением таких потоков и процессов.

#

# MaxRequestsPerChild: Число запросов, которое позволено обрабатывать

# дочернему процессу до переполнения. При переполнении дочерний процесс

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

# непрерывной работе, если Apache (или используемые им библиотеки),

# допускают утечку памяти или других ресурсов. На большинстве систем

# это не требуется, но некоторые (например, Solaris) имеют заметные

# утечки в библиотеках. Если нет других рекомендаций, для Win32

# установите значение 0 (без ограничений).

#

MaxRequestsPerChild 0

#

# ThreadsPerChild: Число одновременно выполняющихся потоков (т.е.

# запросов), которое допускает сервер. Установите это значение в

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

# одновременно означает, что они обслуживаются медленнее) и объемом

# системных ресурсов, который вы можете предоставить серверу.

#

ThreadsPerChild 50

#

# Listen: Позволяет привязать Apache к конкретному адресу IP, и/или


# порту, в дополнение к порту, определенному по умолчанию. См. также

# директиву .

#

#Listen 3000

#Listen 12.34.56.78:80

#

# BindAddress: Этой опцией вы можете обеспечить поддержку виртуальных

# хостов. Данная директива используется для указания серверу адреса IP,

# который необходимо отслеживать. Она может содержать *, адрес IP или

# полное имя домена Интернета. См. также директивы и Listen.

#

#BindAddress *

#

# Поддержка динамически разделяемых объектов (DSO, Dynamic Shared Object)

#

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

# библиотека DSO, вам следует поместить в этом месте соответствующую

# строку LoadModuleТогда модуль будет доступен

# прежде

обращения к нему.

# За детальными разъяснениями механизмов DSO вы можете обратиться к

# файлу README.DSO в дистрибутиве Apache 1.3, а также выполнить

# команду 'apache -l', чтобы получить список уже встроенных

# (статически скомпонованных и таким образом всегда доступных)

# модулей сервера Apache.

#

# Внимание: Порядок, в котором загружаются модули, имеет большое

# значение. Не меняйте нижеследующий порядок без консультации со

# специалистом.

#

#LoadModule anon_auth_module modules/ApacheModuleAuthAnon.dll

#LoadModule dbm_auth_module modules/ApacheModuleAuthDBM.dll

#LoadModule digest_auth_module modules/ApacheModuleAuthDigest.dll

#LoadModule cern_meta_module modules/ApacheModuleCERNMeta.dll

#LoadModule digest_module modules/ApacheModuleDigest.dll

#LoadModule expires_module modules/ApacheModuleExpires.dll

#LoadModule headers_module modules/ApacheModuleHeaders.dll

#LoadModule proxy_module modules/ApacheModuleProxy.dll

#LoadModule rewrite_module modules/ApacheModuleRewrite.dll

#LoadModule speling_module modules/ApacheModuleSpeling.dll

#LoadModule info_module modules/ApacheModuleInfo.dll

#LoadModule status_module modules/ApacheModuleStatus.dll

#LoadModule usertrack_module modules/ApacheModuleUserTrack.dll

#

# Директива ExtendedStatus определяет, будет ли Apache генерировать


# детальную информацию о состоянии (ExtendedStatus On) или только

# общую информацию (ExtendedStatus Off) при обращении к функции

# server-status. Значение по умолчанию — Off.

#

#ExtendedStatus On

### Раздел 2: Конфигурация сервера по умолчанию

#

# Директивы этого раздела устанавливают значения, используемые "главным

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

# хостами. Эти значения обусловливают также установки по умолчанию для

# любых контейнеров , которые вы будете определять

# здесь далее.

#

# Любые из директив раздела могут быть включены в контейнер

# ; в таком случае установки по умолчанию будут

# переопределены ими для этого виртуального хоста.

#

#

# Если в директиве ServerType (установленной ранее в разделе "Глобальное

# окружение") задано значение inetd, следующие несколько директив не

# имеют никакого эффекта, поскольку их значение определено конфигурацией

# inetd. Переходите к директиве ServerAdmin.

#

# Port: Номер порта, к которому подключен сервер.

#

Port 80

#

# ServerAdmin: Ваш адрес, по которому следует направлять сообщения о

# проблемах с сервером. Этот адрес появится на некоторых сгенерированных

# сервером страницах, таких, как сообщения об ошибках.

#

ServerAdmin you@your.address

#

# Директива ServerName задает имя хоста, возвращаемое клиенту, если это

# имя отличается от того имени, которое получила программа (например,

# используйте www вместо реального имени хоста).

#

# Внимание: Вы не можете просто выдумывать имена хостов в надежде, что

# это сработает. Имя, которое вы определяете здесь, должно быть

# действительным именем DNS для вашего хоста. В случае затруднений с

# пониманием изложенного справьтесь у

# администратора сети.

# Если ваш хост не имеет зарегистрированного имени DNS, вы можете указать

# здесь его адрес IP. В таком случае вам придется обращаться к хосту по

# адресу (например, http://123.45.67.89/) и это может сильно осложнить


# переадресацию ресурсов.

#

ServerName localhost

#

# DocumentRoot: Каталог, в котором будут находиться ваши документы (т.е.

# Web-страницы). По умолчанию, все запросы выбираются из этого каталога;

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

# (links) и псевдонимы (aliases).

#

DocumentRoot "z:/home/localhost/www"

#

# Каждый каталог, к которому Apache имеет доступ, может быть

# сконфигурирован в отношении свойств и сервисов, которые могут быть

# разрешены и/или запрещены в этом каталоге (и его подкаталогах).

#

# Сначала мы определяем свойства "по умолчанию".

#



  Options Indexes Includes

  AllowOverride All

  allow from all



#

# Обратите внимание, что с этого места и далее вы должны явным образом

# указывать свойства, которые могут быть разрешены, — так что, если что-то

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

# это свойство ниже.

#

# Здесь должен быть указан каталог, который вы установили как

# DocumentRoot.

#

#;

#

# Опции могут иметь значения None, All или любую комбинацию из

# Indexes, Includes, FollowSymLinks, ExecCGI или MultiViews.

#

# Заметьте, что MultiViews должен быть указан отдельно —

# Options All для этого не достаточно.

#

#   Options Indexes FollowSymLinks MultiViews

#

# Директива перечисляет опции, которые могут быть переопределены в

# файлах .htaccess. Значением может быть All или любая комбинация из

# Options, FileInfo, AuthConfig и Limit.

#

#    AllowOverride None

#

# Эти директивы определяют, какие пользователи имеют доступ к информации,

# расположенной на этом сервере.

#

#    Order allow,deny

#    Allow from all

#


#

# UserDir: Название каталога, которое прибавляется к именам

# пользовательских домашних каталогов при получении запроса ~user

# (например, http://www.server.com/~username).

#

# Под Win32 мы в настоящее время не пытались устанавливать каталог


# регистрации пользователя, поэтому приходится работать с форматом,

# приведенным ниже.

#



    UserDir "C:/Program Files/Apache Group/Apache/users/"



#

# DirectoryIndex: Имя файла (или файлов), используемое в качестве

# предопределенной страницы-указателя или оглавления. Если вы указываете

# несколько имен, разделяйте их пробелами.

#



        DirectoryIndex index.htm index.html



#

# AccessFileName: Имя файла, который сервер ищет в каждом каталоге для

# определения прав доступа.

#

AccessFileName .htaccess

#

# Следующие строки предотвращают доступ к файлам .htaccess со стороны

# Web-клиентов. Поскольку файлы .htaccess нередко содержат информацию об

# аутентификации, доступ к ним запрещен из соображений безопасности. Вы

# можете удалить эти строки (или поставить символ комментария),

# если допускаете, чтобы посетители могли просматривать содержимое файлов

# .htaccess из Web. Если вы поменяете значение директивы AccessFileName

# выше, не забудьте внести и сюда соответствующие изменения.

#



    Order allow,deny

    Deny from all



#

# CacheNegotiatedDocs: По умолчанию с каждым документом Apache отправляет

# инструкцию "Pragma: no-cache", что является указанием proxy-серверам не

# кэшировать данный документ. Если раскрыть следующую строку, то

# поведение proxy-серверов изменится и им будет разрешено кэшировать

# документы.

#

#CacheNegotiatedDocs

#

# UseCanonicalName: (Впервые в версии 1.3.) Если эта директива включена

# (On), то всякий раз, когда Apache требуется создать ссылку на самого

# себя (self-referencing URL, т.е. адрес сервера, с которого поступает

# ответ на запрос), для формирования "канонического имени" он будет

# использовать значения директив ServerName и Port, когда это возможно.

# Если директива выключена (Off), Apache будет по возможности

# использовать значения, предоставленные клиентом. Эта директива влияет


# также на значения переменных SERVER_NAME и SERVER_PORT в CGI-сценариях.

#

UseCanonicalName On

#

# Директива TypesConfig описывает расположение файла mime.types

# (или его эквивалента).

#



    TypesConfig conf/mime.types



#

# Директива DefaultType определяет MIME-тип, который будет использоваться

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

# признакам, например, по расширению имени файла. Если ваш сервер

# содержит по большей части тексты или HTML-документы, text/plain

# является приемлемым решением. Если большая часть содержимого является

# исполняемыми файлами или изображениями, вы можете поменять значение на

# application/octet-stream, чтобы предотвратить попытку браузера

# показать содержимое двоичного файла.

#

DefaultType text/plain

#

# Модуль mod_mime_magic позволяет серверу использовать разнообразные

# приемы определения типа файла по его содержимому. Директива

# MIMEMagicFile указывает ему файл, где даны описания таких приемов.

# По умолчанию mod_mime_magic не включен в состав сервера (вы должны

# загрузить его сами с помощью директивы LoadModule — см. абзац DSO в

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

# с этим модулем), поэтому директива MIMEMagicFile заключена в контейнер

# . Это означает, что она будет обработана только в том случае,

# если модуль mod_mime_magic уже загружен.

#



    MIMEMagicFile conf/magic



#

# Директива HostnameLookups определяет, регистрировать ли клиентов по

# именам, или только по адресам IP, т.е. www.apache.org (On) или

# 204.62.129.132 (Off). По умолчанию — Off, поскольку для снижения

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

# возможность, зная о последствиях, т. к. отслеживание по именам означает,

# что каждый клиентский запрос приведет как минимум

к еще одному запросу

# к серверу имен для преобразования IP-адреса в имя.


#

HostnameLookups Off

#

# ErrorLog: Расположение файла регистрации ошибок. Если вы не определяете

# директиву ErrorLog внутри контейнера , сообщения об

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

# ниже файл. В противном случае все сообщения направятся в специфичный

# для виртуального хоста журнал.

#

ErrorLog logs/error.log

#

# LogLevel: Определение характера ошибок, которые записываются в

# error.log. Возможные значения в порядке убывания количества сообщений:

# debug, info, notice, warn, error, crit, alert, emerg.

#

LogLevel warn

#

# Следующие директивы указывают псевдонимы некоторых форматов, которые

# используются в директиве CustomLog (см. ниже).

#

LogFormat "%h %l %u %t \"%r\" %>s %b \"%{User-Agent}i\"" combined

LogFormat "%h %l %u %t \"%r\" %>s %b" common

LogFormat "%{Referer}i -> %U" referer

LogFormat "%{User-agent}i" agent

#

# Расположение и формат файла регистрации (лога). Если вы не определяете

# никаких лог-файлов внутри контейнера , сведения

# будут записываться здесь. Если же вы определяете отдельный лог-файл

# для виртуального хоста, доступ будет отслеживаться в этом логе,

# но не здесь.

#

CustomLog logs/access.log common

#

# Если вы хотите, чтобы имелся агент ссылочных логов (referer logfiles

# agent), раскомментируйте следующие директивы.

#

#CustomLog logs/referer.log referer

#CustomLog logs/agent.log agent

#

# Если вы предпочитаете иметь один лог-файл с информацией о доступе,

# агентах и ссылках (комбинированный формат лог-файла), вы можете

# использовать следующую директиву.

#CustomLog logs/access.log combined

#

# Позволяет добавить дополнительную строку, содержащую версию сервера и

# имя виртуального хоста на страницах, сгенерированных сервером

# (сообщениях об ошибках, листингах каталогов FTP, в вывод модулей

# mod_status и mod_info, но не в CGI-документах). Чтобы дополнительно


# включить ссылку mailto:, содержащую значение директивы ServerAdmin,

# установите значение EMail.

# Допустимые значения: On | Off | Email

#

ServerSignature On

#

# Apache по умолчанию анализирует первую строку каждого CGI-сценария.

# Если эта строка является комментарием и выглядит так: символ (#),

# затем восклицательный знак (!) и, наконец, путь к

# программе-интерпретатору, по которому осуществляется запуск

# сценария, Apache запускает этот интерпретатор.

# Например, для perl-сценариев, стартуемых под управлением perl.exe

# из каталога C:\Program Files\Perl, эта строка должна выглядеть так:

# !c:/program files/perl/perl

# Внимание: вы не должны вставлять пробелы перед символом (#). Êðîìå

# òîãî, указанная специальная строка должна быть именно первой строкой

# файла. Конечно, для запускаемого файла должна быть разрешена обработка

# CGI — например, путем указания директивы ScriptAlias или

# Options ExecCGI.

#

# Тем не менее, Apache для Windows позволяет в дополнение к "магической"

# строке использовать Реестр для поиска ассоциаций с расширениями.

# Команда для запуска файла указанного типа в этом случае ищется в

# Реестре точно так же, как это происходит, например, при работе

# Проводника, когда вы выполняете двойной щелчок на файле. Действия по

# запуску сценария могут быть сконфигурированы из меню Вид Проводника.

# Там необходимо выбрать Свойства папки

и переключиться на вкладку

# Типы файлов. Нажатие на кнопку Изменить позволяет задать действие,

# которое Apache выполнит при попытке открытия файла. Если это не

# удастся, Apache будет искать интерпретатор при помощи "магической"

# строки. Возможно, поведение сервера изменится в Apache версии 2.0.

#

# Чтобы разрешить это специфичное для Windows поведение сервера и, таким

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

# со следующей директивы:

#

ScriptInterpreterSource registry


#

# Эта директива может быть помещена в отдельный блок или

# в файл .htaccess с указанием в качестве значения registry

# (поведение Windows) или script (анализ "магической" строки, принятый

# в Unix). В таком случае она будет перекрывать директиву, расположенную

# здесь, в главном конфигурационном файле сервера.

#

#

# Псевдонимы: Можно добавлять любое количество псевдонимов (без

# ограничений).

# Формат: Alias псевдоним действительное_имя

#



    # Обратите внимание, что если вы включаете завершающий слэш в

    # "псевдоним", то сервер потребует его присутствия и в URL. Так,

    # /icons не будет разыменован в данном примере, только /icons/.

    #

    Alias /icons/ "C:/Program Files/Apache Group/Apache/icons/"

   

        Options Indexes MultiViews

        AllowOverride None

        Order allow,deny

        Allow from all

   


    #

    # ScriptAlias: Указывает каталог, который содержит серверные

    # сценарии. Свойства ScriptAlias’ов такие же, как и у простых

    # псевдонимов, за исключением того, что документы в каталоге

    # "действительное_имя" считаются приложениями и выполняются

    # на сервере, а не отправляются клиенту. К директиве

    # ScriptAlias применяются те же правила в отношении

    # завершающего /, что и к Alias.

    #

    ScriptAlias /cgi-bin/ "z:/home/localhost/cgi/"

    ScriptAlias /cgi/ "z:/home/localhost/cgi/"



# Конец определений псевдонимов.

#

# Директива Redirect позволяет сообщить клиенту о документе, который

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

# в другое место. Она информирует клиента о его новом адресе.

#

# Формат: Redirect старый_URL новый_URL

#

#

# Директивы, управляющие генерацией сервером  листингов каталогов.

#




    #

    # FancyIndexing означает, что вы предпочитаете листинги с

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

    # IndexOptions см. сопроводительную документацию.   

    #

    IndexOptions FancyIndexing

    #

    # Директивы AddIcon* указывают серверу, какими ярлыками

    # будут украшены имена файлов в листинге каталога. Ярлыки    

    # изображаются только в режиме FancyIndexing.

    #

    AddIconByEncoding (CMP,/icons/compressed.gif) x-compress x-gzip

    AddIconByType (TXT,/icons/text.gif) text/*

    AddIconByType (IMG,/icons/image2.gif) image/*

    AddIconByType (SND,/icons/sound2.gif) audio/*

    AddIconByType (VID,/icons/movie.gif) video/*

    AddIcon /icons/binary.gif .bin .exe

    AddIcon /icons/binhex.gif .hqx

    AddIcon /icons/tar.gif .tar

    AddIcon /icons/world2.gif .wrl .wrl.gz .vrml .vrm .iv

    AddIcon /icons/compressed.gif .Z .z .tgz .gz .zip

    AddIcon /icons/a.gif .ps .ai .eps

    AddIcon /icons/layout.gif .html .shtml .htm .pdf

    AddIcon /icons/text.gif .txt

    AddIcon /icons/c.gif .c

    AddIcon /icons/p.gif .pl .py

    AddIcon /icons/f.gif .for

    AddIcon /icons/dvi.gif .dvi

    AddIcon /icons/uuencoded.gif .uu

    AddIcon /icons/script.gif .conf .sh .shar .csh .ksh .tcl

    AddIcon /icons/tex.gif .tex

    AddIcon /icons/bomb.gif core

    AddIcon /icons/back.gif ..

    AddIcon /icons/hand.right.gif README

    AddIcon /icons/folder.gif ^^DIRECTORY^^

    AddIcon /icons/blank.gif ^^BLANKICON^^

    #

    # DefaultIcon определяет ярлык для файла по умолчанию

    # если он не задан явно.

    #

    DefaultIcon /icons/unknown.gif

    #

    # AddDescription позволяет размещать краткое описание после имени

    # файла в индексах (листингах каталогов), сгенерированных сервером.

    # Такие описания выводятся только в режиме FancyIndexing.

    #

    # Формат: AddDescription "строка_описания" .расширение_имени_файла

    #

    #AddDescription "GZIP compressed document" .gz


    #AddDescription "tar archive" .tar

    #AddDescription "GZIP compressed tar archive" .tgz

    #

    # ReadmeName задает имя README-файла, который добавляется к листингу

    # каталога по умолчанию.

    #

    # HeaderName указывает имя файла, выводимого в

    # заголовке листингов каталога.   

    #

    # Если задана директива MultiViews в числе значений Options,

    # сначала сервер попытается открыть файл имя.html и включит его в

    # листинг, если файл существует. Если файл имя.html не существует,

    # сервер переориентируется на открытие файла

    # имя.txt и включение его в листинг в виде простого текста.

    #

    ReadmeName README

    HeaderName HEADER

    #

    # IndexIgnore описывает набор имен файлов, которые должны быть

    # исключены из листинга. В именах допустимы метасимволы подстановки

    # в стиле shell.

    IndexIgnore .??* *~ *# HEADER* README* RCS CVS *,v *,t



# Конец секции директив управления листингами.

#

# Типы документов.

#



    #

    # AddEncoding позволяет вам заставить определенные браузеры

    # (Mosaic/X 2.1+) распаковывать информацию "на лету".

    # Внимание: это свойство поддерживают не все браузеры. Несмотря

    # на сходство имен, нижеприведенные директивы Add* не

    # имеют ничего общего с директивами оформления FancyIndexing,

    # приведенными выше.   

    #

    AddEncoding x-compress Z

    AddEncoding x-gzip gz tgz

    #

    #

    # AddLanguage позволяет указать язык документа. Вы можете затем

    # использовать протокол обмена (content negotiation) для выдачи

    # браузеру документа на том языке, который он (браузер) предпочитает.

    #

    # Примечание 1: Суффикс не обязательно должен совпадать с буквенным

    # кодом языка — те, у кого есть документы на польском языке

    # (стандартный сетевой буквенный код pl), могут воспользоваться

    # директивой AddLanguage pl .po во избежание конфликта с


    # распространенным суффиксом сценариев на языке Perl.

    #

    # Примечание 2: Нижеследующие примеры показывают, что в нескольких

    # случаях двухбуквенный код языка не совпадает с двухбуквенным кодом

    # страны.

    # Например, "Датский/da" вместо "Дания/dk".

    #

    # Примечание 3: В случае ltz мы нарушаем требования RFC, используя

    # трехбуквенный код. Но уж тут ничего не поделаешь. В будущем,

    # возможно, несоответствия с RFC1766 будут устранены.

    #

    # Коды языков:

    # датский (Danish) da; голландский, Нидерланды (Dutch) nl;

    # английский (English) en; эстонский (Estonian) ee;

    # французский (French) fr; немецкий (German) de;

    # новогреческий (Greek-Modern) el; итальянский (Italian) it;

    # португальский (Portuguese) pt;

    # люксембургский (Luxembourgeois*) ltz;

    # испанский (Spanish) es; шведский (Swedish) sv;

    # каталонский (Catalan) ca; чешский (Czech) cz;

    # русский (Russian) ru.

    #

    AddLanguage da .dk

    AddLanguage nl .nl

    AddLanguage en .en

    AddLanguage et .ee

    AddLanguage fr .fr

    AddLanguage de .de

    AddLanguage el .el

    AddLanguage he .he

    AddCharset ISO-8859-8 .iso8859-8

    AddLanguage it .it

    AddLanguage ja .ja

    AddCharset ISO-2022-JP .jis

    AddLanguage kr .kr

    AddCharset ISO-2022-KR .iso-kr

    AddLanguage no .no

    AddLanguage pl .po

    AddCharset ISO-8859-2 .iso-pl

    AddLanguage pt .pt

    AddLanguage pt-br .pt-br

    AddLanguage ltz .lu

    AddLanguage ca .ca

    AddLanguage es .es

    AddLanguage sv .se

    AddLanguage cz .cz

    AddLanguage ru .ru

    AddLanguage tw .tw

    AddCharset Big5         .Big5    .big5

    AddCharset WINDOWS-1251 .cp-1251

    AddCharset CP866        .cp866

    AddCharset ISO-8859-5   .iso-ru

    AddCharset KOI8-R       .koi8-r

    AddCharset UCS-2        .ucs2

    AddCharset UCS-4        .ucs4

    AddCharset UTF-8        .utf8

    # LanguagePriority позволяет определить первоочередность некоторых


    # языков при установлении протокола обмена.

    #

    # Возможно, вы захотите изменить предложенный порядок языков. Просто

    # перечислите их в порядке убывания приоритета.

    #

   

        LanguagePriority en da nl fr de el it ja no pl pt ru ca es sv tw

   


    #

    # AddType позволяет слегка подправить mime.types, не редактируя его,

    # или объявить конкретные файлы имеющими определенный тип.

    #

    # Например, модуль PHP3 (этот модуль не является частью дистрибутива

    # сервера Apache), обычно использует следующие объявления:   

    #

    #AddType application/x-httpd-php3 .php3

    # AddType application/x-httpd-php3-source .phps

    #

    # В случае PHP 4.x укажите:

    #

    AddType application/x-httpd-php .php

    # AddType application/x-httpd-php-source .phps

    # Следующие строки не относятся к заданию типов документов,

    # но их удобно поместить сюда для подключения PHP:

    #

    ScriptAlias /_php/ "C:/Program Files/PHP4/"

    Action application/x-httpd-php "/_php/php.exe"

    AddType application/x-tar .tgz

    #

    # AddHandler позволяет отобразить определенные расширения имен файлов

    # на обработчиков вне связи с определениями типов файлов. Обработчики

    # могут быть как встроены в сервер, так и объявлены директивой

    # Action (см. ниже).

    #

    # Если вы хотите использовать файлы, вставляемые сервером в ваши

    # документы (SSI — server side includes), снимите комментарий

    # со следующих строк:

    #

    # для использования сценариев CGI —

    #

    AddHandler cgi-script .bat .exe .cgi

    #

    # для HTML-файлов, предварительно обрабатываемых

    # сервером (server-parsed HTML files):

    #

    AddType text/html .shtml

    AddHandler server-parsed .shtml .html .htm

    #

    # Раскомментируйте следующую строку, чтобы разрешить Apache передачу

    # специальных файлов, которые не сопровождаются стандартными


    # заголовками HTTP (send-asis HTTP file).

    #

    # AddHandler send-as-is asis

    #

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

    # сервером, раскройте следующую директиву:   

    #

    # AddHandler imap-file map

    #

    # Если вы хотите задействовать карты типов (type maps, см.

    # документацию), используйте:   

    #

    # AddHandler type-map var



# Конец блока директив описания типов документов.

#

# Директива Action позволяет определить приложение, выполняющее сценарии,

# когда запрашиваются содержащие их файлы. Это устраняет необходимость

# многократного упоминания URL часто используемых процессоров

# CGI-сценариев.

# Формат: Action псевдоним_типа /псевдоним_пути/обработчик

#         Action среда/тип /псевдоним_пути/обработчик

#

#

# MetaDir: определяет имя каталога, в котором Apache может найти файлы с

# метаинформацией. Эти файлы содержат дополнительные заголовки HTTP,

# включаемые при отправке определенных документов.

#

# MetaDir .web

#

# MetaSuffix устанавливает суффикс имени файла, содержащего метаинформацию.

#

# MetaSuffix .meta

#

# Настраиваемая реакция на ошибки (собственный стиль Apache) может быть

# трех типов.

#

# 1) простой текст

#    ErrorDocument 500 "Сервер сказал а-я-яй!"

#    Внимание: знак двойной кавычки просто означает, что далее следует

#    текст.

#

# 2) локальная переадресация

#    Чтобы перенаправить на локальный документ:

#    ErrorDocument 404 /missing.html

#    Перенаправлять можно и на сценарий, и на документ, использующий

#    включения на стороне сервера:

#    ErrorDocument 404 /cgi-bin/missing_handler.pl

#

# 3) внешняя переадресация

#    ErrorDocument 402 http://some.other_server.com/info.html

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

#    станут недоступны при такой переадресации.

#

# Установки, связанные с браузером пользователя.

#



    #

    # Следующие директивы отменяют поддержку долговременных соединений


    # (keepalives) и "смывание" заголовков HTTP. Первая директива

    # отменяет их для Netscape 2.x и браузеров, которые "притворяются",

    # что они — Netscape (известны некоторые проблемы с такими 

    # браузерами). Вторая директива предназначена для Microsoft Internet

    # Explorer 4.0b2, реализация HTTP/1.1 которого не полна и не

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

    # откликах 301 или 302 (переадресация).

    #

BrowserMatch "Mozilla/2" nokeepalive

BrowserMatch "MSIE 4\.0b2;" nokeepalive downgrade-1.0 force-response-1.0

    #

    # Следующая директива отключает отклики по HTTP/1.1 браузерам,

    # которые нарушают стандарты HTTP/1.0 и не могут разобрать

    # основной отклик 1.1.   

    #

    BrowserMatch "RealPlayer 4\.0" force-response-1.0

    BrowserMatch "Java/1\.0" force-response-1.0

    BrowserMatch "JDK/1\.0" force-response-1.0



# Конец настроек, связанных с браузерами.

#

# Следующая группа директив управляет отчетами о состоянии сервера,

# имеющего URL http://servername/server-status. Для приведения в

# соответствие с вашими нуждами измените .your_domain.com.

#

#

#    SetHandler server-status

#    Order deny,allow

#    Deny from all

#    Allow from .your_domain.com

#


#

# Эта группа директив управляет отчетами конфигурации удаленного

# сервера http://servername/server-info (требуется, чтобы был загружен

# mod_info.c). Замените .your_domain.com на имя вашего домена.

#

#

#    SetHandler server-info

#    Order deny,allow

#    Deny from all

#    Allow from .your_domain.com

#


#

# Поступали сообщения, что некие люди пытаются злоупотреблять древней

# ошибкой старых версий Apache. Ошибка касалась CGI-сценария,

# поставлявшегося с Apache.

# Раскрыв следующие строки, вы можете переадресовать эти атаки


# на регистрирующий сценарий на phf.apache.org. А можете регистрировать

# их сами, используя сценарий support/phf_abuse_log.cgi.

#

#

#    Deny from all

#    ErrorDocument 403 http://phf.apache.org/phf_abuse_log.cgi

#


#

# Директивы proxy-сервера.

#

#

    # Раскройте следующую строку для того, чтобы разрешить

    # работу с proxy.

    # ProxyRequests On

    #

    #   Order deny,allow

    #   Deny from all

    #   Allow from .your_domain.com

    #


 

    #

    # Разрешить/запретить обработку заголовков HTTP/1.1 Via:.

    # Возможные значения: Off | On | Full | Block. Full добавляет в

    # заголовок версию сервера, Block удаляет все исходящие

    # заголовки Via:.   

    #

    # ProxyVia On

    #

    # Для разрешения также кэширования отредактируйте и раскройте

    # следующие строки (нельзя включать кэширование без указания

    # CacheRoot):   

    #

    # CacheRoot "C:/Program Files/Apache Group/Apache/proxy"

    # CacheSize 5

    # CacheGcInterval 4

    # CacheMaxExpire 24

    # CacheLastModifiedFactor 0.1

    # CacheDefaultExpire 1

    # NoCache a_domain.com another_domain.edu joes.garage_sale.com

#


# Конец настроек proxy-сервера.

### Раздел 3: Виртуальные хосты

#

# Директива VirtualHost: Если вы хотите держать на своей машине несколько

# хостов, следует для каждого из них завести контейнер VirtualHost.

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

# документации по адресу http://www.apache.org/docs/vhosts/. Для проверки

# конфигурации ваших виртуальных хостов вы можете задавать опцию -S

# командной строки.

#

# Если вы хотите использовать именные виртуальные хосты (name-based

# virtual hosts), вам необходимо определить для них как минимум один

# адрес IP (и номер порта).

#

NameVirtualHost 127.0.0.1:80

#

# Пример использования директивы VirtualHost:


# В контейнер VirtualHost может включаться почти любая

# директива Apache.

#

#

#    ServerAdmin webmaster@host.some_domain.com

#    DocumentRoot /www/docs/host.some_domain.com

#    ServerName host.some_domain.com

#    ErrorLog logs/host.some_domain.com-error_log

#    CustomLog logs/host.some_domain.com-access_log common

#


#

#


# Далее идут настройки для виртуальных хостов, описанных во второй

# части этой книги.

#----localhost



  ServerAdmin webmaster@localhost.ru

  ServerName localhost

  DocumentRoot "z:/home/localhost/www"

  ScriptAlias /cgi/ "z:/home/localhost/cgi/"

  ErrorLog z:/home/localhost/error.log

  CustomLog z:/home/localhost/access.log common



#----hacker



  ServerAdmin webmaster@hacker.ru

  ServerName hacker

  DocumentRoot "z:/home/hacker/www"

  ScriptAlias /cgi/ "z:/home/hacker/cgi/"

  ErrorLog z:/home/hacker/error.log

  CustomLog z:/home/hacker/access.log common



#----cracker



  ServerAdmin webmaster@cracker.ru

  ServerName cracker

  DocumentRoot "z:/home/cracker/www"

  ScriptAlias /cgi/ "z:/home/cracker/cgi/"

  ErrorLog z:/home/cracker/error.log

  CustomLog z:/home/cracker/access.log common



# Конец главного файла конфигурации Apache.

[1] Русский язык, изначально обладающий гигантской свободой в выборе слова, постоянно развивается. То, что казалось неприемлемым вчера, сегодня становится нормой, и наоборот. Безусловно, у любого обратившего внимание на эти строки при прочтении слов "закачать" и "скачать" вряд ли возникнут ассоциации с бригадой мускулистых администраторов, придающих передаваемым по сети файлам необходимую кинетическую энергию для последующего перемещения под напором, или другие неверные мысли, несмотря на большое количество смысловых оттенков употребления этих слов (убаюкивать, вызвать головокружение, или же в понимании "подлого приема садовников, торговцев присадками (раскачивать деревце, не давая ему укорениться)"). Вообще говоря, о твердых правилах в условиях возрастающего слияния разговорных терминов и литературного языка говорить не приходится. В толковом словаре С. И. Ожегова и Н. Ю. Шведовой дано пояснение идиоме "Закачаешься!" как выражения высокой оценки чего-либо. Редакторы вовсе не стремятся убивать живое изложение, и поэтому если вы, уважаемые читатели, также видите эти строки, значит было решено — "быть закачиваемому" (из примеров к статье "Закачать" толкового словаря живого великорусского языка В. И. Даля). — Ред.


[AL1]Обороты “и это хорошо” и “ и это правильно” очень часто встречаются и мне не кажутся удачными

[AL2]такой синтаксис отнюдь не исключение, к примеру можно взять макроассемблер

[E3]это так? в коде нет никаких операторов вывода

[E4]Иначе надо писать “обеих”, а это — неблагозвучный термин

[В. О.5]Самодокументруемые?

[В. О.6]разобраться

[В. О.7]не что иное?

[В. О.8]?

[В. О.9]Запятая?

[В. О.10]По поводу точек в программировании

[В. О.11]шести?

[В. О.12]Мне кажется, это не всем понравится

[В. О.13]?

[В. О.14]Вряд ли это понравится читателю, тем более, что Вы очень хорошо изложили суть

[В. О.15]?

[В. О.16]Запятая?

[В. О.17]Почему? Хэш — он и в Африке хэш.

[В. О.18]!

[В. О.19]Это как-то уж слишком по-программистки,  но не по-книжному

[В. О.20]а разве должно быть иначе? Автоинкремент и автодекремент, по логике, разве применимы к булевскому типу?

[В. О.21]Потенциальной?

[В. О.22]Прочитал и не понял

[В. О.23]Извините, но требования издательства не допускают такого оформления

[В. О.24]ценна ли здесь эта фраза?

[В. О.25]3.1459…

[В. О.26]а вдруг 4? Как насчет у-Си-дки и Perlюстрации?

[В. О.27]Из-за "уменьшив"

[В. О.28]?

[В. О.29]Может, изменить название переменной — а то "a" отдельно от последующих слов поначалу не воспринимается?

[В. О.30]!

[В. О.31]!

[В. О.32]Пробел внесен сознательно?

[В. О.33]Наличие пробелов не вредит смыслу?

[В. О.34]Си-подобные операторы или нет?

[В. О.35]Противопоставление Си неверно, Вы, безусловно, это понимаете

[В. О.36]Ни что иное

[В. О.37]считаю, что эту формулировку следует исключить или же я бы посоветовал Вам заменить этот жаргон на изначальное название символа — "коммерческое эт". Мне кажется, "оживляжа" и так достаточно


[В. О.38]может быть, лучше что-то нейтральное?

[AL39]Я думаю, Вы имели в виду именно это, или же "осознанна"?

[AL40]такие слова не пропускает корректор

[AL41]мне кажется, так лучше. Ваше мнение?

[AL42]ENVIROPMENT?

[AL43]надежно?

[AL44]написать "кнопкой" или оставить Ваш стиль?

[AL45]"Обновите ваш PHP!"

[AL46]"наша версия PHP отстала от жизни"

[E47]?

[E48]?

[E49]Не понял

[E50]?

[E51]в обоих смыслах

[E52]обычно уточняют что-то вроде “В двух строках разной длины каждый символ более длинной строки без соответствующего символа в более короткой строке принимает значение "больше";

например, 'Xs' больше, чем 'X'. Пустые строки могут быть равны только другим пустым строкам, и они являются наименьшими текстовыми значениями”

[E53]Для однообразия

[E54]Для устранения “коридора” (коридоры образуют пробелы между словами в строках, которые следуют одна за другой)

[E55]первых или последних?

[E56]?

[E57]Это особенность алгоритма или тавтология? Если первое, то тогда какой же это хэш?

[E58]В разделе всего одна функция. Заголовок не совсем соответствует.

[E59]Скользит, перебирает элементы?

[E60]Не понял, речь идет о перегрузке функций? Или это не описанная ранее особенность PHP?

[E61]Плоховато, но более ничего не удалось подобрать

[E62]При выборке небольших размеров

[E63]почему же, для интерактивных карт могут применяться (речь идет о больших системах, содержащих мелкомасштабные карты земной поверхности)

[E64]“…все равно, какой ориентации придерживается слэш”?

[E65]?

[E66]Не понял, кажется, здесь нарушена логика

[E67]блочно?

[E68]Чем отличается от предыдущего?

[E69]

[E70]звучит как “по пути”

[E71]Может лучше было было бы использовать структурный элемент типа “Совет”? Или оставить до просмотра коректором? Я спрошу, имеется ли что-нибудь типа “заповедь” или “резюме”


[E72]

[E73]?

[E74]Alias – псевдоним или синоним?

[E75] Разделитель внутри есть? Или он и есть – пробел?

[E76]Милли или микро?

[E77]Åñëè àêöåíò äåëàåòñÿ íà ïóòàíèöó ìèëëè-ìèêðî, то может, стоит употребить кавычки?

[E78]Это ее не касается?

[E79]Вот-вот, Word, как и справочники, считает, что папу-основоположника звали ГрИгорием… (ñðàâíèòå: Julian и Gregorian)

[E80]странновато звучит фраза: и конкретно и вместе с тем “кажется”

[E81]Несколько строк?

[E82]На что?

[E83]Ну это Вы зря…

[E84]желательно бы добавлять номер, поскольку мне нужно выделять ссылки курсивом

[E85]?

[E86]Эта “навороченность” имеет место…?

[E87]?

[E88]Не удовлетворяет требованиям

[E89]а у BHV ничего подобного нет?

[E90]?

[DK91]Подойдет.

[E92]4.07 Netscape не поддерживает, 4.6 – не помню.

[DK93]Правда? Очень странно… Может быть, Вы просто не поставили в нем поддержку PNG при инсталляции?

[E94]?

[DK95]Подойдет.

[E96](на первый взгляд, не “технологичная”) ?

[DK97]Да. Но нужно ли уточнять?..

[E98]Может, стоило бы уточнить определение: ведь по сути если цвет, указанный в качестве “прозрачного”, содержится в “не фоновой” части, он выводится. А не отображается от только для фоновой части при равенстве цвета  фона и “прозрачного” цвета. Меня просто несколько коробит от таких определений, поскольку постигая это на практике, я вынужден был все выяснять сам. А извлекать по крохам такие сведения из примеров не есть хорошо

[DK99]Вы точно в этом уверены? Если у нас есть картинка, и у нее 10-й цвет назначен прозрачным, то В ЛЮБОМ СЛУЧАЕ точки с цветом 10 не будут выведены в браузер (а значит, то, что под ними было, останется нетронутым). И что значит – «фоновая часть»?


[E100]E9

[DK101] Вы хотите убрать такую формулировку? Я думал, читатель улыбнется, когда это прочтет – это была моя первоочередная задача. Ведь чем более непринужденно написана книга, тем легче ее читать, не правда ли?..

[E102]функция идет от API, где именно множество параметров, и во всех языках наследуется именно со множеством, если только не ссылается на массивы координат

[DK103]Но, все же, использование большого числа параметров в функции – дурной стиль, об этом еще, кажется, Керниган писал.

[E104]Не только потому. Изначально эта функция на удивление медленная на системном уровне (BIOS).

[DK105]Думаю, при выводе точки BIOS не используется.

[E106]моноширинном, монохромном?

[DK107]Имеется в виду – только 2 цвета: цвет фона и цвет текста, никаких разноцветных букв. Термин «монохномный» слишком часто ассоциируется с «черно-белым».

[E108]Нет никакого связанного определения

[DK109]Запямятовал написать.

[E110]вот именно, что от корня. Возвращаясь к вопросу во второй (кажется), главе.

То есть само наличие слэша перед путем не говорит о том, что этот путь абсолютный

[DK111]Я всегда считал, что «абсолютный путь» означает «путь от корня»?..

[DK112]Имеется в виду использование функции realpath().

[E113]А как насчет переносимости?

[DK114]Нет, именно «прямоугольника» - все углы прямые (хотя прямоугольник – это тоже параллелограмм). Возможно, он будет наклонен, но все равно останется прямоугольником.

[E115]почему Вы его не вставили в книгу? Может стоит?

[DK116]Потому что он будет не осень хорошо смотреться на бумаге, да еще в книге. Кроме того, пользователь не может, задавая различные параметры и наклон текста, «перерисовать» его прямо на странице книги. На всякий случай я высылаю Вам и рисунок (23.1). Доверяю Вашему решению – вставлять его или нет.

[DK117]В том-то идея, что она ничего не предоставляет программисту, а только выводит «статистику».

[DK118]Нет. У меня выводится: «4.0.3pl1»


[E119] Приложения по требованиям издательства нумеруются по-русски, буквами.

У Вас только два приложения (пока?)

[DK120]Ошибся.

[E121]«захватывать»?

[E122]отладке?

[DK123]Нет, при ПРОГРАММИРОВАНИИ.

[E124]?

[E125]Не понял смысла. Кстати, финализатор и шаблонизатор – стандартные термины?

[DK126]Финализатор – да, шаблонизатор – нет (о том, что это такое, рассказывается пятой части, которую я пока еще не дописал).

[E127]Качеств?

[DK128]Правил.

[E129]??

ILoveYou в разрезе

Дмитрий КИРСАНОВ

------------------

Сам по себе вирус ILoveYou является текстом программы на языке Visual

Basic. При переименовании кода Visual Basic в файл с расширением VBS

программа становится исполняемой. Это и лежит в основе как этого

вируса, так и Melissa и еще сотен им подобных.

ILoveYou простой, но изощренный вирyс - автор решил оставить свою метку

везде, где он только может :). В то же время он пренебрег простейшим

шифрованием текста, и более того, оставил о себе подпись, составляющyю

первые две строки:

rem barok -loveletter(vbe)

rem by: spyder / ispyder@mail.com / @GRAMMERSoft Group / Manila,

Philippines

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

автор не так давно изучил Visual Basic. Настолько детально были

соблюдены книжные правила написания программ.

Вот с чего вирус начинается:

set wscr=CreateObject("WScript.Shell")

rr=wscr.RegRead("HKEY_CURRENT_USER\Software\Microsoft\Windows Scripting

Host\Settings\Timeout")

if (rr>=1) then...

ILoveYou читает вышеуказанную ветку реестра, и если значение больше или

равно единице, то обнуляет его.

После этого он узнает все специальные папки системы: Windows, System и

temp. Используя полученные данные, копирует себя в файлы

SYSTEM\MSKernel32.vbs, WINDOWS\Win32DLL.vbs и

SYSTEM\LOVE-LETTER-FOR-YOU.TXT.vbs.

Следующим шагом троянца становится запись в реестр:


"HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\

Run\MSKernel32" теперь содержит ссылку на MSKernel32.vbs, а

"HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\

RunServices\Win32DLL" на Win32DLL.vbs.

В результате этого вышеуказанные скрипты запускаются каждый раз при

запуске Windows. Но, в чем отличительная черта именно этого вируса, все

повторяется сначала! Опять зараженный компьютер начинает рассылать свои

копии кому попало, опять пишет в реестр... Очевидно, это просто ошибка

автора скрипта.

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

файл WinFAT32.exe. И если есть, то играет сам с собой в рулетку,

получая случайное число от 1 до 4. И вот здесь скрипт делает совсем

неприятную вещь - в зависимости от полученного числа, изменяет

стартовую страницу в Internet Explorer на одно из 4-х значений:

1. "http://www.skyinet.net/~young1s/

HJKhjnwerhjkxcvytwertnMTFwetrdsfmhPnjw6587345gvsdf7679njbvYT/

WIN-BUGSFIX.exe"

2. "http://www.skyinet.net/~angelcat/

skladjflfdjghKJnwetryDGFikjUIyqwerWe546786324hjk4jnHHGbvbmKLJKjhkqj4w/

WIN-BUGSFIX.exe"

3. "http://www.skyinet.net/~koichi/

jf6TRjkcbGRpGqaq198vbFV5hfFEkbopBdQZnmPOhfgER67b3Vbvg/ WIN-BUGSFIX.exe"

4. "http://www.skyinet.net/~chu/

sdgfhjksdfjklNBmnfgkKLHjkqwtuHJBhAFSDGjkhYUgqwerasdjhPhja

sfdglkNBhbqwebmznxcbvnmadshfgqw237461234iuy7thjg/ WIN-BUGSFIX.exe"

Информацию о файле WIN-BUGSFIX.exe вирyс помещает в

"HKEY_Current_User\Software\Microsoft\Internet Explorer\Main\Start Page"

Более того, папкой для загружаемых из Internet файлов становится C:\ ,

а значит, как только пользователь запускает Explorer, броyзер тянется

за Win-Bugsfix.exe. И, если пользователь соглашается на загрузку файла,

вирyс предлагает сохранить его в корневом каталоге диска C. При

известной беспечности пользователя, может сработать. К сожалению,

Win-Bugsfix.exe оказалась для меня недостyпной, поэтомy не удалось


пообщаться с этой программой. Однако, можно предположить, что ее

назначение - уничтожить FAT-32.

Если пользователь загрузил файл в корневой каталог, то при следующем

срабатывании троянец запишет в

HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\

CurrentVersion\Run\WIN-BUGSFIX команду запуска этого файла. При

очередной загрузке программа сработает. И тогда же троянец изменит

стартовую страницу Explorer на about:blank.

В продолжение своей деструктивной деятельности он берет список всех

дисков компьютера и один за другим листает все файлы. Все vbs-, vbe-,

js-, jse-, css-, wsh-, sct- и hta-файлы становятся копиями троянца,

также он удаляет файлы jpeg и jpg, заменяя их файлами с тем же именем

но с расширением VBS. Скажем спасибо автору троянца, что он забыл

поставить команду удаления для mp3- и mp2-файлов. Для них будут просто

созданы файлы с расширением vbs. Правда, с атрибутом Read-Only,

наверное, против неопытных пользователей.

Затем, если вирyсy посчастливилось найти папку с mIRC, в файл

script.ini он записывает команды, суть которых сводится к тому, что

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

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

под видом LOVE-LETTER-FOR-YOU.HTM.

Ну, и в заключение, троянец рассылает через MS Outlook каждому адресату

в адресной книжке собственнyю копию. Тема письма - "ILOVEYOU",

приложенный файл - LOVE-LETTER-FOR-YOU.TXT.vbs из системной папки,

HTML-послание содержит мета-таги, с указанием на автора, один из

которых, Description, говорит: "Простой, но, я думаю, он хорош...".

Последнее замечание насчет самого послания. Для того чтобы оно

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

активные скрипты и ActiveX контролы, т. к. по умолчанию Outlook Express

всегда будет предупреждать пользователя о возможной опасности

содержимого.

[DK130]Спасибо за присланную интересную статью. Думаю, добавления слова «типа» будет достаточно?..


[E131]Только реляционная

[E132]понять, что имеется в виду, можно только приблизительно. Насколько до меня дошло, подразумевается просто раскрученный рулон, то есть его вытянутость?

[E133]В конце предыдущего раздела говорится, что “обычно” è “хостинг-провайдер”.

Текст абзаца несколько избыточен

[E134]При работе с установленным соединением или в принципе?

[E135]«велик, могуч и богат»?

[DK136]Ну да, но тут главное – то, что в этой главе описаны далеко не все возможности.

[DK137]Да, пожалуй.

[E138]Настоятельно рекомендую как-то продолжить абзац, поскольку слово “результат” ðàñõîäèòñÿ ñ îáùåïðèíÿòîé òåðìèíîëîãèåé. Например: “Я же придерживаюсь и буду придерживаться другой терминологии: результат — “он и в Африке” результат (это, конечно, грубовато)”. Иначе боюсь, с имеющимся количеством правок текст не пройдет дальше. Термин dataset – стандартен, а если взглянуть в PHP Manual (у меня имеется только PHP3), то вот характерный пример: “mysql_result() returns the contents of one cell from a MySQL result set.” Обратите внимание – “результирующего набора”!

[DK139]Думаю, средний читатель не особо знаком с базами данных, поэтому чем проще терминология, тем, наверное, будет лучше.

[E140]?

[DK141]О «текущей строке» говорится чуть позже, так что, думаю, пока будет лучше оставить «очередную». У меня такой метод – от простого к сложному: сначала рассказываю обо всем загрубленно (то есть, на частных примерах), а потом детализирую и исправляюсь.

[E142]Опять-таки по поводу “результата”: “в результате” чего-то…

[DK143]Тут «в результате» – не устойчивый оборот, а «в чем?»

[E144]вот у базы данных синонимов нет

[E145]а решает ли это задачу именно в данном контексте? Ведь AUTO_INCREMENT

допустим только для одного поля таблицы. А если другой “администратор” опередит?

[DK146]Безусловно решает. Просто нужно получать идентификатор уже ПОСЛЕ вставки записи. Об этом рассказано ниже.

[E147]

[DK148]Мне не очень нравится это слово. Далеко не все читатели поймут, что оно обозначает здесь, в заглавии. Может, лучше вернуть?..

[DK149]Сыграем на контрастах?..

[E150]примечание для верстальщика – сохранить интервал (пустую строку)

[E151]Примечание для верстки – обойтись без переноса


Файл конфигурации PHP php.ini

Приложение 2, которое вы видите перед собой, уважаемый читатель, включает полный перевод на русский язык комментариев внутри файла конфигурации PHP php.ini.
Файл конфигурации PHP php.ini

Директивы в листинге П2.1 полностью соответствуют рекомендациям по установке PHP для Windows, представленным в части II книги. Впрочем, чтобы получить этот файл, мне понадобилось всего пара изменений в настройках PHP по умолчанию (настройки по умолчанию хранятся в файле php.ini-dist) — не то, что в случае с Apache.
Если вы установили PHP как модуль Apache, перед вами открываются дополнительные возможности: вы можете задавать значения некоторых директив прямо в файлах httpd.conf
или .htaccess. В силу специфики синтаксиса файлов конфигурации Apache, для отделения имени директивы и ее значения нужно использовать пробел, а не знак =. Кроме того, имена директив PHP должны быть предварены префиксом php_. Например, директива из php.ini
auto_prepend_file=top.html
будет выглядеть в httpd.conf или .htaccess так:
php_auto_prepend_file top.html
Приведенного листинга с комментариями должно быть вполне достаточно для понимания роли большинства директив PHP. Именно поэтому я уделил им так мало страниц в частях IV и V данной книги. И все-таки, если у вас возникнут какие-то затруднения, их легко сможет разрешить документация, которую можно получить, например, с официального сайта PHP: http://www.php.net.
Листинг П2.1. Файл php.ini
[PHP]
;;;;;;;;;;;;;;;;;
; Об этом файле ;
;;;;;;;;;;;;;;;;;
; Этот файл содержит большинство установок PHP. Чтобы PHP смог его
; обнаружить, он должен называться 'php.ini'. Интерпретатор ищет файл в
; текущем каталоге, в случае неудачи — в каталоге, указанном в
; переменной окружения PHPRC, и, наконец, в каталоге, заданном при
; компиляции и сборке PHP (именно в таком порядке).
; В системе Windows путь, указанный при компиляции PHP,
; соответствует каталогу Windows (в большинстве случаев это
; c:\windows). Папка, в которой будет производиться поиск файла
; 'php.ini', может быть также определена с использованием ключа –c

; командной строки.

;

; Синтаксис файла крайне прост.  Пробельные символы (то есть, пробелы,

; символы табуляции и т. д.), строки, начинающиеся с точки с запятой (;)

; игнорируются (как вы, наверное, уже догадались). Заголовки секций

; (например, [Foo]) также пропускаются, но, возможно, будут учитываться

; в будущих версиях PHP.

;

; Директивы задаются примерно так:

; directive=value

; Имена директив чувствительны к регистру символов — foo=bar не то же

; самое, что FOO=bar.

;

; Значение value может быть строкой, числом, константой PHP (например,

; E_ALL или M_PI), одной из INI-констант (On, Off, True, False, Yes, No

; или None), выражением (например, E_ALL & ~E_NOTICE), а также строкой

; в кавычках ("foo").

;

; В выражениях могут использоваться только побитовые и логические

; операторы, а также скобки:

; |      поразрядное ИЛИ (OR)

; &      поразрядное И (AND)

; ~      поразрядное НЕ (NOT)

; !      логическое отрицание (NOT)

;

; В качестве логических флагов со значением "истина" могут быть

; использованы значения 1, On, True или Yes. Значение "ложь" дают 0, Off,

; False и No.

;

; Пустая строка может быть задана, если "не указать ничего" после знака

; равенства, или же указать слово None:

;   foo=        ; устанавливаем foo равным пустой сторке

;   foo=none    ; аналогично

;   foo="none"  ; устанавливаем foo равным строке 'none'

;

; Если вы используете константы в качестве части значения директивы и эти

; константы определяются в каком-нибудь динамически загружаемом

; расширении (модуле PHP или Zend), вы можете указывать их только после

; строки, которая загружает расширение.

;

; Все значения в файле php.ini-dist соответствуют встроенным значениям

; по умолчанию. Если php.ini не задействуется, или же вы удалите из него

; некоторые строки,

будут установлены значения по умолчанию.

;;;;;;;;;;;;;;;;;;;

; Настройки языка ;

;;;;;;;;;;;;;;;;;;;


; Разрешает работу PHP для сервера Apache.

engine=On

; Разрешает использовать короткие тэги
; только тэги .

short_open_tag=On

; Позволяет использовать тэги <% %> а-ля ASP.

asp_tags=Off

; Число значащих цифр после запятой, которые отображаются для чисел с

; плавающей точкой.

precision=14

; Признак коррекции дат (проблема 2000 года, которая может

; вызвать непонимание со стороны браузеров, которые

; на это не рассчитывают)

y2k_compliance=Off

; Использование буферизации вывода. Позволяет посылать заголовки (включая

; Cookies) после вывода текста. Правда, это происходит ценой

; незначительного замедления вывода.

; Вы можете разрешить буферизацию во время выполнения сценария путем

; вызова функций буферизации, или же включить ее по умолчанию с помощью

; следующей директивы:

output_buffering=Off

; Директива неявной отсылки говорит PHP о том, что выводимые данные нужно

; автоматически передавать браузеру после вывода каждого блока данных.

; Ее действие эквивалентно вызовам функции flush() после

; каждого использования print() или echo() и после каждого HTML-блока.

; Включение этой директивы серьезно замедляет работу, поэтому ее

; рекомендуется применять лишь в отладочных целях.

implicit_flush=Off

; Параметр определяет, должен ли PHP использовать возможность всегда

; передавать аргументы функциям по ссылке при выполнении сценария.

; Этот метод устарел, и, скорее всего, он не будет

; поддерживаться в будущих версиях PHP/Zend.

; Описание того, каким способом должен быть передан аргумент —

; по ссылке или по значению — рекомендуется указывать при объявлении

; функции. Лучше всего, если вы попробуете установить параметр в Off

; и проверите, все ли сценарии по-прежнему работают. Если это так,

; то все в порядке, и сценарии будут совместимы и с будущими версиями

; PHP. В противном случае вы будете получать предупреждения каждый раз,

; когда аргументы передаются ненадлежащим образом и по значению там,


; где должны передаваться по ссылке.

allow_call_time_pass_reference=On

; Безопасный режим

safe_mode=Off

safe_mode_exec_dir=

; Установка некоторых переменных окружения может потенциально породить

; "дыры" в защите сценариев. Следующая директива содержит разделенный

; запятыми список префиксов. В режиме включенного безопасного режима

; пользователь сможет изменять только те переменные окружения, имена

; которых начинаются с перечисленных префиксов.

; По умолчанию пользователь имеет возможность устанавливать только

; переменные окружения, начинающиеся с PHP_ (например,

; PHP_FOO=something).

; Замечание: если эта директива пуста, PHP позволяет пользователям

; модифицировать любые переменные окружения!

safe_mode_allowed_env_vars=PHP_

; Следующая директива содержит разделенный запятыми список имен

; переменных окружения, которые конечный пользователь не сможет изменять

; путем вызова putenv().

; Эти переменные будут защищены даже в том случае, если директива

; разрешает их использовать.

safe_mode_protected_env_vars=LD_LIBRARY_PATH

; Эта директива позволяет вам запрещать вызовы некоторых функций

; из соображений безопасности. Список задается в виде имен функций,

; разграниченных запятыми. Директива действует независимо от того,

; установлен ли безопасный режим или нет!

disable_functions=

; Цвета для режима раскраски синтаксиса. Любой цвет, допустимый в тэге

; , допустим и здесь.

highlight.string=#DD0000

highlight.comment=#FF8000

highlight.keyword=#007700

highlight.bg=#FFFFFF

highlight.default=#0000BB

highlight.html=#000000

; Другие директивы

; Следующая директива указывает, должен ли PHP добавлять заголовок

; X-Powered-by в заголовки, посылаемые браузеру, и, таким образом,

; обнаруживать себя. Это никак не может повлиять на безопасность

; сценария, однако позволяет пользователю определить, использовался

; ли PHP для генерации страницы, или нет.

expose_php=On

;;;;;;;;;;;;;;;;;;;;;;;;


; Ограничения ресурсов ;

;;;;;;;;;;;;;;;;;;;;;;;;

; Максимальное возможное время выполнения сценария в секундах. Если

; сценарий будет выполняться дольше, PHP принудительно завершит его.

max_execution_time=30

; Максимальный объем памяти, выделяемый сценарию (8MB)

memory_limit=8M

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Обработка ошибок и подключений ;

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Директива error_reporting должна задаваться в виде битового

; поля. Его значение можно устанавливать с помощью следующих констант,

; объединенных оператором | (OR):

; E_ALL              - Все предупреждения и ошибки.

; E_ERROR            - Критические ошибки времени выполнения.

; E_WARNING          - Предупреждения времени выполнения.

; E_PARSE            - Ошибки трансляции.

; E_NOTICE           - Замечания времени выполнения (это такие

;                      предупреждения, которые, скорее всего,

;                      свидетельствуют о логических ошибках в

;                      сценарии, — например, использовании

;                      неинициализированной переменной).

; E_CORE_ERROR       - Критические ошибки в момент старта PHP.

; E_CORE_WARNING     - Некритические предупреждения во время старта PHP.

; E_COMPILE_ERROR    - Критические ошибки времени трансляции.

; E_COMPILE_WARNING  - Предупреждения времени трансляции.

; E_USER_ERROR       - Сгенерированные пользователем ошибки.

; E_USER_WARNING     - Сгенерированные пользователем предупреждения.

; E_USER_NOTICE      - Сгенерированные пользователем замечания.

; Пример:

; показывать все ошибки, за исключением замечаний

; error_reporting = E_ALL & ~E_NOTICE

; показывать только сообщения об ошибках

; error_reporting=E_COMPILE_ERROR|E_ERROR|E_CORE_ERROR

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

error_reporting= E_ALL

; Печать ошибок и предупреждений прямо в браузер.

; Для готовых сайтов рекомендуется отключать следующую директиву и

; использовать вместо нее журнализацию (см. ниже). Включенная директива


; display_errors в "рабочих" сайтах может открыть доступ пользователю к

; секретной информации: например, полному пути к документу, используемой

; базе данных и т. д.

display_errors=On

; Даже если display_errors включена, ошибки, возникающие во время старта

; PHP, не отображаются. Рекомендуется устанавливать следующую директиву

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

; ее при отладке.

display_startup_errors=Off

; Сохранять ли сообщения об ошибках в файле журнала. Журнал может

; определяться настройками сервера, быть связанным с потоком stderr

; или же задаваться директивой error_log, описанной ниже. Как уже было

; сказано, в коммерческих проектах желательно использовать именно

; журнализацию, а не отображать ошибки в браузер.

log_errors=Off

; Сохранять ли последнее сообщение об ошибке или предупреждение в

; переменной $php_errormsg

track_errors=On

; Строка, которая выводится перед сообщением об ошибке.

;error_prepend_string=""

; Строка, которая отображается после сообщения.

;error_append_string="
"

; Раскомментируйте, чтобы вести журнал в указанном файле.

;error_log=filename;

; Раскройте, чтобы использовать системный журнал.

;error_log=syslog

; Предупреждать, когда оператор + применяется к строкам.

warn_plus_overloading=Off

;;;;;;;;;;;;;;;;;;;;

; Обработка данных ;

;;;;;;;;;;;;;;;;;;;;

; Замечание: track_vars всегда включена, начиная с PHP 4.0.3.

; Следующая директива определяет, в каком порядке PHP будет

; регистрировать данные, полученные методами GET, POST, а также

; переменные окружения и встроенные переменные (соответственно, значение

; задается буквами G, P, C, E и S, например, EGPCS или GPC). Регистрация

; производится на основе чтения этой строки слева направо, новые значения

; переопределяют старые.

variables_order="EGPCS"

; Должен ли PHP регистрировать EGPCS-переменные как глобальные

; переменные. Возможно, вы захотите отключить эту возможность, если не


; хотите "засорять" глобальную область видимости сценария. Это имеет

; смысл, если вы используете директиву track_vars — в этом случае вы

; можете получить доступ к GPC-данным через массив $HTTP_???_VARS.

; Желательно так писать сценарии, чтобы они по возможности

; старались обходиться без директивы register_globals. Использование

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

потенциально

; может породить проблемы в защите сценария, если программист не особенно

; позаботится об их устранении.

register_globals=On

; Следующая директива указывает PHP, обязан ли он создавать переменные

; $argv è $argc на основе информации, поступившей методом GET. Если вы не

; используете эти переменные, отключите директиву register_argc_argv

для

; небольшого убыстрения работы PHP.

register_argc_argv=On

; Максимальный размер данных POST, который PHP сможет принять.

post_max_size=8M

; Следующая директива устарела — используйте variables_order.

gpc_order="GPC"

; Автоматическая обработка кавычек и апострофов:

; использовать ли автокавычки для входящих GET/POST/Cookie äàííûõ

magic_quotes_gpc=Off

; заключать ли данные в автокавычки во время выполнения, например,

; для данных из SQL, exec() и т. д.

magic_quotes_runtime=Off

; Нужно ли PHP оформлять автокавычки в стиле Sybase-style (заменять '

; на '', à не на \')

magic_quotes_sybase=Off

; Следующие директивы указывают PHP, содержимое каких файлов он должен

; обрабатывать до и после вывода сценария.

auto_prepend_file=

auto_append_file=

; Начиная с версии 4.0b4, PHP всегда сообщает браузеру об используемой

; кодировке в заголовке Content-type. Для того чтобы запретить это,

; просто установите следующую директиву пустой. По умолчанию

; используется text/html без указания кодировки.

default_mimetype="text/html"

;default_charset="iso-8859-1"

;;;;;;;;;;;;;;;;;;;


; Пути и каталоги ;

;;;;;;;;;;;;;;;;;;;

; Для UNIX: "/path1:/path2".

; Для Windows: "\path1;\path2"

include_path=

; Корневой каталог для PHP-сценариев.

; Игнорируется, если значение равно пустому "".

doc_root=

; Каталог, который PHP использует при открытии сценария вида

; /~username. Не оказывает действия, если значение равно "".

user_dir=

; Каталог, в котором хранятся динамически загружаемые расширения.

extension_dir=C:/Program Files/PHP4/extensions

; Следующая директива разрешает или запрещает использование функции dl().

; Функция dl() работает неправильно

в многопоточных Web-серверах,

; например, в IIS или Zeus, и автоматически отключается для них.

enable_dl=On

;;;;;;;;;;;;;;;;;;

; Закачка файлов ;

;;;;;;;;;;;;;;;;;;

; Разрешает PHP обрабатывать закачку файлов

file_uploads=On

; Каталог для временных файлов, в который PHP помещает закачанные

; файлы (используется системный временный каталог, если в директиве

; указана пустая строка)

;upload_tmp_dir=

; Максимальный размер закачанного файла

upload_max_filesize=2M

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Динамически загружаемые расширения ;

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Если вы хотите, чтобы какие-то модули загружались автоматически,

; задавайте директиву extension в формате:

; extension=modulename.extension

; Например, для Windows:

; extension=msql.dll

; или для UNIX:

; extension=msql.so

; Должно быть указано только имя, без пути. Чтобы задать

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

; extension_dir, описанную выше.

; Модули для Windows

; Замечание: поддержка MySQL и ODBC теперь включена в ядро PHP, так что

; для нее уже не нужны никакие библиотеки DLL.

;extension=php_cpdf.dll

;extension=php_cybercash.dll

;extension=php_db.dll

;extension=php_dbase.dll

;extension=php_domxml.dll

;extension=php_dotnet.dll

;extension=php_exif.dll

;extension=php_fdf.dll

extension=php_gd.dll


;extension=php_gettext.dll

;extension=php_ifx.dll

;extension=php_imap.dll

;extension=php_interbase.dll

;extension=php_java.dll

;extension=php_ldap.dll

;extension=php_mhash.dll

;extension=php_mssql65.dll

;extension=php_mssql70.dll

;extension=php_oci8.dll

;extension=php_oracle.dll

;extension=php_pdf.dll

;extension=php_pgsql.dll

;extension=php_sablot.dll

;extension=php_swf.dll

;extension=php_sybase_ct.dll

;extension=php_zlib.dll

;;;;;;;;;;;;;;;;;;;;;;;;;

; Установки для модулей ;

;;;;;;;;;;;;;;;;;;;;;;;;;

[Syslog]

; Нужно или нет определять различные переменные Syslog, такие как

; $LOG_PID, $LOG_CRON и т. д. Для ускорения работы рекомендуется

; выключать следующую директиву. Во время выполнения сценария вы

; можете включить или выключить директиву путем вызова

; функции define_syslog_variables().

define_syslog_variables=Off

[mail function]

; Только для Win32 — используемый SMTP-сервер.

SMTP=mail.dklab.ru

; Только для Win32 — поле From: по умолчанию.

sendmail_from= dk@dklab.ru

; Только для UNIX — задает путь и аргументы программы sendmail (по

; умолчанию — 'sendmail -t -i').

;sendmail_path=

[Debugger]

debugger.host=localhost

debugger.port=7869

debugger.enabled=False

[Logging]

; Следующие директивы используются сценарием-примером.

; При потребности в детальном описании см. examples/README.logging.

;logging.method=db

;logging.directory=/path/to/log/directory

[Java]

;java.class.path=.\php_java.jar

;java.home=c:\jdk

;java.library=c:\jdk\jre\bin\hotspot\jvm.dll

;java.library.path=.\

[SQL]

sql.safe_mode=Off

[ODBC]

;uodbc.default_db=Not yet implemented

;uodbc.default_user=Not yet implemented

;uodbc.default_pw=Not yet implemented

; Разрешает или запрещает устойчивые соединения

uodbc.allow_persistent=On

; Проверка доступности соединения перед его использованием.

uodbc.check_persistent=On

; Макс. число устойчивых соединений. -1 означает, что ограничений нет.


uodbc.max_persistent=-1

; Макс. число соединений (устойчивых + неустойчивых).

uodbc.max_links=-1

; Установки для LONG-полей.

uodbc.defaultlrl=4096

; Установки для бинарных данных. 0 означает режим passthru, 1 – режим

; as is, 2 – преобразование в символы.

uodbc.defaultbinmode=1

; См. документацию по odbc_binmode и odbc_longreadlen для более

; детального разъяснения смысла директив uodbc.defaultlrl

и

; uodbc.defaultbinmode.

[MySQL]

mysql.allow_persistent=On

mysql.max_persistent=-1

mysql.max_links=-1

; Порт по умолчанию для функции mysql_connect(). Если не задан, функция

; попытается использовать переменную $MYSQL_TCP_PORT или запись mysql-tcp

; в /etc/services, а также заданную во время компиляции PHP константу

; MYSQL_PORT (именно в таком порядке). К PHP для Win32 применимо только

; последнее.

mysql.default_port=

; Определяет имя сокета для локальных соединений MySQL. Если он не задан,

; использует встроенное значение по умолчанию.

mysql.default_socket=

; Хост по умолчанию для mysql_connect() (не работает в безопасном режиме).

mysql.default_host=

; Пользователь по умолчанию (не работает в безопасном режиме).

mysql.default_user=

; Пароль по умолчанию (не работает в безопасном режиме).

; Замечание: идея хранить пароль в этом файле просто отвратительна. Любой

; пользователь, который может запускать PHP, сможет узнать пароль путем

; выполнения:

; echo cfg_get_var("mysql.default_password")

; Конечно, узнать пароль сможет также и пользователь, который имеет права

; на чтение для файла php.ini.

mysql.default_password=

[mSQL]

msql.allow_persistent=On

msql.max_persistent=-1

msql.max_links=-1

[PostgresSQL]

pgsql.allow_persistent=On

pgsql.max_persistent=-1

pgsql.max_links=-1

[Sybase]

sybase.allow_persistent=On

sybase.max_persistent=-1

sybase.max_links=-1

;sybase.interface_file="/usr/sybase/interfaces"

; Максимальный уровень серьезности отображаемых ошибок.

sybase.min_error_severity=10


; Минимальный уровень серьезности отображаемых ошибок.

sybase.min_message_severity=10

; Режим совместимости со старыми версиями PHP 3.0.

; Если следующая директива установлена в On, PHP будет автоматически

; присваивать тип результату на основе его типа в Sybase, вместо того,

; чтобы преобразовывать полученные значения в строки. Этот режим

; совместимости, возможно, в будущем не будет поддерживаться, так что

; лучше исправьте свои сценарии, если вам он нужен.

sybase.compatability_mode=Off

[Sybase-CT]

sybct.allow_persistent=On

sybct.max_persistent=-1

sybct.max_links=-1

sybct.min_server_severity=10

sybct.min_client_severity=10

[bcmath]

; Число десятичных цифр для всех bcmath-функций.

bcmath.scale=0

[browscap]

;browscap=extra/browscap.ini

[Informix]

ifx.default_host=

ifx.default_user=

ifx.default_password=

ifx.allow_persistent=On

ifx.max_persistent=-1

ifx.max_links=-1

; Если следующая директива установлена в On, выражение select возвращает

; содержимое поля типа text blob вместо его идентификатора.

ifx.textasvarchar=0

; Заставляет команду select возвращать значение поля типа byte blob

; вместо его идентификатора.

ifx.byteasvarchar=0

; Принуждает PHP удалять завершающие пробелы из колонок с типом char

; фиксированного размера. Может помочь пользователям Informix SE.

ifx.charasvarchar=0

; Если установлена, содержимое полей text и byte сохраняется в файле,

; вместо того, чтобы храниться в памяти.

ifx.blobinfile=0

; Если установлена в 0, значения NULL возвращаются как пустые строки,

; иначе они возвращаются как строки 'NULL'.

ifx.nullformat=0

[Session]

; Определяет режим хранения данных сессий.

session.save_handler=files  

; Следующая директива задает аргумент, передаваемый save_handler-у.

; В случае режима сохранения в файлах здесь должен указываться каталог,

; в который будут помещены файлы сессий.

session.save_path=C:\Program Files\PHP4\sessiondata

; Должен ли PHP использовать Cookies.

session.use_cookies=1


session.name=PHPSESSID 

; Инициализировать ли сессии при старте.

session.auto_start=0      

; Время жизни Cookie для сессии. Если до закрытия браузера, то 0.

session.cookie_lifetime=0      

; Путь для Cookie с идентификатором сессии.

session.cookie_path=/

; Домен для Cookie с идентификатором сессии.

session.cookie_domain=

; Функция, используемая для сериализации данных. Значение php задает

; стандартную функцию.

session.serialize_handler=php

; Вероятность того, что при очередном запуске сценария, работающего с

; сессиями, будет вызвана функция "сборки мусора" для очистки сессий,

; которые пользователь уже покинул.

session.gc_probability=1

; После указанного здесь промежутка времени сохраненные

; данные будут удалены автоматически сборщиком мусора.

session.gc_maxlifetime=1440

; Проверять ли HTTP Referer на предмет того, не является ли ID сессии

; "фальшивым".

session.referer_check=

; Указывает, сколько байтов читать из файла.

session.entropy_length=0      

;session.entropy_length=16

; Файл, используемый для генерации идентификаторов сессии.

session.entropy_file=

;session.entropy_file=/dev/urandom

; Установите одно из значений nocache, private, public для определения

; аспектов кэширования HTTP.

session.cache_limiter=nocache

; Документ будет считаться устаревшим по истечении заданного

; здесь количества минут

session.cache_expire=180

; Использовать ли поддержку "переходящих" SID. Действует, если PHP был

; скомпилирован с включенной опцией --enable-trans-sid.

session.use_trans_sid=1      

[MSSQL]

;extension=php_mssql.dll

mssql.allow_persistent=On

mssql.max_persistent=-1

mssql.max_links=-1

mssql.min_error_severity=10

mssql.min_message_severity=10

; Режим совместимости со старыми версиями PHP 3.0.

mssql.compatability_mode=Off

[Assertion]

;assert.active=On

; Генерирует предупреждения PHP для каждых неудавшихся проверок

; выражений.

;assert.warning=On

; По умолчанию не завершать программу в случае неудачи.


;assert.bail=Off

; Пользовательская функция, которая будет вызвана при неудаче проверки.

;assert.callback=0

; Вычислять выражения в eval с использованием текущих установок

; error_reporting. Установите в true, если вы хотите, чтобы действие

; режима error_reporting(0) было сохранено и при переходе через

; границу eval().

;assert.quiet_eval=0

[Ingres II]

ingres.allow_persistent=On

ingres.max_persistent=-1

ingres.max_links=-1

; База данных по умолчанию (формат: [node_id::]dbname[/srv_class]

ingres.default_database=

ingres.default_user=

ingres.default_password=

[Verisign Payflow Pro]

pfpro.defaulthost="test.signio.com"

pfpro.defaultport=443

pfpro.defaulttimeout=30

; IP-адрес proxy-сервера по умолчанию (если требуется).

; pfpro.proxyaddress=

; Порт proxy-сервера по умолчанию

; pfpro.proxyport=

; Логин для proxy-сервера по умолчанию

; pfpro.proxylogon=

; Пароль для proxy-сервера по умолчанию

; pfpro.proxypassword=



Файл конфигурации PHP php.ini




Фильтры блоков

После того, как тело блока вычислено, шаблонизатор производит его дополнительную обработку. Делается это с помощью специальных функций-фильтров. Например, "склеивание" названий осуществляется именно таким фильтром. Система устроена таким образом, что позволяет добавлять и удалять фильтры прямо в процессе работы. Для этого программисту достаточно лишь написать код функции-фильтра, а затем добавить имя этой функции в специальную таблицу фильтров (см. исходный код шаблонизатора).
В той версии шаблонизатора, которую мы сейчас рассматриваем, имеется и еще один "стандартный" фильтр. Его задача — удалить из тел блоков все начальные символы табуляции, сколько бы их ни было. Это позволяет программистам и дизайнерам свободно делать отступы в HTML-коде документов, не думая о том, что символы табуляции будут увидены пользователем при просмотре кода страницы. Впрочем, возможно, это и излишество (в конце концов, кому какое дело, как выглядит исходный код страницы).
Фильтры блоков

Ради интереса вы можете написать фильтр, который превращает все символы перевода строки в пробелы. Таким образом, исходный код страницы, которую получит пользователь, будет представлять собой одну длинную строку. Код этого фильтра занимает буквально одну строку на PHP!



Финализаторы

Слава богу, разработчики PHP предусмотрели возможность указать в программе функцию-финализатор, которая будет автоматически вызвана, как только работа сценария завершится — неважно, из-за ошибки или легально. В такой функции мы можем, например, записать информацию в кэш или обновить какой-нибудь файл журнала работы программы. Что же нужно для этого сделать?
Во-первых, написать саму функцию и дать ей любое имя. Желательно также, чтобы она была небольшой, и чтобы в ней не было ошибок, потому что сама функция, вполне возможно, будет вызываться перед завершением сценария из-за ошибки. Во-вторых зарегистрировать ее как финализатор, передав ее имя стандартной функции Register_shutdown_function().
int Register_shutdown_function(string $func)
Регистрирует функцию с указанным именем с той целью, чтобы она автоматически вызывалась перед возвратом из сценария. Функция будет вызвана как при окончании программы, так и при вызовах exit() или die(), а также при фатальных ошибках, приводящих к завершению сценария — например, при синтаксической ошибке.
Конечно, можно зарегистрировать несколько финальных функций, которые будут вызываться в том же порядке, в котором они регистрировались.
Правда, есть одно "но". Финальная функция вызывается уже после закрытия соединения с браузером клиента. Поэтому все данные, выведенные в ней через echo, теряются (во всяком случае, так происходит в Unix-версии PHP, а под Windows CGI-версия PHP и echo работают прекрасно). Так что лучше не выводить никаких данных в такой функции, а ограничиться работой с файлами и другими вызовами, которые ничего не направляют в браузер.
Последнее обстоятельство, к сожалению, ограничивает функциональность финализаторов: им нельзя поручить, например, вывод окончания страницы, если сценарий по каким-то причинам прервался из-за ошибки. Вообще говоря, надо заметить, что в PHP никак нельзя в случае ошибки в некотором запущенном коде проделать какие-либо разумные действия (кроме, разумеется, мгновенного выхода). Это несколько может ограничивать область применимости PHP для написания шаблонизатора (о шаблонах будет подробно рассказано в части V этой книги).[E125] [DK126]



Формат данных

В свое время я говорил, что все данные из формы при передаче их на сервер упаковываются в строку при помощи символов ?, & и =. Легко видеть, что при загрузке файлов такой способ, хотя и приемлем, но будет существенно увеличивать размер передаваемой информации. Действительно, ведь большинство файлов— бинарные, а мы знаем, что при URL-кодировании данные таких файлов сильно "распухают" — примерно в три раза (например, простой нулевой байт при URL?кодировании превратится в %00). Это сильно замедлит передачу и увеличит нагрузку на канал. И вот, отчасти специально для решения указанной проблемы был изобретен другой формат передачи данных, отличный от того, который мы до сих пор рассматривали.

В нем уже не используются пресловутые символы ? и &. Кроме того, похоже, в случае применения такого формата передачи может быть задействован только метод POST, но не метод GET. Нас это вполне устроит — ведь файлы обычно большие, и доставлять их через GET вряд ли разумно...
Если нужно указать браузеру, что в какой-то форме следует применять другой формат передачи, следует в соответствующем тэге задать атрибут enctype=multipart/form-data. (Кстати говоря, если этот атрибут не указан, то форма считается обычной, что эквивалентно enctype=application/x-www-form-urlencoded — именно так обозначается привычный нам формат передачи.) После этого данные, поступившие от нашей формы, будут выглядеть как несколько блоков информации (по одному на элемент формы). Каждый такой блок очень напоминает HTTP-формат "заголовки-данные", используемый при традиционном формате передачи. Выглядит блок примерно так (\n, как всегда, обозначает символ перевода строки):
-----------------Èäåíòèôèêàòîð_íà÷àëà\n
Content-Disposition: form-data; name="èìÿ"\n
\n
çíà÷åíèå\n

Например, пусть у нас есть форма:

Листинг 3.7. Multipart-форма



Name:

Box:

Area: Ýòî êàêîé-òî òåêñò





Данные, поступившие по нажатии кнопки submit на сервер, будут иметь

следующий вид:

----------------127462537625367\n

Content-Disposition: form-data; name="Name"\n

\n

Ìîå èìÿ\n

----------------127462537625367\n

Content-Disposition: form-data; name="Box"\n

\n

1\n

----------------127462537625367\n

Content-Disposition: form-data; name="Area"\n

\n

Ýòî êàêîé-òî òåêñò\n

Заметьте, что несколько дефисов и число (которое мы ранее назвали

Идентификатор_начала) предшествуют каждому блоку. Более того, строка из дефисов и этого числа служит своеобразным маркером, который разделяет блоки. Очевидно, эта строка должна быть уникальной во всех данных. Именно так ее и формирует браузер. Правда, сказанное означает, что сегодня идентификатор будет одним, а завтра, возможно, совсем другим. Так что нам придется, прежде чем анализировать данные, считать этот идентификатор в буфер (им будет последовательность символов до первого символа \n).

Формат данных


Стандарт протокола HTTP говорит нам, что идентификатор начала также должен быть доступен через одну из переменных окружения. Но я не помню и не хочу знать ее название — сейчас объясню, почему. Некоторые браузеры (особенно старые) путают этот идентификатор и присылают его неправильно — с двумя предшествующими минусами (а остальные — без них), так что сценарии, не рассчитывающие на такой подвох, перестанут работать. Никогда не полагайтесь на эту переменную окружения (даже если узнаете, как она называется)! Вместо этого читайте последовательность символов до первого перевода строки и воспринимайте именно ее как разделитель.

Далее алгоритм разбора должен быть следующим: в цикле мы пропускаем символы идентификатора и перевода строки, извлекаем подстроку имя="что-то"

(не обращая внимания на Content-Disposition), дожидаемся двух символов перевода строки и затем считаем значением соответствующего поля все те данные, которые размещены до строки \nИдентификатор

(или же до конца, если такой строки больше нет). Как видите, все довольно просто.

Формат данных


Стандарт HTTP предписывает, чтобы перевод строки содержал два символа — \r\n, а не один \n. Как вы уже, наверное, чувствуете, существуют браузеры, которые об этом и не догадываются и посылают только один \n. Так что, будьте готовы к тому, чтобы правильно обрабатывать и эту ситуацию.


Функции для преобразований символов

Web-программирование — одна из тех областей, в которых постоянно приходится манипулировать строками: разрывать их, добавлять и удалять пробелы, перекодировать в разные кодировки, наконец, URL-кодировать и декодировать. В PHP реализовать все эти действия вручную, используя только уже описанные примитивы, просто невозможно из соображений быстродействия. Поэтому-то и существуют встроенные функции, описанные в этом разделе.
string strtr(string $str, string $from, string $to)
Эта функция применяется не столь широко, но все-таки иногда она бывает довольно полезной. Делает она вот что: в строке $str заменяет все символы, встречающиеся в $from, на их "парные"
(то есть расположенные в тех же позициях, что и во $from) из $to. Функция работает существенно быстрее, чем ereg_replace(), которую мы рассмотрим в главе, посвященной регулярным выражениям. Правде, она имеет вместе с тем несколько меньшую функциональность...
Следующие несколько функций предназначены для быстрого URL-кодирования и декодирования.
string UrlEncode(string $st)
Функция URL-кодирует строку $st и возвращает результат. Эту функцию удобно применять, если вы, например, хотите динамически сформировать ссылку на какой-то сценарий, но не уверены, что его параметры содержат только алфавитно-цифровые символы. В этом случае воспользуйтесь функцией так:
echo "
Теперь, даже если переменная $UserData
включает символы =, &
или даже пробелы, все равно сценарию будут переданы корректные данные.
string UrlDecode(string $st)
Производит URL-декодирование строки. В принципе, используется значительно реже, чем UrlEncode(), потому что PHP и так умеет перекодировать входные данные автоматически.
string RawUrlEncode(string $st)
Почти полностью аналогична UrlEncode(), но только пробелы не преобразуются в +, как это делается при передаче данных из формы, а воспринимаются как обычные неалфавитно-цифровые символы. Впрочем, этот метод не порождает никаких дополнительных несовместимостей в коде.

string RawUrlDecode(string $st)

Аналогична UrlDecode(), но не воспринимает + как пробел.

Давайте теперь рассмотрим функцию, которая обычно используется в комбинации с echo. Основное ее назначение — гарантировать, что в выводимой строке ни один участок не будет воспринят как тэг.

string HtmlSpecialChars(string $str)

Заменяет в строке некоторые символы (такие как
амперсант[E53] , кавычки и знаки "больше" и "меньше") на их HTML-эквиваленты, так, чтобы они выглядели на странице "самими собой". Самое типичное применение этой функции — формирование параметра value

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

$v) {?>

  Èìÿ:

  Òåêñò:

 




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

Функции для преобразований символов


Начинающие Web-программисты для решения задачи запрета тэгов часто пытаются просто удалить их из строки — например, применив функцию strip_tags(). Это метод довольно плох, потому что всегда существует вероятность того, что злоумышленник сможет "обмануть" эту функцию. Конечно, еще хуже метод с применением регулярных выражений, потому что, как известно, с их помощью вовсе невозможно выделить некоторые тэги из строки — например, тэги такого вида: .

string StripSlashes(string $st)

Заменяет в строке $st íåêîòîðûå предваренные слэшем символы на их однокодовые эквиваленты. Это относится к следующим символам: ", ', \ и никаким другим.

string AddSlashes(string $st)

Вставляет слэши только перед следующими символами: ', "

и \. Функцию очень удобно использовать при вызове eval() (эта функция выполняет строку, переданную ей в параметрах, так, как будто имеет дело с небольшой PHP-программой; о ней (функции) мы еще поговорим, и при том очень подробно).


Функции для работы с DNS

Здесь мы рассмотрим несколько очень полезных функций для работы с DNS-серверами и IP-адресом.



Функции для работы с одиночными символами

string chr(int $code)
Возвращает строку из одного символа с кодом $code. Эта функция полезна для вставки каких-либо непечатаемых символов в строку — например, кода нуля или символа прогона страницы, а также при работе с бинарными файлами. Пример из листинга 12.2 позволяет вам просмотреть, какие коды соответствуют всем символам, которые можно отобразить в браузере. Иногда эта программа оказывается очень полезной.
Листинг 12.2. Программа: печать всей таблицы символов
// Сначала создаем массив того, что мы собираемся выводить,
// не заботясь о форматировании (дизайне) информации
for($i=0,$x=0; $x<16; $x++) {
  for($y=0; $y<16; $y++) {
    $Chars[$x][$y]=array($i,chr($i));
    $i++;
  }
}
// Теперь выводим накопленную информацию, используя идеологию
// вставки участков кода в HTML-документ
?>


 
 
   
 
 


      :  
     
   

?>
int ord(char $ch)
Эта функция, наоборот, возвращает код символа в $ch. Например, ord(chr($n)) всегда равно $n — конечно, если $n заключено между нулем и числом 255.
int strrpos(string $where, char $what)
Данная функция, хотя и похожа внешне на strpos() (см. ниже), несет несколько иную нагрузку. Она ищет в строке $where последнюю позицию, в которой встречается символ $what (если $what — строка из нескольких символов, то выявляется только первый из них, остальные не играют никакой роли — обратите на это особое внимание!). В случае, если искомый символ не найден, возвращается false
(см. замечание по этому поводу для strpos()). Вообще, могу сказать, что функция strrpos() применяется очень
редко. Слишком уж она не универсальна.



Функции и области видимости

По синтаксису описания функций PHP, на мой взгляд, довольно близок к идеальной концепции, которую многие программисты лелеют в своем воображении[E49] . Вот несколько основных достоинств этой концепции:
r вы можете использовать параметры по умолчанию (а значит, функции с переменным числом параметров);
r области видимости переменных внутри функций представляются в древовидной форме, как и в других языках программирования;
r существует удобная инструкция return, которой так не хватает в Паскале;
r тип возвращаемого значения может быть любым;
r как мы увидим дальше, функции можно использовать не только по их прямому назначению, но и для автоматизации создания "библиотекарей" и даже написания своего собственного интерфейса библиотечных файлов.
К сожалению, разработчики PHP не предусмотрели возможность создания локальных функций (то есть одной внутри другой), как это сделано, скажем, в Паскале или в Watcom C++. Однако кое-какая эмуляция локальных функций все же есть: если функцию B() определить в теле функции A(), то она, хоть и не став локальной, все же будет "видна"
для программы ниже своего определения. Замечу для сравнения, что похожая схема существует и в языке Perl. Впрочем, как показывает практика программирования на Си (вот уже 30 лет), это не такой уж серьезный недостаток.
В системе определения функций в PHP есть и еще один небольшой недочет, который особенно неприятен тем, кто до этого программировал на других языках. Дело в том, что все переменные, которые объявляются и используются в функции, по умолчанию локальны для этой функции. При этом существует только один (и при том довольно некрасивый) способ объявления глобальных переменных — инструкция global (на самом деле есть и еще один, через массив $GLOBALS, но об этом чуть позже). С одной стороны, это повышает надежность функций в смысле их независимости от основной программы, а также гарантирует, что они случайно не изменят и не создадут глобальных переменных. С другой стороны, разработчики PHP вполне могли бы предугадать нужность инструкции, по которой все переменные функции становились бы по умолчанию глобальными — это существенно упростило бы программирование сложных сценариев.



Функции изменения регистра

Довольно часто нам приходится переводить какие-то строки, скажем, в верхний регистр, т.е. делать все прописные буквы в строке заглавными. В принципе, для этой цели можно было бы воспользоваться функцией strtr(), рассмотренной выше, но она все же будет работать не так быстро, как нам иногда хотелось бы. В PHP есть функции, которые предназначены специально для таких нужд. Вот они.
string strtolower(string $str)
Преобразует строку в нижний регистр. Возвращает результат перевода.
Надо заметить, что при неправильной настройке локали (про локаль будет рассказано чуть позже, а пока скажу только, что это набор правил по переводу символов из одного регистра в другой, переводу даты и времени, денежных единиц и т. д.) функция будет выдавать, мягко говоря, странные результаты при работе с буквами кириллицы. Возможно, в несложных программах, а также если нет уверенности в поддержке соответствующей локали операционной системой, проще будет воспользоваться "ручным"
преобразованием символов, задействуя функцию strtr():
$st=strtr($st,
"ÀÁÂÃÄÅЁÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙsÛÜÝÞß", "àáâãäåёæçèéêëìíîïðñòóôõö÷øùúûüýþÿ");
Главное достоинство данного способа — то, что в случае проблем с кодировкой для восстановления работоспособности сценария вам придется всего лишь преобразовать его в ту же кодировку, в которой у вас хранятся документы на сервере.
string strtoupper(string $str)
Переводит строку в верхний регистр. Возвращает результат преобразования. Эта функции также прекрасно работает со строками, составленными из "английских"
букв, но с "русскими" буквами может возникнуть все та же проблема.



Функции манипулирования целыми файлами

На самом деле всех перечисленных выше функций достаточно для реализации обмена с файлами любой сложности. Однако часто бывает нужно работать с файлами не построчно (или поблочно), а целиком. Функции, описанные в этом разделе, как раз для этого и предназначены.
bool copy(string $src, string $dst)
Копирует файл с именем $src в файл с именем $dst. При этом, если файл $dst на момент вызова существовал, осуществляется его перезапись. Функция возвращает true, если копирование прошло успешно, а в случае провала — false.
bool rename(string $oldname, string $newname)
Переименовывает (или перемещает, что одно и то же) файл с именем $oldname в файл с именем $newname. Если файл $newname уже существует, регистрируется ошибка, и функция возвращает false. То же происходит и при всех прочих неудачах. Если же все прошло успешно, возвращается true.
Функции манипулирования целыми файлами

Функция не выполняет переименование файла, если его новое имя расположено в другой файловой системе (на другой смонтированной системе в Unix или на другом диске в Windows). Так что никогда не используйте rename() для получения загруженного по HTTP файла (о загрузке подробно рассказано в пятой части книги) — ведь временный каталог /tmp вашего хостинг-провайдера скорее всего располагается на отдельном разделе диска.
bool unlink(string $filename)
Удаляет файл с именем $filename. В случае неудачи возвращает false, иначе — true.
Функции манипулирования целыми файлами

На самом-то деле файл удаляется только в том случае, если число "жестких" ссылок на него стало равным 0. Правда, эта схема специфична для Unix-систем.
list File(string $filename)
Считывает файл с именем $filename целиком и возвращает массив-список, каждый элемент которого соответствует строке в прочитанном файле. Функция работает очень быстро — гораздо быстрее, чем если бы мы использовали fopen()
и читали файл по одной строке. Неудобство этой функции состоит в том, что символы конца строки (обычно \n), не вырезаются из строк файла, а также не транслируются, как это делается для текстовых файлов.
array get_meta_tags(string $filename, int $use_include_path=false);
Функция открывает файл и ищет в нем все тэги до тех пор, пока не встретится закрывающий тэг . Если очередной тэг имеет вид:

то пара название=>содержимое
добавляется в результирующий массив, который под конец и возвращается. Функцию удобно использовать для быстрого получения всех метатегов из указанного файла (что работает гораздо быстрее, чем соответствующее использование fopen() и затем чтение и разбор файла по строкам). Если необязательный параметр $use_include_path
установлен, то поиск файла осуществляется не только в текущем каталоге, но и во всех тех, которые назначены для поиска инструкциями include и require.



Функции округления

mixed abs(mixed $number)
Возвращает модуль числа. Тип параметра $number
может быть float
или int, а тип возвращаемого значения всегда совпадает с типом этого параметра.
double round(double $val)
Округляет $val до ближайшего целого и возвращает результат, например:
$foo = round(3.4); // $foo == 3.0
$foo = round(3.5); // $foo == 4.0
$foo = round(3.6); // $foo == 4.0
int ceil(float $number)
Возвращает наименьшее целое число, не меньшее $number. Разумеется, передавать в $number целое число бессмысленно.
int floor(float $number)
Возвращает максимальное целое число, не превосходящее $number.



Функция count()

Мы можем определить размер (число элементов) в массиве при помощи стандартной функции count():
$num=count($Names);  // òåïåðü â $num — ÷èñëî ýëåìåíòîâ â ìàññèâå
Сразу отмечу, что count() работает не только с массивами, но и с объектами и даже с обычными переменными (для последних count() всегда равен 1, как будто переменная — это массив с одним элементом). Впрочем, ее очень редко применяют для чего-либо, отличного от массива — разве что по-ошибке.



Функция отправки письма

bool mail(string $to, string $subject, string $msg [,string $headers])
Функция mail() посылает сообщение с телом $msg (это может быть "многострочная строка", т. е. переменная, содержащая несколько строк, разделенных символом перевода строки) [E81]по адресу $to. Можно задать сразу нескольких получателей, разделив их адреса пробелами в параметре $to. Пример:
mail("rasmus@lerdorf.on.ca ca.ok@oklab.ru,
     "My Subject",
     "Line 1\nLine 2\nLine 3"
);
В случае, если указан четвертый параметр, переданная в нем строка вставляется между концом стандартных почтовых заголовков (таких как To, Content-type и т. д.) и началом текста письма. Обычно этот параметр используется для задания дополнительных заголовков письма. Пример:
mail("ssb@guardian.no dk@dizain.ru",
     "the subject",
     "Line 1\nLine 2\nLine 3",
     "From: webmaster@$SERVER_NAME\n".
     "Reply-To: webmaster@$SERVER_NAME\n".
     "X-Mailer: PHP/" . phpversion()
);
Необходимо добавить, что этот пример довольно-таки неказист. Гораздо лучше было бы включить указанные заголовки прямо в тело письма $msg
(в начало тела), отделив их от самого письма пустой строкой (прямо как в стандарте HTTP). То же самое применимо и к параметру $subject: лучше задавать в нем всегда пустую строку и указывать заголовок Subject в самом письме. Всегда старайтесь поступать таким образом. Далее будет ясно, зачем.



Генерация функций

В последнем примере мы рассмотрели, как можно создать 100 функций с разными именами, написав программу длиной в 2 строчки. Это, конечно, впечатляет, но мы должны жестко задавать имена функций. Почему бы не поручить эту работу PHP, если нас не особо интересуют получающиеся имена?
Листинг 24.2. Генерация "анонимных" функций
$Funcs=array();
for($i=0; $i<=100; $i++) {
  $id=uniqid("F");
  eval("function $id() { return $i*$i; }");
  $Funcs[]=$id;
}
Теперь мы имеем список $Funcs, который содержит имена наших сгенерированных функций. Как нам вызвать какую-либо из них? Это очень просто:
echo $Funcs[12]();  // âûâîäèò 144
Однако мы могли бы написать с тем же результатом и
echo Func12();
при том условии, если бы воспользовались кодом генерации функций из листинга 24.1. Кажется, что так короче? Тогда не торопитесь. Все хорошо, если мы точно знаем, что надо вызвать 12-ю функцию, но как же быть, если номер хранится в переменной — например, в $n? Вот решение:
echo $Funcs[$n]();  // âûâîäèò ðåçóëüòàò ðàáîòû $n-é ôóíêöèè
Не правда ли, просто? Выглядит явно лучше, чем такой код:
$F="Func$n";
$F();
Генерация функций

Тут нам не удастся обойтись без временной переменной $F (вариант с дополнительной eval() тоже не подойдет, т. к. у функции могут быть строковые параметры, и придется перед всеми кавычками ставить слэши, чтобы поместить их в параметр функции eval().
Оказывается, в PHP версии 4 существует функция, которая поможет нам упростить генерацию "анонимных"
функций, подобных полученным в примере из листинга 24.2. Называется она create_function().
string create_function(string $args, string $code)
Создает функцию с уникальным именем, выполняющую действия, заданные в коде $code (это строка, содержащая программу на PHP). Созданная функция будет принимать параметры, перечисленные в $args. Перечисляются они в соответствии со стандартным синтаксисом передачи параметров любой функции. Возвращаемое значение представляет собой уникальное имя функции, которая была сгенерирована. Вот несколько примеров:

$Mul=create_function('$a,$b', 'return $a*$b;');

$Neg=create_function('$a', 'return -$a;');

echo $Mul(10,20);  // выводит 200

echo $Neg(2);      // выводит -2

Генерация функций


Не пропустите последнюю точку с запятой в конце строки, переданной вторым параметром create_function()!

Давайте теперь перепишем наш пример из листинга 24.2 с учетом create_function(). Это довольно несложно. Обратите внимание, насколько сократился код.

$Funcs=array();

for($i=0; $i<=100; $i++)

  $Funcs[]=create_function("","return $i*$i;");

echo $Funcs[12]();  // выводит 144

И последний пример применения анонимных функций — в программах сортировки с использованием пользовательских функций:

$a=array("orange", "apple", "apricot", "lemon");

usort($a,create_function('$a,$b', 'return strcmp($a,$b);'));

foreach($a as $key=>$value) echo "$key: $value
\n";


Генерация кода во время выполнения

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



Генератор данных

Конечно, это еще далеко не весь сценарий. Вы, наверное, заметили, что сердце шаблона — цикл foreach вывода записей — использует непонятно откуда взявшуюся переменную $Book, по контексту — двумерный массив. Кроме того, при отправке формы тоже ведь нужно предусмотреть некоторые действия (а именно,  добавление записи в книгу).
Мы видим, что где-то должен быть скрыт весь этот код. Он, действительно, располагается в отдельном файле с именем gbook.php. Отличительная черта этого файла — то, что в нем нет никакого намека на то, как нужно форматировать результат работы сценария. Именно поэтому я называю его генератором данных (листинг 30.2).
Листинг 30.2. Генератор данных: gbook.php
define("GBook","gbook.dat"); // имя файла с данными гостевой книги
// Загружает гостевую книгу с диска. Возвращает содержание книги.
function LoadBook($fname)
{  $f=@fopen("gbook.dat","rb"); if(!$f) return array();
   $Book=Unserialize(fread($f,100000)); fclose($f);
   return $Book;
}
// Сохраняет содержимое книги на диске.
function SaveBook($fname,$Book)
{  $f=fopen("gbook.dat","wb");
   fwrite($f,Serialize($Book));
   fclose($f);
}
// Исполняемая часть сценария.
// Сначала — загрузка гостевой книги.
$Book=LoadBook(GBook);
// Обработка формы, если сценарий вызван через нее.
// Если сценарий запущен после нажатия кнопки Добавить...
if(!empty($doAdd)) {
  // Добавить в книгу запись пользователя — она у нас хранится
  // в массиве $New, см. форму в шаблоне. Запись добавляется,
  // как водится, в начало книги.
  $Book=array(time()=>$New)+$Book;
  // Записать книгу на диск.
  SaveBook(GBook,$Book);
}
// Все. Теперь у нас в $Book хранится содержимое книги в формате:
// array (
//   время_добавления => array(
//   (или id) name => имя_пользователя,
//            text => текст_пользователя
//            ),
//   . . .
// );
// Вот теперь загружаем шаблон страницы.

include "gbook.htm";

?>

Как видим, исполняемая часть довольно небольшая и, действительно, занимается лишь подготовкой данных для их последующего вывода в шаблоне. Шаблон рассматривается этой составляющей как обычный PHP-файл, который она подключает при помощи инструкции include. Ясно, что весь код шаблона (хотя его и очень мало) выполнится в том же контексте, что и генератор данных, а значит, ему будет доступна переменная $Book.

Логически весь код можно разбить на 3 части. Первая из них — задание конфигурации сценария, в нашем случае она состоит всего лишь в определении одной-единственной константы GBook, хранящей имя файла гостевой книги. Во второй части, которую можно назвать "прикладным интерфейсом" гостевой книги, содержатся описания функций, достаточных для работы с гостевой книгой. Это, конечно, функции загрузки и сохранения наполнения книги на диске. Наконец, третья часть генератора данных обрабатывает запросы пользователей на добавление данных в книгу.

Таким образом, для работы нашего сценария нужны три файла: генератор данных, шаблон книги и файл с записями книги. В принципе, это минимум, если только не привлекать для хранения записей базу данных (что, безусловно, лучше в больших программах). Однако в нашем случае проще как раз работать с файлами, поэтому я на них и остановился.

Генератор данных


Обратите внимание: для того чтобы теперь переделать гостевую книгу так, чтобы она использовала базу данных, а не файл, достаточно изменить всего лишь 2 функции: LoadBook() и SaveBook(). Ни других частей генератора данных, ни, тем более, шаблона это не затронет. На самом деле, такой подход не является случайностью: он очень тесно связан с трехуровневой схемой построения интерактивных сценариев, о которой мы скоро будем говорить.


в переменной QUERY_STRING сохраняется значение

r

Формат: GET сценарий?параметры HTTP/1.0

r Переменные окружения: REQUEST_URI; в переменной QUERY_STRING сохраняется значение параметры, в переменной REQUEST_METHOD — ключевое слово GET.

Этот заголовок является обязательным (если только не применяется метод POST) и определяет адрес запрашиваемого документа на сервере. Также задаются параметры, которые пересылаются сценарию (если сценарию ничего не передается, или же это обычная статическая страница, то все символы после знака вопроса и сам знак опускаются). Вместо строки HTTP/1.0 может быть указан и другой протокол — например, HTTP/1.1. Именно его соглашения и будут учитываться сервером при обработке данных, поступивших от пользователя, и других заголовков.

Строка сценарий?параметры задается в том же самом формате, в котором она входит в URL. Неплохо было бы назвать эту строку как-нибудь более реалистично, чтобы учесть возможность присутствия в ней командных параметров. Такое название действительно существует и звучит как URI (Universal Resource Identifier — Универсальный идентификатор ресурса). Очень часто его смешивают с понятием URL (вплоть до того, что это происходит даже в официальной документации по стандартам HTTP). Давайте договоримся, что в будущем я всегда буду называть словом URL полный

путь к некоторой Web-странице вместе с параметрами, и условимся, что под словом URI будет пониматься его

часть, расположенная после имени (или IP-адреса) хоста и номера порта.


Главный модуль шаблонизатора

Основной код шаблонизатора, который и выполняет всю работу, помещен в библиотеку Template.phl. Она содержит все функции, которые могут потребоваться в шаблонах и блочных страницах. Главная функция модуля— RunUrl() — "запускает" страницу, путь к которой (относительно корневого каталога сервера) передается в параметрах. Результат работы этой функции — содержимое блока Output, порожденного страницей.
В листинге 30.14 приводится полный код шаблонизатора с комментариями.
Листинг 30.14. Модуль шаблонизатора: Template.phl
// Константы, задающие некоторые значения по умолчанию
define("DefGlue"," | ");             // символ склейки по умолчанию
define("Htaccess_Name",".htaccess"); // имя .htaccess-файла
// Имена "стандартных" блоков
define("BlkTemplate","template");   // шаблон страницы
define("BlkOutput","output");       // этот блок выводится в браузер
define("BlkDefGlue","defaultglue"); // символ для "склейки" по умолчанию
// Рабочие переменные
$GLOBALS["BLOCK"]=array();      // массив тел всех блоков
$GLOBALS["BLOCK_INC"]=array();  // аналог $INC библиотекаря
$GLOBALS["CURBLOCK_URL"]=false; // URL текущего обрабатываемого файла
$GLOBALS["bSingleLine"]=0;      // обрабатываемый файл — .htaccess?
// В следующем массиве перечислены имена функций-фильтров,
// которые будут вызваны для каждого блока, когда получено его
// содержимое. Вы, возможно, захотите добавить сюда и другие
// фильтры (например, исполняющие роль простейшего макропроцессора,
// заменяющего одни тэги на другие). Формат функций:
// void FilterFunc(string $BlkName, string &$Value, string $BlkUrl)
$GLOBALS["BLOCKFILTERS"]=array(
  "_FBlkTabs",
  "_FBlkGlue"
  //*** Здесь могут располагаться имена ваших функций-фильтров.
);
// Возвращает тело блока по его имени. Регистр символов не учитывается.

function Blk($name)

{ return @$GLOBALS["BLOCK"][strtolower($name)];

}

// Добавляет указанный URL в список путей поиска. При этом путь

// автоматически преобразуется в абсолютный URL (за текущий каталог

// принимается каталог текущего обрабатываемого файла).

function Inc($url)

{ global $CURBLOCK_URL,$SCRIPT_NAME;

  $CurUrl=$CURBLOCK_URL; if(!$CurUrl) $CurUrl=$SCRIPT_NAME;

  if($url[0]!="/") $url=abs_path($url,dirname($CurUrl));

  $GLOBALS["BLOCK_INC"][]=$url;

}

// Устанавливает имя текущего блока и, возможно, его значение.

// Все данные, выведенные после вызова этой функции, будут принадлежать

// телу блока $name. Если задан параметр $value, тело сразу

// устанавливается равным $value, а весь вывод просто проигноруется.

// Это удобно для коротких однострочных блоков, особенно расположенных

// в файлах .htaccess. Из того, что было выведено программой в

// стандартный поток, будут удалены начальные и концевые пробелы,

// а также вызовутся все функции-фильтры. Окончанием вывода,

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

// другого блока (то есть еще один вызов Block()).

function Block($name=false, $value=false)

{ global $BLOCK,$bSingleLine,$CURBLOCK_URL;

  // Объявляем некоторые флаги состояния

  static $Handled=false;  // в прошлый раз вывод был перехвачен

  static $CurBlock=false; // имя текущего обрабатываемого блока

  // Если имя блока задано, перевести его в нижний регистр

  if($name!==false) $name=strtolower(trim($name));

  // Вывод был перехвачен. Значит, что до этого вызова уже

  // была запущена функция Block(). Теперь блок, который

  // она обрабатывала, закончился, и его надо добавить в массив

  // блоков (или же проигнорировать этот вывод).

  if($Handled) {

    // Имя предыдущего блока было задано?

    if($CurBlock!==false) {

      // Добавляем в массив блоков.

      $BLOCK[$CurBlock]=trim(ob_get_contents());

      // Если блок однострочный (из файла .htaccess), то


      // удаляем все строки, кроме первой.

      if(@$bSingleLine)

        $BLOCK[$CurBlock]=ereg_Replace("[\r\n].*","",$BLOCK[$CurBlock]);

      // Запускаем фильтры

      _ProcessContent($CurBlock,$BLOCK[$CurBlock],$CURBLOCK_URL);

    }

    // Завершаем перехват потока вывода

    ob_end_clean();  $Handled=0;

  }

  // Если имя блока задано (а это происходит практически всегда),

  // значит, функция была вызвана нормальным образом, а не только для

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

  if($name!==false) {

    // Перехватываем поток вывода

    ob_start(); $Handled=1;

    // Тело явно не задано, значит, нужно его получить путем

    // перехвата выходного потока. Фактически, сейчас мы просто

    // говорим системе, что текущий блок — $name, и что как только

    // она встретит другой блок или конец файла, следует принять

    // выведенные данные и записать их в массив.

    if($value===false) {

      $CurBlock=$name;

    } else {

      // Тело задано явно. Записать блок в массив, но все равно

      // перехватить выходной поток (чтобы потом его проигнорировать).

      _ProcessContent($name,$value,$CURBLOCK_URL);

      $BLOCK[$name]=$value;

      $CurBlock=false;

    }

  }

}

// Загружает файл с URL $name и добавляет блоки, которые в нем

// находились, к списку существующих блоков. Параметр $name может

// задавать относительный URL, в этом случае производится его

// поиск в глобальном массиве $INC (том же самом, который использует

// библиотекарь). Если в качестве $name задано не имя файла, а имя

// каталога, то анализируется файл .htaccess, расположенный

// в этом каталоге. На момент загрузки файла текущий каталог

// изменяется на тот, в котором расположен файл.

function Load($name)

{ global $BLOCK,$bSingleLine,$CURBLOCK_URL,$BLOCK_INC;

  // Перевести все пути в $INC в абсолютные

  AbsolutizeINC();

  // Если путь относительный, ищем по $BLOCK_INC

  $fname=false;


  if($name[0]!='/') {

    // Перебираем все каталоги включения

    foreach($BLOCK_INC as $v) {

      $fname=Url2Path("$v/$name"); // Определяем имя файла

      if(file_exists($fname)) { $name="$v/$name"; break; }

    }

    // Если не нашли, $fname остается равной false

  } else {

    // Абсолютный URL — перевести его в имя файла

    $fname=Url2Path($name);

  }

  // Обрабатываем файл, имя которого вычислено по URL.

  // Сначала проверяем, существует ли такой файл.

  if($fname===false || !file_exists($fname))

    die("Couldn't open \"$name\"!");

  // Это каталог — значит, используем .htaccess

  $Single=false;

  if(@is_dir($fname)) {

    $name.="/".Htaccess_Name;

    $fname.="/".Htaccess_Name;

    $Single=1;

  }

  // Если файла до сих пор не существует (это может случиться, когда

  // мы предписали использовать .htaccess, а его в каталоге нет),

  // "мирно" выходим. Ничего страшного, если в каталоге нет .htaccess'а.

  if(!file_exists($fname)) return;

  // Запускаем файл. Для этого сначала запоминаем текущее состояние

  // и каталог, затем загружаем блоки файла (просто выполняем файл),

  // а в конце восстанавливаем состояние.

  $PrevSingle=$bSingleLine; $bSingleLine=@$Single;

  $SaveDir=getcwd(); chdir(dirname($fname));

  $SaveCBU=$CURBLOCK_URL; $CURBLOCK_URL=$name;

    // Возможно, в файле присутствуют начальные пробелы или другие

    // нежелательные символы (например, в .htaccess это может

    // быть знак комментария). Все они включатся в блок с

    // именем _PreBlockText (его вряд ли целесообразно использовать).

    Block("_PreBlockText");

    // Делаем доступными все глобальные переменные.

    foreach($GLOBALS as $k=>$v) if(!@Isset($$k)) global $$k;

    // Запускаем файл.

    include $fname;

    // Сигнализируем, что блоки закончились (достигнут конец файла).

    // При этом чаще всего будет осуществлена запись данных последнего


    // блока файла в массив.

    Block();

  chdir($SaveDir);

  $CURBLOCK_URL=$SaveCBU;

  $bSingleLine=$PrevSingle;

}

// Главная функция шаблонизатора. Обрабатывает указанный файл $url

// и возвращает тело блока Output. В выходной поток ничего не печатается

// (за исключением предупреждений, если они возникли).

function RunUrl($url)

{ global $BLOCK;

  // Собираем все блоки.

  _CollectBlocks($url);

  // Находим и запускаем главный шаблон. Мы делаем это в последнюю

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

  // страница. Шаблон — обычный блочный файл. В нем обязательно должен

  // присутствовать блок Output.

  $tmpl=@$BLOCK[BlkTemplate];

  if(!$tmpl) {

    die("Cannot find the template for $url ".

      "(have you defined ".BlkTemplate." block?)");

  }

  Load($tmpl);

  // Возвращаем блок Output.

  if(!isSet($BLOCK[BlkOutput])) {

    die("No output from template $tmpl ".

      "(have you defined ".BlkOutput." block?)");

  }

  return $BLOCK[BlkOutput];

}

// Эта функция предназначена для внутреннего использования. Она собирает

// блоки из файла, соответствующего указанному $url, в том числе и блоки

// из всех .htaccess-файлов "надкаталогов".

function _CollectBlocks($url)

{ global $BLOCK;

  $url=abs_path($url,dirname($GLOBALS["SCRIPT_NAME"]));

  // Если путь — не /, то обратиться к "надкаталогу".

  if(strlen($url)>1) _CollectBlocks(dirname($url));

  // Загрузить блоки самого файла.

  Load($url);

}

// Запускает все фильтры для блока.

function _ProcessContent($name,&$cont,$url)

{ foreach($GLOBALS["BLOCKFILTERS"] as $F)

    $F($name,$cont,$url);

}

// "Склеивание" блоков.

// Если тело блока начинается с [name], то оно не просто

// записывается в массив блоков, а "пристыковывается" к значению,


// уже там находящемуся, причем в качестве символа-соединителя

// выступает тело блока с именем name. Если строка name не задана

// (то есть указаны []), используется блок с именем DefaultGlue,

// а если этого блока нет, то соединитель по умолчанию — " | ".

function _FBlkGlue($name,&$cont,$url)

{ global $BLOCK;

  if(ereg("^\\[([^]])*]",$cont,$P)) {

    $c=substr($cont,strlen($P[0])); // тело блока после [name]

    $n=$P[1];                       // имя соединителя

    // Есть с чем "склеивать"?

    if(!empty($BLOCK[$name])) {

      $glue=@$BLOCK[$n];

      if(!Isset($glue)) $glue=@$BLOCK[BlkDefGlue];

      if(!Isset($glue)) $glue=DefGlue;

      $cont=$BLOCK[$name].$glue.$c;

    }

    // "Склеивать" нечего — просто присваиваем.

    else $cont=$c;

  }

}

// Удаление начальных символов табуляции из тела блока.

// Теперь можно выравнивать HTML-код в документах с помощью табуляции.

// Это оказывается чрезвычайно удобным, если мы используем тэги,

// например, в таком контексте:

// < ?foreach($Book as $k=>$v) {? >

//   

//      < ?=$Book['name']? >

//      < ?=$Book['text']? >

//   

// < ?}? >

function _FBlkTabs($name,&$cont,$url)

{ // используем регулярное выражение в формате PCRE, т. к. это —

  // единственный приемлемый способ решения задачи

  $cont=preg_replace("/^\t+/m","",$cont);

}

?>


Глобальные переменные

Если вы, прочитав последние строки, уже начали испытывать сочувствие к функциям в PHP (или, если вы прикладной программист, сочувствие к разработчикам PHP), то спешу вас заверить: разумеется, в PHP есть способ, посредством которого функции могут добраться и до любой глобальной переменной в программе (не считая, конечно, передачи параметра по ссылке). Однако для этого они должны проделать определенные действия, а именно: до первого использования в своем теле внешней переменной объявить ее "глобальной" (листинг 11.10):
Листинг 11.10. Использование global
function Silly()
{ global $i;
  $i=rand();
  echo $i;
}
for($i=0; $i!=10; $i++) Silly();
Вот теперь-то переменная $i будет везде едина: что в функции, что во внешнем цикле (для последнего это приведет к немедленному его "зацикливанию", во всяком случае, на ближайшие несколько минут, пока rand() не выкинет 10). А вот еще один пример, который показывает удобство использования глобальных переменных внутри функции (листинг 11.11):
Листинг 11.11. Пример функции
$Monthes[1]="ßíâàðü";
$Monthes[1]="Февраль";
... è ò. ä.
$Monthes[12]="Äåêàáðü";
. . .
// Âîçâðàùàåò íàçâàíèå ìåñÿöà ïî åãî íîìåðó. Íóìåðàöèÿ íà÷èíàåòñÿ ñ 1! function GetMonthName($n)
{ global $Monthes;
  return $Monthes[$n];
}
. . .
echo GetMonthName(2); // выводит "Февраль"
Согласитесь, массив $Monthes, содержащий названия месяцев, довольно объемист. Поэтому описывать его прямо в функции было бы, мягко говоря, неудобно. В то же время функция GetMonthName() представляет собой довольно преемлемое средство для приведения номера месяца к его словесному эквиваленту (что может потребоваться во многих программах). Она имеет единственный и понятный параметр: это номер месяца. Как бы мы это сделали без глобальных переменных?



Графические примитивы

Здесь мы рассмотрим минимальный набор функций для работы с картинками. Приведенный список функций не полон и постоянно расширяется вместе с развитием GD. Но все же он содержит те функции, которые вы будете употреблять в 99% случаев. За полным списком функций обращайтесь к документации или на http://ru.php.net.



Григорианский[E79] календарь

Григорианский календарь — это как раз тот самый календарь, который мы постоянно используем в своей жизни. В России он был введен Петром I в 1700 году.
Описываемые далее три функции представляют большой интерес, если вам понадобится автоматически формировать календари в сценариях. Все они имеют дело с так называемым Julian Day Count (JDC). Что это такое?
Каждой дате соответствует свой JDC. Ведь, фактически, JDC — это всего лишь число дней, прошедших с определенной даты (кажется, где-то с 4714-го года до нашей эры[E80] ).
Зачем это нужно? Например, нам заданы две даты в формате "дд.мм.гггг". Нужно вычислить количество дней между этими датами. Поставленная задача как раз легко решается через перевод обеих дат в JDC и определение разности получившихся величин.
int GregorianToJD(int $month, int $day, int $year)
Преобразует дату в формат JDC. Допустимые значения года для григорианского календаря — от 4714 года до нашей эры до 9999 года нашей эры.
string JDToGregorian(int $julianday)
Преобразует дату в формате JDC в строку, выглядящую как месяц/число/год. Наверняка затем вы захотите разбить эту строку на составляющие, чтобы работать с ними по отдельности. Для этого воспользуйтесь функцией explode():
$jd = GregorianToJD(10,11,1970);
echo "$jd
\n";
$gregorian = JDToGregorian($jd);
echo "$gregorian
\n";
$list=explode($gregorian,"/");
mixed JDDayOfWeek(int $julianday, int $mode)
Последняя функция этой серии — JDDayOfWeek() — тоже совершенно незаменима: она возвращает день недели, на который приходится указанная JDC-дата. Фактически, это единственное, чего нам не хватало бы для формирования календарика. Параметр $mode задает, в каком виде должен быть возвращен результат:
r 0 — номер дня недели (0 — воскресенье, 1 — понедельник, и т. д.);
r 1 — английское название дня недели;
r 2 — сокращение английского названия дня недели.
В PHP существует еще множество функций для работы с другими календарями — в том числе с Республиканским, Юлианским и т. д. Объем книги не позволяет привести здесь их описания.



Группирующие скобки

Последний пример наводит на рассуждения о том, нельзя ли как-нибудь сгруппировать отдельные символы, чтобы не писать по несколько раз одно и то же. В нашем примере строка abc встречается в выражении аж 3 раза. Но мы не можем написать выражение так: abc1|22|333, потому что оператор |, естественно, пытается применить себя к как можно более длинной последовательности команд.
Именно для цели управления оператором альтернативы (но не только) и служат группирующие круглые скобки (...). Нетрудно догадаться по смыслу, что выражение из последнего примера можно записать с их помощью так: abc(1|22|333).
Конечно, скобки могут иметь произвольный уровень вложенности. Это бывает полезно для сложных выражений, содержащих много условий, а также для еще одного применения круглых скобок, которое мы сейчас и рассмотрим.



Группы символов

Было бы глупо, если бы RegEx позволял нам задавать части искомых строк только непосредственно, как это было рассмотрено выше. Поэтому существуют несколько спецсимволов, обозначающих сразу группу букв. Эта возможность— один из краеугольных камней, основ регулярных выражений. Самый важный из таких знаков — точка "." — обозначает один любой символ. Например, выражение a.b имеет совпадение для строк azb или aqb, но не "срабатывает"
для, скажем, aqwb èëè ab. Позже мы рассмотрим, как можно заставить точку обозначать ровно один (или, к примеру, ровно пять) любых символов.
Но это далеко не все. Возможно, вы захотите искать не любой символ, а один из нескольких указанных. Для этого наши символы нужно заключить в квадратные скобки. К примеру, выражение a[xXyY]c соответствует строкам, в которых есть подстроки из трех символов, начинающиеся с а, затем одна из букв x, X, y, Y и, наконец, буква c. Если нужно вставить внутрь квадратных скобок символ [ или ], то следует просто поставить перед ним обратный слэш (напоминаю, в строках PHP — два
слэша), чтобы отменить его специальное действие.
Если букв-альтернатив много, и они идут подряд, то не обязательно перечислять их все внутри квадратных скобок — достаточно указать первую из них, потом поставить дефис и затем — последнюю. Такие группы могут повторяться. Например, выражение [a-z] обозначает любую букву от a до z включительно, а выражение [a-zA-Z0-9_] задает любой алфавитно-цифровой символ.
Существует и другой, иногда более удобный способ задания больших групп символов. В языке RegEx в скобках [ и ] могут встречаться не только одиночные символы, но и специальные выражения. Эти выражения определяют сразу группу символов. Например, [:alnum:]
задает любую букву или цифру, а [:digit:] — цифру. Вот полный список таких выражений:
r [:alpha:] — буква;
r [:digit:] — цифра;
r [:alnum:] — буква или цифра;

r [:space:] — пробельный символ;

r [:blank:] — пробельный символ или символы с кодом 0 и 255;

r [:cnrtl:] — управляющий символ;

r [:graph:] — символ псевдографики;

r [:lower:] — символ нижнего регистра;

r [:upper:] — символ верхнего регистра;

r [:print:] — печатаемый символ;

r [:punct:] — знак пунктуации;

r [:xdigit:] — цифра или буква от A до Z.

Как видим, все эти выражения задаются в одном и том же виде — [:что_то:]. Хочу еще раз обратить ваше внимание на то, что они могут встречаться только внутри квадратных скобок. Например, допустимы такие регулярные выражения:

abc[[:alnum:]]+          // abc, затем îäíà èëè áîëåå áóêâà èëè öèôðà

abc[[:alpha:][:punct]0]  // abc, далее буква, знак пунктуации или 0

но совершенно недопустимы следующее:

abc[:alnum:]+            // íå ðàáîòàåò!

Еще одно привлекательное свойство выражений [:что_то:]

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

с верными параметрами). Таким образом, выражение [[:alpha:]]+ удовлетворяет любому слову как на английском, так и на русском языке. Добиться этого при помощи "обычного" использования [...]

было бы очень тяжело.


Характеристика языка PHP

Дочитав до этого места, вы уже должны проникнуться мыслью, что писать сценарии на Си, мягко говоря, неудобно. (Если подобного ощущения у вас нет, значит, я плохо написал первую часть и ее придется переделывать…).
Так на чем же писать? Многие тут же ответят: "Конечно, на том, на чем обычно пишут сценарии — на Perl!". Да, это распространенная точка зрения. Однако у Perl, наряду с его неоспоримыми достоинствами, существуют и недостатки. Причем недостатки весьма серьезные. Вот один из них: Perl не приспособлен непосредственно для программирования сценариев. Это в некотором роде универсальный язык, поэтому он не поддерживает напрямую того, чего бы нам хотелось. А вот и второй: у Perl синтаксис не способствует читабельности программы. Он не похож ни на Си, ни на Паскаль (а эти языки замечательно зарекомендовали себя как самодокументирующиеся[В. О.5] ). Вообще, я сам принадлежу к той категории людей, которые очень болезненно воспринимают непродуманный синтаксис языка программирования, отсюда и мое отношение к Perl...
PHP — язык, специально нацеленный на работу в Интернете, язык с универсальным (правда, за некоторыми оговорками) и ясным синтаксисом, удивительно похожим на Си, сочетающий достоинства Perl и Си. И хотя этот язык еще довольно молодой, он (точнее, его интерпретатор) установлен уже на порядка миллиона серверов по всему миру, и цифра продолжает расти. Новое поколение PHP — четвертое — должно вообще стереть все преимущества Perl перед PHP, как с точки зрения быстродействия обработки программ (а третья версия PHP сильно отставала от Perl при обработке больших циклов), так и с точки зрения синтаксиса. Наконец, большинство PHP-сценариев (особенно не очень больших размеров) работают быстрее аналогичных им программ, написанных на Perl (конечно, если сравнивать с обычными Perl-сценариями, а не программами, запускаемыми под управлением mod_perl).
Думаю, у PHP есть лишь один серьезный недостаток, который менее выражен у Perl: это — его медлительность при работе с большими и сложными сценариями. Однако работы по преодолению этой трудности давно ведутся и, если верить разработчикам PHP, версия 4 является уже компилятором, построенным примерно на том же принципе, что и компилятор Perl. Давайте поговорим на последнюю тему чуть подробнее.



Here-документ

В четвертой версии PHP появился и еще один способ записи строковых констант, который исторически называется here-документом (встроенный документ). Фактически он представляет собой альтернативу для записи многострочных констант. Выглядит это примерно так:
$a=<< Äàëåå èäåò êàêîé-òî òåêñò,
âîçìîæíî, ñ ïåðåìåííûìè, êîòîðûå èíòåðïîëèðóþòñÿ:
íàïðèìåð, $name áóäåò èíòåðïîëèðîâàíà çäåñü.
MARKER;
Строка MARKER может быть любым алфавитно-цифровым идентификатором, не встречающимся в тексте here-документа в виде отдельной строки. Синтаксис накладывает 2 ограничения на here-документы:
r после << r завершающая строка MARKER; должна оканчиваться точкой с запятой, после которой до конца строки не должно быть никаких инструкций.
Эти ограничения настолько стесняют свободу при использовании here-документов, так что, думаю, вам стоит совсем от них отказаться. Например, следующий код работать не будет, как бы нам этого ни хотелось (функция strip_tags() удаляет тэги из строки):
echo strip_tags(<< Êàêîé-òî òåêñò ñ òýãàìè [В. О.32] — ýòîò ïðèìåð ÍÅ ðàáîòàåò!
EOD;
Надеюсь, в будущем разработчики PHP изменят ситуацию к лучшему, но пока они этого не сделали.



Хэш-функции

string md5(string $st)
Возвращает хэш-код строки $st, основанный на алгоритме корпорации RSA Data Security под названием "MD5 Message-Digest Algorithm". Хэш-код — это просто строка, практически уникальная [E57] для каждой из строк $st. То есть вероятность того, что две разные
строки, переданные в $st, дадут нам одинаковый хэш-код, стремится к нулю.
Хэш-функции

Я где-то читал об одном опыте, в котором принимали участие более 1000 мощных компьютеров, на протяжении года генерировавшие хэш-коды для строк, и за все время не было обнаружено ни одного совпадения MD5-кодов для различных строк. Более того, математически доказано, что они могли бы с тем же результатом заниматься этим на протяжении еще нескольких тысяч лет.
В то же время, если длина строки $st может достигать нескольких тысяч символов, то ее MD5-код занимает максимум 32 символа.
Для чего нужен хэш-код и, в частности, алгоритм MD5? Например, для проверки паролей на истинность. Пусть, к примеру, у нас есть система со многими пользователями, каждый из которых имеет свой пароль. Можно, конечно, хранить все эти пароли в обычном виде, или зашифровать их каким-нибудь способом, но тогда велика вероятность того, что в один прекрасный день этот файл с паролями у вас украдут. Если пароли были зашифрованы, то, зная метод шифрования, не составит особого труда их раскодировать. Однако можно поступить другим способом, при использовании которого даже если файл с паролями украдут, расшифровать его будет математически невозможно. Сделаем так: в файле паролей будем хранить не сами пароли, а их (MD5) хэш-коды. При попытке какого-либо пользователя войти в систему мы вычислим хэш-код только что введенного им пароля и сравним его с тем, который записан у нас в базе данных. Если коды совпадут, значит, все в порядке, а если нет — что ж, извините...
Конечно, при вычислении хэш-кода какая-то часть информации о строке $st
безвозвратно теряется. И именно это позволяет нам не опасаться, что злоумышленник, получивший файл паролей, сможет его когда-нибудь расшифровать. Ведь в нем нет самих паролей, нет даже их каких-то связных частей!

Алгоритм MD5 специально был изобретен для того, чтобы как раз и обеспечить описанную выше схему. Так как все же есть вероятность того, что у разных строк MD5-коды совпадут, то, чтобы не дать возможность злоумышленнику войти в систему, перебирая пароли с бешеной скоростью, алгоритм MD5 работает довольно медленно. И его нельзя никак убыстрить, потому что это будет уже не MD5. Так что даже на самых мощных компьютерах вряд ли получится перебирать более нескольких тысяч паролей в секунду, а это совсем маленькая скорость, капля в океане возможных MD5-кодов.

int crc32(string $str)

Функция crc32() вычисляет 32-битную контрольную сумму строки $str. То есть, результат ее работы — 32-битное (4-байтовое) целое число. Эта функция работает гораздо быстрее md5(), но в то же время выдает гораздо менее надежные "хэш-коды"

для строки. Так что, теперь, чтобы получить методом случайного подбора для двух разных строк одинаковые "хэш-коды", вам потребуется не триллион лет работы самого мощного компьютера, а всего лишь… год-другой. Впрочем, если не использовать генератор случайных чисел, а разобраться в алгоритме вычисления 32-битной контрольной суммы, эту же задачу легко можно решить буквально за секунду, потому что алгоритм crc32 имеет неизмеримо большую предсказуемость, чем MD5.

string crypt(string $str [,string $salt])

Алгоритм шифрования DES до недавнего времени был стандартным для всех версий Unix и использовался как раз для кодирования паролей пользователей (тем же самым способом, о котором мы говорили при рассмотрении функции md5()).

Но в последнее время MD5 постепенно начал его вытеснять. Это и понятно: MD5 гораздо более надежен. Рекомендую и вам везде применять md5() вместо

crypt(). Впрочем, функция crypt() все же может понадобиться вам в одном случае: если вы хотите сгенерировать хэш-код для другой программы, которая использует именно алгоритм DES (например, для сервера Apache).

Õэш-код для одной и той же строки, но с различными значениями $salt (кстати, это должна быть обязательно двухсимвольная строка) дает разные результаты. Если параметр $salt пропущен, PHP сгенерирует его случайным образом, так что не удивляйтесь работе следующего примера:

$st="This is the test";

echo crypt($st)."
";  // можем получить, íàïðèìåð, 7N8JKLKbBWEhg

echo crypt($st)."
";  // а здесь появится, íàïðèìåð, Jsk746pawBOA2

Как видите, два одинаковых вызова crypt()

без второго параметра выдают совершенно разные хэш-коды. За деталями работы функции обращайтесь к документации PHP.


Хост

Хост— с точки зрения пользователя как будто то же, что и узел. В общем-то, эти понятия очень часто смешивают. Это обусловлено тем, что любой узел является хостом. Но хост — совсем не обязательно отдельный узел, если это — виртуальный хост. Часто хост имеет собственное уникальное доменное имя. Иногда (обычно просто чтобы не повторяться) я буду называть хосты серверами, что, вообще говоря, совершенно не верно. Фактически, все, что отличает хост от узла — это то, что он может быть виртуальным. Итак, еще раз: любой узел — хост, но не любой хост — узел, и именно так я буду понимать хост в этой книге.



Хостинг-провайдер (хостер)

Организация, которая может создавать хосты (виртуальные или обычные) в Интернете и продавать их различным клиентам, обычно за определенную плату. Существует множество хостинг-провайдеров, различающихся по цене, уровню обслуживания, поддержке telnet-доступа (то есть доступа в режиме терминала к операционной системе машины) и т.д. Они могут оказывать услуги по регистрации доменного имени в Интернете, а могут и не оказывать. При написании этой книги я рассчитывал, что читатель собирается воспользоваться услугами такого хостинг-провайдера, который предоставляет возможность использования PHP (их сейчас большинство). Если вы еще не выбрали хостинг-провайдера и только начинаете осваивать Web-программирование, не беда: во второй части книги подробно рассказано, как можно установить и настроить собственный Web-сервер на любом компьютере с установленной операционной системой Windows. (Это можно сделать даже на той самой машине, на которой будет работать браузер — ведь драйверу протокола TCP совершенно безразлично, где выполняется процесс, к которому будет осуществлено подключение, хоть даже и на том же самом компьютере.) Используя этот сервер, вы сможете немного потренироваться. Кроме того, он незаменим при отладке тех программ, которые вы в будущем планируете разместить на настоящем хосте в Интернете.



Хостинг

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



HTTP_ACCEPT

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



HTTP_HOST

Доменное имя Web-сервера, на котором запустился сценарий. Эту переменную окружения довольно удобно использовать, например, для генерации полного пути, который требуется в заголовке Location, чтобы не привязываться к конкретному серверу (вообще говоря, чем меньше сценарий задействует "зашитую" в него информацию об имени сервера, на котором он запущен, тем лучше — в идеале ее не должно быть вовсе).



HTTP_REFERER

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



HTTP_USER_AGENT

Идентифицирует браузер пользователя. Если в данной переменной окружения присутствует подстрока MSIE, то это— Internet Explorer, в противном случае, если в наличии лишь слово Mozilla, — Netscape.



Идентификатор сессии и имя группы

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



Идентификатор сессии

Мы уже говорили с вами, зачем нужен идентификатор сессии (SID). Фактически, он является именем временного хранилища, которое будет использовано для хранения данных сессии между запусками сценария. Итак, один SID — одно хранилище. Нет SID, нет и хранилища, и наоборот.
В этом месте очень легко запутаться. В самом деле, как же соотносится идентификатор сессии и имя группы? А вот как: имя — это всего лишь собирательное название для нескольких сессий (то есть, для многих SID), запущенных разными пользователями. Один и тот же клиент никогда
не будет иметь два различных SID
в пределах одного имени группы. Но его браузер вполне может работать (и часто работает) с несколькими SID, расположенными логически в разных "пространствах имен".
Итак, все SID уникальны и однозначно определяют сессию на компьютере, выполняющем сценарий — независимо от имени сессии. Имя же задает "пространство имен", в которое будут сгруппированы сессии, запущенные разными пользователями. Один клиент может иметь сразу несколько активных пространств имен (то есть несколько имен групп сессий).
string session_id([string $sid])
Функция возвращает текущий идентификатор сессии SID. Если задан параметр $sid, то у активной сессии изменяется идентификатор на $sid. Делать это, вообще говоря, не рекомендуется.
Фактически, вызвав session_id() до session_start(), мы можем подключиться к любой (в том числе и к "чужой") сессии на сервере, если знаем ее идентификатор. Мы можем также создать сессию с угодным нам идентификатором, при этом автоматически установив его в Cookies пользователя. Но это — не лучшее решение,
— предпочтительнее переложить всю "грязную работу"
на PHP.



Идеология

Большинство сценариев пишутся на различных языках программирования без всякого отделения кода от шаблона страницы. Зачем же тогда нам это нужно? Что заставляет нас искать новые пути в Web-программировании?
Причина всего одна. Это— желание поручить разработку качественного и сложного сценария сразу нескольким людям, чтобы каждый из них занимался своим делом, которое, как предполагается, он знает лучше всего. Одна группа людей (назовем ее "программисты") занимается тем, что касается взаимодействия программы с пользователем и обработки данных. Другая же группа (для простоты я буду говорить о ней как о "дизайнерах"), наоборот, отвечает лишь за эстетическую часть работы. Разумеется, программисты и дизайнеры — не единственные категории, которые нужно сформировать при создании крупных сайтов. Безусловно, требуется еще одно лицо, которое бы "связывало" и координировало их между собой. Им может быть человек, не имеющий выдающихся достижений ни в Web-дизайне, ни в Web-программировании, но в то же время наделенный хорошей интуицией и знаниями. Если этого человека нет, кому-то все равно придется выполнять его работу (например, одному из программистов), что, конечно же, будет немного противоречить желаниям последнего. В результате работа над проектом затянется и, возможно, "обрастет" излишними сложностями технического характера.
Идеология

Я убежден, что нельзя быть одновременно хорошим программистом и выдающимся дизайнером в указанном только что понимании. Эти две профессии взаимоисключают друг друга, поскольку требуют совершенно разных складов мышления. Если у вас нет раздвоения личности, вы без труда определите для себя, к какой категории людей принадлежите сами.
Зачем нам вообще понадобилось распределять разработку Web-сценариев по нескольким направлениям? Отвечаю последовательно. Во-первых, так создаются гораздо более качественные программы и Web-страницы. Во-вторых, сроки выполнения работы значительно сокращаются за счет организации параллельного выполнения задания. Если вас это все равно не убедило, вспомните о том, что именно так организуются практически все крупные Web-студии по всему миру.

Что же получается, если в своих сценариях вы будете смешивать код и оформление сценария? Фактически, его поддержкой и доработкой не сможет заняться никто, кроме вас самого. В самом деле: программиста будет раздражать постоянно встречающиеся вставки HTML-кода, а дизайнера — опасность случайно изменить какую-нибудь важную функцию программы. Иными словами, такой метод (да и можно ли назвать его методом?) совершенно не подходит при разработке мало-мальски крупных проектов.

Идеология


С горечью отмечаю, что разработчики PHP практически не приблизили нас к решению проблемы отделения кода от шаблона страницы. Создается впечатление, что они преследовали как раз противоположные цели: максимально упростить совмещение HTML и PHP за счет снижения функциональности последнего. Когда мы будем разбирать код шаблонизатора ниже в этой главе, вы увидите, на какие "увертки" нам придется пойти, чтобы обойти все "подводные камни", невольно расставленные для нас авторами PHP.


Имя группы сессий

Что, не совсем понятно? Хорошо, тогда рассмотрим пример. Пусть разработчик A написал сценарий счетчика, приведенный в листинге 25.1. Он использует переменную $count, и не имеет никаких проблем. До тех пор, пока разработчик B, ничего не знающий о сценарии A, не создал систему статистики, которая тоже использует сессии. Самое ужасное, что он также регистрирует переменную $count, не зная о том, что она уже "занята". В результате, как всегда, страдает пользователь: запустив сначала сценарий разработчика B, а потом— A, он видит, что данные счетчиков перемешались. Непорядок!
Нам нужно как-то разграничить сессии, принадлежащие одному сценарию, от сессий, принадлежащих другому. К счастью, разработчики PHP предусмотрели такое положение вещей. Мы можем давать группам сессий непересекающиеся имена, и сценарий, знающий имя своей группы сессии, сможет получить к ней доступ. Вот теперь-то разработчики A и B могут оградить свои сценарии от проблем с пересечениями имен переменных. Достаточно в первой программе указать PHP, что мы хотим использовать группу с именем, скажем, sesA, а во второй — sesB.
string session_name([string $newname])
Эта функция устанавливает или возвращает имя группы сессии, которая будет использоваться PHP для хранения зарегистрированных переменных. Если $newname
не задан, то возвращается текущее имя. Если же этот параметр указан, то имя группы будет изменено на $newname, при этом функция вернет предыдущее имя.
Имя группы сессий

Session_name() лишь сменяет имя текущей группы и сессии, но не создает новую сессию и временное хранилище! Это значит, что мы должны в большинстве случаев вызывать session_name(имя_группы) еще до ее инициализации — вызова session_start(), в противном случае мы получим совсем не то, что ожидали.
Если функция session_name()
не была вызвана до инициализации, PHP будет использовать имя по умолчанию — PHPSESID.
Имя группы сессий

Кстати говоря, имя группы сессий, устанавливаемое рассматриваемой функцией, — это как раз имя того самого Cookie, который посылается в браузер клиента для его идентификации. Таким образом, пользователь может одновременно активизировать две и более сессий — с точки зрения PHP он будет выглядеть как два ли более различных пользователя. Однако не забывайте, что, случайно установив в сценарии Cookie, имя которого совпадает с одним из имен группы сессий, вы "затрете" Cookie.
Вот простой пример применения этой функции.
session_name("CounterScript"
session_start();
session_register("count");
$count=@$count+1;
?>
В текущей сессии Вы открыли эту страницу раз(а).
Рекомендую всегда указывать имя группы сессии вручную, не полагаясь на значение по умолчанию. За это вам скажут спасибо разработчики других сценариев, когда они захотят использовать вашу программу вместе со своими.



Имя хоста

Следом за протоколом идет имя узла, на котором размещается запрашиваемая страница (в нашем примере— www.somehost.com). Это может быть не только доменное имя хоста, но и его IP-адрес. В последнем случае, как нетрудно заметить, мы сможем обращаться только к узлам (невиртуальным хостам), потому что лишь они однозначно идентифицируются указанием их IP-адреса.



Имя и расширение файла

Задача: для имени файла в $fname установить расширение out независимо от его предыдущего расширения.
Решение:
$fname=ereg_Replace(
  '([[:alnum:]])(\\.[[:alnum:].]*)?$',
  '\\1.out',
  $fname
);
Обратите внимание на довольно интересную структуру этого выражения: мы не можем просто "привязать"
его к концу строки при помощи $, что обусловлено спецификой работы RegEx. Мы также привязываем начало выражения к любой букве или цифре, которой оканчивается имя файла.



Имя каталога и файла

Цель: разбить полное имя файла $path на имя каталога $dir и и имя файла $fname.
Средства:
$fname = ereg_Replace(".*[\\/]","",$path);
$dir   = ereg_Replace("[\\/]?[^\\/]*$","",$path);



Информационные функции

Прежде всего давайте познакомимся с двумя функциями, одна из которых выводит текущее состояние всех параметров PHP, а вторая — версию интерпретатора.
int phpinfo()
Эта функция, которая в общем-то не должна появляться в законченной программе,  выводит в браузер[DK117] большое количество различной информации, касающейся настроек PHP и параметров вызова сценария. Именно, в стандартный выходной поток (то есть в браузер пользователя) печатается:
r версия PHP;
r опции, которые были установлены при компиляции PHP;
r информация о дополнительных модулях;
r переменные окружения, в том числе и установленные сервером при получении запроса от пользователя на вызов сценария;
r версия операционной системы;
r состояние основных и локальных настроек интерпретатора;
r HTTP-заголовки;
r лицензия PHP.
Как видим, вывод довольно объемист. Воочию в этом можно убедиться, запустив такой сценарий:
phpinfo();
?>
Надо заметить, что функция phpinfo() в основном применяется при первоначальной установке PHP для проверки его работоспособности. Думаю, для других целей использовать ее вряд ли целесообразно — слишком уж много информации она выдает.
string phpversion()
Функция phpversion(), пожалуй, могла бы по праву занять первое место на соревнованиях простых функций, потому что все, что она делает — возвращает текущую версию PHP.[DK118]
int getlastmod()
Завершающая функция этой серии — getlastmod() — возвращает время последнего изменения файла, содержащего сценарий. Она не так полезна, как это может показаться на первый взгляд, потому что учитывает время изменения только главного файла, того, который запущен сервером, но не файлов, которые включаются в него директивами require или include. Время возвращается в формате timestamp (то есть, это число секунд, прошедших с 1 января 1970 года до момента модификации файла), и оно может быть затем преобразовано в читаемую форму, например:
echo "Ïîñëåäíåå èçìåíåíèå: ".date("d.m.Y H:i.s.", getlastmod());
// Âûâîäèò ÷òî-òî âðîäå 'Ïîñëåäíåå èçìåíåíèå: 13.11.2000 11:23.12'



Инициализация объекта. Конструкторы

До сих пор мы не особенно задумывались, каким образом были созданы объекты $Obj1 и $Obj2 и к какой таблице они прикреплены. Однако вполне очевидно, что эти объекты не должны существовать сами по себе — это просто не имеет смысла. Поэтому нам, наравне с уже описанными методами, придется написать еще один — а именно, метод, который бы:
r "привязывал"
только что созданный объект-таблицу к таблице в MySQL;
r сбрасывал индикатор ошибок;
r заполнял свойство Fields;
r делал другую работу по инициализации объекта.
Назовем это метод, например, Init():
class MysqlTable {
 . . .
 // Привязывает объект-таблицу к таблице с именем $TblName
 function Init($TblName)
 { $this->TableName=$TblName;
 $this->Error=0;
 получаем и заполняем $this->Fields
 }
}
. . .
$Obj=new MysqlTable; $Obj->Init("test");
А вдруг между вызовами new и Init() случайно произойдет обращение к таблице? Или кто-то по ошибке забудет вызвать Init()
для созданного объекта (что обязательно случится, дайте только время)? Это приведет к непредсказуемым последствиям. Поэтому, как и положено в ООП, мы можем завести метод вместо Init(), который будет вызываться автоматически сразу же после инструкции new
и проводить работы по инициализации объекта. Он называется конструктором, или инициализатором. Чтобы PHP мог понять, что конструктор следует вызывать автоматически, ему (конструктору) нужно дать то же имя, что и имя класса. В нашем примере это будет выглядеть так:
class MysqlTable {
 function MysqlTable($TblName)
 { команды, ранее описанные в Init();
 }
}
$Obj=new MysqlTable("test"); // создаем и сразу же инициализируем объект
Обратите внимание на синтаксис передачи параметров конструктору. Если бы мы случайно пропустили параметр test, PHP выдал бы сообщение об ошибке. Таким образом, теперь в программе потенциально не могут быть созданы объекты-таблицы, ни к чему не привязанные.



Инструкция array() и многомерные массивы

Вернемся к предыдущему примеру. Нам необходимо написать программу, которая по фамилии некоторого человека из группы будет выдавать его имя. Поступим так же, как и раньше: будем хранить данные в ассоциативном массиве (сразу отбрасывая возможность составить ее из огромного числа конструкций if-else как неинтересную):
$Names["Ivanov"] ="Dmitry";
$Names["Petrova"]="Helen";
Теперь можно, как мы знаем, написать:
echo $Names["Petrova"];   // âûâåäåò Helen
echo $Names["Oshibkov"];  // îøèáêà: â ìàññèâå íåò òàêîãî ýëåìåíòà!
Идем дальше. Прежде всего обратим внимание: приведенным выше механизмом мы никак не смогли бы создать пустой массив. Однако он очень часто может нам понадобиться, например, если мы не знаем, что раньше было в массиве $Names, но хотим его проинициализировать указанным путем. Кроме того, каждый раз задавать массив указанным выше образом не очень-то удобно— приходится все время однообразно повторять строку $Names...
Так вот, существует и второй способ создания массивов, выглядящий значительно компактнее. Я уже упоминал его несколько раз — это использование оператора array(). Например:
// ñîçäàåò ïóñòîé ìàññèâ $Names
$Names=array();
// ñîçäàåò òàêîé æå ìàññèâ, êàê â ïðåäûäóùåì ïðèìåðå ñ èìåíàìè $Names=array("Ivanov"=>"Dmitry", "Petrova"=>"Helen");

// ñîçäàåò ñïèñîê ñ èìåíàìè (íóìåðàöèÿ 0,1,2) $NamesList=array("Dmitry","Helen","Sergey");

Теперь займемся вопросом, как формировать двумерные (и вообще многомерные) массивы. Это довольно просто. В самом деле, я уже говорил, что значениями переменных (и значениями элементов массива тоже, поскольку PHP не делает никаких различий между переменными и элементами массива) может быть все, что угодно, в частности — опять же массив. Так, можно создавать ассоциативные массивы (а можно — списки) с любым числом измерений. Например, если кроме имени о человеке известен также его возраст, то можно инициировать массив $Names так:

$Names["Ivanov"]  = array("name"=>"Dmitry","age"=>25);

$Names["Petrova"] = array("name"=>"Helen", "age"=>23);

или даже так:

$Names=array(

  "Ivanov" => array("name"=>"Dmitry","age"=>25),

  "Petrova"=> array("name"=>"Helen", "age"=>23)

);

Как же добраться до нужного нам элемента в нашем массиве? Нетрудно догадаться по аналогии с другими языками:

echo $Names["Ivanov"]["age"];   // íàïå÷àòàåò "25"

echo $Names["Petrova"]["bad"];  // îøèáêà: íåò òàêîãî ýëåìåíòà "bad"

Довольно несложно, не правда ли? Кстати, мы можем видеть, что ассоциативные массивы в PHP удобно использовать как некие структуры, хранящие данные. Это похоже на конструкцию struct в Си (или record в Паскале). Пожалуй, это единственный возможный способ организации структур, но он очень гибок.


Инструкция list()

Пусть у нас есть некоторый массив-список $List с тремя элементами: имя человека, его фамилия и возраст. Нам бы хотелось присвоить переменным $name, $surname и $age эти величины. Это, конечно, можно сделать так:
$name=$List[0];
$surname=$List[1];
$age=$List[2];
Но гораздо изящнее будет воспользоваться инструкцией list(), предназначенной как раз для таких целей:
list($name,$surname,$age)=$List;
Согласитесь, выглядит несколько приятнее. Конечно, list()
можно задействовать для любого количества переменных: если в массиве не хватит элементов, чтобы их заполнить, им просто присвоятся неопределенные значения.
Что, если нам нужны только второй и третий элемент массива $List?

В этом случае имеет смысл пропустить первый параметр в инструкции list(), вот так:
list(,$surname,$age)=$List;
Таким образом, мы получаем в $surname
и $age
фамилию и возраст человека, не обращая внимания на его имя в первом аргументе.
Инструкция list()

Разумеется, можно пропускать любое число элементов, как слева или справа, так и посередине списка. Главное— не забыть проставить нужное количество запятых.



Инструкция return

Синтаксис оператора return абсолютно тот же, что и в Си, за исключением одной очень важной детали. Если в Си функции очень редко возвращают большие объекты (например, структуры), а массивы они не могут возвратить вовсе (это явный прокол в концепции Си), то в PHP можно использовать return абсолютно для любых объектов (какими бы большими они ни были), причем без заметной потери быстродействия. Вот пример простой функции, возвращающей квадрат своего аргумента:
function MySqrt($n)
{  return $n*$n;
}
echo MySqrt(4);   // âûâîäèò 16
Сразу несколько значений функции, разумеется, возвратить не могут. Однако, если это все же очень нужно, то можно вернуть ассоциативный массив или же список, например так (листинг11.3):
Листинг 11.3. Возвращение массива
function Silly()
{  return array(1,2,3);
}
// присваивает массиву значение array(1,2,3)
$arr=Silly();
// присваивает переменным $a, $b, $c первые значения из списка
list($a,$b,$c)=Silly();
В этом примере использован оператор list(), который мы уже рассматривали.
Если функция не возвращает никакого значения, т. е. инструкции return в ней нет, то считается, что функция возвратила ложь (то есть, false). Все же часто лучше вернуть false явно (если только функция не объявлена как процедура, или void-функция по Си-терминологии), например, задействуя return false, потому что это несколько яснее.



Int, long

Целое число, либо вещественное число (в последнем случае дробная часть отсекается), либо строка, содержащая число в одном из перечисленных форматов. Если строку не удается перевести в int, то вместо нее подставляется 0, и никаких предупреждений не генерируется!



Integer

Целое число со знаком, обычно длиной 32 бита (от –2147 483 648 до 2 147 483 647, если это еще кому-то может быть интересно).



Интерфейс CGI

Термин CGI (Common Gateway Interface— Общий шлюзовой интерфейс) обозначает набор соглашений, которые должны соблюдаться Web-серверами при выполнении ими различных Web-приложений. Вскоре мы расшифруем его смысл гораздо более подробно. Фактически, до недавнего времени все Web-программирование представляло собой программирование CGI-приложений. В последнее время ситуация изменилась. И хотя CGI все еще остается негласным стандартом для Web-приложений, механизм работы CGI-программ несколько обновился.
В этой и следующей главах мы будем разбирать основы традиционного CGI-программирования, не касаясь напрямую PHP. В качестве языка для примеров выбран Си, поскольку его компиляторы можно найти практически в любой операционной системе, и по той причине, что он "наиболее красиво" показывает, почему… его не следует использовать в Web-программировании. Да-да, это не опечатка. Вскоре вы поймете, что я хотел сказать.



Интерфейс

Как можно заметить из листинга 30.4, интерфейс сценария гостевой книги стал гораздо проще, чем это было с генератором данных из листинга 30.2. Файл, в котором содержится его код, называется точно так же, как и файл генератора. Это и не удивительно: "снаружи" интерфейс выглядит как полноценный генератор данных, а о существовании ядра шаблон даже и не "подозревает".
Листинг 30.4. Интерфейс: gbook.php
include "kernel.php";  // Загружаем ядро.
$Book=LoadBook(GBook); // Загрузка гостевой книги.
// Обработка формы, если сценарий запущен через нее.
if(!empty($doAdd)) {
  // Добавить в книгу запись пользователя.
  $Book=array(time()=>$New)+$Book;
  // Записать книгу на диск.
  SaveBook(GBook,$Book);
}
// Загрузка шаблона не нужна — теперь, наоборот, шаблон
// вызывает интерфейс.
?>
Как видим, интерфейс занимается только той работой, для которой он и предназначен: выступает "посредником" между ядром и шаблоном. Самым первым загружается ядро — файл kernel.php (я люблю так его называть). Дальше осуществляется исключительно обработка и "расшифровка" входных данных и формирование выходных.



IP-адрес

Любой компьютер, подключенный к Интернету и желающий обмениваться информацией со своими "сородичами", должен иметь некоторое уникальное имя, или IP-адрес. Вот уже 30 лет (думаю, и в ближайшее десятилетие тоже) IP-адрес выглядит примерно так:
127.12.232.56
Как мы видим, это — четыре 8-разрядных числа (то есть принадлежащих диапазону от 0 до 255 включительно), соединенные точками. Не все числа допустимы в записи IP-адреса: ряд из них используется в служебных целях (например, адрес 127.0.0.1 выделен для обращения к локальной машине — той, на которой был произведен запрос, а число 255 соответствует широковещательной рассылке в пределах текущей подсети). Мы не будем здесь обсуждать эти исключения детально.
Возникает вопрос: ведь компьютеров в Интернете миллионы (а скоро будут миллиарды). Как же мы, простые пользователи, запросив IP-адрес машины, в считанные секунды с ней соединяемся? Как "оно" (и что это за "оно"?) узнает, где на самом деле расположен компьютер и устанавливает с ним связь, а в случае неверного адреса адекватно на это реагирует? Вопрос актуален, поскольку машина, с которой мы собираемся связаться, вполне может находиться за океаном, и путь к ней пролегает через множество промежуточных серверов.
В деталях вопрос определения пути к адресату довольно сложен. Однако достаточно нетрудно представить себе общую картину, точнее, некоторую ее модель. Предположим, что у нас есть 1 миллиард компьютеров (давайте завысим цифры), каждый из которых напрямую соединен с 11 (к примеру) другими через кабели. Получается этакая паутина из кабелей, не так ли? Кстати, это объясняет, почему одна из наиболее популярных служб Интернета, базирующаяся на протоколе HTTP, названа WWW
(World Wide Web, или Всемирная паутина).
IP-адрес

Следует заметить, что в реальных условиях, конечно же, компьютеры не соединяют друг с другом таким большим количеством каналов. Вместо этого применяются всевозможные внутренние таблицы, которые позволяют компьютеру "знать", где конкретно располагаются некоторые ближайшие его соседи. То есть любая машина в Сети имеет информацию о том, через какие узлы должен пройти сигнал, чтобы достигнуть самого близкого к ней адресата — а если не обладает этими знаниями, то получает их у ближайшего "сородича" в момент загрузки операционной системы. Разумеется, размер таких таблиц ограничен и они не могут содержать маршруты до всех машин в Интернете (хотя в самом начале развития Интернета, когда компьютеров в Сети было немного, именно так и обстояло дело). Потому-то я и провожу аналогию с одиннадцатью соседями.

Итак, мы сидим за компьютером номер 1 и желаем соединиться с машиной somehost с таким-то IP-адресом. Мы даем нашему компьютеру запрос: выясни-ка у своих соседей, не знают ли они чего о somehost. Он рассылает в одиннадцать сторон этот запрос (считаем, что это занимает 0,1 с, т. к. все происходит практически одновременно — размер запроса не настолько велик, чтобы сказалась задержка передачи данных), и ждет, что ему ответят.

Что же происходит дальше? Нетрудно догадаться. Каждый из компьютеров окружения действует по точно такому же плану. Он спрашивает у своих десятерых соседей, не слышали ли они чего о somehost. Это, в свою очередь, занимает еще 0,1 с. Что же мы имеем? Всего за 0,2 с проверено уже 11´10=

=110 компьютеров. Но это еще не все, ведь процесс нарастает лавинообразно. Нетрудно подсчитать, что за время порядка 1 секунды мы "разбудим" 10 в десятой степени машин, т. е. в 10 раз больше, чем мы имеем!

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

IP-адрес


В действительности дело обстоит куда сложнее. Отличия от представленной схемы частично заключаются в том, что компьютеру совсем не обязательно "запрашивать" всех своих соседей — достаточно ограничиться только некоторыми из них. Для убыстрения доступа все возможные IP-адреса делятся на четыре группы — так называемые адреса подсетей классов A, B, C и D. Но для нас сейчас это не представляет никакого интереса, поэтому не будем задерживаться на деталях. О TCP/IP можно написать целые тома (что и делается).


Исключительная блокировка

Вернемся к нашему примеру с процессами-писателями. Каждый такой процесс страстно желает, чтобы в некоторый момент (точнее, когда он уже почти готов начать писать) он был единственным, кому разрешена запись в файл.
Он хочет стать исключительным.
Отсюда и название блокировки, которую процесс должен для себя установить. Вызвав функцию flock($f,LOCK_EX), он может быть абсолютно уверен, что все остальные процессы не начнут без разрешения писать в файл, пока он не выполнит все свои действия и не вызовет flock($f, LOCK_UN) или не закроет файл.
Откуда такая уверенность? Дело в том, что если в данный момент наш процесс не единственный претендент на запись, операционная система просто не выпустит его из "внутренностей"
функции flock(), т. е. не допустит его продолжения, пока процесс-писатель не станет единственным. Момент, когда процесс, использующий исключительную блокировку, становится активным, знаменателен еще и тем, что все остальные процессы-писатели ожидают (все в той же функции flock()), когда же он, наконец, закончит свою работу с файлом. Как только это произойдет, операционная система выберет следующий исключительный процесс, и т. д.
Что ж, давайте теперь рассмотрим, как в общем случае должен быть устроен процесс-писатель, желающий установить для себя исключительную блокировку (листинг 15.2).
Листинг 15.2. Модель процесса с исключительной блокировкой
// инициализация
// . . .
$f=fopen($f,"a+") or die("Не могу открыть файл на запись!");
flock($f,LOCK_EX); // ждем, пока мы не станем единственными
// В этой точке мы можем быть уверены, что только эта
// программа работает с файлом
// . . .
fflush($f);        // записываем все изменения на диск
flock($f,LOCK_UN); // говорим, что мы больше не будем работать с файлом
fclose($f);
// Завершение
// . . .
?>
Заметьте, что при открытии файла мы использовали не деструктивный режим w (который удаляет файл, если он существовал), а более "мягкий" — a+.

Это неспроста. Посудите сами: удаление файла идеологически есть изменение его содержимого. Но мы не должны этого делать до получения исключительной блокировки (вспомните пример со светофором)! Поэтому, если вам нужно обязательно каждый раз стирать содержимое файла, ни в коем случае не используйте режим открытия w — применяйте a+ и функцию ftruncate(), описанную выше. Например:

$f=fopen($f,"a+") or die("Не могу открыть файл на запись!");

flock($f,LOCK_EX);    // ждем, пока мы не станем единственными

ftruncate($f,0);      // очищаем все содержимое файла

Исключительная блокировка


Зачем мы используем fflush() перед тем, как разблокировать файл? Все очень просто: отключение блокировки не ведет к сбросу внутреннего файлового буфера, т. е. некоторые изменения могут быть "сброшены" в файл уже после того, как блокировка будет снята. Мы, разумеется, этого не хотим, вот и заставляем PHP принудительно записать все изменения на диск.

Исключительная блокировка


Устанавливайте исключительную блокировку, когда вы собираетесь изменять файл. Всегда используйте при этом режим открытия r, r+ или a+. Никогда не применяйте режим w. Снимайте блокировку так рано, как только сможете, и не забывайте перед этим вызвать fflush().[E71]


Использование формы

Как теперь нам сделать, чтобы пользователь мог в удобной форме ввести свое имя и возраст? Очевидно, нам придется создать что-то вроде диалогового окна Windows, только в браузере. Итак, нам понадобится обычный HTML-документ (например, с именем form.html и расположенный в корневом каталоге) с элементами этого диалога— полями ввода текста и кнопкой, при нажатии на которую запустится наш сценарий. Текст этого документа приведен в листинге 2.1.
Листинг 2.1. Документ /form.html с формой


Ââåäèòå èìÿ:


Ââåäèòå âîçðàñò:




Использование формы

Вы можете заметить, что некоторые атрибуты тэгов я написал в кавычках (например, name="age"), а некоторые — нет. Как показывает практика, везде, где это не конфликтует с синтаксисом HTML (то есть, в текстах, в которых нет пробелов и букв кириллицы), можно кавычки опускать. Мне лично нравится заключать значения полей name и value в кавычки, а остальные — писать без них. Правда, стандарт на язык HTML это не допускает (он требует обязательного наличия кавычек), но большинство браузеров относится к этому весьма и весьма лояльно.
Загрузим наш документ в браузер. Получим примерно следующее:
Использование формы

Рис. 2.1.
HTML-форма
Теперь, если занести в поле name свое имя, а в поле для возраста — возраст и нажать кнопку Нажмите кнопку!, браузер обратится к сценарию по URL, указанному в атрибуте action тэга формы:
http://www.somehost.com/script.cgi
Он передаст через ? все параметры, которые помещены внутрь тэгов input в форме, отделяя их амперсандом (&). Имена полей и их значения будут разделены знаком =. Теперь вы понимаете, почему мы с самого начала использовали эти символы?
Итак, реальный URL, который будет сформирован браузером при старте сценария, будет таким (учитывая, что на странице был пользователь по имени Vasya и ему 20 лет):
http://www.somehost.com/script.cgi?name=Vasya&age=20
Самое, пожалуй, полезное, что можно вынести из рассмотренного примера, — то, что все URL-перекодирования и преобразования осуществляются браузером автоматически. То есть, пользователю теперь совершенно не нужно об этом задумываться и ломать голову над путаницей шестнадцатеричных кодов и управляющих символов.



Использование карманов в функции сопоставления

И даже на том, что было описано выше, возможности карманов не исчерпываются. Мы можем задействовать содержимое карманов и в функции ereg()— раньше, чем закончится сопоставление. А именно, управлять ходом поиска на основе данных в карманах.
В качестве примера рассмотрим такую далеко не праздную задачу. Известно, что в строке есть подстрока, обрамленная какими-то HTML-тэгами (например, или
), но неизвестно, какими. Требуется поместить эту подстроку в карман, чтобы в дальнейшем с ней работать. Разумеется, закрывающий тэг должен соответствовать открывающему — например, к тэгу  парный — , а к 
.
Задача решается с помощью такого регулярного выражения:
<([[:alnum:]]+)>([^<]*)
При этом результат окажется во втором кармане, а имя тэга — в первом. Вот как это работает: PHP пытается найти открывающий тэг, и, как только находит, записывает его имя в первый карман (так как это имя обрамлено в выражении первой парой скобок). Дальше он смотрит вперед и, как только наталкивается на Вот фрагмент программы, который все описанное делает тремя строчками:
$str = "Hello, this word is bold!";
if(ereg("<([[:alnum:]]+)>([^<]*)",$str,$Pockets))
  echo "Ñëîâî '$Pockets[2]' îáðàìëåíî òýãîì '<$Pockets[1]>'";



Использование карманов в функции замены

Мы рассмотрели только самый простой способ использования карманов — прямой их просмотр после выполнения поиска. Однако возможности, предоставляемые языком RegEx, куда шире. Особенно часто эти возможности применяются для замены с помощью регулярных выражений.
Предположим, нам нужно все слова в строке, начинающиеся с "доллара" $, сделать "жирными", — обрамить тэгами и , — для последующего вывода в браузер. Это может понадобиться, если мы хотим текст некоторой программы на PHP вывести так, чтобы в нем выделялись имена переменных. Очевидно, выражение для обнаружения имени переменной в строке будет таким: \$[a-zA-Z_][[:alnum:]]*.
Но как нам использовать его в функции ereg_Replace()? Вот фрагмент программы, которая делает это:
$str=" // ê ïðèìåðó
$str=ereg_Replace("(\\$[a-zA-Z_][[:alnum:]]*)","\\1",$str);
Использование карманов в функции замены

Пожалуйста, обратите опять внимание на то, что слэши должны удваиваться.
Нетрудно догадаться, как "оно"
работает: просто во время замены везде вместо сочетания \1 подставляется содержимое кармана номер 1.



Использование регулярных выражений в PHP

Вернемся на минуту опять к практике. Любое регулярное выражение в PHP— это просто строка, его содержащая, поэтому функции, работающие с регулярными выражениями, принимают их в параметрах в виде обычных строк.



Использование самопереадресации

Термин самопереадресация (или, в английском варианте, self-redirect) означает свойство сценария подавать в браузер клиента запрос, заставляющий его (браузер) заново выполнить и загрузить этот сценарий с сервера. Звучит, как языческое заклинание, не правда ли? Пожалуй, с первого взгляда не совсем ясно, зачем же может понадобиться эта хваленая самопереадресация в Web-программировании.
Рассмотрим пример. Предположим, у нас имеется сценарий— гостевая книга наподобие той, эскиз которой мы рассматривали в главе 30. С точки зрения пользователя сценарий представляет собой страницу с адресом http://www.ourserver.ru/book/index.html. Если набрать этот адрес в браузере, появится, во-первых, форма с предложением добавить новое сообщение в книгу, а во-вторых, список ранее добавленных "посланий". В атрибуте action тэга
указан адрес той же самой страницы index.html (это вписывается в трехуровневую схему разработки сценариев), поэтому после набора сообщения и нажатия на кнопку отправки фактически снова загружается та же самая страница. Только перед ее загрузкой генератор данных гостевой книги определяет, что необходимо добавить новую запись, и делает это.
В общем-то, довольно стандартная схема. Пусть пользователь набрал свое послание и отправил его на сервер. Перед ним появится список сообщений, первым из которых будет его собственное. Пока вроде бы все верно. И теперь пользователь, ничего не подозревая, нажимает на кнопку Обновить
в браузере, заставляя последний, как он думает, перезагрузить страницу гостевой книги.
Но в действительности происходит совсем не то, что он ожидает. Если данные формы были посланы методом POST, браузер выведет на экран диалоговое окно запроса примерно такого содержания: "Вы пытаетесь обновить данные страницы, которая была сгенерирована с применением метода POST. Повторить отправку данных (да или нет)?" Если пользователь нажмет кнопку Нет, то гостевая книга не перезагрузится, а появится совершенно бесполезная стандартная страница с сообщением о том, что "данные устарели". Если же он подтвердит вторичную отправку данных, его сообщение будет добавлено в книгу еще раз, а потому "размножится". Довольно нетрудно понять, почему так происходит: ведь браузер "не знает", что в действительности пользователь хочет лишь вторично "зайти" на адрес страницы книги, а не повторить отправку всех данных формы.

Однако ситуация становится еще плачевнее, если мы применяем в нашей гостевой книге метод GET. В этом случае при нажатии на кнопку Обновить браузер "без лишних разговоров" пошлет данные формы на сервер повторно, так что сообщение будет лишний раз добавлено в гостевую книгу без предупреждений. И это тоже понятно: ведь метод GET — не что иное, как простое изменение URL страницы, а именно, добавление в его конец символа ?, после которого следуют параметры (в том числе текст записи).

Использование самопереадресации


Впрочем, метод GET

практически никогда не применяется в интерактивных сценариях, таких как гостевые книги, форумы и т. д. Мы уже говорили в первой части книги на эту тему, но она настолько важна, что я повторюсь. Если для одних и тех же данных формы при их многократной отправке страница всегда выглядит одинаково, значит, эти данные логично передавать методом GET. В противном случае необходимо применять метод POST. Такое положение вещей связано также и с тем, что некоторые proxy-серверы могут кэшировать страницы, полученные методом GET, но они никогда не кэшируют их при использовании POST.

Самопереадресация — это как раз то средство, которое позволяет разрешить рассмотренный конфликт в сторону пользователя. В самом деле, предположим, что при получении уведомления о новом сообщении генератор данных вставляет их в базу данных, а затем посылает браузеру заголовок, заставляющий его перезагрузить страницу гостевой книги. В этом случае страница уже не будет представлять собой результат работы метода POST, это будет обычный HTML-документ, загруженный с сервера, как будто бы пользователь считал файл только что самостоятельно и "вручную". Неудивительно, что кнопка браузера Обновить будет работать так, как ей и положено.

Впрочем, при использовании самопереадресации очень легко наткнуться на один неприятный "подводный камень". Это — ошибка некоторых версий браузера Netscape, заключающаяся в том, что любые страницы, полученные им в результате самопереадресации, он ошибочно принимает за пустые (и соответственно отображает). И все же выход есть: достаточно немного модифицировать URL страницы, чтобы браузер "подумал", что это уже другой документ, а не тот же самый. Листинг 33.3 показывает, как это можно сделать. В целях экономии места я разместил шаблон страницы и генератор данных в одном файле.


Листинг 33.3. Самопереадресация


// Считываем содержимое базы данных.

$Book=@Unserialize(join("",File("book.dat")));

if(!$Book) $Book=array();

// Проверяем, не нужно ли добавить запись...

if(@$Go) {

  array_unshift($Book,$Text);

  $f=fopen("book.dat","w");

  fwrite($f,Serialize($Book));

  fclose($f);

  // Внимание! Самопереадресация. Обратите внимание на то,

  // какой заголовок мы посылаем.

  Header("Location: http://$HTTP_HOST$REQUEST_URI?".time());

  exit; // Завершить сценарий.

}

?>



Введите текст:







$v) {?>

 

 




Мы обеспечиваем "уникальность" URL страницы гостевой книги за счет добавления в его конец текущего времени в секундах, прошедших с 1 января 1970 года (так называемый Unix timestamp). Вряд ли пользователь будет обновлять страницу чаще, чем раз в секунду, поэтому такой способ прекрасно подходит для наших целей.

Обратите внимание на то, что в заголовке Location мы передаем полный URL страницы, включая имя хоста. Большинство браузеров умеют "понимать" и сокращенные пути (например, без указания имени сервера), но некоторые — нет, так что лучше не искушать судьбу.


Ядро

Ядро— это самая ответственная, но, на мой взгляд, в то же время и самая скучная часть работы программиста. Действительно, оно напрямую не взаимодействует с шаблоном страницы, а значит, не имеет права "общаться" с пользователем.
Ядро в идеале должно содержать лишь набор функций, которые позволяют исчерпывающим образом работать с объектом программы. В этом смысле идеально его объектно-ориентированное построение. Об объектно-ориентированном программировании на PHP будет вкратце рассказано в главе 31, а пока не будем усложнять и без того "скользкую"
задачу и посмотрим, что представляет собой ядро нашей гостевой книги (листинг 30.5).
Листинг 30.5. Ядро: kernel.php
// Загружаем конфигурацию.
include "config.php";
// Загружает гостевую книгу с диска. Возвращает содержимое книги.
function LoadBook($fname)
{  $f=@fopen("gbook.dat","rb");
   if(!$f) return array();
   $Book=Unserialize(fread($f,100000));
   fclose($f);
   return $Book;
}
// Сохраняет данные книги на диске.
function SaveBook($fname,$Book)
{  $f=fopen("gbook.dat","wb");
   fwrite($f,Serialize($Book));
   fclose($f);
}
?>
Действительно, здесь нет ничего, кроме определений функций и… еще одной инструкции include (вздохните с облегчением — на этот раз последней). Она добавляет конфигурационные данные нашей книги — всего лишь одну-единственную константу GBook, определяющую имя файла, в котором гоствевая книга и будет храниться. "Для порядка" приведу и его (листинг 30.6).
Листинг 30.6. Конфигурация: config.php
define("GBook","gbook.dat"); // имя файла с данными книги
?>
Ядро

Что же у нас получилось в результате? Мы "растянули" простой сценарий на целых 5 файлов (если считать еще и .htaccess, то на 6). Что ж, если вы так думаете, я с вами соглашусь. Тут все дело в том, что для простых сценариев (а именно такой мы и рассматривали) трехуровневая схема построения оказывается чересчур уж "тяжеловесной". Про такую ситуацию в народе говорят: "из пушки по воробьям". Что же касается сложных систем, не следует забывать, что "единственность" ядра может сэкономить нам количество файлов, если у комплекса много различных интерфейсов (например, разветвленная система администрирования), не говоря уже о простоте отладки и поддержки. Кроме того, можно полностью разделить работу по написанию ядра и интерфейса между несколькими людьми.



Явное использование константы SID

В PHP существует одна специальная константа с именем SID. Она всегда содержит имя группы текущей сессии и ее идентификатор в формате имя=идентификатор. Вспомните: именно в таком формате данные принимаются, когда они приходят из Cookies браузера. Таким образом, нам достаточно просто-напросто передать значение константы SID в сценарий, чтобы он "подумал", будто бы данные пришли из Cookies. Вот пример:
Листинг 25.3. Sesget.php: простой пример использования сессий без Cookies
session_name("test");
session_start();
session_register("count");
$count=@$count+1;
?>

Ñ÷åò÷èê


 òåêóùåé ñåññèè ðàáîòû ñ áðàóçåðîì Âû îòêðûëè ýòó ñòðàíèöó
ðàç(à). Çàêðîéòå áðàóçåð, ÷òîáû îáíóëèòü ýòîò ñ÷åò÷èê.

>Click here!

Если набрать в браузере адрес вроде такого:
http://www.somehost.ru/sesget.php
то создастся новая сессия с уникальным идентификатором. Разумеется, если сразу же нажать кнопку Обновить, счетчик не увеличится, потому что при каждом запуске будет создаваться новое временное хранилище— у PHP просто нет информации об идентификаторе пользователя. Теперь обратите внимание на предпоследнюю строчку листинга 25.3. Видите, как хитро мы передаем в сценарий, запускаемый через гиперссылку, данные об идентификаторе текущей сессии? Теперь с его точки зрения они якобы пришли из Cookies…
Явное использование константы SID

Все будет работать так, как описано, только в том случае, если в браузере действительно отключены Cookies. Если же они включены, PHP просто не будет генерировать константу SID (она будет пустой) и задействует Cookies. Все вполне логично.



Эффект прозрачности

Функцию imageColorClosest() можно и нужно использовать, если мы не хотим допустить разрастания палитры и уверены, что требуемый цвет в ней уже есть. Однако есть и другое, гораздо более важное, ее применение — определение эффекта прозрачности для изображения. "Прозрачный"
цвет рисунка — это просто те точки, которые в браузер не выводятся[E98] [DK99] . Таким образом, через них "просвечивает" фон. Прозрачный цвет у картинки всегда один, и задается он при помощи функции imageColorTransparent().
int imageColorTransparent(int $im [,$int col])
Функция imageColorTransparent()
указывает GD, что соответствующий цвет $col (заданный своим идентификатором) в изображении $im должен обозначиться как прозрачный. Возвращает она идентификатор установленного до этого прозрачного цвета, либо false, если таковой не был определен ранее.
Эффект прозрачности

Не все форматы поддерживают задание прозрачного цвета — например, JPEG не может его содержать.
Например, мы нарисовали при помощи GD птичку на кислотно-зеленом фоне и хотим, чтобы этот фон как раз и был "прозрачным"
(вряд ли у птички есть части тела такого цвета, хотя с нашей экологией все может быть...). [E100] [DK101] В этом случае нам потребуются такие команды:
$tc=imageColorClosest($im,0,255,0);
imageColorTransparent($im,$tc);
Обратите внимание на то, что применение функции imageColorAllocate() здесь совершенно бессмысленно, потому что нам нужно сделать прозрачным именно тот цвет, который уже присутствует в изображении, а не новый, только что созданный.



Эмуляция браузера через telnet

Между прочим, при передаче запроса браузер "притворяется" пользователем, который запустил telnet-клиента (программу, которая, грубо говоря, умеет подключаться к заданному IP-адресу и порту, посылать по нему то, что набирается на клавиатуре, и отображать на экране поступающие "снаружи" данные) и вводит строки заголовков вручную— т. е., в текстовом виде. Например, вместо того чтобы набрать в браузере http://www.somehost.com/, попробуйте в командной строке ОС (Unix, Windows 95/98/NT/2000 или любой другой) выполнить следующие команды (вместо нажимая соответствующую клавишу):
telnet www.somehost.com 80
GET /index.html HTTP/1.0

Вы увидите, как перед вами промелькнут строки HTML-документа index.html. Очень рекомендую проделать описанную процедуру, чтобы избавиться от духа мистицизма при упоминании о протоколе HTTP. Все это не так сложно, как иногда может показаться.
Эмуляция браузера через telnet

Если у вас указанная процедура не удалась, и сервер все время шлет сообщение "Bad Request", то проверьте регистр символов, в котором вы набираете команды. Все буквы должны быть заглавными, а название протокола HTTP/1.0 — идти без пробелов.
Посмотрим теперь, как работает сервер. А происходит все следующим образом: он считывает все заголовки запроса и дожидается маркера "\n\n" (или, что то же самое, "пустого" заголовка), а как только его получает, начинает разбираться — что же ему за информация пришла, и выполнять соответствующие действия.
С помощью заголовков реализуются такие механизмы, как контроль кодировок, Cookies, метод POST и т. д. Если же сервер не понимает какого-то заголовка, он его либо пропускает, либо жалуется отправителю (в зависимости от воли администратора, который настраивал сервер).



Эмуляция функции virtual()

Функция virtual()
работает только в том случае, если PHP установлен как модуль Apache. Проблемы начинаются, если это не так, и какой-то уже готовый сценарий интенсивно использует вызовы virtual(). Тогда мы должны будем либо переделать сценарий, либо написать эмуляцию для функции virtual() (благо в "сценарном"
варианте PHP эта функция отсутствует, так что можно без оглядки на ключевые слова создать процедуру с именем virtual()). Вот как мы здесь поступим:
if(!function_exists("virtual")) {
  // Условно определяемая функция
  function Virtual($url)
  { //* здесь должен идти код для преобразования относительного
    //* URL (заданного относительно текущего каталога) в абсолютный.
    //* Мы не будем сейчас останавливаться на этом вопросе — оставим
    //* его для 5-й части книги.
    global $HTTP_HOST,$SERVER_PORT;
    $f=@fopen("http://$HTTP_HOST:$SERVER_PORT$url","r");
    if(!$f) {
      echo "[an error ocurred while processing this directive: $url]";
      return false;
    }
    // Теперь просто читаем все и выводим с помощью echo
    while(($s=fread($f,10000))!="") echo $s;
    fclose($f);
  }
}
Обращаю ваше внимание на то, что используется не обычный fopen(), а сетевая его разновидность, на что указывает префикс http:// в имени файла. Единственное здесь сложное место — преобразование относительного URL в абсолютный. Но эта задача, конечно, вполне разрешима, и мы займемся ей уже скоро — в пятой части книги — наряду с остальными проблемами прикладного характера.


Эмуляция функции virtual()





Этап первый: установка

1. Запустите только что полученный файл дистрибутива Apache. В появившемся диалоговом окне нажмите кнопку Next
(рис. 4.1), а затем — кнопку Yes, чтобы согласиться с условиями лицензии.
Этап первый: установка

Рис. 4.1. Установка Apache
Этап первый: установка

Рис. 4.2. Каталог для установки сервера
2. Нажимайте кнопку Next
в открывающихся окнах до тех пор, пока не появится запрос о выборе каталога для установки Apache (рис. 4.2). Рекомендую вам оставить тот каталог, который предлагается по умолчанию (пусть это, например, C:\Program Files\Apache Group\Apache). Запомните его на будущее.
3. В появившемся окне установите флажок Typical (Обычная) и нажмите кнопку Next (рис. 4.3).
4. Программа инсталляции Apache предложит создать папку в меню Пуск в папке Программы. Позвольте ей это сделать, нажав кнопку Next. Начнется процесс копирования программного обеспечения.
5. После окончания копирования нажмите кнопку Finish. Процесс установки сервера завершен, впереди — его настройка.
Этап первый: установка

Рис. 4.3. Тип установки



Этап третий: тестирование Apache

Поздравляем — вы настроили свой Apache, и он должен уже работать! Для запуска сервера нажмите кнопку Пуск, затем выберите Программы, Apache Web Server, Management и Start Apache, при этом всплывет окно, очень похожее на Сеанс MS-DOS, и ничего больше не произойдет. Не закрывайте его и не трогайте до конца работы с Apache.
Если окно открывается и тут же закрывается, это означает, что вы допустили какую-то ошибку в файле httpd.conf. В этом случае придется искать неточность. Проще всего это сделать, как указано ниже.
1. Запустите Сеанс MS-DOS. Для этого нажмите кнопку Пуск, затем выберите Выполнить. Наберите в появившемся диалоговом окне строку command и нажмите клавишу . Появится подсказка командной строки.
2. Наберите следующие команды DOS:
c:
cd "\Program Files\Apache Group\Apache"
apache.exe
3. Если до этого Apache не выполнялся, то вы получите сообщение об ошибке и номер строки в httpd.conf, где она произошла. Исправьте httpd.conf и повторите описанный процесс сначала, до тех пор, пока в окне не отобразится что-то вроде "Apache/1.3.14 (Win32) running..."
Несколько слов о том, как можно упростить запуск и завершение сервера.

В Windows можно назначить любому ярлыку функциональную комбинацию клавиш, нажав которые, вы запустите связанное с ним приложение. Так что щелкните правой кнопкой мыши на панели задач, в контекстном меню выберите Свойства, затем Настройка меню и кнопку Дополнительно. В открывшемся Проводнике присвойте ярлыку Start Apache комбинацию клавиш ++, а ярлыку Stop Apache++. Теперь вы сможете запускать сервер нажатием ++ и останавливать его, нажав ++.
Теперь проверим, правильно ли мы настроили сервер.



Этап второй: настройка файла конфигурации Apache

На этом этапе вам нужно определиться с каталогом, в котором будут храниться ваши сайты. По умолчанию Apache использует для этого C:\Program Files\

Apache Group\Apache\htdocs, где сразу после установки можно найти документацию по серверу. Думаю, для серьезных целей такая дислокация не очень подходит— слишком уж длинное имя, поэтому я рекомендую создать для всех сайтов отдельный виртуальный диск (например, с именем Z:) при помощи утилиты subst, входящей в Windows. Итак, вам нужно проделать ряд действий.
1. Выберите каталог, в котором будут храниться ваши сайты (их может быть несколько). Пусть, например, это будет C:\INTERNET. Ваш каталог будет содержать корневой каталог нового диска Z:.
2. В начале файла autoexec.bat (но после команды @echo off, если она у вас там есть) напишите такую строку:
subst Z: C:\INTERNET
3. Перезагрузите компьютер, чтобы новый логический диск Z: создался. Теперь все, что сохранено в каталоге C:\INTERNET, будет отображаться на панели диска Z:, как будто это — обычный жесткий диск.
Этап второй: настройка файла конфигурации Apache

Имеются сведения, что в Windows 95/98 есть ошибка. В результате при использовании subst пути иногда "сами по себе" преобразуются в абсолютные (то есть, например, в нашем случае Z: преобразуется в C:\INTERNET), причем в процессе работы какой-нибудь программы и совершенно неожиданно для нее. Указанная ошибка чаще всего проявляется в неработоспособности Perl-транслятора (если его не совсем корректно настроить). При работе с PHP никаких побочных эффектов не наблюдалось.
Вы можете также создать диск Z: с помощью какой-нибудь программы для виртуальных разделов (например, с помощью встроенной в Windows 95/98 программы DriveSpace). Это решение, пожалуй, даже лучше, чем использование subst, как с точки зрения экономии памяти, и с точки зрения быстродействия. Ведь что такое Web-сайт, как не набор очень небольших файлов? А DriveSpace как раз и оптимизирует работу с такими файлами. Как использовать DriveSpace, смотрите во встроенной в Windows документации.

r Создайте на диске Z:

каталог home, а в нем — каталог localhost. В нем будет храниться содержимое главного хоста Apache — того, который доступен по адресу http://localhost. Перейдите в последний созданный каталог. Создайте в нем каталоги cgi

и www. В первом будут храниться CGI-сценарии, а во втором — ваши документы и программы на PHP. Замечу, что подобную операцию вам нужно будет проделывать каждый раз при создании нового виртуального хоста (о них мы поговорим чуть позже). Полученная структура каталогов показана на рис. 4.4.

Откройте в Блокноте

файл конфигурации httpd.conf, который расположен в подкаталоге conf каталога Apache (в нашем примере это C:\Program Files\Apache Group\Apache). Впрочем, вы можете и не искать этот файл вручную, а воспользоваться командой Edit configuration, пройдя по цепочке меню Пуск ú Программы ú Apache Web

Server ú Management. Httpd.conf — единственный файл, который вам нужно настроить. Вам предстоит найти и изменить в нем некоторые строки, а именно те, о которых упоминается далее. Во избежание недоразумений не трогайте все остальное. Следует заметить, что в файле каждый параметр сопровождается несколькими строками комментариев, разобраться в которых с первого раза довольно тяжело (впрочем, вы можете обратиться к Приложению Б, в котором приведен полный перевод этих комментариев на русский язык). Поэтому не обращайте на них особого внимания.

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

Этап второй: настройка файла конфигурации Apache


Рис. 4.4. Структура каталогов главного хоста

r Задайте значение параметра ServerName следующим образом:

ServerName localhost

Только не забудьте раскрыть комментарий для поля ServerName, т. е. убрать символ # перед этим параметром (установленный по умолчанию), поскольку все, что идет после этого символа и до конца строки, Apache игнорирует.


r В поле DocumentRoot укажите тот каталог, в котором будут размещены ваши HTML-файлы. Мы ранее договорились, что это будет z:\home\localhost\www)

DocumentRoot z:/home/localhost/www

r Найдите секцию, начинающуюся строкой и заканчивающийся строкой (такие блоки содержат установки для заданного каталога и всех его подкаталогов). Этот блок может содержать множество комментариев — не обращайте на них внимания. Его нужно заменить на секцию следующего вида:



  Options Indexes Includes

  AllowOverride All

  Allow from all



Этим вы обеспечите, что в данном блоке будут храниться настройки для всех каталогов по умолчанию (так как z: — корневой каталог). А именно, для всех каталогов по умолчанию предоставляется возможность автоматической генерации индекса — списка содержимого каталога при просмотре его в браузере, а также поддержка SSI и разрешение использовать файлы .htaccess для индивидуальных настроек каталогов.

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

r Инициализируйте параметр DirectoryIndex

так:

DirectoryIndex index.htm index.html

Это — так называемые файлы индекса, которые автоматически возвращаются сервером при обращении к какому-либо каталогу, если не указано имя HTML-документа. В принципе, можно добавить сюда и другие имена, например, index.php, и т. д. Тем не менее, дополнительные настройки все же лучше делать в файлах .htaccess для каждого сайта в отдельности.

r Найдите и исправьте следующий параметр:


ScriptAlias /cgi-bin/ "z:/home/localhost/cgi/"

Добавьте после него еще такую строчку:

ScriptAlias /cgi/ "z:/home/localhost/cgi/"

Да, именно так, с двумя слэшами — в начале и в конце. Это будет тот каталог, в котором должны располагаться ваши CGI-сценарии. Подобный параметр говорит Apache о том, что, если будет указан путь вида http://localhost/cgi-bin, то на самом деле следует обратиться к каталогу z:/home/localhost/cgi. Мы используем два псевдонима для CGI-каталога потому, что /cgi-bin/ будет доступен не только главному хосту localhost, но и всем остальным виртуальным хостам. В то же время  у каждого из них будет дополнительно свой CGI-каталог /cgi/.

r Теперь следует найти блок параметров, начинающийся с
Program Files/Apache Group/Apache/cgi-bin"> и заканчивающийся
. Это — настройки для CGI-каталога. Так как мы не собираемся указывать никаких дополнительных параметров взамен тех, которые уже установлены по умолчанию, этот блок нужно удалить.

r Найдите и настройте (не забудьте раскрыть комментарий!) следующий параметр:

AddHandler cgi-script .bat .exe .cgi

Он говорит Apache о том, что файлы с расширениями exe, bat и cgi надо рассматривать как CGI-модули.

r И последнее — установите следующие параметры:

AddType text/html .shtml

AddHandler server-parsed .shtml .html .htm

Этим вы заставляете Apache обрабатывать файлы с указанными расширениями процессором SSI.

r Теперь не забудьте сохранить изменения и закройте Блокнот.


Каналы и символические ссылки

Вообще-то, каналы и символические ссылки — совершенно разные вещи. Однако у меня есть по крайней мере два веских основания для того, чтобы сгруппировать их описания в одном месте.
r И то и другое сколько-нибудь результативно можно использовать лишь в системах на основе Unix, в других же операционных системах функции либо не реализованы, либо просто не работают.
r Примерно лишь один сценарий на PHP из тысячи может нуждаться в создании и использовании каналов и символических ссылок.



"Карманы"

Пока что мы научились только определять, соответствует ли строка регулярному выражению и, возможно, предпринимать какие-то действия по замене найденной части на другую подстроку. Однако на практике часто бывает нужно не просто узнать, где в строке имеется совпадение (и что оно из себя представляет), но также и разбить это совпадение на части, ради которых, собственно, и велась вся работа.
Вот пример, проясняющий ситуацию. Пусть нам в строке задана дата в формате DD-MM-YYYY, и в ней могут встретиться паразитные пробелы в начале и конце. Нас интересует, что же все-таки за дату нам передали. То есть, мы точно знаем, что эта строка— именно дата, но вот где в ней день, где месяц и где год?
Посмотрим, что же предлагает нам RegEx и PHP для решения рассматриваемой задачи. Для начала установим, что все правильные даты должны соответствовать выражению
^ *(([0-9]+)-([0-9]+)-([0-9]+)) *$
Для простоты мы не проверяем, что длина каждого поля не должна превышать 2 (для года — 4) символа. Все строки, не удовлетворяющие этому выражению, заведомо не являются датами.
Мы не зря ограничили отдельные части регулярного выражения скобками, хотя, на первый взгляд, можно было бы их опустить. И вот почему: любой блок, обрамленный в выражении скобками, выделяется как единое целое и записывается в так называемый "карман"
(номер кармана соответствует порядку открывающихся скобок). В нашем случае в первый карман запишется дата, но уже без ведущих и концевых пробелов (это обеспечивает самая внешняя пара скобок), во второй — как раз день, в третий — месяц и, наконец, в четвертый — год.

Обратите еще раз внимание на порядок нумерации карманов — она идет по номеру открывающейся скобки.
Как уже упоминалось, в нулевой карман в любом случае записывается все найденное совпадение. В данном примере это будет вся строка.
Как получить содержимое наших карманов? Очень просто: как раз тот список, который передается по ссылке функции ereg() третьим параметром, и есть карманы. Исходя из этого, имеем следующую программу на PHP, выполняющую требуемые действия:

$str=" 15-16-2000 "; // ê ïðèìåðó

// Ðàçáèâàåì ñòðîêó íà êóñêè ïðè ïîìîùè ereg

ereg("^ *(([0-9]+)-([0-9]+)-([0-9]+)) *$",$str,$Pockets);

// Òåïåðü ðàçáèðàåìñÿ ñ êàðìàíàìè

echo "Äàòà áåç ïðîáåëîâ: $Pockets[1]
"

echo "Äåíü: $Pockets[2]
";

echo "Ìåñÿö: $Pockets[3]
";

echo "Ãîä: $Pockets[4]
";

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

^ *([0-9]+) *[-./] *([0-9]+) *[-./] *([0-9]+) *$


Класс таблицы MySQL

Пожалуй, я слишком далеко заглянул в будущее. Вернемся назад к основам. Чтобы определить метод внутри класса, используется следующий синтаксис:
сlass MyClass {
 . . .
 function Method(параметры)
 { . . .
 }
 . . .
}
Давайте будем потихоньку набрасывать план нашего класса MySQL-таблицы. Во первых, зададимся вопросом: зачем нам вообще это нужно? Почему бы не пользоваться обычными функциями для работы с MySQL? Ответ не вполне очевиден, поэтому оставим его на потом. А пока будем считать, что такой класс нам необходим (а он действительно необходим, т. к.
значительно упрощает работу с базой данных).
Во-вторых, сформулируем правило: обращаться к какой-то таблице MySQL только посредством нашего класса, а точнее, объекта этого класса, связанного с таблицей. Как же его связать? Очевидно, объект должен содержать имя таблицы, к которой он "привязан". Так как в программе могут использоваться одновременно несколько таблиц и несколько объектов, то, наверное, логично это самое имя хранить в виде свойства.
Что бы еще хотелось знать об объекте-таблице? Конечно, имена и типы ее полей. Поместим их в свойство-массив. Наконец, в процессе работы наверняка иногда будут возникать ошибки. Чтобы как-то сигнализировать о них, предлагаю в класс-таблицу ввести еще одно свойство — Error. Оно будет равно нулю, если предыдущая операция (например, добавление записи) прошла успешно, и тексту ошибки — в противном случае.
Вот что у нас пока получилось:
class MysqlTable {
 var $TableName; // Имя таблицы в базе данных
 var $Fields; // Массив полей. Ключ — имя поля, значение — его тип
 var $Error; // Индикатор ошибки
 . . .
}
Согласитесь, это почти все данные, которые должны храниться в объекте-таблице. Все остальное (например, записи) находится в базе данных. Нам нужно научиться каким-то образом легко извлекать и добавлять (а также удалять, подсчитывать и обновлять) эти записи путем простых запросов к объекту-таблице. Для этого я предлагаю написать соответствующие методы (листинг 31.1).

Класс таблицы MySQL


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

Листинг 31.1. Эскиз класса таблицы

class MysqlTable {

 var $TableName; // Имя таблицы в базе данных

 var $Fields; // Массив полей. Ключ — имя поля, значение — его тип

 var $Error; // Индикатор ошибки

 // Добавляет в таблицу запись $Rec. $Rec должна представлять из себя

 // обычный ассоциативный массив. В будущем мы придем к тому, что

 // массив $Rec будет представлен даже древовидной структурой,

 // т. е. будет иметь подмассивы.

 // Как вы понимаете, непосредственной поддержки этого в MySQL нет,

 // но мы "ее" реализуем.

 function Add($Rec) { команды; }

 // Возвращает массив записей (ключ — id записи, значение —

 // ассоциативный массив, в точности такой же, какой был помещен

 // некогда в таблицу при помощи Add), удовлетворяющих выражению

 // $Expr. Возвращаются только первые $Num (или менее) записей.

 // Сортировка осуществляется в соответствии с критерием $Order.

 function Select($Expr,$Num=1e10,$Order="id desc") { команды; }

 // Удаляет из таблицы все записи, удовлетворяющие выражению $Expr.

 function Delete($Expr) { команды; }

 // Удаляет из таблицы все записи (например, при помощи вызова

 // Delete("1=1") и удаляет саму таблицу из базы данных. Этот

 // метод довольно опасен!

 function Drop() { команды; }

}

Пока, пожалуй, хватит. Я не буду здесь углубляться в то, как устроен каждый из названных методов. Этим мы займемся в свое время. А пока обратите внимание на то, что мы попытались определить все операции, которые вообще применимы к таблице MySQL (на самом деле, это далеко не полный их перечень, но пока нам и такого количества вполне достаточно). Это очень важно, потому что потом, когда будем использовать объекты класса MysqlTable, мы сможем вообще забыть, что такое MySQL и язык запросов SQL, или поручить разработку программы, обращающейся к MysqlTable, человеку, не разбирающемуся в SQL.

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

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


Классический перебор

Давайте опять вернемся к нашему примеру, в котором массив $Names хранил связь имен людей и их возрастов. Вот как можно перебрать этот массив при помощи прямого перебора:
for(Reset($Names); list($k,$v)=each($Names); /*ïóñòî*/)
  echo "Âîçðàñò $k— $v\n";
В самом начале заголовка цикла мы видим нашу старую знакомую Reset(). Дальше переменным $k и $v присваивается результат работы функции each(). Третье условие цикла попросту отсутствует (чтобы это подчеркнуть, я включил на его место комментарий).
Что делает функция each()? Во-первых, возвращает небольшой массив (я бы даже сказал, список), нулевой элемент которого хранит величину ключа текущего элемента массива $Names, а первый — значение текущего элемента. Во-вторых, она продвигает указатель текущего элемента к следующей позиции. Следует заметить, что если следующего элемента в массиве нет, то функция возвращает не список, а false. Именно поэтому она и размещена в условии цикла for. Становится ясно, почему мы не указали третий блок операторов в цикле for: он просто не нужен, ведь указатель на текущий элемент и так смещается функцией each().



Классы и объекты

Ключевым понятием ООП является класс. Класс— это просто тип переменной. Ну, не совсем просто... На самом деле переменная класса (далее будем ее называть объектом класса) является в некотором смысле автономной сущностью. Обычно такой объект имеет набор свойств и операций
(или методов), которые могут быть с ним проведены. Например, мы можем рассматривать тип int как класс. Тогда переменная этого "класса" будет обладать одним свойством (ее целым значением), а также набором методов (сложение, вычитание, инкремент и т. д.).
В языке C++ мы могли бы, действительно, объявить тип int именно таким образом. Однако в PHP дело обстоит немного хуже: мы не имеем права переопределять стандартные операции (сложение, вычитание и т. д.) для объектов. Например, если бы мы захотели добавить в язык комплексные числа, в C++ это можно было сделать без особых затруднений (и класс комплексных чисел по использованию практически не отличался бы от встроенного типа int), однако в PHP у нас такое добавление не удастся. Альтернативное решение состоит в том, чтобы везде вместо + и других операций использовать вызовы соответствующих функций — например, Add(), которые бы являлись методами класса.
Но обо всем по порядку. Давайте посмотрим, как создать класс в PHP. Это довольно несложно:
class MyName {
 описания свойств
 . . .
 определения методов
}
Замечу, что здесь не создается объекта
класса, а только определяется новый тип. Чтобы создать объект класса MyName, в PHP нужно воспользоваться специальным оператором new:
$Obj = new MyName;
Вот теперь в программе существует объект $Obj, который "ведет себя" так же, как и все остальные объекты класса MyName.



Ключи и значения

array array_flip(array $Arr)
Эта функция "
пробегает" [E59] по массиву и меняет местами его ключи и значения. Исходный массив $Arr
не изменяется, а результирующий массив просто возвращается. Конечно, если в массиве присутствовали несколько элементов с одинаковыми значениями, учитываться будет только последний из них:
$A=array("a"=>"aaa", "b"=>"aaa", "c"=>"ccc");
$A=array_flip($A);
// теперь $A===array("aaa"=>"b", "ccc"=>"c");
list array_keys(array $Arr [,mixed $SearchVal])
Функция возвращает список, содержащий все ключи массива $Arr. Если задан необязательный параметр $SearchVal, то она вернет только те ключи, которым соответствуют значения $SearchVal.
Ключи и значения

Фактически, эта функция с заданным вторым параметром является обратной по отношению к оператору [] — извлечению значения по его ключу.
list array_values(array $Arr)
Функция array_values() возвращает список всех значений в ассоциативном массиве $Arr. Очевидно, такое действие бесполезно для списков, но иногда оправдано для хэшей.
bool in_array(mixed $val, array $Arr)
Возвращает true, если элемент со значением $val присутствует в массиве $Arr. Впрочем, если вам часто приходится проделывать эту операцию, подумайте: не лучше ли будет воспользоваться ассоциативным массивом и хранить данные в его ключах, а не в значениях? На этом вы можете сильно выиграть в быстродействии.
array array_count_values(list $List)
Эта функция подсчитывает, сколько раз каждое значение встречается в списке $List, и возвращает ассоциативный массив с ключами — элементами списка и значениями — количеством повторов этих элементов. Иными словами, функция array_count_values()
подсчитывает частоту появления значений в списке $List. Вот пример:
$List=array(1, "hello", 1, "world", "hello");
array_count_values($array);
// возвращает array(1=>2, "hello"=>2, "world"=>1)



Кнопка отправки формы (submit)

  [name=èìÿ]
  value=òåêñò_êíîïêè
>
Создает кнопку подтверждения с именем name (если этот атрибут указан) и названием (текстом, выводимым поверх кнопки), присвоенным атрибуту value. Как уже говорилось, если задан параметр name, после нажатия кнопки отправки сценарию вместе с другими парами будет передана и пара имя=текст_кнопки (если нажата не эта кнопка, а другая, будет передана строка другой, нажатой, кнопки). Это особенно удобно, когда в форме должно быть несколько кнопок submit, определяющих различные действия (например, кнопки Сохранить и Удалить в сценарии работы с записью какой-то базы данных) — в таком случае чрезвычайно легко установить, какая же кнопка была нажата, и предпринять нужные действия.



Кнопка сброса формы (reset)

  value=òåêñò_êíîïêè
>
Пожалуй, это самый простой элемент формы. Тэг создает кнопку, при нажатии на которую все элементы формы в браузере будут сброшены (точнее, установлены в то состояние, которое было задано в их атрибутах по умолчанию). Причем отправка формы не производится, т. е. для сценария кнопка reset незаметна.



Код и шаблон страницы

Что и говорить, конечно, очень удобно, что PHP позволяет комбинировать код программы с обычным HTML-текстом, но этой возможностью все же не стоит злоупотреблять. И особенно в больших сценариях. Это чередование очень плохо смотрится:
сначала код, потом — вставки HTML, а затем — опять код. Кроме того, вашему HTML-верстальщику будет крайне трудно понять, где же в этом сценарии именно "его" участки, которые он может править и изменять.
Впрочем, особых проблем здесь нет: я предлагаю отделять почти весь код сценария от текста, задающего внешний вид страницы. А именно — хранить их в разных файлах. Я уже неоднократно затрагивал такой подход в этой книге, все время ссылаясь (не совсем явно) на настоящую главу. Что же, теперь настало время по достоинству оценить тот выигрыш, который дает нам отделение кода от шаблона страницы.
Думаете, сейчас мы будем углубляться в "дебри теории", далекой от практики и вряд ли вам полезной? Ничего подобного. Я просто расскажу, как можно удобно строить свои программы, а в конце приведу довольно "внушительный"
код шаблонизатора (так я называю систему управления страницами и шаблонами), который призван сделать работу Web-программиста максимально простой и эффективной.
Код и шаблон страницы

Некоторые программисты утверждают, что отделению кода от шаблона страницы уделяют слишком много внимания — чрезмерно много. Если и вы так думаете, — что же, я не буду с вами спорить и критиковать вашу точку зрения. Если бы я не занимался этой проблемой столько времени, то, возможно, и сам бы так считал. Будем честны: отвечает ли проблема отделения кода от шаблона страницы тому вниманию и количеству страниц, что я ей здесь уделил? Откровенно говоря, не отвечает. В действительности, чтобы полностью рассказать о возможных решениях задачи, потребовалось бы написать отдельную книгу размером в тысячу страниц. Я же ограничусь всего кое-какими рассуждениями и примером простейшего шаблонизатора.



Кодировки и форматы данных

Ранее упоминалось, что и в методе GET, и в методе POST данные доставляются в URL-кодированном виде. Что это значит?
Уж не знаю, откуда взялась эта дурная традиция (может, из стремления сохранить совместимость с древними программами, которыми вот уже лет 20 никто не пользуется), но почему-то все Интернет-сервисы— начиная от

E-mail и заканчивая Web — как-то очень "не любят"  байты со значениями, превышающими 127. Поэтому применяется изощренный способ перекодировки, который все символы в диапазонах 0 .. 32 и 128 .. 256 представляет в URL-кодированном виде. Например, если нам нужно закодировать символ с шестнадцатеричным кодом 9E, это будет выглядеть так: %9E. Помимо этого, пробел представляется символом плюс (+). Так что будьте готовы к тому, что вашим сценариям будут передаваться данные именно в таком виде.

В частности, все буквы кириллицы преобразуются в подобную абракадабру (соответственно, размер данных увеличивается примерно в 3 раза!). Поэтому программы должны всегда быть готовы перекодировать информацию туда-сюда-обратно.
Но это только пол-беды. Дело в том, что существует еще такая неприятная проблема, как кодировки символов кириллицы. И неприятно не столько то, что они существуют, сколько то, что они все не подчиняются никакому единому логическому правилу, в отличие он ASCII. Если при этом текст, который пришел, допустим, в кодировке KOI-8-R, просматривают в WIN-кодировке, получается редкостная путаница.
Казалось бы, чего сложного — выполнить автоматическое перекодирование в читабельный вид полученного текста (кстати говоря, относительно часто этот текст даже снабжен указанием, в какой же он кодировке). Однако, насмотревшись на разнообразные программные продукты, складывается такое впечатление, что эта проблема сложнее, чем создание искусственного интеллекта! А дело все в том, что "интеллектуальные" серверы вместо того, чтобы присылать и принимать текст всегда в фиксированной кодировке и переложить эту проблему на плечи браузеров, зачем-то сами занимаются перекодировкой. И браузеры в своем большинстве — тоже. Так что иногда бывает, что текст приходит "зашифрованным"

с помощью каких- то двух экзотических кодировок, что окончательно его портит.

Кодировки и форматы данных


Существуют даже специальные программы, которые пытаются раскодировать текст, который по ошибке был преобразован несколько раз и потому приобрел нечитаемый вид. Одна из них — почтовый декодер Лебедева, работающий в online-режиме. Само наличие таких программ красноречиво свидетельствует, как далеко все зашло в вопросе о статусе русских кодировок.

Что может быть глупее? А все по той причине, что нет строгого стандарта на кириллицу и что, якобы, где-то в мире существуют браузеры, которые не умеют перекодировать информацию. Скажите на милость, зачем они тогда вообще нужны, если не умеют делать даже такой простой вещи, как табличные преобразования? Или это сделано для тех, кто читает Web-страницы не через браузер, а по telnet'у? И почему же из-за жалкой горстки пользователей должна страдать остальная часть населения страны?

Ну ладно-ладно, я уже успокоился. Прошу прощения, что влез на стол и кричал. Давайте продолжим.


Комплексная замена в строке

В предыдущей главе мы рассматривали функцию strtr(),
которая заменяла в строке одни буквы на другие, и функцию str_replace(), осуществляющую контекстный поиск и замену. В свете ассоциативных массивов эти две функции объединяются в одну, также называющуюся strtr()[E60], но несущую в себе возможности str_replace().
string strtr(string $st, array $Substitutes)
Эта функция (заметьте — с двумя параметрами, а не с тремя, как обычная strtr()!) берет строку $st и проводит в ней контекстный поиск и замену: ищутся подстроки — ключи в массиве $Substitutes — и замещаются на соответствующие им значения. Таким образом, теперь мы можем выполнить несколько замен сразу, не используя str_replace() в цикле:
$Subs=array(
  "" => "Larry",
  "