САМОУЧИТЕЛЬ PHP 4

         

Этап третий: тестирование Apache


Поздравляем — вы настроили свой Apache, и он должен уже работать! Для запуска сервера нажмите кнопку Пуск, затем выберите Программы, Apache Web Server, Management и Start Apache, при этом всплывет окно, очень похожее на Сеанс MS-DOS, и ничего больше не произойдет. Не закрывайте его и не трогайте до конца работы с Apache.

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

1.     Запустите Сеанс MS-DOS. Для этого нажмите кнопку Пуск, затем выберите Выполнить. Наберите в появившемся диалоговом окне строку command и нажмите клавишу <Enter>. Появится подсказка командной строки.

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 комбинацию клавиш <Ctrl>+<Alt>+<A>, а ярлыку Stop Apache — <Ctrl>+<Alt>+<S>. Теперь вы сможете запускать сервер нажатием <Ctrl>+<Alt>+<A> и останавливать его, нажав <Ctrl>+<Alt>+<S>.

Теперь проверим, правильно ли мы настроили сервер.



Этап второй: настройка файла конфигурации 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:, как будто это — обычный жесткий диск.

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



Рис. 4.4. Структура каталогов главного хоста

r    Задайте значение параметра ServerName следующим образом:

ServerName localhost

Только не забудьте раскрыть комментарий для поля ServerName, т. е. убрать символ # перед этим параметром (установленный по умолчанию), поскольку все, что идет после этого символа и до конца строки, Apache игнорирует.



r    В поле DocumentRoot укажите тот каталог, в котором будут размещены ваши HTML-файлы. Мы ранее договорились, что это будет z:\home\localhost\www)

DocumentRoot z:/home/localhost/www

r    Найдите секцию, начинающуюся строкой <Directory диск:/> и заканчивающийся строкой </Directory> (такие блоки содержат установки для заданного каталога и всех его подкаталогов). Этот блок может содержать множество комментариев — не обращайте на них внимания. Его нужно заменить на секцию следующего вида:

<Directory z:/>

  Options Indexes Includes

  AllowOverride All

  Allow from all

</Directory>

Этим вы обеспечите, что в данном блоке будут храниться настройки для всех каталогов по умолчанию (так как z: — корневой каталог). А именно, для всех каталогов по умолчанию предоставляется возможность автоматической генерации индекса — списка содержимого каталога при просмотре его в браузере, а также поддержка SSI и разрешение использовать файлы .htaccess для индивидуальных настроек каталогов.

r    Найдите аналогичный блок, начинающийся строкой <Directory "C:/Program Files/Apache Group/Apache/htdocs"> и заканчивающийся ограничителем </Directory>. Там будет много комментариев, не обращайте на них внимание. Эту секцию вам нужно удалить, т. к. все настройки для каталога со страничками должны наследоваться от настроек по умолчанию, которые мы только что установили.

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    Теперь следует найти блок параметров, начинающийся с <Directory "C:/

Program Files/Apache Group/Apache/cgi-bin"> и заканчивающийся </Directory>. Это — настройки для 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] <br>"

echo "Äåíü: $Pockets[2] <br>";

echo "Ìåñÿö: $Pockets[3] <br>";

echo "Ãîä: $Pockets[4] <br>";

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

^ *([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).




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

Листинг 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)


<input type=submit

  [name=èìÿ]

  value=òåêñò_êíîïêè

Создает кнопку подтверждения с именем name (если этот атрибут указан) и названием (текстом, выводимым поверх кнопки), присвоенным атрибуту value. Как уже говорилось, если задан параметр name, после нажатия кнопки отправки сценарию вместе с другими парами будет передана и пара имя=текст_кнопки (если нажата не эта кнопка, а другая, будет передана строка другой, нажатой, кнопки). Это особенно удобно, когда в форме должно быть несколько кнопок submit, определяющих различные действия (например, кнопки Сохранить и Удалить в сценарии работы с записью какой-то базы данных) — в таком случае чрезвычайно легко установить, какая же кнопка была нажата, и предпринять нужные действия.



Кнопка сброса формы (reset)


<input type=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(

  "<name>" => "Larry",

  "<time>" => date("d.m.Y")

);

$st="Ïðèâåò, <name>! Ñåé÷àñ <time>";

echo strtr($st,$Subs);

А вот как можно "отменить" действие функции HtmlSpecialChars():

$Trans=array_flip(get_html_translation_table());

$st=strtr($st, $Trans);

В результате мы из строки, в которой все спецсимволы заменены на их HTML-эквиваленты, получим исходную строку во всей ее первозданной красе. Функции get_html_translation_table() не уделено много внимания в этой книге. Она возвращает таблицу преобразований, которая применяется при вызове HtmlSpecialChars().

Функция strtr()

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



Конкатенация строк


Самая, пожалуй, распространенная операция со строками — это их конкатенация, или присоединение к одной строке другой. В ранних версиях PHP для этого, как и для сложения чисел, использовался оператор +, что постоянно приводило к путанице: если к числу прибавляется строка, что должно получиться — число или строка? Если число, то вдруг наша строка содержала на самом деле не число, а какой-то текст? В новой — третьей — версии интерпретатора разработчики отказались от этого механизма и объявили, что + следует применять только для сложения чисел, и никак иначе. Что же касается конкатенации строк, то для нее ввели специальный оператор "."

(точка).

Оператор "."

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

$a=array(10,20,30);

echo $a // Âíèìàíèå! Íåîæèäàííûй ðåçóëüòàò!

Есть и другой, более специализированный, способ конкатенации строк. Он обычно используется, когда значения строковых или числовых переменных перемежаются с обычными словами. Если, к примеру, у нас в $day хранится текущее число, в $month — название месяца и в $year — год, то вывести строку вида "Сегодня 8 мая 2000 года" можно так:

echo "Ñåãîäíÿ $day $month $year ãîäà";

При этом в строку, вырабатываемую инструкцией echo, автоматически в нужных местах вставятся значения наших переменных. Это позволяет констатировать тот факт, что в PHP все переменные начинаются с $.



Конструкции языка


Ну вот мы и подобрались к языковым конструкциям. Некоторые из них нами уже применялись, и не раз — например, инструкция if. В этой главе приводится полное описание всех языковых конструкций PHP. Их не так много, и это достоинство PHP [AL1] . Как показывает практика, чем более лаконичен синтаксис языка, тем проще его использовать в повседневной практике. PHP — отличный пример этому.

О терминологии

Иногда я применяю слово "конструкция", а иногда — "инструкция". В данной книге эти два термина совершенно эквивалентны. Наоборот, термины "опе­ратор" и "операция" несут разную смысловую нагрузку: любая операция есть оператор, но не наоборот. Например, echo — оператор, но не операция, а ++ — операция.

Инструкция if-else

Начнем с самой простой инструкции — условного оператора. Его формат таков:

if(ëîãè÷åñêîå_âûðàæåíèå)

  èíñòðóêöèÿ_1;

else

  èíñòðóêöèÿ_2;

Действие его следующее: если логическое_выражение истинно, то выполняется инструкция_1, а иначе — инструкция_2. Как и в любом другом языке, конструкция else может опускаться,

в этом случае при получении должного значения просто ничего не делается.

Пример:

if($a>=1&&$b<=10) echo "Âñå OK";

  else echo "Íåâåðíîå çíà÷åíèå â ïåðåìåííîé!";

Если инструкция_1 или инструкция_2 должны состоять из нескольких ко­манд, то они, как всегда, заключаются в фигурные скобки. Напри­мер:

if($a>$b) { print "a áîëüøå b"; c=$b; }

elseif($a==$b) { print "a ðàâíî b"; $c=$a; }


else { print "a ìåíüøå b"; $c=$a; }

Это не опечатка[AL2] : elseif слитно, вместо else if. Так тоже можно писать, хотя это, по-моему, и не удобочитаемо.

Конструкция if-else имеет еще один альтернативный синтаксис:

if(ëîãè÷åñêîå_âûðàæåíèå):

  êîìàíäû;

elseif(äðóãîå_ëîãè÷åñêîå_âûðàæåíèå):

  äðóãèå_êîìàíäû;

else:

  èíà÷å_êîìàíäû;

endif

Обратите внимание на расположение двоеточия (:)! Если его про­пустить, будет сгенерировано сообщение об ошибке. И еще: как обычно, блоки elseif и else можно опускать.

Использование альтернативного

синтаксиса

В предыдущих главах нами уже неоднократно рассматривался пример вставки HTML-кода в тело сценария. Для этого достаточно было просто закрыть скобку ?>, написать этот код, а затем снова открыть ее при помощи <?, и продол­жать программу.

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

if-else и других конструкций языка.

Чаще всего, однако, нужно бывает делать не вставки HTML внутрь программы, а вставки кода внутрь HTML. Это гораздо проще для дизайне­ра, который, возможно, в будущем захочет переоформить ваш сценарий, но не сможет разобраться, что ему изменять, а что не трогать. Поэтому це­лесообразно бывает отделять HTML-код от программы,

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



Вот, например, как будет выглядеть наш старый знакомый сценарий, который приветствует пользователя по имени, с использованием альтернативного синтаксиса if-else (листинг 9.1):

Листинг 9.1. Альтернативный синтаксис if-else

<?if(@$go):?>

  Ïðèâåò, <?=$name?>!

<?else:?>

  <form action=<?=$REQUEST_URI?> method=post>

  Âàøå èìÿ: <input type=text name=name><br>

  <input type=submit name=go value="Îòîñëàòü!">

<?endif?>

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

Цикл с предусловием while

Эта конструкция также унаследована непосредственно от Си. Ее предназначение — цикличное выполнение команд в теле цикла, включающее предварительную проверку, нужно ли это делать (истинно ли логическое выражение в заголовке). Если не нужно (выражение ложно), то конструкция заканчивает свою работу, иначе выполняет очередную итерацию и начинает все сначала. Выглядит цикл так:

while(ëîãè÷åñêîå_âûðàæåíèå)

  èíñòðóêöèÿ;

где, как обычно, логическое_выражение — логическое выражение, а

инструкция — простая или составная инструкция тела цикла. (Очевидно, что внутри последнего должны производиться какие-то манипуляции, которые будут иногда изменять значение нашего выражения, иначе оператор зациклится. Это может быть, например, простое увеличение некоего счетчика, участвующего в выражении, на единицу.) Если выражение с самого начала ложно, то цикл не выполнится ни разу. Например:

$i=1; $p=1;

while($i<32) {

  echo $p," ";

  $p=$p*2; // ìîæíî áûëî áû íàïèñàòü $p*=2



  $i=$i+1; // ìîæíî áûëî áû íàïèñàòü $i+=1 èëè äàæå $i++

}

Данный пример выводит все степени двойки до 31-й включительно.

Как и инструкция if, цикл while имеет альтернативный синтаксис, что упрощает его применение вперемешку с HTML-кодом:

while(ëîãè÷åñêîå_âûðàæåíèå):

  êîìàíäû;

endwhile;

Цикл с постусловием do-while

В отличие от цикла while, этот цикл проверяет значение выражения не до, а после

каждого прохода. Таким образом, тело цикла выполняется хотя бы один раз. Выглядит оператор так:

do {

  êîìàíäû;

} while(ëîãè÷åñêîå_âûðàæåíèå);

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

Альтернативного синтаксиса для do-while разработчики PHP не предусмотрели (видимо, из-за того, что, в отличие от прикладного программирования, этот цикл довольно редко используется при программировании сценариев).

Универсальный цикл for

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

не такие тривиальные, как простая переборка значения счетчика (а именно для этого используется for в Паска­ле и чаще всего в Си). Формат конструкции такой:

for(èíèöèàëèçèðóþùèå_êîìàíäû; óñëîâèå_öèêëà; êîìàíäû_ïîñëå_ïðîõîäà)



  òåëî_öèêëà;

Работает он следующим образом. Как только управление доходит до цикла, первым делом выполняются операторы, включенные в инициализирующие_команды (слева направо). Эти команды перечисляются там через запятую, например:

for($i=0,$j=10,$k="Test!; ......)

Затем начинается итерация. Первым делом проверяется, выполняется ли условие_цикла (как в конструкции while). Если да, то все в порядке, и цикл продолжается. Иначе осуществляется выход из конструкции. Например:

// ïðèáàâëÿåì ïî îäíîé òî÷êå

for($i=0,$j=0,$k="Test"; $i<10; .....) $k.=".";

Предположим, что тело цикла проработало одну итерацию. После этого вступают в действие команды_после_прохода

(их формат тот же, что и у ини­циализирующих операторов). Например:

for($i=0,$j=0,$k="Points"; $i<100; $j++,$i+=$j) $k=$k.".";

Хочется добавить, что приведенный пример (да и вообще любой цикл for) можно реализовать и через while, только это будет выглядеть не так изящно и лаконично. Например:

$i=0; $j=0; $k="Points";

while($i<100) {

  $k.=".";

  $j++; $i+=$j;

}

Вот, собственно говоря, и все... Хотя нет. Попробуйте угадать: сколько точек добавится в конец переменной $k после выполнения цикла?

Как обычно, имеется и альтернативный синтаксис конструкции:

for(èíèöèàëèçèðóþùèå_êîìàíäû; óñëîâèå_öèêëà; êîìàíäû_ïîñëå_ïðîõîäà):

  îïåðàòîðû;

endfor;

Инструкции break

и continue



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

for($i=0; $i<10; $i++) {

  for($j=0; $j<10; $j++) {

    If($A[$i]==$A[$j]) break(2);

  }

}

if($i<10) echo 'Íàéäåíû ñîâïàäàþùèå ýëåìåíòû â ìàòðèöå \$A!';

В этом примере инструкция break осуществляет выход не только из второго, но и из первого цикла, поскольку указана с параметром 2.



Применение такой формы записи break — новинка PHP версии 4. Честно говоря, я не встречал ни одного другого языка, который бы использовал подобный (на мой взгляд, крайне удачный) синтаксис. Спасибо вам, разработчики PHP!

Инструкцию break удобно использовать для циклов поисков: как только очередная итерация цикла удовлетворяет поисковому условию, поиск обрывается. Например, вот цикл, который ищет в массиве $A первый нулевой элемент:

for($i=0; $i<count($A); $i++)

  if($A[$i]==0) break;

if($i<count($A)) echo "Íóëåâîé ýëåìåíò íàéäåí: i=$i";

Стандартная функция count(), которую мы еще не рассматривали, просто возвращает число элементов в массиве $A.

Инструкция continue так же, как и break, работает только "в паре" с циклическими конструкциями. Она немедленно завершает текущую итера­цию цикла и переходит к новой (конечно, если выполняется условие цикла для цикла с предусловием). Точно так же, как и для break, для continue можно указать уровень вложенности цикла, который будет продолжен по возврату управления.



В основном continue позволяет вам сэконо­мить количество фигурных скобок в коде и увеличить его удобочитаемость. Это чаще всего бывает нужно в циклах-фильтрах, когда требуется перебрать некоторое количество объектов и выбрать из них только те, которые удовлетворяют определенным условиям. Например, вот цикл, который обнуляет те элементы массива $A, которые удовлетворяют нескольким условиям:

for($i=0; $i<count($A); $i++) {

  if(!óñëîâèå1($A[$i])) continue;

  . . .

  if(!óñëîâèåN($A[$i])) continue;

  $A[$i]=0;

}



Грамотное использование break

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

Нетрадиционное использование

do-while и break

Есть один интересный побочный эффект, который дает нам инструкция break, и который довольно удобно использовать для обхода "лишних" опе­раторов (кстати, его можно применять и в Си). Необходимость такого об­хода возникает довольно часто, причем именно при программировании сценариев. Рассмотрим соответствующий пример (листинг 9.2):

Листинг 9.2. Модель сценария для обработки формы

. . .

$WasError=0; // èíäèêàòîð îøèáêè — åñëè íå 0, òî áûëà îøèáêà

// Åñëè íàæàëè êíîïêó Submit (ñ èìåíåì $doSubmit)...

if(@$doSubmit) do {

  // Ïðîâåðêà âõîäíûõ äàííûõ



  if(íåïðàâèëüíîå èìÿ ïîëüçîâàòåëÿ) { $WasError=1; break; }

  . . . è ò. ä.

  if(íåïðàâèëüíûå äàííûå) { $WasError=1; break; }

  . . . è ò. ä.

  // Äàííûå â ïîðÿäêå. Îáðàáàòûâàåì èõ.

  âûïîëíÿåì äåéñòâèÿ;

  âûâîäèì ðåçóëüòàò;

  çàâåðøàåì ñöåíàðèé;

} while(0);

. . .

Âûâîäèì ôîðìó, ÷åðåç êîòîðóþ ïîëüçîâàòåëü áóäåò çàïóñêàòü ýòîò ñöåíàðèé, è, âîçìîæíî, îòîáðàæàåì ñîîáùåíèå îá îøèáêå â ñëó÷àå, åñëè $WasError!=0.

Здесь представлен наиболее обычный способ для организации сценариев-диалогов. Запустив сценарий без параметров, пользователь видит форму с приглашением ввести свое имя, пароль и некоторые другие дан­ные. При нажатии кнопки запускается тот же самый сценарий, который опре­деляет, что была нажата кнопка doSubmit, и первым делом проверяет имя и пароль. Если они заданы неверно, то отображается опять наша форма (и где-нибудь красным цветом сообщение об ошибке), в противном случае сценарий завершается и выдает страницу с результатом.



Мы видим, что указанный алгоритм можно реализо­вать наиболее удобно, имея какой-то способ обрывания блока "проверки-и-завершения"

и возврата к выводу формы заново. Как раз это и делает конструкция

if(÷òî_òî) do { ... } while(0);

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

цикл мы можем использовать для быстрого выхода из него посредством break.

Многие сразу возразят, что в таких случаях удачнее будет задействовать функции и оператор return. Однако в PHP как раз это довольно неудобно, поскольку для того, чтобы из функции добраться до глобальной перемен­ной (коей является любой элемент формы), нужно проделать несколько допол­нительных шагов. Это, конечно, недостаток PHP, и о нем мы поговорим чуть позже.

Цикл foreach

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

foreach(ìàññèâ as $key=>$value)

  êîìàíäû;

Здесь команды

циклически выполняются для каждого элемента массива, при этом очередная пара ключ=>значение

оказывается в переменных $key и $value. Давайте рассмотрим пример (листинг 9.3), где покажем, как мы можем отобразить содержимое всех глобальных переменных при помощи foreach:

Листинг 9.3. Вывод всех глобальных переменных

<?

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

  echo "<b>$k</b> => <tt>$v</tt><br>\n";

?>

У цикла foreach имеется и другая форма записи, которую следует применять, когда нас не интересует значение ключа очередного элемента. Выглядит она так:

foreach(ìàññèâ as $value)

  êîìàíäû;

В этом случае доступно лишь значение

очередного элемента массива, но не его ключ. Это может быть полезно, например, для работы с массивами-списками.





Цикл foreach оперирует не исходным массивом, а его копией. Это означает, что любые изменения, которые вносятся в массив, не могут быть "видны" из тела цикла. Что позволяет, например, в качестве массива использовать не только переменную, но и результат работы какой-нибудь функции, возвращающей массив (в этом случае функция будет вызвана всего один раз — до начала цикла, а затем работа будет производиться с копией возвращенного значения).

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

Конструкция switch-case

Часто вместо нескольких расположенных подряд инструкций if-else целесо­образно воспользоваться специальной конструкцией switch-case:

switch(âûðàæåíèå) {

  case çíà÷åíèå1: êîìàíäû1; [break;]

  case çíà÷åíèå2: êîìàíäû2; [break;]

  . . .

  case çíà÷åíèåN: êîìàíäûN; [break;]

  [default: êîìàíäû_ïî_óìîë÷àíèþ; [break]]

}

Делает она следующее: вычисляет значение выражения (пусть оно равно,

например, V), а затем пытается найти строку, начинающуюся с case V:. Если та­кая строка обнаружена, выполняются команды, расположенные сразу после нее (причем на все последующие операторы case что_то внимание не обраща­ется, как будто их нет, а код после них остается без изменения). Если же найти такую строку не удалось, выполняются команды после default (когда они заданы).

Обратите внимание на операторы break (которые условно заключены в квадратные скобки, чтобы подчеркнуть их необязательность), добавленные после каждой строки команд, кроме последней (для которой можно бы­ло бы тоже указать break, что не имело бы смысла). Если бы не они, то при равенстве V=значение1 сработали бы не только команды1, но и все нижележащие.



Вот альтернативный синтаксис для конструкции switch-case:

switch(âûðàæåíèå):

  case çíà÷åíèå1: êîìàíäû1; [break;]

  . . .

  case çíà÷åíèåN: êîìàíäûN; [break;]

  [default: êîìàíäû_ïî_óìîë÷àíèþ; [break]]

endswitch;

Инструкция require

Эта инструкция позволяет нам разбить текст программы на несколько файлов. Ее формат такой:

require èìÿ_ôàéëà;

При запуске (именно при запуске, а не при исполнении!) программы интерпретатор просто заменит инструкцию на содержимое файла имя_файла (этот файл может также содержать сценарий на PHP, обрамленный, как обыч­но, тэгами <? и ?>). Причем сделает он это только один раз (в от­личие от include, который рассматривается ниже): а именно, непосредс­твенно перед запуском программы. Это бывает довольно удобно для вклю­чения в вывод сценария всяких "шапок"

с HTML-кодом. Например (листинги 9.4, 9.5 и 9.6):

Листинг 9.4. Файл header.htm

<html>

<head><title>Title!</title></head>

<body bgcolor=yellow>

Листинг 9.5. Файл footer.htm

&copy;My company, 1999.

</body></html>

Листинг 9.6. Файл script.php

<?

require "header.htm";

. . . ðàáîòàåò ñöåíàðèé è âûâîäèò ñàìî òåëî äîêóìåíòà

require "footer.htm";

?>

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



Инструкция include

Эта инструкция практически идентична require, за исключением того, что вклю­чаемый файл вставляется "в сердце"

нашего сценария не перед его выпол­нением, а прямо во время.

Какая разница? Поясню. Пусть у нас есть 10 текстовых файлов с именами file0.php, file1.php и так далее до file9.php, содержимое которых просто десятичные цифры 0, 1 ...… 9 (по одной цифре в каждом файле). Запустим такую програм­му:

for($i=0; $i<10; $i++) {

  include "file$i.php";

}

В результате мы получим вывод[E3] , состоящий из 10 цифр: "0123456789". Из этого мы можем заключить, что каждый из наших файлов был включен по одному разу прямо во время выполнения цикла! (Попробуй­те теперь вместо include подставить require. Сравните результат.)

Вы, должно быть, обратили внимание на, казалось бы, лишние фигур­ные скобки вокруг include. Попробуйте их убрать.

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

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

А вдруг в этом файле несколько команд? Тогда в цикле выполнится только первая из них, а остальные будут запущены уже после

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



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

Трансляция и проблемы с include

Как мы знаем, перед исполнением PHP транслирует программу во внутреннее представление. Это означает, что в памяти создается как бы "полу­фабрикат", из которого исключены все комментарии, лишние пробелы, некоторые имена переменных и т. д. Впоследствии это внутреннее представление интерпретируется (выполняется). Однако мы знаем также, что в программе могут встретиться такие места, "подводные камни"



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

Одним из таких "камней" как раз и является инструкция include. Как только управление программы доходит до нее, PHP вынужден приостановиться и ждать, пока транслятор не оттранслирует код включаемого файла. А это достаточно отрицательно сказывается на быстродействии программы, особенно большой. Поэтому, если вы пишете большой и сложный сценарий, применяйте инструкцию require вместо include, где только можно.

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

Что же оптимальнее — require или include? Если вы точно уве­рены, что определенный файл нужно присоединить ровно один раз и в точно определенное место, то воспользуйтесь require. В противном случае более удачным выбором будет include.

Инструкции однократного включения

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

Чтобы стало яснее, я расскажу вам притчу. Как-то раз разработчик Билл написал несколько очень полезных функций для работы с файлами Excel и решил объединить их в библиотеку — файл xllib.php (листинг 9.7):

Листинг 9.7. Библиотека xllib.php

<?

Function LoadXlDocument($filename) { . . . }

Function SaveXlDocument($filename,$doc) { . . . }

?>

Разработчик Вася захотел сделать то же самое для работы с документами Microsoft Word, в результате чего на свет явилась библиотека wlib.php. Так как Word и Excel связаны между собой, Вася использует в своей библиотеке (листинг 9.8) возможности, предоставляемые библиотекой xllib.php — подключает ее командой require:



Листинг 9.8. Библиотека wlib.php

<?

require "xllib.php";

Function LoadWDocument($filename) { . . . }

Function SaveWDocument($filename,$doc) { . . . }

?>

Эти две библиотеки стали настолько популярны в среде Web-програм­мистов, что скоро все стали их внедрять в свои программы. При этом, конечно же, никому нет дела до того, как эти библиотеки на самом деле устроены — все просто подключают их к своим сценариям при помощи require, не задумываясь о возможных последствиях.

Но в один прекрасный день одному неизвестному программисту потребовалось работать и с документами Word, и с документами Excel. Он, не долго думая, подключил к своему сценарию обе эти библиотеки (листинг 9.9):

Листинг 9.9. Подключение библиотек xllib.php

и wlib.php[E4] 

<?

require "wlib.php";

require "xllib.php";

$wd=LoadWDocument("document.doc");

$xd=LoadXlDocument("document.xls");

?>

Каково же было его удивление, когда при запуске этого сценария он получил сообщение об ошибке, в котором говорилось, что в файле xlib.php функция LoadXlDoc() определена дважды!..

Что же произошло? Нетрудно догадаться, если проследить за тем, как транслятор PHP "разворачивает"

код листинга 9.9. Вот как это происходит:

//require "wlib.php";

  //require "xllib.php";

    Function LoadXlDocument($filename) { . . . }

    Function SaveXlDocument($filename,$doc) { . . . }

  Function LoadWDocument($filename) { . . . }

  Function SaveWDocument($filename,$doc) { . . . }

//require "xllib.php";

  Function LoadXlDocument($filename) { . . . }

  Function SaveXlDocument($filename,$doc) { . . . }

$wd=LoadWDocument("document.doc");

$xd=LoadXlDocument("document.xls");

Как видим, файл xllib.php был включен в текст сценария дважды: первый раз косвенно через wlib.php, и второй раз — непосредственно из программы. Поэтому транслятор, дойдя до выделенной строки, обнаружил, что функция LoadXlDocument() определяется второй раз, на что честно и прореагировал.



Конечно, разработчик сценария мог бы исследовать исходный текст библиотеки wlib.php и понять, что во второй раз xllib.php включать не нужно. Но согласитесь — это не выход. Действительно, при косвенном подключении файлов третьего и выше уровней вполне могут возникнуть ситуации, когда без модификации кода библиотек будет уже не обойтись. А это недопустимо. Как же быть?

Что ж, после столь длительного вступления (возможно, слишком длительного?) наконец настала пора рассказать, что думают по этому поводу разработчики PHP. А они предлагают простое решение: инструкции include_once и require_once.

Инструкция require_once работает точно так же, как и require, но за одним важным исключением. Если она видит, что затребованный файл уже был ранее включен, то она ничего не делает. Разумеется, такой метод работы требует от PHP хранения полных имен всех подсоединенных файлов где-то в недрах интерпретатора. Так он, собственно говоря, и поступает.

Инструкция include_once работает совершенно аналогично, но включает файл во время исполнения программы, а не во время трансляции.



Как я уже говорил, в PHP существует внутренняя таблица, которая хранит полные имена всех включенных файлов. Проверка этой таблицы осуществляется инструкциями include_once и require_once. Однако добавление имени включенного файла производят также и функции require

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

Везде, где только можно, применяйте инструкции с суффиксом once. Постарайтесь вообще отказаться от require

и include. Это во многом упростит разбиение большой и сложной программы на относительно независимые модули.


Конструкция or die()


Давайте еще раз посмотрим на предыдущие примеры. Обратите внимание на доселе не встречавшуюся нам конструкцию or die().

Ее особенно удобно применять как раз при работе с файлами. Как мы знаем, оператор or имеет очень низкий приоритет (даже ниже, чем у =), поэтому в нашем примере всегда выполняется уже после присваивания. Иными словами, первая строчка примера с точки зрения PHP выглядит так:

($f=fopen("/home/user/file.txt", "r")) or die("Ошибка!");

Конечно, то, что or обозначает "логическое ИЛИ" в нашем случае не так интересно (ибо возвращаемое значение просто игнорируется). Нас же сейчас интересует другое свойство оператора: выполнять второй свой операнд только в случае ложности первого. Смотрите: если файл открыть не удалось, fopen() возвращает false, а значит, осуществляется вызов die() "на другом конце" оператора or.

Заметьте, что нельзя просто так заменить or

на, казалось бы равнозначный ему оператор ||, потому что последний имеет гораздо более высокий приоритет — выше, чем у =. Таким образом, в результате вызова функции

$f=fopen("/home/user/file.txt", "r") || die("Ошибка!");

в действительности будет выполнено

$f = (fopen("/home/user/file.txt", "r") || die("Ошибка!"));

Как видите, это не совсем то, что нам нужно.



Контроль ошибок


В процессе работы программы в ней могут возникать ошибки. Одна из самых сильных черт PHP — возможность отображения сообщений об ошибках прямо в браузере, не генерируя пресловутую 500-ю Ошибку сервера (Internal Server Error), как это делают другие языки. В зависимости от состояния интерпретатора сообщения будут либо выводиться в браузер, либо подавляться. Для установки режима вывода ошибок служит функция Error_Reporting().

int Error_Reporting([int $level])

Устанавливает уровень строгости для системы контроля ошибок PHP, т. е. величину параметра error_reporting в конфигурации PHP, который мы недавно рассматривали. Рекомендую первой строкой сценария ставить вызов:

Error_Reporting(1+2+4+8);

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

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

Однажды я просидел несколько часов, тщетно пытаясь найти ошибку в сценарии (он работал, но неправильно). После того как я включил полный контроль ошибок, все выяснилось в течение 5 минут. Вот вам и выигрыш по времени...



Копирование изображений


int imageCopyResized(int $dst_im, int $src_im, int $dstX, int $dstY,

                     int $srcX, int $srcY, int $dstW, int $dstH,

                     int $srcW, int $srcH)

Эта функция— одна из самых мощных и универсальных, хотя и выглядит просто ужасно. С помощью нее можно копировать изображения (или их участки), перемещать и масштабировать их…. Пожалуй, 10 параметров для функции — чересчур, но разработчики PHP пошли таким путем[E102] [DK103] . Что же, это их право...

Итак, $dst_im задает идентификатор изображения, в который будет помещен результат работы функции. Это изображение должно уже быть создано или загружено и иметь надлежащие размеры. Соответственно, $src_im — идентификатор изображения, над которым проводится работа. Впрочем, $src_im и $dst_im могут и совпадать.

Параметры ($srcX, $srcY, $srcW, $srcH) (обратите внимание на то, что они следуют при вызове функции не подряд!) задают область внутри исходного изображения, над которой будет осуществлена операция — соответственно, координаты ее верхнего левого угла, ширину и высоту.

Наконец, четверка ($dstX, $dstY, $dstW, $dstH) задает то место на изображении $dst_im, в которое будет "втиснут"

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

Таким образом, с помощью функции

imageCopyResized() мы можем:

r    копировать изображения;

r    копировать участки изображений;

r    масштабировать участки изображений;

r    копировать и масштабировать участки изображения в пределах одной картинки.

В последнем случае возникают некоторые сложности, а именно, когда тот блок, из которого производится копирование, частично налагается на место, куда он должен быть перемещен. Убедиться в этом проще всего экспериментальным путем. Почему разработчики GD не предусмотрели средств, которые бы корректно работали и в этом случае, не совсем ясно.



Копирование объектов


Так уж устроен PHP, что в нем все переменные, в том числе и объекты (а что такое объект, как не переменная определенного класса?), всегда рассматриваются как простой набор значений и копируются целиком. Например, если у нас есть громадный массив $A и мы выполняем оператор $B=$A, то все содержимое $A будет скопировано в $B один-в-один. Возможно, это как раз то, что и требуется, но вот с объектами сложных классов все обстоит совсем иначе. Предположим, например, что мы выполнили команды:

$Obj1=new MysqlTable("test");

$Obj2=$Obj1;

$Obj1->Drop();

Объект-таблица $Obj1 благополучно уничтожится и пометит в своих свойствах, что он уничтожен, и больше использоваться не должен, но вот $Obj2 об этом и не "догадается". $Obj2

по-прежнему будет "считать", что он— "единственный и неповторимый"

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

Этого, к сожалению, нельзя избежать в PHP. А именно, мы не можем никак контролировать процесс копирования объектов. И в этом — безусловная слабость PHP. Так что будьте особенно бдительны.



Корректный перевод IP-адреса в доменное имя


Функция gethostbyaddr()

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

Рассмотрим это на примере. Пусть вам надо определить доменное имя компьютера, расположенного по адресу 195.84.12.34.

Давайте предположим, что эта машина принадлежит симпатичному[DK149] хакеру, который настроил свой DNS-сервер так, чтобы он говорил: "Я являюсь хостом whitehouse.gov", если его об этом спросят по адресу 195.84.12.34. Так что, выполнив код:

echo gethostbyaddr("195.84.12.34");

мы получим вывод whilehouse.gov.

Произошла подмена!

Как же нам быть? А вот как. Предположим, мы получили от хоста с некоторым IP-адресом

информацию, что его "зовут" whitehouse.gov. Обратимся же к нему и получим его IP-адрес, а потом сравним, тот ли это адрес, который мы запрашивали вначале:

$ip="195.84.12.34";

$host=gethostbyaddr($ip);

// Если была ошибка, $host==$ip

if($host==$ip) die("Неверный ip-адрес $ip!");

$check_ip=gethostbyname($host);

// Если была ошибка, $check_ip==$host

if($check_ip==$host) die("Неверное доменное имя $host!");

// Ну вот, теперь сверяем данные

if($ip==$check_ip)

  echo "По адресу $ip расположен хост $host";

else

  echo "По адресу $ip расположен хост злоумышленника!!!";

Длинно? Да. Но это еще, к сожалению, не все. Ведь у одного и того же доменного имени могут быть сразу несколько IP-адресов. Нас устроит, если хотя бы один из них совпадет с затребованным нами. Так что придется воспользоваться функцией gethostbynamel()

и циклом перебора списка IP-адресов. Вот что у нас получится:

Листинг 27.2. Безопасная функция получения доменного имени

<?



Косвенный перебор элементов массива


Довольно часто при программировании на PHP нам приходится перебирать все без исключения элементы некоторого массива. Если наш массив — список, то эта задача, как мы уже знаем, не будет особенно обременительной:

// Ïóñòü $Names — ñïèñîê èìåí. Ðàñïå÷àòàåì èõ â ñòîëáèê

for($i=0; $i<count($Names); $i++)

  echo $Names[$i]."\n";

Я стараюсь везде, где можно, избегать помещения имени переменной-массива в кавычки — например, предыдущий пример я не пишу вот так:

for($i=0; $i<count($Names); $i++)

  echo "$Names[$i]\n";

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

$Names=array(

  array(’name’=>’Вася’, ’age’=>20),

  array(’name’=>’Билл’, ’age’=>40)

);

for($i=0; $i<count($Names); $i++)

  echo "{$Names[$i][’age’]}\n";

Давайте теперь предположим, что массив $Names ассоциативный: его ключи — имена людей, а значения, сопоставленные ключам — например, возраст

этих людей. Для перебора такого массива можно воспользоваться конструкцией наподобие следующей:

for(Reset($Names); ($k=key($Names)); Next($Names))

  echo "Âîçðàñò $k — {$Names[$k]} ëåò\n";

Эта конструкция опирается на еще одно свойство ассоциативных массивов в PHP. А именно, мало того, что массивы являются направленными, в них есть еще и такое понятие, как текущий элемент. Функция Reset() просто устанавливает этот элемент на первую позицию в массиве. Функция key() возвращает ключ, который имеет текущий элемент (если он указывает на конец массива, возвращается пустая строка, что позволяет использовать вызов key() в контексте второго выражения for). Ну а функция Next() просто перемещает текущий элемент на одну позицию вперед.


На самом деле, две простейшие функции, — Reset() и Next(), — помимо выполнения своей основной задачи, еще и возвращают некоторые значения, а именно:

r    функция Reset() возвращает значение первого элемента массива (или пустую строку, если массив пуст);

r    функция Next() возвращает значение элемента, следующего за текущим (или пустую строку, если такого элемента нет).

Иногда (кстати, гораздо реже) бывает нужно перебрать массив с конца, а не с начала. Для этого воспользуйтесь такой конструкцией:

for(End($Names); ($k=key($Names)); Prev($Names))

  echo "Âîçðàñò $k — {$Names[$k]} ëåò\n";

По контексту несложно сообразить, как это работает. Функция End() устанавливает позицию текущего элемента в конец массива, а Prev() передвигает ее на один элемент назад.

И еще. В PHP имеется функция current(). Она очень напоминает key(), только возвращает не ключ, а величину текущего элемента (если он не указывает на конец массива).


Квантификаторы повторений


Перейдем теперь к рассмотрению так называемых квантификаторов— специальных знаков, использующихся для уточнения действия предшествующих им символов первого класса.



Линии


int imageLine(int $im, int $x1, int $y1, int $x2, int $y2, int $col)

Эта функция рисует сплошную тонкую линию в изображении $im, проходящую через точки ($x1,$y1) и ($x2,$y2), цветом $col. Линия получается слабо связанной (про связность см. чуть ниже).

int imageDashedLline(int $im,int $x1,int $y1,int $x2,int $y2,int $col)

Функция imageDashedLine() работает почти так же, как и imageLine(), только рисует не сплошную, а пунктирную линию. К сожалению, ни размер, ни шаг штрихов задавать нельзя, так что, если вам обязательно нужна пунктирная линия произвольной фактуры, придется заняться математическими расчетами и использовать imageLine().



List


Обычно это массив с целыми ключами, пронумерованными от 0 и следующими подряд. Так как список является разновидностью ассоциативного массива, то обычно вместо параметров функций типа list можно подставлять и параметры типа array. При этом, скорее всего, функция "ничего не заметит" и будет работать с этим массивом как со списком, "мысленно" пронумеровав его элементы. Можно также сказать, что список представляет собой упорядоченный набор значений (который можно, например, отсортировать в порядке возрастания), тогда как ассоциативный массив— упорядоченный набор пар значений, каждую из которых логически бессмысленно разъединять.



Location


Формат: Location: http://www.otherhost.com/somepage.html

Этот заголовок особенный и определяет, что браузер пользователя должен немедленно перейти по указанному адресу, не дожидаясь тела документа ответа (как будто бы пользователь сам набрал в адресной строке нужный URL). Так что, очевидно, если вы собираетесь использовать заголовок Location, то никакого документа выводить не надо.

Рекомендуется всегда указывать в заголовке Location абсолютный путь вместе с именем хоста, а не относительный. Дело в том, что, как показывает практика, не все браузеры правильно реагируют на относительные пути и вытворяют все, что им заблагорассудится.

В браузере Netscape имеется ошибка, проявляющаяся, когда сценарий выводит заголовок Location с указанием перейти на собственный URL (то есть, сам на себя, для этого даже придуман специальный термин— self-redirect). Такое решение не так бесполезно, как кажется, и используется, например, в гостевых книгах. В этом случае Netscape прекрасно принимает ответ сценария, но затем почему-то сообщает о том, что "документ не содержит данных". Как решить указанную проблему, см. в части V

книги.



Логические операции


Эти операции предназначены исключительно для работы с логическими выражениями и также возвращают false или true.

r    ! a — истина, если a ложно, и наоборот.

r    a && b — истина, если истинны и a, и b.

r    a || b — истина, если истинны или a, или b, или они оба.

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

$logic = 0&&(time()>100);

стандартная функция time() никогда не будет вызвана.

Будьте осторожны с логическими операциями — не забывайте про удваивание символа. Обратите внимание, что, например, | и || — два совершенно разных оператора, один из которых может потенциально возвращать любое число, а второй — только false и true.



Логические переменные


Существует и еще один гипотетический тип переменных— логический. Логическая переменная может содержать одно из двух значений: false (ложь) или true (истина). Любое ненулевое число (и непустая строка), а также ключевое слово true символизирует истину, тогда как 0, пустая строка и слово false — ложь. Таким образом, любое ненулевое выражение (в частности, значение переменной) рассматривается в логическом контексте как истина. Вы можете пользоваться константами false и true в зависимости от логики программы.

Ключевые слова false и true — не совсем обычные константы. Раньше я говорил, что false

является просто синонимом для пустой строки, а true — для единицы. Именно так они выглядят, если написать следующие операторы:

echo false;  // âûâîäèò ïóñòóþ ñòðîêó, ò. å. íè÷åãî íå âûâîäèò

echo true;   // âûâîäèò 1

Теперь давайте рассмотрим такую программу (листинг 7.1).

Листинг 7.1. Логические величины

<?

$a=100;

if($a==1) echo "ïåðåìåííàÿ ðàâíà 1!<br>"

if($a==true) echo "ïåðåìåííàÿ èñòèííà!<br>"

?>

Если бы true была в точности равна константе 1, то вывелись бы обе строки, не правда ли? А отображается только последняя. Это говорит о том, что не все так просто. Мы видим, что в операторах сравнения (например, в операторе сравнения на равенство ==, а также в операторах >, < и т. д.) PHP интерпретирует один из операндов как логический, если другой также логический. Следующий пример (листинг 7.2) показывает, что, вообще говоря, PHP хранит для каждой переменной признак, является ли она логической.


Листинг 7.2. Логические переменные

<?

$a=100;

$b=true;

echo "a = $a<br>";

echo "b = $b<br>";

if($a==$b) echo 'а "равно" b!';

?>

Как ни странно, но программа печатает, что "а=100 и b=1", а затем с гордостью заявляет, что "a равно b". Хотя в данном примере мы прекрасно понимаем, что так и должно быть (потому что на самом-то деле переменные сравниваются как логические), поэтому будьте осторожны, когда вместо $a используется, например, число, возвращенное функцией. Иначе это может породить ошибку, которая "убьет" несколько часов на ее поиски.

Конечно, при выполнении арифметических операций над логической переменной она превращается в обычную, числовую переменную. Однако при написании этой книги я наткнулся на интересное исключение: по-видимому, операторы ++ и -- для увеличения и уменьшения переменной на 1 не работают с логическими переменными [В. О.20] (листинг 7.3):

Листинг 7.3. Особенности операторов ++ и --

<?

$b=true;

echo "b: $b<br>";

$b++;

echo "b: $b<br>";

?>

Эта программа выводит оба раза значение 1, во всяком случае, в моей версии PHP 4.03.



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


Логические выражения


Логические выражения — это выражения, у которых могут быть только два значения: ложь и истина (или, что почти то же самое, 0 и 1). Что, поверили? Напрасно — на самом деле абсолютно любое выражение может рассматриваться как логическое в "логическом" же контексте (например, как условие для конструкции if-else). Ведь, как уже говорилось, в качестве истины может выступать любое ненулевое число, непустая строка и т. д., а под ложью подразумевается все остальное.

Для логических выражений справедливы все те выводы, которые мы сделали насчет логических переменных. Эти выражения чаще всего возникают при применении операторов >, < и == (равно), || (логическое ИЛИ)[В. О.28] , && (логическое И), ! (логическое НЕ) и других. Например:

$a = 10<5;          // $a=false

$a = $b==1;         // $a=true, åñëè $b=5

$a = $b>=1&&$b<=10  // $a=true, åñëè $b â ïðåäåëàõ îò 1 äî 10

$a = !($b||$c)&&$d; // $a=true, åñëè $b è $c ëîæíû, à $d — èñòèííî

Как осуществляется проверка истинности той или иной логической переменной? Да точно так же, как и любого логического выражения:

$b = $a>=1&&$a<=10; // ïðèñâàèâàåì $b çíà÷åíèå ëîãè÷åñêîãî âûðàæåíèÿ

if($b) echo "a â íóæíîì äèàïàçîíå çíà÷åíèé[В. О.29] ";



Локальные переменные


Наконец-то мы подошли вплотную к вопросу о "жизни и смерти"

переменных. Действительно, во многих приводимых выше примерах мы рассматривали аргументы функции (передаваемые по значению, а не по ссылке) как некие временные объекты, которые создаются в момент вызова и исчезают после окончания функции. Например (листинг11.8):

Листинг 11.8. Локальные переменные (параметры)

$a=100;  // Глобальная переменная, равная 100

function Test($a)

{ echo $a; // выводим значение параметра $a

  // Этот параметр не имеет к глобальной $a никакого отношения!

  $a++; // изменяется только локальная копия значения, переданного в $a

}

Test(1);  // выводит 1

echo $a;  // выводит 100 — глобальная $a, конечно, не изменилась

В действительности такими же свойствами будут обладать не только аргументы, но и все другие переменные, инициализируемые или используемые внутри функции. Вот пример (листинг 11.9):

Листинг 11.9. Локальные переменные

function Silly()

{ $i=rand();   // записывает в $i случайное число

  echo $i;     // выводит его на экран

  // Эта $i не имеет к $i никакого отношения!

}

for($i=0; $i!=10; $i++) Silly();

Здесь переменная $i в функции будет не той переменной $i, которая используется в программе для организации цикла. Поэтому, собственно, цикл и проработает только 10 "витков", напечатав 10 случайных чисел (а не будет крутиться долго и упорно, пока "в рулетке" функции rand() не выпадет 10.

Собственно говоря, это нас устраивает. Действительно, мало ли какие имена переменных использует функция для своих личных целей... Какое до этого дело программе (которая вообще может быть написана другим человеком)? Вот и получается, что каждая функция — "узник"

в своем тесном мирке, живущий и обменивающийся с "окружающим миром" через свои параметры и возвращаемое значение.



Magic_quotes_gpc on|off


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

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

будет ее "размножать":

<?

// Äåëàåì ÷òî-íèáóäü, åñëè íàæàòà êíîïêà Go!

?>

<form action=<?echo $SCRIPT_NAME?> method=post>

<input type=text name=name value="<?=@HtmlSpecialChars($name)?>">

<input type=text name=email value="<?=@HtmlSpecialChars($email)?>">

<input type=submit name=submit value="Go!">

</form>

Мы получаем явно не то, что требовалось: мы хотели просто, чтобы значение поля text сохранялось неизменным между запусками сценария. Оператор @ подавляет сообщение об ошибке для следующего за ним выражения, если она происходит (в нашем случае — при первом запуске сценария, когда переменные $name и $email еще не инициализированы).



Манипулирование каталогами


Вот несколько полезных функций для работы с каталогами.

bool mkdir(string $name, int $perms)

Создает каталог с именем $name и правами доступа $perms. Права доступа для каталогов указываются точно так же, как и для файлов. Чаще всего значение $perms устанавливают равным 0770 (предваряющий ноль обязателен — он указывает PHP на то, что это — восьмеричная константа, а не десятичное число). Например:

mkdir("my_directory",0755); // ñîçäàåò подкаталог â òåêóùåм каталоге

mkdir("/data"); // создает подкаталог data в корневом каталоге

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

Вы, наверное, заметили, что атрибуты доступа 0770 означают "доступен для чтения, записи и исполнения для владельца и его группы". Что означает атрибут исполнения, установленный для каталога? Может быть, он разрешает пользователям запускать из него программы? А вот и нет. Право на "исполнение" показывает, что пользователь сможет просмотреть

содержимое каталога. Конечно, все это специфично для операционных систем семейства Unix.

bool rmdir(string $name)

Удаляет каталог с именем $name. В случае успеха возвращает true, иначе — false. Как всегда, действуют стандартные ограничения файловой системы на эту операцию.

bool chdir(string $path)

Сменяет текущий каталог на указанный. Если такого каталога не существует, возвращает false. Параметр $path

может определять и относительный путь, задающийся от текущего каталога. Вот несколько примеров:

chdir("/tmp/data");   // ïåðåõîäèì по абсолютному пути

chdir("./somathing"); // переходим в подкаталог текущего каталога

chdir("something");   // то же самое

chdir("..");          // переходим в родительский каталог

chdir("~/data");      // переходим в /home/ПОЛЬЗОВАТЕЛЬ/data (для Unix)

string getcwd()

Возвращает полный путь к текущему каталогу, начиная от "корня"

(/). Если такой путь не может быть отслежен (это иногда бывает в Unix из-за того, что права на чтение для родительских каталогов могут быть сняты), вызов "проваливается"

и возвращает false.

Эта функция появилась в PHP совсем недавно. Так что если ее не окажется в вашей версии, обновите ее поскорее, либо напишите заменитель (что не так-то просто).



Массив $GLOBALS


В принципе, есть и второй способ добраться до глобальных переменных. Это— использование встроенного в язык массива $GLOBALS. Последний представляет собой хэш, ключи которого есть имена глобальных переменных, а значения — их величины.

Этот массив доступен из любого места в программе — в том числе и из тела функции, и его не нужно никак дополнительно объявлять. Итак, приведенный выше пример можно переписать более лаконично:

// Âîçâðàùàåò íàçâàíèå ìåñÿöà ïî åãî íîìåðó. Íóìåðàöèÿ íà÷èíàåòñÿ ñ 1! function GetMonthName($n) { return $GLOBALS["Monthes"][$n]; }

Кстати, тут мы опять сталкиваемся с тем, что не только переменные, но даже и массивы могут иметь совершенно любую структуру, какой бы сложной она ни была. Например, предположим, что у нас в программе есть ассоциативный массив $A, элементы которого — двумерные массивы чисел. Тогда доступ к какой-нибудь ячейке этого массива с использованием $GLOBALS мог бы выглядеть так:

$GLOBALS["A"][First"][10][20];

То есть получился четырехмерный массив!

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

r    присвоить этот массив какой-либо переменной целиком, используя оператор =;

r    как следствие, передать его функции "по значению" — можно передавать только по ссылке.


Однако остальные операции допустимы. Мы можем при желании, например, по одному перебрать у него все элементы и, скажем, вывести их значения на экран. И, наконец, третье: добавление нового элемента в $GLOBALS равнозначно созданию новой глобальной переменной (конечно, предваренной символом $ в начале имени, ведь в самом массиве ключи — это имена переменных без

символа доллара), а выполнение операции Unset() для него равносильно уничтожению соответствующей переменной.

А теперь я скажу нечто весьма интересное все о том же массиве $GLOBALS. Как вы думаете, какой элемент (то есть, глобальная переменная) всегда в нем присутствует? Это — элемент GLOBALS, "которая"

также является массивом, и в "которой"

также есть элемент GLOBALS... Так что же было первей — курица или яйцо (только не надо мне говорить, что первым был петух)?

А собственно, почему бы и нет? С чего это мы все привыкли, что в большом содержится малое, а не, скажем, наоборот? Почему множество не может содержать себя же в качестве элемента? Очень даже может, и $GLOBALS — тому наглядный пример.

В PHP версии 3 такая ситуация была чистой воды шаманством. Однако с появлением в четвертой версии PHP ссылок все вернулось на круги своя. На самом-то деле элемент с ключом GLOBALS

является не обычным массивом, а лишь ссылкой на $GLOBALS. Вот поэтому все и работает так, как было описано.

Вооружившись механизмом создания ссылок, мы можем теперь наглядно продемонстрировать, как работает инструкция global, а также заметить один ее интересный нюанс. Как мы знаем, global $a говорит о том, что переменная $a является глобальной, т. е., является синонимом глобальной $a. Синоним в терминах PHP — это ссылка. Выходит, что global создает ссылку? Да, никак не иначе. А вот как это воспринимается транслятором:

function Test()

{ global $a;

  $a=10;

}

Приведенное описание функции Test() полностью эквивалентно следующему описанию:

function Test()

{ $a=&$GLOBALS[’a’];

  $a=10;



}

Из второго фрагмента видно, что оператор Unset($a)

в теле функции не уничтожит глобальную переменную $a, а лишь "отвяжет" от нее ссылку $a. Точно то же самое происходит и в первом случае. Вот пример:

$a=100;

function Test()

{ global $a;

  Unset($a);

}

Test();

echo $a;  // выводит 100, т. е. настоящая $a не была удалена в Test()!



Эта особенность инструкции global появилась только в PHP версии 4, т. е. когда начали поддерживаться ссылки! Если вы запустите приведенный только что пример на PHP версии 3, то при исполнении echo увидите предупреждение: $a не определена. Помните это при переносе старых сценариев на новый PHP версии 4.

Как же нам удалить глобальную $a

из функции? Существует только один способ: использовать для этой цели $GLOBALS['a']. Вот как это делается:

function Test() { unset($GLOBALS['a']); }

$a=100;

Test();

echo $a; // Ошибка! Переменная $a не определена!


Математические функции


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



Max_execution_time


Директива устанавливает время (в секундах), через которое работа сценария будет принудительно прервана. Используется она в основном для того, чтобы запретить пользователям захватывать[E121]  слишком много ресурсов центрального процессора и избежать "зависания"

сценария.



Механизм работы сессий


Как же работают сессии? Для начала должен существовать механизм, который бы позволил PHP идентифицировать каждого пользователя, запустившего сценарий. То есть при следующем запуске PHP нужно однозначно определить, кто его запустил: тот же человек, или другой. Делается это путем присвоения клиенту так называемого уникального идентификатора сессии. Чтобы этот идентификатор был доступен при каждом запуске сценария, PHP помещает его в Cookies браузера.

Использовать Cookies не обязательно, существует и другой способ. Мы поговорим о нем чуть позже.

Теперь, зная идентификатор (дальше для краткости я буду называть его SID), PHP может определить, в каком же файле на диске хранятся данные пользователя.

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

ее из сессии, или же уничтожить вообще все данные сессии.

Где же находится то промежуточное хранилище, которое использует PHP? Вообще говоря, вы вольны сами это задать, написав соответствующие функции и зарегистрировав их как обработчики сессии. Впрочем, делать это не обязательно: в PHP уже существуют обработчики по умолчанию, которые хранят данные в файлах (в системах Unix для этого обычно используется директория /tmp). Если вы не собираетесь создавать что-то особенное, вам они вполне подойдут.



Метод POST


Мы подошли к сути метода POST. А что, если мы в предыдущем примере зададим вместо GET слово POST и после последнего заголовка (маркера \n\n) начнем передавать какие-то данные? В этом случае сервер их воспримет и также передаст сценарию. Только нужно не забыть проставить заголовок Content-length в соответствии с размером данных, например:

POST /script.cgi HTTP/1.0\n

Content-length: 5\n

\n

Test!

Сервер начнет обработку запроса, не дожидаясь передачи данных после маркера конца заголовков. Иными словами, сценарий запустится сразу же после отправки \n\n, а уж ждать или не ждать, пока придет строка Test! длиной 5 байтов — его дело.

Последнее означает, что сервер никак не интерпретирует POST-данные (точно так же, как он не интерпретирует некоторые заголовки), а пересылает их непосредственно сценарию. Но как же сценарий узнает, когда данные кончаются, т. е. когда ему прекращать чтение информации, поступившей от браузера? В этом ему поможет переменная окружения Content-Length, и именно на нее следует ориентироваться. Чуть позже мы рассмотрим этот механизм подробнее.

Зачем нужен метод POST? В основном для того, чтобы передавать большие объемы данных. Например, при загрузке файлов через Web (см. ниже) или при обработке больших форм. Кроме того, метод POST часто используют для эстетических целей: дело в том, что при применении GET, как вы, наверное, уже заметили, URL сценария становится довольно длинным и неизящным, а POST-запрос оставляет URL без изменения.



Метод POST и формы


Что же теперь нужно сделать, чтобы послать данные не методом GET, а методом POST? Нетрудно догадаться: достаточно вместо method=GET указать method=POST. Больше ничего менять не надо.

Если не задать параметра action в тэге <form> вообще, то по умолчанию подразумевается метод GET.

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

Однако в рассмотренной схеме не все гладко с точки зрения простоты: сценарий один, а файла-то два (документ с формой и файл сценария). Есть простое обходное решение этой проблемы, которое рекомендуется применять всюду, где это только возможно: пусть сценарий в первую очередь проверяет, запущен ли он с параметрами или без них. Если параметров нет, то сценарий выдает пользователю HTML-документ с формой, в противном случае — результаты работы. Это удобно еще и потому, что, возможно, вы захотите, чтобы пользователь обязательно ввел свое имя. То есть, если он забудет это сделать, ему будет выдана все та же форма с сообщением напротив поля ввода для имени: "Извините, но Вы забыли ввести свое имя. Попробуйте еще, вдруг на этот раз получится?". А в следующей главе мы попутно рассмотрим, как проще всего определить, был запущен сценарий по нажатии кнопки или же просто набором его URL в браузере.

Приведенная схема минимизации количества документов стандартна и весьма универсальна (ее применют 99% сценариев, которые можно найти в Интернете). Она еще и удобна для пользователя, потому что не создает "мертвых" ссылок (любой URL сценария, который он наберет, пусть даже и без параметров, будет корректным). Однако программирование этой схемы на Си (и на некоторых других языках) вызывает определенные проблемы. Язык PHP таких проблем лишен.



Методы


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

Фактически, свойства хранят в себе состояние объекта в данный момент времени, тогда как методы (функции обработки) являются чем-то вроде механизма посылки запроса

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

$Obj->Drop(); // таблица $Obj удаляет сама себя!

Конечно, у методов, как и у обычных функций, могут быть параметры.

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



Мини-шаблонизатор


Конечно, пользователю будет приятно, если письмо (пусть даже и сгенерированное программой) будет адресовано ему лично. Например, в поле From содержится фамилия и имя клиента, а первые строки текста звучат как-нибудь вроде: "Уважаемый ФИО!". Так что нам придется формировать текст письма "на лету"— проставлять в нем нужное имя, фамилию, тему и т. д. по общему шаблону.

В идеале такой шаблон должен ничем не отличаться от небольшого PHP-сценария с тэгами <? и ?> и возможностью использования команды echo или print, не говоря уж о всех остальных инструкциях. Но вот беда: как нам этот самый шаблон "развернуть", превратить в письмо-строку, которую потом мы будем посылать по почте? Пусть, например, у нас есть следующий шаблон письма (разделителем заголовков и тела письма служит маркер ~StartOfMail, обрабатываемый функцией PostMail()):

To: "<?=$Name?>" <<?=$email?>>

Subject: <?=$Subject?>

~StartOfMail

Дорогой <?=$Name?>!

Только что Вы подписались на наш лист рассылки.

Пожалуйста, подтвердите свое желание получать новости нашего сайта.

Если бы мы писали сценарии на PHP версии 3, задача обработки такого шаблона была бы практически невыполнимой. К счастью, при использовании PHP версии 4 все проще: в нем имеются функции "перехвата" стандартного выходного потока (о них мы уже говорили в главе 30 ).

Давайте начнем проектирование функции PostMail() с написания своеобразного "мини-шаблонизатора" — функции, которая умеет "разворачивать" шаблоны наподобие приведенного выше, возвращая окончательный текст. Назовем ее, к примеру, ExpandTemplate()

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

Листинг 32.1. Функции обработки шаблонов: Minitemplate.phl

<?

// Эта функция используется для внутренних целей. Она возвращает

// "развернутый" шаблон $templ. Перед обработкой создаются переменные,


// имена которых содержатся в ключах массива $Vars, а значения — в

// соответствующих значениях массива. Если $Vars===false, то вместо

// него используется массив $GLOBALS ( то есть делаются доступными все

// глобальные переменные). Значение параметра $ReadFile "истина"

// указывает, что в $templ хранится не содержимое шаблона, а имя файла,

// из которого его можно получить.

// Замечание: параметр $Vars передается по ссылке, т. к. для

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

// копирование.

function _RunTemplate($tmpl, $ReadFile, &$Vars)

{ // Перехватываем стандартный поток вывода

  ob_start();

  // Если $Vars опущен, использовать вместо него $GLOBALS. Мы

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

  // копировать значения, чем экономим время.

  if($Vars===false) $Vars=&$GLOBALS;

  // Делаем доступными коду шаблона все переменные. Также создаем

  // ссылки из соображений производительности.

  foreach($Vars as $k=>$v) $$k=&$Vars[$k];

  // Включаем файл по include, либо же запускаем eval().

  if($ReadFile) { include $tmpl; }

    else eval("?>$tmpl;<?");

  // Получаем содержимое буфера и закрываем его

  $MTResult=ob_get_contents();

  ob_end_clean();

  // Возвращаем развернутый шаблон

  return $MTResult;

}

// Функция "разворачивает" шаблон, тело которого расположено

// в файле $fname. Перед запуском переменные из $Vars делаются

// доступными шаблону (если этот параметр не опущен).

function ExpandFile($fname,$Vars=false)

{ return _RunTemplate($fname,true,$Vars);

}

// Функция "разворачивает" тело шаблона, явно заданное в $tmpl.

// Рекомендуется везде, где можно, применять ExpandFile() вместо

// данной функции, потому что это упрощает отладку.

function ExpandTemplate($tmpl,$Vars=false)

{ return _RunTemplate($tmpl,false,$Vars);

}

?>



Зачем нам две различных функции для "раскрытия" шаблона — ExpandTemplate() и ExpandFile()? Почему бы не использовать всегда ExpandTemplate(), предварительно загружая тело шаблона с помощью функций чтения файлов? Все дело в тонкостях обработки ошибочных ситуаций в PHP. А именно, в случае ошибки внутри файла, загружаемого по include, PHP сообщит нам имя этого файла. Если же ошибка произойдет в eval(), выведется только номер строки, что сильно затруднит отладку. Поэтому рекомендуется везде, где это допустимо, вызывать функцию ExpandFile().


Mixed


Все, что угодно. Это может быть целое или дробное число, строка, массив или объект... Например, параметр типа mixed имеет стандартная функция gettype() или функция settype(). Если написано, что функция возвращает mixed, это значит, что тип результата зависит от операндов и уточняется при описании функции.

При написании функций ни в коем случае не набирайте эти имена типов! Они нужны только для того, чтобы уточнить синтаксис какой-то функции. Хотя, возможно, в будущих версиях эти типы все же можно будет указывать явно. Что ж, посмотрим...



Мнимые символы


Мнимые символы — это просто участок строки между соседними символами (да, именно так, как это ни абсурдно), удовлетворяющий некоторым свойствам. Фактически, мнимый символ — это некая позиция в строке. Например, символ ^ соответствует началу строки (заметьте: не первому символу строки, а в точности началу строки, позиции перед первым символом), а $ — ее концу (опять же, позиции за концом стро­ки).

Чтобы это понять, давайте рассмотрим выражение ^abc, которое соответствует любой строке, начинающейся с abc, и выражение abc$, соответствующее строке с abc

на "хвосте". Наконец, выражение ^abc$ сопоставимо только со строкой abc, и в этом смысле оно эквивалентно сравнению на равенство.

Существуют еще два мнимых символа, задающих начало и конец слова. Первый из них обозначается как [[:<:]]

и указывает на позицию перед первой буквой очередного слова. Последний записывается в виде [[:>:]]

и сигнализирует о позиции после последнего символа слова. Под словом здесь понимается фрагмент строки, удовлетворяющий выражению [[:alnum:]]+, ò. å., любая последовательность из букв и цифр.

Язык RegEx поддерживает только четыре уже рассмотренных нами мнимых символа. Этого нельзя сказать о формате PCRE, в котором, наоборот, количество таких символов доведено до абсурда.

Вот пример использования мнимых символов:

$st="    string    ";

if(ereg("[[:<:]]([[:alnum:]]+)[[:>:]]",$st,$Pock))

  echo "Найдено слово: $Pock[1]";



Многоугольники


int imagePolygon(int $im, list $points, int $num_points, int $col)

Эта функция рисует в изображении $im многоугольник, заданный своими вершинами. Координаты углов передаются в массиве-списке $points, причем $points[0]=x0, $points[1]=y0, $points[2]=x1, $points[3]=y1, и т.д. Параметр $num_points указывает общее число вершин — на тот случай, если в массиве их больше, чем нужно нарисовать. Многоугольник не закрашивается — только рисуется его граница цветом $col.

int imageFilledPolygon(int $im, list $points, int $num_points, int $col)

Функция imageFilledPolygon() делает практически то же самое, что и imagePolygon(), за исключением одного очень важного свойства: полученный многоугольник целиком заливается цветом $col. При этом правильно обрабатываются вогнутые части фигуры, если она не выпукла.



Множественность блоков


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

r    определять имя шаблона индивидуально для каждой страницы;

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

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

Блок — участок текста, имеющий имя (не обязательно уникальное), посредством которого можно ссылаться на этот текст. Мы уже видели, как это происходит в простейшем случае (см. листинг 30.10). Функция шаблонизатора Blk() возвращает текст (или содержимое, или тело) блока, имя которого указанно в ее параметрах. Содержимое блока может быть задано многократно, при этом последующее определение "затирает" текст предыдущего. Чуть ниже мы увидим, насколько данное качество оказывается полезным на практике.

Как же определять новые блоки в файле страницы? Для этого существует конструкция <?Block("имя")?>. Пример ее использования приведен в листинге 30.11.

Листинг 30.11. Файл данных страницы: /phil/index.html

<?Block("Title","[]Философия")?>

<?Block("Text")?>

Конфликт индуцирует смысл жизни. Объект деятельности, пренебрегая

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

класс эквивалентности, открывая новые горизонты. Закон внешнего мира

может быть получен из опыта[E150] .

<?Block("Cite")?>

Философия, конечно, порождена временем. Информация, как следует из

вышесказанного, непредвзято подчеркивает принцип восприятия, отрицая

очевидное.


Из листинга 30. 11 следует, что мы можем задавать содержимое блока двумя разными способами. Самый простой — указать текст непосредственно вторым параметром функции Block(), как это сделано для блока Title. Второй способ незаменим для блоков, тела которых состоят из большого количества строк. А именно, мы можем опустить второй параметр функции Block(), в этом случае весь текст, который расположен до начала следующего блока либо до конца файла, будет восприниматься как тело. Я буду называть такие блоки многострочными. Особенностью многострочных блоков в том шаблонизаторе, который мы с вами сейчас напишем, является то, что из их содержимого удаляются начальные и концевые пробельные символы, в том числе символы перевода строки. В результате та пустая строка, которая присутствует в листинге, не попадет в шаблон — она будет удалена.



Текст, не принадлежащий ни одному из блоков, игнорируется. Например, мы могли бы написать какие-нибудь комментарии сразу после первой строки листинга 30.11, и они были бы пропущены.

Наверное, вы уже догадались, как мы будем задавать имя шаблона для той или иной страницы. Название шаблона — не что иное, как содержимое блока Template, который воспринимается шаблонизатором как специальный. Но, конечно, мы не собираемся определять этот блок в каждой странице — иначе чем этот способ лучше использования участков header и footer? Посмотрим, что предлагает нам шаблонизатор.


Модификация тэгов


Задача: в тексте, заданном в $text, у всех тэгов <img> заменить в src расширение файла рисунка на gif, вне зависимости от того, какое расширение было у него до этого и было ли вообще.

Решение:

$text=eregi_Replace(

  '(<img[^>]*src="?[[:alnum:]/\\]*)(\\.[[:alnum:]]*)?',

  '\\1.jpg',

  $text

);



Модификаторы и флаги типов


К типу можно также присоединять модификаторы, которые задают его "поведение"

и те операции, которые можно (или, наоборот, запрещено) выполнять с соответствующими столбцами. Самые распространенные из них сведены в табл. 26.6.

Таблица 26.6. Основные модификаторы  MySQL

Модификатор

Описание

not null

Означает, что поле не может содержать неопределенное значение— в частности, поле обязательно должно быть инициализировано при вставке новой записи в таблицу (если не задано значение по умолчанию)

primary key

Отражает, что поле является первичным ключом, т. е. идентификатором записи, на которой можно ссылаться

auto_increment

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

Default

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



Модульность программы. Написание "библиотекаря"


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

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

Тем не менее, должен вас огорчить: к сожалению, разработчики PHP не предусмотрели в языке сколько-нибудь удобной поддержки модульности. Однако не впадайте в уныние: дело в том, что такую поддержку можно в язык добавить, причем относительно несложными приемами самого PHP и сравнительно небольшими затратами с точки зрения быстродействия. Этим мы и займемся в настоящей главе.



Multipart-формы


Мы помним, что в большинстве случаев данные из формы в браузере, передающиеся методом GET или POST, приходят к нам в одинаковом формате:

поле1=значение1&поле2=значение2&...

При этом все символы, отличные от "английских"

букв и цифр (и еще некоторых) URL-кодируются: заменяются на %XX, где XX — шестнадцатеричный код символа. Это сильно замедляет закачку больших файлов.

В принципе, multipart-формы призваны одним махом решить эту проблему. Нам нужно в соответствующем тэге <form> задать параметр:

enctype=multipart/form-data

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

-----------------Идентификатор_начала\n

Content-Disposition: form-data; name="имя" [;другие параметры]\n

\n

значение\n

Браузер автоматически формирует строку Идентификатор_начала из расчета, чтобы она не встречалась ни в одном из передаваемых файлов (и ни в одном из других полей формы). Это означает, что сегодня идентификатор будет одним, а завтра, возможно, совсем другим.



Начнем с примеров


На мой взгляд, проще всего разбираться с регулярными выражениями на примерах. Так мы и поступим, если вы не против. Ведь вы не против?..



Name


Вместо этой строки нужно задать имя, закрепленное за Cookie. Имя должно быть URL-кодированным текстом, т.е. состоять только из алфавитно-циф­ровых символов. Впрочем, обычно имена для Cookies выбираются именно так, чтобы их URL-кодированная форма совпадала с оригиналом.



Наши требования


Возможно, вы возразите: "Как же нет никакой поддержки модульности?

А инструкция include?" Да, разумеется, уж лучше использовать include, вместо того чтобы хранить всю программу в одном-единственном файле. Но дело в том, что применение этой инструкции довольно-таки неудобно по той простой причине, что поиск подключаемых файлов проводится только в тех каталогах, которые указал администратор при установке PHP. У многих хостинг-провайдеров мы не можем изменять по своему усмотрению эти каталоги, а указание относительных путей (например, ../../php/somefile.php) оказывается довольно проблематичным (пред­ставьте только, сколько всего нам придется изменять, если мы захотим расположить нашу программу в другом месте).

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

Помните, что при помощи include или require нельзя один и тот же файл загружать дважды (как это часто бывает, если один модуль вызывает другой, но программа об этом "не знает"

и еще раз подключает первый— опять же, стандартный случай). В самом деле, если в этом файле находится, к примеру, описание какой-нибудь функции, то при следующем его включении PHP выдаст ошибку: повторное объявление функции. Конечно, последняя проблема полностью решается подстановкой include_once вместо include, что работает, кстати, только в PHP версии 4.

Отсюда мы можем сформулировать главные два требования.

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

r    Один и тот же модуль не должен загружаться дважды, даже если программа попытается это выполнить.

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

Как я уже говорил, мы можем написать нужную нам "инструкцию", которая будет загружать модуль с применением указанных принципов прямо на PHP. Назовем ее Uses() и оформим в виде функции.

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



Наследование


Создание самодостаточных объектов — довольно неплохая идея. Однако это далеко не единственная возможность ООП. Сейчас мы займемся наследованием — одним из основных понятий ООП.

Итак, пусть у нас есть некоторый класс A с определенными свойствами и методами. Но то, что этот класс делает, нас не совсем устраивает — например, пусть он выполняет большинство функций, по сути нам необходимых, но не реализует некоторых других. Зададимся целью создать новый класс B, как бы "расширяющий"

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

class A {

 function TestA() { ... }

 function Test() { ... }

}

class B {

 var $a; // объект класса A

 function B(параметры_для_A, другие_параметры)

 { $a=new A(параметры_для_A);

 инициализируем другие поля B

 }

 function TestB() { ... }

 function Test() { ... }

}

Поясню: в этой реализации объект класса B содержит в своем составе подобъект класса A в качестве свойства. Это свойство — лишь "частичка" объекта класса B, не более того. Подобъект не "знает", что он в действительности не самостоятелен, а содержится в классе B, поэтому не может пред­принимать никаких действий, специфичных для этого класса.

Но вспомним, что мы хотели получить расширение

возможностей класса A, а не нечто, содержащее объекты A. Что означает "расширение"? Лишь одно: мы бы хотели, чтобы везде, где допустима работа с объектами класса A, была допустима и работа с объектами класса B. Но в нашем примере это совсем не так.

r    Мы не видим явно, что класс B лишь расширяет возможности A, а не является отдельной сущностью.

r    Мы должны обращаться к "части A" класса B через $obj->a->TestA(), а к членам самого класса B как $obj->TestB(). Последнее может быть довольно утомительным, если, как это часто бывает, в B будет использоваться очень много методов из A и гораздо меньше — из B. Кроме того, это заставляет нас постоянно помнить о внутреннем устройстве класса B.


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

(или расширение возможностей) классов.

class B extends A {

 function B(параметры_для_A, другие_параметры)

 { $this->A(параметры_для_A);

 инициализируем другие поля B

 }

 function TestB() { ... }

 function Test() { ... }

}

Ключевое слово extends говорит о том, что создаваемый класс является лишь "расширением"

класса A, и не более того. То есть B содержит те же самые свойства и методы, что и A, но, помимо них и еще некоторые дополнительные, "свои".

Теперь "часть A" находится прямо внутри класса B

и может быть легко доступна, наравне с методами и свойствами самого класса B. Например, для объекта $obj класса B допустимы выражения $obj->TestA()

и $obj->TestB(). Итак, мы видим, что, действительно, класс B

является воплощением идеи "рас­ширение функциональности класса A". Обратите также внимание: мы можем теперь забыть, что B

унаследовал от A

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



Немного о терминологии: принято класс A называть базовым, а класс B — производным от A. Иногда базовый класс также называют суперклассом, а производный — подкласcом.

Зачем может понадобиться наследование? Например, мы написали класс Mysql-таблицы и хотели бы дополнительно иметь класс Guestbook (гостевая книга). Очевидно, в классе Guestbook будет много методов, которые нужны для того же, что и методы из MysqlTable, поэтому было бы разумным сделать его производным от MysqlTable:

class Guestbook extends MysqlTable {

 . . .

 методы и свойства, которых нет в MysqlTable

 и которые относятся к гостевой книге

}

Многие языки программирования поддерживают множественное наследование (то есть такое, когда, скажем, класс B наследует члены не одного, а сразу нескольких классов — например, A и Z). К сожалению, в PHP таких возможностей нет.


Наследование блоков


Наверное, вы думаете, что страница /phil/index.html, которая генерируется листингом30.11, состоит только из трех блоков — Title, Text и Cite. Это не так. Страница, без сомнения, включает перечисленные блоки, но она также состоит и из всех блоков, которые заданы для каталогов /phil и /. Каталоги ведь ничем не хуже файлов. Соответственно, каждый каталог также может иметь собственный набор блоков, которые будут унаследованы

всеми файлами в нем, а также файлами его подкаталогов.

Предположим, что для каталога /phil определяется блок Title, содержащий, скажем, строку Weekly. В то же время файл index.html

также определяет блок Title. Что произойдет в этом случае? А произойдет следующее: в шаблоне будет доступно только тело последнего блока. Иными словами, тот блок, который определяется в файле, заменит собой

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

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

Мы знаем, что блоки файла хранятся в самом этом файле. Где же находятся блоки каталога? Конечно, в специальном файле, расположенном в этом каталоге.

Хранить блоки каталогов в отдельном централизованном хранилище (наподобие Реестра Windows) было бы большим просчетом. Этим мы перечеркнули бы принцип минимизации зависимостей данных, о котором так много сказано в этой главе.

Я предлагаю использовать в качестве такого файла .htaccess. Чтобы Apache не "ругался" на не непонятные ему директивы <?Block(...)?>, мы снабдим их символами комментария  # в начале строки. Конечно, таким способом мы не сможем задавать многострочные блоки для каталогов, но, как показывает практика, в большинстве случаев это и не нужно. В листинге 30.12 показан пример файла .htaccess, расположенного в корневом каталоге сервера и задающего значения блоков по умолчанию.


Листинг 30.12. Блоки для корневого каталога: /.htaccess

#<?Inc("templ")?>

#<?Block("DefaultGlue"," | ")?>

#<?Block("Template","default.tmpl")?>

#<?Block("Title","Тестовый сервер")?>

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

  Action templhandler "/php/TemplateHandler.php?"

  # Документы этого типа мы желаем "пропускать" через наш обработчик.

  AddHandler templhandler .html .htm

Обратите внимание на то, что в приведенном файле конфигурации задаются также и некоторые директивы Apache, которые заставляют сервер запускать программу шаблонизатора каждый раз, когда пользователь обращается к HTML-документу. Мы уже знакомы с этими директивами: в главе 29

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



Наверное, вы уже заметили, что блочный файл, который обрабатывается шаблонизатором, представляет собой ни что иное, как код на PHP с вызовами управляющих функций типа Block(). Этим мы достигаем множества преимуществ, самое главное из которых — значительное ускорение работы шаблонизатора по сравнению со способом "ручного" разбора файлов. Кроме того, отладочные качества сценария при таком подходе ничего не теряют: файлы блоков загружаются с помощью include, а значит, случись там ошибка, PHP исправно покажет имя файла и номер строки, где это произошло. Правда, остается единственный недостаток: несколько некрасивый синтаксис определения блоков, естественный лишь для программиста, но не для дизайнера. Что же, всегда приходится идти на какие-то жертвы…

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

Шаблонизатор также обрабатывает специальным образом еще один блок. Его название — Output. Тело именно этого блока выводится в браузер пользователя, когда вся страница уже обработана. Обычно блок Output

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


Настройка параметров PHP


Все параметры находятся в файле php.ini. Задаются они в формате

параметр=значение, на одной строке может определяться только один параметр. Любые символы, расположенные после ;

и до конца строки, игнорируются (таким образом, точка с запятой— это признак начала комментария).

Если PHP установлен как модуль Apache, применяется несколько другой способ конфигурирования. Можно задавать настройки PHP в главном конфигурационном файле сервера httpd.conf или в файлах .htaccess. Только для этого перед именем каждого параметра нужно поставить префикс php_ и, конечно же, как это принято в Apache, разделять имя параметра и его значение не знаком равенства, а пробелом.

Некоторые из следующих далее настроек можно переопределить в сценарии с помощью специальных функций (такой, например, как Error_Reporting()), некоторые — нельзя. За полным списком настроечных директив PHP обращайтесь к Приложению 2[E119] [DK120] .



Недостатки


У любой медали есть оборотная сторона и, как часто бывает, от ее качества зависит довольно много. Имеется она и у двухуровневой схемы построения сценариев. Давайте систематизируем все недостатки и постепенно будем их исправлять.

1.     Что такое для пользователя "гостевая книга"? Конечно же, это прежде всего страница. А для разработчика сценария? Разумеется, программный код. Получается, что взгляды пользователя несколько отличаются от воззрений разработчика. Как разрешить сформулированную неувязку? Для этого нужно посмотреть на нашу систему "генератор данных — шаблон" со стороны. Что мы видим? Генератор данных загружает данные с диска, а затем обращается к шаблону, чтобы тот их вывел. Но пользователь хочет иметь перед глазами прежде всего шаблон, а не работу генератора! Мы же заставляем его запускать программу. Возможно, следующее положение и покажется спорным, но на практике оно полностью оправдывает себя. А именно, предлагается поменять направление обмена данными между шаблоном и генератором данных. Пусть шаблон запрашивает данные у генератора, а тот их ему предоставляет. Согласитесь, это укладывается даже в замечательно зарекомендовавшую себя модель обмена "клиент-

сервер": шаблон — это клиент, а генератор данных — сервер.

2.     Хотя шаблон двухуровневой схемы и является подчиненным элементом, он все же вынужден ссылаться на имя генератора данных через атрибут action тэга <form>. Конечно, это вносит лишь дополнительную неразбериху и является еще одним стимулом к замене понятий "главный" и "подчиненный".

3.     Генератор данных состоит из излишне большого числа логических блоков, связанных лишь односторонне. В самом деле, если мы будем писать систему администрирования для нашей гостевой книги, нам опять понадобятся функции загрузки и сохранения данных (то есть, функции LoadBook() и SaveBook()). Поэтому логично будет выделить их в отдельный файл, который я здесь буду называть ядром сценария. Ядро — это третий компонент в трехуровневой схеме построения программы, о которой мы сейчас будем говорить. Разумеется, в сложных системах ядро может состоять из десятков (и даже сотен) файлов. Вообще говоря, оно также содержит и сведения о конфигурации (константу GBook), так что часто бывает удобно выделить эти данные в отдельный файл.


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

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

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


Недостатки косвенного перебора


Давайте теперь поговорим о достоинствах и недостатках такого вида перебора массивов. Основное достоинство— "читабельность"

и ясность кода, а также то, что массив мы можем перебрать как в одну, так и в другую сторону. Однако существуют и недостатки.